教程类:在超算互联网(SCNet)平台轻松玩转多模态大模型LLaVA的推理、预训练与微调。

实验平台简介-SCNet

本实验在SCNet(超算互联网)平台进行。SCNet国家超算互联网可将全国众多超算中心连接起来,构建一体化的超算算力网络和服务平台。目前已有超过200家应用、数据、模型等服务商入驻国家超算互联网,并提供超过3200款商品。这些商品覆盖科学计算、工业仿真、人工智能模型训练等前沿数字化创新领域,满足经济社会发展对先进计算服务的需求。国家超算互联网正式上线将有助于缓解目前算力供需矛盾,为数字中国建设、数字经济发展等提供坚实支撑。


本实验在超算互联网提供的算力上进行。

我们首先进入官网超算互联网 (scnet.cn),注册账户。

找到“计算资源”,申请资源“异构加速卡AI 显存64GB PCIE”

选择加速卡与镜像,创建Notebook

官方镜像基本上提供了所有与AI相关基本软件,如:深度学习框架Pytorch,deepspeed、Miniconda等,免去了配置环境的步骤,实现开机即用。如需使用其他AI软件,可以从光合开发者平台中下载DCU移植版的软件:cancon.hpccube.com

DCU加速卡简介

DCU(Deeplearning Computing Unit)是新一代国产AI加速卡,是基于通用GPGPU架构设计,性能可对标NVIDIA类产品,具有应用生态完善,迁移成本低的特点,基于PyTorch、TensorFlow等主流框架实现的代码无需转码,可直接使用,是构建AI算力的不二之选,具有较高性价比。

DCU的兼容性很好、生态完善,可以轻松部署运行主流的开源大模型。

LLaVA简介

LLaVA论文名:Visual Instruction Tuning 视觉指令微调

论文:*2304.08485 (arxiv.org)

代码:[haotian-liu/LLaVA: NeurIPS’23 Oral] Visual Instruction Tuning (LLaVA) built towards GPT-4V level capabilities and beyond. (github.com)

什么是指令微调?什么是视觉指令微调?

首先回答什么是指令微调。所谓的指令微调就是一种特殊的有监督微调(supervised fine-tuning),不同之处在于数据集的输入输出格式上。指令微调是在(指令,输出)数据集上进行微调的,目的是让模型对于指令的输出尽可能与人类期望的输出对齐。指令微调的特殊之处在于其数据集的结构,即由人类指令和期望的输出组成的配对。这种结构使得指令微调专注于让模型理解和遵循人类指令。

什么是视觉指令微调?

当指令中嵌入了图像数据时,我们就称为视觉指令微调。

LLaVA论文中的技术方案

  • 使用仅用语言的GPT-4生成了一个语言-图像指令跟随数据(instruction-following data)

模型结构

LLaVA的模型结构很简单,由CLIP视觉编码器ViT-L/14、Vicuna大语言模型和一个Projection层组成。

  • CLIP视觉编码器:负责将输入图像Xv转换视觉特征向量Zv。视觉编码器参数在整个模型训练过程中都保持冻结。

  • Projection层:负责将视觉特征向量Zv通过一个简单的线性变换矩阵W(LLaVA 1.5版本使用MLP层实现),把视觉特征空间转换到语义特征空间,与LLM的语言embedding tokens对齐。

  • 大语言模型Vicuna:把由图像转换而来的embedding tokens与指令tokens拼接在一起作为输入,生成模型回复Xa。

  • 关于参数冻结的理解

    在Pytorch中,使用一行代码实现参数冻结:param.requires_grad = False

    被冻结的参数依然参与反向传播计算,因为误差反向传播是按照链式法则逐层向后进行的。但是梯度不再更新,同时不再为梯度保留空间,不占用显存。

模型训练

LLaVA模型的预训练分为两个阶段。

  • Stage 1:第一阶段冻结视觉编码器与LLM参数,仅训练Projection层,将图像特征Hv与大模型的word embedding 对齐。
  • Stage 2:第二阶段仅冻结视觉编码器,同时更新LLM参数和projection层参数进行端到端微调。

推理

安装LLaVA代码库

  1. 下载LLaVA代码

    git clone https://github.com/haotian-liu/LLaVA.git
    cd LLaVA
  2. 安装

    pip install --upgrade pip  # enable PEP 660 support
    pip install -e .
  3. 安装训练需要的包

    pip install -e ".[train]"
    pip install flash-attn --no-build-isolation # 注意DCU版flash-attn请从光合开发者平台下载移植版https://www.hpccube.com/sso/login?service=https://developer.hpccube.com/tool/

