文章

Qwen-2.5-7B-VL-SFT with Official Code

本文介绍了如何使用官方代码对 Qwen-2.5-7B-VL(-Instruct) 进行 SFT(Supervised Fine-Tuning)。提供了详细的环境配置、数据集准备、训练脚本编写等步骤,希望帮助大家顺利完成模型微调。

Qwen-2.5-7B-VL-SFT with Official Code

官方文档

Source

Qwen2.5-VL 的微调我们只需要他库里的 Qwen2.5-VL/qwen-vl-fintune 文件夹即可。一般来说我们只需要对 qwenvl/data/__init__.pyscripts/sft.sh(我是在 sft_7b.sh 上更改的) 这两个脚本稍稍修改就能顺利运行。读取文件还有特殊步骤的就要在 qwenvl/data/data_qwen.pyqwenvl/data/data_qwen_packed.py 做同样修改即可。

环境配置

官方有给出必要的各个包的版本要求,我的 python 版本是 3.11,方便参考,我也把我的 requirements.txt 放在这里。

requirements.txt

数据集

查看你的数据格式是否满足官方的格式,数据的格式根据官方提供的示例更改即可。重点说一下 dataset dictionary 的格式,以我的 dataset dictionary 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"dataset1": {
    "data_path": "/data/my_dataset/images/",
    "annotation_path": "/data/my_dataset/annotations1.jsonl",
    "length": 11739,
    "sampleing_rate": 0.05437
  },
"dataset2": {
    "data_path": "/data/my_dataset/images/",
    "annotation_path": "/data/my_dataset/annotations2.jsonl",
    "length": 33456,
    "sampling_rate": 0.567
  },
}

如果你有和我一样的数据集汇总文件 Dataset-Dictionary.json,那么你在文件 qwenvl/data/__init__.py中就可以直接读取你的 Dataset-Dictionary.json 文件作为你的data_dict,从而不需要像官方的一样在 qwenvl/data/__init__.py 文件里一条条列出来。这是我在文件里添加的读取为 data_dict 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 假设你的 JSON 文件路径为 'Dataset-Dictionary.json'
json_file_path = '/path/to/your/Dataset-Dictionary.json'

# 读取 JSON 文件并解析为 python 字典
with open(json_file_path, 'r', encoding='utf-8') as f:
    raw_data = json.load(f)

# 筛选出每个数据项中需要的两个字段,此处按需更改,为了方便我就只要这两个属性
data_dict = {
    key: {
        'annotation_path': value['annotation_path'],
        'data_path': value['data_path']
    }
    for key, value in raw_data.items()
}

注意正常我们的 Dataset-Dictionary.json 里会提供每个数据的 repeat_time 或者 sampling_rate ,这个属性我在上面的代码里丢掉了,后面我会在 sft.sh 脚本里加上。

训练脚本

Model Path

1
2
3
4
5
6
7
llm=/path/to/your/model #你的模型路径
#对应下面的
args="
    ...
    --model_name_or_path "${llm}" \
    ...
    "

Datasets

1
2
3
4
5
6
7
8
datasets=$(jq -r 'to_entries | map("\(.key)%\((.value.repeat_time * 100 + 0.5) | floor)") | join(",")' /path/to/your/Dataset_Dictionary.json)
#对应下面的
...
args="
    ...
    --dataset_use ${datasets} \
    ...
    "

此处的脚本是为了读取 dataset dictionary ​所有的数据集名称以及repeat_time 或者 sampling_rate形成该参数需要传入的格式。

训练前一定要在终端 echo 出你的 datasets 检查是否满足要求的格式。

记得检查有没有安装 jq 这个工具,没有的话记得安装:

1
apt-get update && apt-get install -y jq

当然你也可以手动输入各个数据集的名称,格式如下(如果你训练的数据集数量少的话):

1
datasets=your_dataset1%100,your_dataset2%76,your_dataset3%1

Output Configuration

运行名称和 checkpoint 保存路径自行设置即可。

