实验平台简介-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 视觉指令微调
代码:[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代码库
-
下载LLaVA代码
git clone https://github.com/haotian-liu/LLaVA.git cd LLaVA
-
安装
pip install --upgrade pip # enable PEP 660 support pip install -e .
-
安装训练需要的包
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(一个实验追踪和管理工具)。
比较重要的超参数
预训练阶段关键超参数如下:
Hyperparameters | Value |
---|---|
bf16 | True |
num_train_epochs | 1 |
per_device_train_batch_size | 32 |
learning_rate | 1e-3 |
weight_decay | 0 |
warmup_ratio | 0.03 |
lr_scheduler_type | "cosine" |
model_max_length | 2048 |
base optimizer | AdamW |
gradient_checkpointing | True |
dataloader_num_workers | 4 |
lazy_preprocess | True |
可能遇到的问题
启动预训练脚本很可能遇到一些很常见的问题,大家按照报错提示解决即可,在这里列出一些我遇到的特殊问题:
报错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文件
-
COCO: train2017
-
GQA: images
-
OCR-VQA: download script, we save all files as
.jpg
(建议从kaggle上面下载OCR-VQA-200K-dataset-zip (kaggle.com)) -
TextVQA: train_val_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