模型推理

我们运行基于Gradio Web UI的网页版Demo进行推理演示。首次运行Demo会自动下载模型权重,亲测下载速度很快。

基于LLaVA独特的前后端架构,我们依次执行以下命令启动网页版demo:

1. 启动controller

任意打开一个终端,执行命令

python -m llava.serve.controller --host 0.0.0.0 --port 10000

启动成功会看到:

2.启动gradio web server

任意打开一个终端,执行命令

python -m llava.serve.gradio_web_server --controller http://localhost:10000 --model-list-mode reload

启动成功会输出一个URL: http://0.0.0.0:7860,记下端口号7860,这即是我们要访问的网页Demo的端口号。

3.加载模型

任意打开一个终端,执行命令

python -m llava.serve.model_worker --host 0.0.0.0 --controller http://localhost:10000 --port 40000 --worker http://localhost:40000 --model-path liuhaotian/llava-v1.5-13b

首次启动会自动从hf上下载模型权重,默认保存在~/.cache目录下。

模型启动成功!

4.访问7860端口

如何使用本地浏览器访问远程服务器的URL呢?请参考网络博客通过ssh在本地打开远程服务器的网页_怎么终端在远程服务器打开的网站在本地操作-CSDN博客

这里我们着重看一下SCNet平台的访问方法。SCNet平台专门提供了“访问自定义服务”的功能,只需要输入服务端口号7860即可从本地访问服务器URL。

启动后会自动弹出网页。

到此为止,我们成功在DCU服务器上部署LLaVA模型。

可能遇到的报错与解决办法

报错:TypeError: LlavaLlamaForCausalLM.forward() got an unexpected keyword argument 'cache_position'
​
解决办法:降低Transformers库的版本installed transformers==4.37.2 and it worked.
报错:libgomp: Thread creation failed: Resource temporarily unavailable
原因:OpenMP系统线程数过大,系统资源不够
解决办法:减少OpenMP线程数 
export OMP_NUM_THREADS=1
减少模型并发线程数

推理效果

可以看到LLaVA的多模态能力十分强大,可以很好地捕捉到图像中的细节。

推理显存分析

llava-1.5-13B(24.3GB bf16权重),实际推理显存占用(默认半精度):28.8GB

8bit量化后显存占用:13.9968GB

4bit量化后显存占用:8.2944GB

flash_attn不影响显存


预训练与微调

Stage 1: LLaVA预训练(冻结CLIP视觉编码器与大语言模型Vicuna-13B,仅训练Projector)

实验平台与软硬件环境

LLaVA预训练阶段的实验平台与软硬件环境与推理阶段相同,我们在SCNet超算互联网申请一台64GB大显存的DCU进行预训练。

硬件配置如下

加速卡异构加速卡AI(DCU) * 1卡
显存64GB
处理器15核心 2*7490 64C
内存110GB

软件环境:

  • jupyterlab

  • pytorch:2.1.0

  • ubuntu20.04

  • dtk24.04.1

  • py3.10

预备阶段:准备预训练数据集

根据LlaVA论文的介绍,模型预训练是在558K subset of the LAION-CC-SBU 数据集上进行的,我们先下载数据集:liuhaotian/LLaVA-Pretrain · Datasets at Hugging Face

数据细节

从数据样例可以看出,该数据集由image、图片索引(id)、对话文本(human与gpt的问答组成一对数据,gpt的回答可以作为训练的label)组成。

数据集结构
  • blip_laion_cc_sbu_558k.json 包含了从图像-标题对生成的多模态合成对话,通过添加随机选择的指令,如“描述这张图片”。它用于 LLaVA 的预训练。使用原始的 CC-3M 标题作为默认答案。

  • blip_laion_cc_sbu_558k_meta.json 包含了图像文件名、图像 URL 和合成 BLIP 标题的元数据。

  • images.zip 包含了从 LAION/CC/SBU 过滤子集中的所有原始图像。

我们将下载好的数据解压后存放在一个文件夹中,按如下方式组织数据,例如在文件夹:/root/private_data/LLaVA/dataset中

├── blip_laion_cc_sbu_558k.json
├── blip_laion_cc_sbu_558k_meta.json
└── images

启动预训练脚本

我们直接启动LLaVA提供的预训练脚本sh pretrain.sh,脚本文件在./LLaVA/scripts/v1_5目录下,如果我们没有事先下载权重,首次启动脚本会自动下大语言模型权重vicuna-13b-v1.5与CLIP视觉编码器权重clip-vit-large-patch14-336,比较方便,亲测下载速度很快。自动下载的模型权重默认保存在系统的.cache目录下,例如~/.cache/huggingface/hub/models--liuhaotian--llava-v1.5-13b。我们可以先以默认方式下载,然后再把模型权重移动到自己喜欢的位置。

