Ascend-SACT/Qwen2.5VL7B-MindspeedMM
模型介绍文件和版本Pull Requests讨论分析
下载使用量0

使用Mindspeed-MM训练框架微调Qwen2.5VL-7B模型指导

1. 概述及场景

Mindspeed-MM是一个基于昇腾平台的开源多模态训练框架,常用在多模态模型SFT全参微调场景。
原Mindspeed开源项目未提供镜像,本工程旨在提供一个可开箱即用的多模态训练环境镜像,并辅以使用说明。
本文以Qwen2.5VL7B的微调场景为例进行说明。相关项目信息请参考原开源项目:
Mindspeed-MM源工程:https://gitcode.com/Ascend/MindSpeed-MM/blob/master/examples/qwen2.5vl/README.md
安装指导:https://gitcode.com/Ascend/MindSpeed-MM/blob/master/docs/user-guide/installation.md#%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97

2. 镜像相关版本说明

镜像名称描述硬件Cann版本mindspeed版本python版本pytorch版本
XXX.tarMindspeed-MM裸机环境镜像800T A28.1.RC1X.X.X3.11x.x.x
XXX-MA.tarMindspeed-MM适配MA环境镜像800T A28.1.RC1X.X.X3.11x.x.x

3. 目录准备:代码、权重、数据

下载Mindspeed-MM代码和模型权重,在本文中假设:
工程下载到目录 /data-VL/MindSpeed-MM/
权重下载到目录 /data-VL/MindSpeed-MM/ckpt/hf_path/Qwen2.5-VL-7B-Instruct/
数据下载到目录 /data-VL/MindSpeed-MM/data/task1/train/

4. 训练数据格式转换

准备训练用的图片和文本QA对
图片位于 /data-VL/MindSpeed-MM/data/task1/train/
QA标注文件位于 /data-VL/MindSpeed-MM/data/task1/train_mllm_format.json
训练要使用mllm格式的标注文件,如果是llava格式还需要转换格式。当然也可以直接写成mllm格式标注文件。 llava格式QA文件示例如下

[
    {
        "id": "20251017_10018_00001",
        "image": "20251017_10018_00001.jpg",
        "conversations": [
            {
                "from": "human",
                "value": "<image>\n请提取这张存单图片中的存单号码和账号号码."
            },
            {
                "from": "gpt",
                "value": "存单号码: 2201361629 账号号码: 51050006574529"
            }
        ]
    },
    {
        "id": "20251017_10018_00002",
        "image": "20251017_10018_00002.jpg",
        "conversations": [
            {
                "from": "human",
                "value": "<image>\n请提取这张存单图片中的存单号码和账号号码."
            },
            {
                "from": "gpt",
                "value": "存单号码: 2201361629 账号号码: 51050006574529"
            }
        ]
    }
]

使用脚本MindSpeed-MM/examples/qwen2vl/llava_instruct_2_mllm_demo_format.py将llava格式转换成mllm格式 注意需修改相关路径

# llava格式QA标注文件路径
llava_json_path = "./data/task1/train_llava_format.json"
# mllm格式QA标注文件路径
mllm_format_json_path = "./data/task1/train_mllm_format.json"
# 训练图片路径
img_path = os.path.join("./data/train", item["image"])

mllm格式标注文件示例如下

[{"images": ["./img\\20251017_10018_00001.jpg"], "messages": [{"role": "user", "content": "<image>\n请提取这张存单图片中的存单号码和账号号码."}, {"role": "assistant", "content": "存单号码: 2201361629 账号号码: 51050006574529"}]}, {"images": ["./img\\20251017_10018_00002.jpg"], "messages": [{"role": "user", "content": "<image>\n请提取这张存单图片中的存单号码和账号号码."}, {"role": "assistant", "content": "存单号码: 2201361629 账号号码: 51050006574529"}]}]

5. 模型格式转换hf_to_mm

Mindspeed-MM需要mm格式权重,参考如下链接完成7b模型格式转换hf_to_mm
https://gitcode.com/Ascend/MindSpeed-MM/blob/master/examples/qwen2.5vl/README.md#jump2.2
转换完的模型保存在 /data-VL/MindSpeed-MM/ckpt/mm_path/Qwen2.5-VL-7B-Instruct/

6. 修改训练配置