1
2
3
4
5
6
7
8
9
run_name=Qwen2.5-VL-7B
output_dir=/path/to/your/model/checkpoint
...
args="
    ...
    --output_dir ${output_dir} \
    --run_name ${run_name} \
    ...
    "

注意每次从头开始训的 output_dir 要不一样。

WANDB

实验数据的可视化与记录集成记录在 WANDB 平台工具,使用 wandb 首先要在网站上创建 ​team​,然后在 team下创建 ​project​,然后 project ​下会记录每个实验的详细数据。创建 project ​也可以在训练的时候一起创建,run_name 是当前创建的 project ​里每个 run ​的名称,一个 project ​可以有很多 ​run​。

1
2
3
4
5
6
7
8
9
10
11
export WANDB_PROJECT=Name-of-your-project
export WANDB_API_KEY=you-can-find-in-website
# 上面两行添加在脚本前面
...
run_name=your-run-name
...
args="
    ...
    --run_name ${run_name} \
    ...
    "

注意这里的 run_name 和上面 Output Configuration 里的 run_name 是 ​同一个变量​。

Hyperparameters

Learning Rate

官方给的脚本里的学习率是 2e-7(我个人觉得有点小,不过大家后续调参的时候可以先试试 2e-5)。

1
2
3
4
5
6
7
lr=2e-7 
...
args="
    ...
    --learning_rate ${lr} \
    ...
    "

Batch Size

1
2
3
4
5
6
7
8
9
10
batch_size=4
grad_accum_steps=4
...
args="
    ...
    --per_device_train_batch_size ${batch_size} \
    --per_device_eval_batch_size $((batch_size*2)) \
    --gradient_accumulation_steps ${grad_accum_steps} \
    ...
    "

注意这里的 batch_size 是指每张卡上的 batch_size,所以真实的 Batch Size = batch_size × grad_accum_steps × 卡的数量 ,比如我现在有16张卡,那我真实的 Batch Size = 4 × 4 × 16 = 256

Epoch

1
2
3
4
5
6
7
epoch=3
...
args="
    ...
    --num_train_epochs ${epoch} \
    ...
    "

Max Pixels

官方设置里的 max_pixels 是50176,我训练的时候翻一倍(100352)会好一点,这个也是因人而异调整。

1
2
3
4
5
args="
    ...
    --max_pixels 100352 \
    ...
    "

Others

其余的超参数各位按需调整。

特殊要求

如果你的数据和我一样不能直接读取,而是存储在 ceph 里的 bucket 上,那就需要在 qwenvl/data/data_qwen.pyqwenvl/data/data_qwen_packed.py 中的函数 process_image_unifiedvideo_decordvideo_torchcodec 做特殊的更改。

Issue

官方的代码貌似有一个 bugissue

qwenvl/data/data_qwen.py #450 行的代码(qwenvl/data/data_qwen_packed.py也一样的问题):

1
2
3
4
5
6
if "image" not in sources[0] and "video" not in sources[0]: 
     grid_thw_merged = None 
     sources = copy.deepcopy([e["conversations"] for e in sources]) 
     data_dict = preprocess_qwen_2_visual( 
         sources, self.tokenizer, grid_thw=grid_thw_merged 
     )

原因是因为 preprocess_qwen_2_visual 这个函数并不接受 grid_thw 这个参数:

1
2
3
4
5
6
def preprocess_qwen_2_visual(
    sources,
    tokenizer: transformers.PreTrainedTokenizer,
    grid_thw_image: List = [],
    grid_thw_video: List = [],
) -> Dict:

修改成下面的代码就可以:

1
2
3
4
5
6
if "image" not in sources[0] and "video" not in sources[0]:
    grid_thw_merged = None
    sources = copy.deepcopy([e["conversations"] for e in sources])
    data_dict = preprocess_qwen_2_visual(
        sources, self.tokenizer, grid_thw_image=grid_thw_merged, grid_thw_video=grid_thw_merged
    )
本文由作者按照 CC BY 4.0 进行授权