pretrain.sh脚本文件内容如下:llava使用deepspeed框架进行预训练,便于结合zero优化器实现显存优化与分布式训练。由于本次实验只在单卡上进行,deepspeed是针对分布式训练(多卡)的优化,所以deepspeed的作用在此约等于无。

#!/bin/bash
​
deepspeed llava/train/train_mem.py \ 
    --deepspeed ./scripts/zero2.json \
    --model_name_or_path lmsys/vicuna-13b-v1.5 \
    --version plain \
    --data_path /root/private_data/LLaVA/dataset/blip_laion_cc_sbu_558k.json \
    --image_folder /root/private_data/LLaVA/dataset/images \
    --vision_tower openai/clip-vit-large-patch14-336 \
    --mm_projector_type mlp2x_gelu \
    --tune_mm_mlp_adapter True \
    --mm_vision_select_layer -2 \
    --mm_use_im_start_end False \
    --mm_use_im_patch_token False \
    --bf16 True \
    --output_dir ./checkpoints/llava-v1.5-13b-pretrain # \
    --num_train_epochs 1 \
    --per_device_train_batch_size 32 \
    --per_device_eval_batch_size 4 \
    --gradient_accumulation_steps 1 \
    --evaluation_strategy "no" \
    --save_strategy "steps" \
    --save_steps 24000 \
    --save_total_limit 1 \
    --learning_rate 1e-3 \
    --weight_decay 0. \
    --warmup_ratio 0.03 \
    --lr_scheduler_type "cosine" \
    --logging_steps 1 \
    --tf32 True \
    --model_max_length 2048 \
    --gradient_checkpointing True \
    --dataloader_num_workers 4 \
    --lazy_preprocess True \
    --report_to wandb

在此我对上述预训练脚本的参数进行一些注释,方便大家按需修改,使用时注意把注释删掉:

#!/bin/bash
​
deepspeed llava/train/train_mem.py # 训练py脚本,请根据自身情况适当修改\ 
    --deepspeed ./scripts/zero2.json # deepspeed zero优化器有关的配置参数文件 可修改\
    --model_name_or_path lmsys/vicuna-13b-v1.5 # 模型权重路径 可修改\
    --version plain \
    --data_path /root/private_data/LLaVA/dataset/blip_laion_cc_sbu_558k.json # 数据集(文本)路径 可修改\
    --image_folder /root/private_data/LLaVA/dataset/images # 数据集(图像)路径 可修改\
    --vision_tower openai/clip-vit-large-patch14-336 # CLIP视觉编码器类型与权重路径 可修改\
    --mm_projector_type mlp2x_gelu # projector层类型 \
    --tune_mm_mlp_adapter True # 预训练阶段要设置为True\
    --mm_vision_select_layer -2 \
    --mm_use_im_start_end False \
    --mm_use_im_patch_token False \
    --bf16 True # 是否启用 bfloat16 精度,这是一种数值精度格式,用于加速训练与节约显存\
    --output_dir ./checkpoints/llava-v1.5-13b-pretrain # 指定训练结果保存的目录\
    --num_train_epochs 1 \
    --per_device_train_batch_size 32 \
    --per_device_eval_batch_size 4 \
    --gradient_accumulation_steps 1 \
    --evaluation_strategy "no" # 指定评估策略。"no" 表示不进行评估。\
    --save_strategy "steps" \
    --save_steps 24000 \
    --save_total_limit 1 \
    --learning_rate 1e-3 \
    --weight_decay 0. \
    --warmup_ratio 0.03 \
    --lr_scheduler_type "cosine" \
    --logging_steps 1 \
    --tf32 True # 是否启用 TensorFloat-32 精度,用于加速训练,DCU暂不支持tf32,使用DCU训练需要删去此项\
    --model_max_length 2048 \
    --gradient_checkpointing True # 是否启用梯度检查点,以节省显存。 \
    --dataloader_num_workers 4  # 指定数据加载器使用 4 个工作线程。\
    --lazy_preprocess True # 是否启用延迟预处理,这有助于节省显存\
    --report_to wandb # 指定将训练结果报告到 Weights & Biases(一个实验追踪和管理工具)。

比较重要的超参数

预训练阶段关键超参数如下:

HyperparametersValue
bf16True
num_train_epochs1
per_device_train_batch_size32
learning_rate1e-3
weight_decay0
warmup_ratio0.03
lr_scheduler_type"cosine"
model_max_length2048
base optimizerAdamW
gradient_checkpointingTrue
dataloader_num_workers4
lazy_preprocessTrue

可能遇到的问题

启动预训练脚本很可能遇到一些很常见的问题,大家按照报错提示解决即可,在这里列出一些我遇到的特殊问题:

报错1:OpenBLAS blas_thread_init: pthread_create: Resource temporarily unavailable
OpenBLAS blas_thread_init: RLIMIT_NPROC 64 current, 64 max
​
解决办法:export OPENBLAS_NUM_THREADS=1
​
pycharm服务器远程报错:not find libgalaxyhip.so.5
解决办法:添加环境变量 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/dtk-24.04.1/lib
​
wandb连接问题:将参数脚本参数 report_to 设为none即可
​
报错2:MIOpen(HIP): Warning [SearchGcnAssembler] No rocm path set while finding /llvm/bin/clang. Recommended to set ROCM_PATH env 
​
报错3:ValueError: --tf32 requires Ampere or a newer GPU arch, cuda>=11 and torch>=1.
解决办法:DCU不支持tf32,在预训练脚本中,把tf32一项删去即可。
​
远程调试代码库打断点技巧:在debug配置中设置对应文件的映射即可打上断点。
F:/LLaVA-main/scripts/v1_5/llava/train/train_mem.py /tmp/pycharm_project_229/scripts/v1_5/llava/train/train_mem.py
第二,不能越级打断点,比如用deepspeed模组启动train_mem.py文件,必需在train_mem.py文件中打断点,如果在train.py打则打不上。虽然说train_mem.py最终会跳转到train.py中执行
​

训练实况

  • 开始训练了!

  • 显存占用:25.9584GB 到 46.9184GB 到 60.896GB 到 47.36GB 到 52.06GB。。。不断地跳变,这是为什么呢?答,显存变化与梯度检查点gradient_checkpointing )的优化策略有关。梯度检查点会在计算过程中动态地丢弃一些中间计算结果(激活值)以节约显存开销。

  • 梯度检查点gradient_checkpointing )核心思想是在模型的前向传播过程中选择性地存储中间结果。通常,反向传播阶段需要保存所有层的激活值以计算梯度。但此方法会占用大量内存,特别是在处理长序列或大模型时。相反,Gradient Checkpointing 在关键点(称为“检查点”)记录中间状态,在其他地方则丢弃这些信息。当需要回溯计算梯度时,只需重新执行从上一个检查点到当前位置的前向传播部分。详见探索高效深度学习:Gradient Checkpointing 技术详解与应用-CSDN博客

  • 从hf官方下载的vicuna-13b-v1.5权重实际上是半精度的(bf16),加载到显存大约需要24.343GB空间(仅16bits权重)

  • vision_tower(clip-vit-large-patch14-336) 大约需要0.768GB显存 clip-vit-large-patch14-336(bf16)(视觉骨架约384M参数)

  • 单卡64GB显存进行LLaVA-13b-v1.5预训练,使用稍微小一些的batch_size=32 + bf16 + 梯度检查点技术,显存刚好够用

  • 关于第一阶段预训练参数冻结的关键代码实现:关键参数tune_mm_mlp_adapter=True,会首先冻结全部模型参数包括:LLM参数、vision_tower参数、projector层参数,然后再单独解冻projector层的参数,来达到仅训练projector层的目的,关键代码如下:

Stage 2: LLaVA 视觉指令微调(仅冻结视觉编码器,同时更新LLM参数和projection层参数进行端到端微调)

准备数据集

请下载数据集注释文件https://huggingface.co/datasets/liuhaotian/LLaVA-Instruct-150K/blob/main/llava_v1_5_mix665k.json

并且下载images文件

将下载好的数据集解压,并按照如下方式组织数据文件,例如在目录./dataset下:

├── coco
│   └── train2017
├── gqa
│   └── images
├── ocr_vqa
│   └── images
├── textvqa
│   └── train_images
└── vg
    ├── VG_100K
    └── VG_100K_2

下载projector权重

为了便于微调,我们可以直接下载官方预训练好的projector权重LLaVA/docs/MODEL_ZOO.md at main · haotian-liu/LLaVA (github.com)

找到匹配的模型与版本号:

在服务器上新建一个文件夹专门放置下载好的projector权重:

记好地址~/private_data/LLaVA/finetuning/projector,后面启动脚本时需要用到

启动微调脚本

微调脚本的路径是./LLaVA/scripts/v1_5/finetune.sh

