LLM - 使用 XTuner 指令微调 多模态大语言模型(InternVL2) 教程

欢迎关注我的CSDN:https://spike.blog.csdn.net/

本文地址:https://spike.blog.csdn.net/article/details/142528967

免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。


XTuner 是高效、灵活且功能齐全的大语言模型和多模态模型微调工具,支持简单配置和轻量级运行,通过配置文件,封装大部分微调场景,降低微调的门槛,同时,支持多种预训练模型,如 InternVL 等,支持多种数据集格式,包括文本、图像或视频等。

相关GitHub:

1. 准备模型

InternVL2-2B 为例,下载 HuggingFace 的 InternVL2-2B 模型:

bash 复制代码
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 模型,推荐:

bash 复制代码
pip install modelscope
modelscope --token [your ms token] download  --model OpenGVLab/InternVL2-2B --local_dir InternVL2-2B

ModelScope 的 Token 是 SDK Token,在网站注册之后获取。

使用 Docker 构建环境:

bash 复制代码
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:用于处理数据

即:

bash 复制代码
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 可用,即:

bash 复制代码
python

import torch
print(torch.__version__)  
print(torch.cuda.is_available())  
exit()

配置 XTuner 库,建议使用源码安装:

bash 复制代码
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,冷笑话数据集

bash 复制代码
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 是图像内容:

bash 复制代码
├── [8.8M]  cn.jsonl
├── [3.5M]  en.jsonl
├── [1.5M]  images
├── [1.3G]  images.zip

数据样本如下:

bash 复制代码
{"text": "『住手!现在的你还不是那家伙的对手!先撤吧!!", "question": null, "star": null, "type": "I2T", "image": "007aPnLRgy1hb39z0im50j30ci0el0wm"}

在构建数据集时,如果 question 是空,替换默认提示文字:请你根据这张图片,讲一个脑洞大开的梗。

安装图像处理的 Python 包,即:

bash 复制代码
pip install datasets matplotlib Pillow timm -i https://pypi.tuna.tsinghua.edu.cn/simple

处理 CLoT-Oogiri-GO 数据集,转换成 XTuner 的格式,参考:

bash 复制代码
{
  "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 FormatSingle-Image Data 格式。

处理之后的数据集样本数量是 39736,文件是 Json 格式,即ex_cn.json,格式如下:

bash 复制代码
[
# ...
    {
        "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

即:

bash 复制代码
# 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 如下:

python 复制代码
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|>']),

运行训练脚本:

bash 复制代码
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 损失函数

即:

bash 复制代码
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:配置缓存

即:

bash 复制代码
├── [ 58K]  20240925_150518.log
└── [4.0K]  vis_data
    ├── [ 15K]  20240925_150518.json
    ├── [4.6K]  config.py
    └── [ 15K]  scalars.json

5. LMDeploy 部署模型

LMDeploy 工具用于部署 VL 模型,注意,如果需要 ModelScope:

bash 复制代码
pip install modelscope
export LMDEPLOY_USE_MODELSCOPE=True

安装环境:

bash 复制代码
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

模型评估代码:

python 复制代码
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 的图像效果:

bash 复制代码
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

即:

bash 复制代码
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 的输出,明显更加简洁,即:

bash 复制代码
小猫咪,你的猫爪好臭啊

7. 其他

CLoT-Oogiri-GO 数据集转换成 XTuner 格式的脚本:

python 复制代码
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

解决方案:

bash 复制代码
apt-get update && apt-get install ffmpeg libsm6 libxext6  -y

参考:

相关推荐
金智维科技官方9 分钟前
制造业如何用Ki-AgentS智能体平台实现设备巡检自动化?
大数据·运维·人工智能
stereohomology10 分钟前
大模型看大模型:推理Token的能耗用电量比对
人工智能
Hello world.Joey11 分钟前
Transformer解读
人工智能·深度学习·神经网络·自然语言处理·nlp·aigc·transformer
机器之心16 分钟前
Sand.ai开源发布MagiCompiler:突破局部编译界限,定义训推性能上限
人工智能·openai
KieranYin29 分钟前
AI编程 | 概念
人工智能
飞Link39 分钟前
LangChain Core 架构深度剖析与 LCEL 高阶实战
人工智能·架构·langchain
liangdabiao42 分钟前
Seedance 2.0 Skill 一键写好剧本上线了coze的技能商店了,免费
人工智能
喵飞云智AI研发社1 小时前
本土AI企业发力 喵飞科技AIGC开年分享会助力天津数字化转型
人工智能·科技·aigc
于过1 小时前
AgentMiddleware is All You Need
人工智能·langchain·llm
LLM精进之路1 小时前
频域+特征融合:深度学习的黄金组合,顶会顶刊的快速通道
人工智能·计算机视觉·目标跟踪