传入训练数据路径,修改:
examples/qwen2.5vl/data_7b.json

"basic_parameters": {
            "template": "qwen2vl",
            "dataset_dir": "./data",
            "dataset": "./data/trian_mllm_format.json",
            "cache_dir": "./data/cache_dir",
            "train_on_prompt": false,
            "mask_history": false,
            "preprocessing_batch_size": 1000,
            "preprocessing_num_workers": 16,
            "max_samples": null,
            "tool_format": null
        }

examples/qwen2.5vl/finetune_qwen2_5_vl_7b.sh
注意几个参数需按实际需要调整

参数说明
NPUS_PER_NODE每节点使用的卡数,默认8卡
LOAD_PATHmm格式权重ckpt路径
SAVE_PATH训练输出权重ckpt路径
train-iters训练迭代次数,默认10000,实际按照你的数据量和GBS(global batch size)计算
save-interval训练保存中间结果ckpt的间隔,以便测试迭代效果
# 默认使用8卡
NPUS_PER_NODE=8
MASTER_ADDR=localhost
MASTER_PORT=6000
NNODES=1
NODE_RANK=0
WORLD_SIZE=$(($NPUS_PER_NODE*$NNODES))

# 默认加载data_7b.json
MM_DATA="./examples/qwen2.5vl/data_7b.json"
MM_MODEL="./examples/qwen2.5vl/model_7b.json"
MM_TOOL="./mindspeed_mm/tools/tools.json"
# 按需修改你的权重路径
LOAD_PATH="ckpt/mm_path/Qwen2.5-VL-7B-Instruct"
# 按需修改你的ckpt保存路径
SAVE_PATH="output"

# train-iters 按需修改迭代次数,原始代码默认10000次
GPT_ARGS="
    --use-mcore-models \
    --tensor-model-parallel-size ${TP} \
    --pipeline-model-parallel-size ${PP} \
    --context-parallel-size ${CP} \
    --context-parallel-algo ulysses_cp_algo \
    --micro-batch-size ${MBS} \
    --global-batch-size ${GBS} \
    --tokenizer-type NullTokenizer \
    --vocab-size 152064 \
    --seq-length 1024 \
    --make-vocab-size-divisible-by 1 \
    --normalization RMSNorm \
    --use-fused-rmsnorm \
    --swiglu \
    --use-fused-swiglu \
    --no-masked-softmax-fusion \
    --lr 1.0e-5 \
    --lr-decay-style cosine \
    --weight-decay 0 \
    --train-iters 10000 \
    --lr-warmup-fraction 0.1 \
    --clip-grad 0.0 \
    --adam-beta1 0.9 \
    --adam-beta2 0.999 \
    --seed 42 \
    --bf16 \
    --load $LOAD_PATH \
    --use-flash-attn \
    --variable-seq-lengths \
    --use-distributed-optimizer \
    --no-load-optim \
    --no-load-rng \
    --no-save-optim \
    --no-save-rng \
    --num-workers 8 \
    --distributed-timeout-minutes 20 \
"
# 按需修改 save-interval
OUTPUT_ARGS="
    --log-interval 1 \
    --save-interval 10000 \
    --eval-interval 10000 \
    --eval-iters 5000 \
    --save $SAVE_PATH \
    --ckpt-format torch \
    --log-tps \
"

7. 裸机微调使用说明

7.1 加载运行镜像

docker load -i image.tar

docker run -itd  \
--privileged=true \
--name mindspeed_mm \
--network=host \
--pid=host \
--detach=true \
--shm-size=64g \
--device=/dev/davinci0 \
--device=/dev/davinci1 \
--device=/dev/davinci2 \
--device=/dev/davinci3 \
--device=/dev/davinci4 \
--device=/dev/davinci5 \
--device=/dev/davinci6 \
--device=/dev/davinci7 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/sbin:/usr/local/sbin \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /root/.cache:/root/.cache \
-v /tmp:/tmp \
-v /data-VL/:/data-VL/ \
vlm_image:tag /bin/bash

docker exec -it mindspeed_mm bash
# 容器内,工作目录位于MindSpeed-MM/
cd /data-VL/MindSpeed-MM/
# 启动微调
bash examples/qwen2.5vl/finetune_qwen2_5_vl_7b.sh

7.2 日志

