LLM - 使用 XTuner 指令微调 多模态大语言模型(InternVL2) 教程
欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/142528967
免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。
XTuner 是高效、灵活且功能齐全的大语言模型和多模态模型微调工具,支持简单配置和轻量级运行,通过配置文件,封装大部分微调场景,降低微调的门槛,同时,支持多种预训练模型,如 InternVL 等,支持多种数据集格式,包括文本、图像或视频等。
相关GitHub:
- InternVL:https://github.com/OpenGVLab/InternVL
- XTuner:https://github.com/InternLM/xtuner
1. 准备模型
以 InternVL2-2B
为例,下载 HuggingFace 的 InternVL2-2B
模型:
export HF_ENDPOINT="https://hf-mirror.com"
pip install -U huggingface_hub hf-transfer
huggingface-cli download --token [your hf token] --resume-download OpenGVLab/InternVL2-2B --local-dir InternVL2-2B
或 下载 ModelScope 的 InternVL2-2B
模型,推荐:
pip install modelscope
modelscope --token [your ms token] download --model OpenGVLab/InternVL2-2B --local_dir InternVL2-2B
ModelScope 的 Token 是 SDK Token,在网站注册之后获取。
使用 Docker 构建环境:
docker run -it \
--privileged \
--network host \
--shm-size 32G \
--gpus all \
--ipc host \
--ulimit memlock=-1 \
--ulimit stack=67108864 \
--name xtuner \
-v [your disk]:[your disk] \
nvcr.io/nvidia/pytorch:23.03-py3 \
/bin/bash
注意:需要继续配置 pip 与 conda 环境
2. 配置 XTuner 环境
配置 XTuner 的 conda 环境,参考 GitHub - xtuner,即:
lmdeploy
:用于部署 VL 大模型streamlit
:用于处理数据
即:
conda create --name xtuner-env python=3.10 -y
conda activate xtuner-env
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install lmdeploy==0.5.3 -i https://pypi.tuna.tsinghua.edu.cn/simple
apt install libaio-dev
pip show transformers # 4.39.3
pip show streamlit # 1.36.0
pip show lmdeploy # 0.5.3
测试 PyTorch 可用,即:
python
import torch
print(torch.__version__)
print(torch.cuda.is_available())
exit()
配置 XTuner 库,建议使用源码安装:
git clone https://github.com/InternLM/xtuner.git
git checkout v0.1.23
pip install -e '.[deepspeed]'
xtuner version
xtuner help
3. 准备 VL 数据集
测试使用的 HuggingFace 数据集: zhongshsh/CLoT-Oogiri-GO,冷笑话数据集
export HF_ENDPOINT="https://hf-mirror.com"
pip install -U huggingface_hub hf-transfer
huggingface-cli download --token [hf token] --repo-type dataset --resume-download zhongshsh/CLoT-Oogiri-GO --local-dir CLoT-Oogiri-GO
使用 I2T (Image to Text) 的数据集进行训练,只选择图像到中文的数据。
核心数据内容 cn.jsonl
是中文注释,images
是图像内容:
├── [8.8M] cn.jsonl
├── [3.5M] en.jsonl
├── [1.5M] images
├── [1.3G] images.zip
数据样本如下:
{"text": "『住手!现在的你还不是那家伙的对手!先撤吧!!", "question": null, "star": null, "type": "I2T", "image": "007aPnLRgy1hb39z0im50j30ci0el0wm"}
在构建数据集时,如果 question 是空,替换默认提示文字:请你根据这张图片,讲一个脑洞大开的梗。
安装图像处理的 Python 包,即:
pip install datasets matplotlib Pillow timm -i https://pypi.tuna.tsinghua.edu.cn/simple
处理 CLoT-Oogiri-GO 数据集,转换成 XTuner 的格式,参考:
{
"id": 0,
"image": "images/00000000.jpg",
"width": 897,
"height": 1152,
"conversations": [
{
"from": "human",
"value": "<image>\nCan you extract any readable text from the image?"
},
{
"from": "gpt",
"value": "Dares Wins Vol. 5 Tommy's Heroes Vol. 6: For Tomorrow Vol. 7: Closing Time miniseries. Clark Kent is being interviewed about Superman's connection to notorious killer Tommy Monaghan. Taking the conversation..."
}
]
}
参考 InternVL - Chat Data Format 的
Single-Image Data
格式。
处理之后的数据集样本数量是 39736
,文件是 Json 格式,即ex_cn.json
,格式如下:
[
# ...
{
"id": 2,
"image": "images/007aPnLRgy1hb39z0im50j30ci0el0wm.jpg",
"width": 450,
"height": 404,
"conversations": [
{
"from": "human",
"value": "<image>\n请你根据这张图片,讲一个脑洞大开的梗。"
},
{
"from": "gpt",
"value": "『住手!现在的你还不是那家伙的对手!先撤吧!!"
}
]
},
#...
]
4. 指令微调(Finetune)
修改训练配置 xtuner/configs/internvl/v2/internvl_v2_internlm2_2b_qlora_finetune_my.py
,即:
data_root
:数据集 的位置data_path
:处理之后,符合 xtuner 格式的 json 数据集image_folder
:图像路径,注意,不包括images
,默认会查找image_folder/images
文件夹max_length
:最大 Token 长度prompt_template
:提示词模版,参考xtuner/utils/templates.py
batch_size
:batch size 影响显存占用accumulative_counts
:梯度累计,影响显存占用dataloader_num_workers
:数据加载,影响速度max_epochs
:运行的最大 epoch
即:
# Model
# path = 'OpenGVLab/InternVL2-2B'
path = "llm/InternVL2-2B"
# Data
# data_root = './data/llava_data/'
# data_path = data_root + 'LLaVA-Instruct-150K/llava_v1_5_mix665k.json'
# image_folder = data_root + 'llava_images'
data_root = 'llm/CLoT-Oogiri-GO/'
data_path = data_root + 'ex_cn.json'
image_folder = data_root
prompt_template = PROMPT_TEMPLATE.internlm2_chat
max_length = 8192
# Scheduler & Optimizer
batch_size = 8 # per_device
accumulative_counts = 2
dataloader_num_workers = 4
max_epochs = 1
其中 PROMPT_TEMPLATE.internlm2_chat
如下:
internlm2_chat=dict(
SYSTEM='<|im_start|>system\n{system}<|im_end|>\n',
INSTRUCTION=('<|im_start|>user\n{input}<|im_end|>\n'
'<|im_start|>assistant\n'),
SUFFIX='<|im_end|>',
SUFFIX_AS_EOS=True,
SEP='\n',
STOP_WORDS=['<|im_end|>']),
参考:xtuner/utils/templates.py
运行训练脚本:
CUDA_VISIBLE_DEVICES=2,3,4,5 NPROC_PER_NODE=4 xtuner train xtuner/configs/internvl/v2/internvl_v2_internlm2_2b_qlora_finetune_my.py --work-dir xtuner/outputs/internvl_v2_internlm2_2b_qlora_finetune_my --deepspeed deepspeed_zero1
CUDA_VISIBLE_DEVICES
的卡数,需要与NPROC_PER_NODE
的数量一致。
运行日志,包括:
lr
学习率eta
预估的训练时间time
单步运行时间data_time
数据处理时间memory
现存占用loss
损失函数
即:
dynamic ViT batch size: 56, images per sample: 7.0, dynamic token length: 3409
09/25 15:07:26 - mmengine - INFO - Iter(train) [ 10/1242] lr: 5.0002e-06 eta: 3:58:14 time: 11.6030 data_time: 0.0238 memory: 37911 loss: 5.6273
09/25 15:08:52 - mmengine - INFO - Iter(train) [ 20/1242] lr: 1.0556e-05 eta: 3:25:19 time: 8.5596 data_time: 0.0286 memory: 37906 loss: 5.7473
09/25 15:10:20 - mmengine - INFO - Iter(train) [ 30/1242] lr: 1.6111e-05 eta: 3:14:46 time: 8.7649 data_time: 0.0290 memory: 37850 loss: 5.1485
09/25 15:11:49 - mmengine - INFO - Iter(train) [ 40/1242] lr: 2.0000e-05 eta: 3:09:50 time: 8.9780 data_time: 0.0293 memory: 37850 loss: 5.0301
09/25 15:13:18 - mmengine - INFO - Iter(train) [ 50/1242] lr: 1.9995e-05 eta: 3:05:54 time: 8.8847 data_time: 0.0283 memory: 37803 loss: 5.0017
09/25 15:14:42 - mmengine - INFO - Iter(train) [ 60/1242] lr: 1.9984e-05 eta: 3:01:05 time: 8.3671 data_time: 0.0271 memory: 37691 loss: 4.7469
09/25 15:17:02 - mmengine - INFO - Iter(train) [ 70/1242] lr: 1.9965e-05 eta: 3:13:06 time: 14.0434 data_time: 0.0302 memory: 37892 loss: 4.9295
09/25 15:18:25 - mmengine - INFO - Iter(train) [ 80/1242] lr: 1.9940e-05 eta: 3:07:30 time: 8.2537 data_time: 0.0324 memory: 37757 loss: 4.8976
09/25 15:19:43 - mmengine - INFO - Iter(train) [ 90/1242] lr: 1.9908e-05 eta: 3:01:51 time: 7.7941 data_time: 0.0348 memory: 37891 loss: 4.7055
09/25 15:20:55 - mmengine - INFO - Iter(train) [ 100/1242] lr: 1.9870e-05 eta: 2:56:03 time: 7.2490 data_time: 0.0288 memory: 37729 loss: 4.8404
dynamic ViT batch size: 40, images per sample: 5.0, dynamic token length: 3405
09/25 15:22:13 - mmengine - INFO - Iter(train) [ 110/1242] lr: 1.9824e-05 eta: 2:51:54 time: 7.7299 data_time: 0.0280 memory: 37869 loss: 5.1263
09/25 15:23:29 - mmengine - INFO - Iter(train) [ 120/1242] lr: 1.9772e-05 eta: 2:48:05 time: 7.6363 data_time: 0.0345 memory: 37937 loss: 4.8139
09/25 15:24:46 - mmengine - INFO - Iter(train) [ 130/1242] lr: 1.9714e-05 eta: 2:44:44 time: 7.6952 data_time: 0.0355 memory: 37875 loss: 5.0916
输出目录 xtuner/outputs/internvl_v2_internlm2_2b_qlora_finetune_my/20240925_150518
:
20240925_150518.log
:日志缓存scalars.json
:运行 loss 相关的日志config.py
:配置缓存
即:
├── [ 58K] 20240925_150518.log
└── [4.0K] vis_data
├── [ 15K] 20240925_150518.json
├── [4.6K] config.py
└── [ 15K] scalars.json
5. LMDeploy 部署模型
LMDeploy 工具用于部署 VL 模型,注意,如果需要 ModelScope:
pip install modelscope
export LMDEPLOY_USE_MODELSCOPE=True
安装环境:
pip install lmdeploy==0.5.3 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install datasets matplotlib Pillow timm -i https://pypi.tuna.tsinghua.edu.cn/simple
模型评估代码:
from lmdeploy import pipeline
from lmdeploy.vl import load_image
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
pipe = pipeline('llm/InternVL2-2B/')
image = load_image('llm/CLoT-Oogiri-GO/007aPnLRgy1hb39z0im50j30ci0el0wm.jpg')
response = pipe(('请你根据这张图片,讲一个脑洞大开的梗。', image))
print(response.text)
注意:如果是 Jupyter 运行,注意清空输出,避免重复加载模型,报错。
测试 InternVL2 的图像效果:
Warning: Flash attention is not available, using eager attention instead.
[WARNING] gemm_config.in is not found; using default GEMM algo
这张图片中的猫咪看起来非常可爱,甚至有点滑稽。让我们来脑洞大开一下,看看这个梗会如何发展:
**梗名:“猫咪的愤怒表情”**
**梗描述:**
1. **场景设定**:猫咪站在一个高处,看起来像是在观察周围的环境。
2. **猫咪的表情**:猫咪张大嘴巴,眼睛瞪得大大的,似乎在发出愤怒的吼声。
3. **背景细节**:背景中有一个楼梯,猫咪似乎在楼梯上,可能是在观察楼上的人或物。
**梗发展**:
- **猫咪的愤怒**:猫咪的愤怒表情非常夸张,仿佛它真的在生气,甚至可能在向人发出警告。
- **猫咪的愤怒原因**:猫咪的愤怒可能与它所处的环境有关,比如楼上的人或物让它感到不安,或者它觉得自己的领地受到了侵犯。
- **猫咪的愤怒反应**:猫咪可能会通过大声吼叫、挠痒痒、甚至跳起来来表达它的愤怒。
**梗应用**:
- **搞笑图片**:这张图片可以用来制作搞笑的社交媒体帖子,猫咪的愤怒表情非常生动,能够引起大家的共鸣和笑声。
- **猫咪行为研究**:通过观察猫咪的愤怒表情,研究者可以更好地了解猫咪的情感表达方式,从而更好地照顾和训练它们。
**总结**:
这张图片通过夸张的猫咪愤怒表情,引发了人们对猫咪行为的思考和讨论。它不仅展示了猫咪的可爱,还通过幽默的方式引发了更多关于猫咪行为和情感的有趣话题。
6. 合并模型
将已训练的 LoRA 模型,合并成完整的模型,即:
- LoRA 模型是 288M
- InternVL2-2B 模型是 4.2G
- 合并之后模型也是 4.2G
即:
cd XTuner
# transfer weights
python xtuner/xtuner/configs/internvl/v1_5/convert_to_official.py \
xtuner/xtuner/configs/internvl/v2/internvl_v2_internlm2_2b_qlora_finetune_my.py \
xtuner/outputs/internvl_v2_internlm2_2b_qlora_finetune_my/iter_1242.pth \
llm/convert_model/
# 重命名
mv llm/convert_model/ llm/InternVL2-2B-my/
注意:需要修改模型
convert_model
名称,确保 LMDeploy 可以识别 PromptTemplate, 参考 XTuner - Load failure with the converted finetune InternVL2-2B model
使用 LMDeploy 测试新模型 InternVL2-2B-my
的输出,明显更加简洁,即:
小猫咪,你的猫爪好臭啊
7. 其他
CLoT-Oogiri-GO 数据集转换成 XTuner 格式的脚本:
import json
import os
import PIL.Image as Image
from tqdm import tqdm
from root_dir import DATA_DIR
class OogirlGoProcessor(object):
"""
将 oogirl_go 数据集转换为 xtuner 格式
"""
def __init__(self):
pass
@staticmethod
def process(json_path, output_path):
print(f"[Info] json_path: {json_path}")
print(f"[Info] output_path: {output_path}")
with open(json_path, "r", encoding="utf-8") as f:
lines = f.readlines()
r_id = 0
dataset_dir = os.path.dirname(json_path)
sample_list = []
for line in tqdm(lines):
data = json.loads(line)
# print(data)
img_name = data["image"]
r_image = f"images/{img_name}.jpg"
img_path = os.path.join(dataset_dir, r_image)
if not os.path.exists(img_path):
continue
img = Image.open(img_path)
r_width, r_height = img.size
# print(f"[Info] w: {r_width}, h: {r_height}")
r_human = data["question"]
if not r_human:
r_human = "请你根据这张图片,讲一个脑洞大开的梗。"
r_gpt = data["text"]
if not r_gpt:
continue
sample_dict = {
"id": r_id,
"image": r_image,
"width": r_width,
"height": r_height,
"conversations": [
{
"from": "human",
"value": f"<image>\n{r_human}"
},
{
"from": "gpt",
"value": r_gpt
}
]
}
sample_list.append(sample_dict)
r_id += 1
print(f"[Info] 全部样本数量: {r_id}")
with open(output_path, "w", encoding="utf-8") as f:
json.dump(sample_list, f, ensure_ascii=False, indent=4)
def main():
json_path = os.path.join(DATA_DIR, "CLoT-Oogiri-GO", "cn.jsonl")
output_path = os.path.join(DATA_DIR, "CLoT-Oogiri-GO", "ex_cn.json")
ogp = OogirlGoProcessor()
ogp.process(json_path, output_path)
if __name__ == '__main__':
main()
BugFix1:ImportError: libGL.so.1: cannot open shared object file: No such file or directory
解决方案:
apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
参考:
- InternVL - 垂直领域场景微调实践
- xTuner - Quickstart
- StackOverflow - ImportError: libGL.so.1: cannot open shared object file: No such file or directory