在启动微调脚本之前,我们需要对一些参数进行个性化修改,比如模型路径、数据集路径等。

#!/bin/bash
  
deepspeed llava/train/train_mem.py \ # 自行修改
    --deepspeed ./scripts/zero3.json \
    --model_name_or_path lmsys/vicuna-13b-v1.5 \ # 自行修改
    --version v1 \
    --data_path /root/private_data/LLaVA/dataset/finetuning/llava_v1_5_mix665k.json \ # 自行修改
    --image_folder /root/private_data/LLaVA/dataset/finetuning/data \ # 自行修改
    --vision_tower openai/clip-vit-large-patch14-336 \ # 自行修改
    --pretrain_mm_mlp_adapter ~/private_data/LLaVA/finetuning/projector/mm_projector.bin \ # 自行修改
    --mm_projector_type mlp2x_gelu \
    --mm_vision_select_layer -2 \
    --mm_use_im_start_end False \
    --mm_use_im_patch_token False \
    --image_aspect_ratio pad \
    --group_by_modality_length True \
    --bf16 True \
    --output_dir /root/private_data/LLaVA/finetuning/checkpoint/llava-v1.5-13b \ # 自行修改
    --num_train_epochs 1 \
    --per_device_train_batch_size 16 \
    --per_device_eval_batch_size 4 \
    --gradient_accumulation_steps 1 \
    --evaluation_strategy "no" \
    --save_strategy "steps" \
    --save_steps 50000 \
    --save_total_limit 1 \
    --learning_rate 2e-5 \
    --weight_decay 0. \
    --warmup_ratio 0.03 \
    --lr_scheduler_type "cosine" \
    --logging_steps 1 \
    --model_max_length 2048 \
    --gradient_checkpointing True \
    --dataloader_num_workers 4 \
    --lazy_preprocess True \
    --report_to wandb

从启动脚本上看,微调脚本与预训练脚本几乎没有差别,都是启动train_mem.py进行训练。一个最重要的参数区别是预训练把参数tune_mm_mlp_adapter=True,而微调则没有设置该参数。

直接尝试全参数微调

直接启动微调脚本会出现显存不够用的情况,因为LLM模型也参与了微调,而且不包括数据batch与激活值。而我们的单卡DCU最大显存只有64GB直接微调行不通。

LoRA微调

LoRA(Low-Rank Adaptation of LLMs),即LLMs的低秩适应,是参数高效微调最常用的方法。

LoRA的本质就是用更少的训练参数来近似LLM全参数微调所得的增量参数,从而达到使用更少显存占用的高效微调。

LoRA的核心思想是,在冻结预训练模型权重后,将可训练的低秩分解矩阵注入到的Transformer架构的每一层中,从而大大减少了在下游任务上的可训练参数量。

在这里插入图片描述

微调脚本
#!/bin/bash
  
deepspeed llava/train/train_mem.py \
    --lora_enable True --lora_r 128 --lora_alpha 256 --mm_projector_lr 2e-5 \ #开启lora微调
    --deepspeed ./scripts/zero3.json \
    --model_name_or_path lmsys/vicuna-13b-v1.5 \
    --version v1 \
    --data_path /root/private_data/LLaVA/dataset/finetuning/llava_v1_5_mix665k.json \
    --image_folder /root/private_data/LLaVA/dataset/finetuning/data \
    --vision_tower openai/clip-vit-large-patch14-336 \
    --pretrain_mm_mlp_adapter ~/private_data/LLaVA/finetuning/projector/mm_projector.bin \
    --mm_projector_type mlp2x_gelu \
    --mm_vision_select_layer -2 \
    --mm_use_im_start_end False \
    --mm_use_im_patch_token False \
    --image_aspect_ratio pad \
    --group_by_modality_length True \
    --bf16 True \
    --output_dir /root/private_data/LLaVA/finetuning/checkpoint/llava-v1.5-13b \
    --num_train_epochs 1 \
    --per_device_train_batch_size 16 \
    --per_device_eval_batch_size 4 \
    --gradient_accumulation_steps 1 \
    --evaluation_strategy "no" \
    --save_strategy "steps" \
    --save_steps 50000 \
    --save_total_limit 1 \
    --learning_rate 2e-4 \
    --weight_decay 0. \
    --warmup_ratio 0.03 \
    --lr_scheduler_type "cosine" \
    --logging_steps 1 \
    --model_max_length 2048 \
    --gradient_checkpointing True \
    --dataloader_num_workers 4 \
    --lazy_preprocess True \
    --report_to wandb
  • 26
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值