观察到如下log说明训练任务成功启动

[2025-10-29 02:21:07] iteration 28/1500 | consumed samples : 5376 | elapsed time per iteration (ms): 7166.4 | learning rate: 1.866667E-06 | global batch size: 192 | loss: 2.061336E-02 | loss scale: 1.0 | grad norm: 1.032 | num zeros: 0.0 | number of skipped iterations:0 | number of nan iterations: 0
[2025-10-29 02:21:14] iteration 29/1500 | consumed samples : 5568 | elapsed time per iteration (ms): 7179.2 | learning rate: 1.933333E-06 | global batch size: 192 | loss: 2.999549E-02 | loss scale: 1.0 | grad norm: 0.712 | num zeros: 0.0 | number of skipped iterations:0 | number of nan iterations: 0

观察到如下log说明训练任务成功结束

[2025-10-29 05:20:20] iteration 1500/1500 | consumed samples : 288000 | elapsed time per iteration (ms): 7342.6 | learning rate: 0.000000E+00 | global batch size: 192 | loss: 3.903987E-07 | loss scale: 1.0 | grad norm: 0.000 | num zeros: 0.0 | number of skipped iterations:0 | number of nan iterations: 0

saving checkpoint at iteration 1500 to ./output in torch format
successfully saved checkpoint from iteration 1500 to ./output [ t 1/1, p 1/2 ]
[after training is done] datetime: 2025-10-29 05:20:32
Elapsed Time Per iteration: 7249.3
Average Samples per Second: 26.485

7.3. 模型格式转换mm_to_hf

微调完的权重如需通过MindIE推理框架部署服务,需转换回hf格式
https://gitcode.com/Ascend/MindSpeed-MM/blob/master/examples/qwen2.5vl/README.md#jump2.3
注意:

参数说明
hf_dir需要放modelscope下载的Qwen2.5-VL-7B-Instruct的目录(保留原始目录文件结构)
save_hf_dir转换后的权重目录,包含safetensor文件,MA部署时直接替换原权重路径即可
mm_dir微调输出的权重ckpt目录,含latest_checkpoint.txt
mm-convert Qwen2_5_VLConverter mm_to_hf \
-- cfg.save_hf_dir "output-mm2hf" \
-- cfg.mm_dir "output" \
-- cfg.hf_config.hf_dir "ckpt/hf_path/Qwen2.5-VL-7B-Instruct" \
-- cfg.parallel_config.1lm_pp_layers [12,16] \
-- cfg.parallel_config.vit_pp_layers [32,0]
-- cfg.parallel_config.tp_size 1

8. MA微调使用说明

8.1 镜像适配MA环境

MA指导文档:https://support.huaweicloud.com/usermanual-standard-modelarts/docker-modelarts_0029.html
参考MA指导文档增加ma-user和ma-group完成docker build,输出的镜像tar文件上传到客户OBS桶,再请MA的操作同事上传到SWR镜像仓库

8.2 准备代码、权重、微调数据

MA环境可以当作一个docker容器,输入输出目录可以理解成OBS远程目录挂载到容器内,环境变量用于传入路径、微调参数,启动指令用来执行完整的微调流程。
所以和裸机一样,我们要先准备好代码、权重、微调数据,上传到OBS。推荐在OBS上新建一个专用目录,例如 s3-bucket-xxx/mindspeed-mm/

子目录说明
MindSpeed-MMMindspeed-MM工程代码和权重
data微调数据
scripts额外编写的处理脚本,例如run.sh
output微调输出mm格式权重的目录
output_mm2hf微调后转换成hf格式的权重保存目录
logMA训练任务日志目录

8.3 创建训练作业

8.3.1 启动指令/脚本

工作目录:/home/ma-user/code
代码目录:/bucket-name/mindspeed-mm/MindSpeed-MM/

启动指令: 训练环境启动后位于工作目录,代码目录会被挂载到工作目录,即 /home/ma-user/code/MindSpeed-MM/

