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
| 镜像名称 | 描述 | 硬件 | Cann版本 | mindspeed版本 | python版本 | pytorch版本 |
|---|---|---|---|---|---|---|
| XXX.tar | Mindspeed-MM裸机环境镜像 | 800T A2 | 8.1.RC1 | X.X.X | 3.11 | x.x.x |
| XXX-MA.tar | Mindspeed-MM适配MA环境镜像 | 800T A2 | 8.1.RC1 | X.X.X | 3.11 | x.x.x |
下载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/
准备训练用的图片和文本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"}]}]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/
传入训练数据路径,修改:
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_PATH | mm格式权重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 \
"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观察到如下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微调完的权重如需通过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 1MA指导文档:https://support.huaweicloud.com/usermanual-standard-modelarts/docker-modelarts_0029.html
参考MA指导文档增加ma-user和ma-group完成docker build,输出的镜像tar文件上传到客户OBS桶,再请MA的操作同事上传到SWR镜像仓库
MA环境可以当作一个docker容器,输入输出目录可以理解成OBS远程目录挂载到容器内,环境变量用于传入路径、微调参数,启动指令用来执行完整的微调流程。
所以和裸机一样,我们要先准备好代码、权重、微调数据,上传到OBS。推荐在OBS上新建一个专用目录,例如 s3-bucket-xxx/mindspeed-mm/
| 子目录 | 说明 |
|---|---|
| MindSpeed-MM | Mindspeed-MM工程代码和权重 |
| data | 微调数据 |
| scripts | 额外编写的处理脚本,例如run.sh |
| output | 微调输出mm格式权重的目录 |
| output_mm2hf | 微调后转换成hf格式的权重保存目录 |
| log | MA训练任务日志目录 |
工作目录:/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.shrun.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.shllava_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
)| 变量类型 | 环境变量名称 | 赋值 | | 输入 | 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
正常启动训练任务后应能观察到图片的拷贝操作,以及迭代的信息打屏,训练结束后应能观察到hf格式转换的拷贝路径。
注意是否本次的改动都正常生效,如果启动中遇到报错(常见包括文件路径找不到,参数不全等),MA会终止训练作业,通过日志可以分析问题。
如果正常启动,日志输出参考上文裸机日志部分