cd MindSpeed-MM && cp $SCRIPT_PATH/* ./ && cp $SCRIPT_PATH/finetune_qwen2_5_vl_7b.sh examples/qwen2.5vl/ && bash run.sh

run.sh

cp -r $DATA_PATH/* data/ && mv data/data_7b.ison examples/qwen2.5vl/ && python llava_2_mllm_format.py --llava-path data/train_llava.json --mllm-path data/train_mllm.json --img-dir data/train_img && bash examples/qwen2.5vl/finetune_qwen2_5_vl_7b.sh && bash mm-convert-mm2hf.sh
  1. 复制训练数据data_7b.json到微调目录
  2. 转换微调数据格式 llava_2_mllm_format.py
  3. 启动微调 finetune_qwen2_5_vl_7b.sh(如果需要开放其他微调参数,请自行修改finetune脚本引入新的环境变量)
  4. 微调结束后将输出权重转换成HF格式 mm-convert-mm2hf.sh

llava_2_mllm_format.py

import argparse
import json
import os
import stat

def convert_llava_to_mllm_format(llava_json_path, mllm_format_json_path, img_dir_path):
    with open(llava_json_path, "r") as f:
        info_json = json.loads(f.read())

    mllm_format_llava_instruct_data = []
    for item in info_json:
        # 处理图像路径
        if not item.get('image'):
            new_item = {
                "images": [],
                "messages": []
            }
        else:
            img_path = os.path.join(img_dir_path, item["image"])
            print(f"img_path: {img_path}")
            if not os.path.exists(img_path):
                continue
            new_item = {
                "images": [img_path],
                "messages": []
            }

        # 处理对话内容
        for turn in item["conversations"]:
            if turn["from"] == "human":
                new_item["messages"].append({"role": "user", "content": turn["value"]})
            elif turn["from"] == "gpt":
                new_item["messages"].append({"role": "assistant", "content": turn["value"]})
            else:
                raise ValueError(f"unknown role: {turn['from']}")
        mllm_format_llava_instruct_data.append(new_item)

    # 写入输出文件
    output_json = json.dumps(mllm_format_llava_instruct_data, ensure_ascii=False)
    if os.path.exists(mllm_format_json_path):
        print(f"{mllm_format_json_path} already exists, please rename it or remove it")
        return
    with os.fdopen(os.open(mllm_format_json_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, stat.S_IWUSR | stat.S_IRUSR), "w") as f:
        f.write(output_json)
    print(f"finish converting dataset into {mllm_format_json_path}")

# 使用示例(可根据实际需求修改路径参数)
if __name__ == "__main__":
    # 解析命令行参数
    parser = argparse.ArgumentParser(description="Convert LLaVA format to MLLM format")
    parser.add_argument("--llava-path", required=True, help="Path to LLaVA JSON file")
    parser.add_argument("--mllm-path", required=True, help="Path to save MLLM format JSON")
    parser.add_argument("--img-dir", required=True, help="Directory containing images")

    args = parser.parse_args()

    # 调用函数
    convert_llava_to_mllm_format(
        llava_json_path=args.llava_path,
        mllm_format_json_path=args.mllm_path,
        img_dir_path=args.img_dir
    )

8.3.2 环境变量

| 变量类型 | 环境变量名称 | 赋值 | | 输入 | DATA_PATH | 训练数据的OBS路径 | | 输入 | SCRIPT_PATH | 训练相关脚本的OBS路径 | | 输出 | OUTPUT | 训练输出mm格式路径 | | 输出 | OUTPUT_MM2HF | 训练输出mm格式转HF格式后的路径 | | 环境变量 | LR | 学习率,默认1.0e-5 | | 环境变量 | SAV_INTERV | 保存ckpt间隔,默认500 | | 环境变量 | TRAIN_ITERS | 迭代轮数,综合考虑数据量,global batch size,loss下降情况而定,默认1500次 | | 环境变量 | WARMUP | 默认0.1 |

注意:不要创建VC_,MA_开头的几个环境变量(MA会根据选择的节点配置自行设定),否则作业任务会卡死在等待状态,无法调用资源。
例如:MA_NUM_HOSTS,VC_TASK_INDEX,VC_WORKER_NUM,VC_WORKER_HOSTS

8.4 训练日志

正常启动训练任务后应能观察到图片的拷贝操作,以及迭代的信息打屏,训练结束后应能观察到hf格式转换的拷贝路径。
注意是否本次的改动都正常生效,如果启动中遇到报错(常见包括文件路径找不到,参数不全等),MA会终止训练作业,通过日志可以分析问题。
如果正常启动,日志输出参考上文裸机日志部分