已关闭
【实践文档】Qwen2.5-VL 接入 Megatron 后端开发实践(组装式接入 + TP/PP 切分 + 权重转换) #481
Ruiyu_Qiu创建于 7月5日关闭于 13 天前
7月7日 关联了看板:MindStudio ISSUE管理
7月8日 关联了里程碑:MindSpeed 26.2.0
yeqm
7月14日 评论:
7月14日 评论:
感谢您对 MindSpeed-MM 社区的贡献!Qwen2.5-VL Megatron 后端开发实践及关联 PR 已收到,后续将由社区 Maintainer 进行审核,请关注评审意见并及时更新。


13 天前 issue状态由 TODO 改变为 DONE
13 天前 关闭了 issue
13 天前 添加了label:resolved
【实践文档】Qwen2.5-VL 接入 Megatron 后端开发实践(组装式接入 + TP/PP 切分 + 权重转换)
1. 迁移对象与目标
源模型为 HF Transformers 的 Qwen2.5-VL(参考实现见
examples/qwen2.5vl/README.md"版本说明":transformerscommit_id=fa56dcc、LLaMA-Factorycommit_id=52f2565)。目标是接入 Megatron 后端:支持 TP/PP、分布式优化器与融合算子,3B/7B/32B/72B 全规格可训。与 FSDP2 后端"每模型一目录"不同,Megatron 后端的模型是组装出来的:
pretrain_vlm.py::model_provider(L35)构建通用组装类VLMModel(mindspeed_mm/models/vlm_model.pyL47),各子模块采用哪个实现,由model.json中对应配置段的model_id查表决定:五个回调(
pretrain_vlm.py:model_providerL35 /get_batchL134 /loss_funcL164 /forward_stepL190 /train_valid_test_datasets_providerL199)与共享训练循环mindspeed_mm/training.py::pretrain(L86)全部复用,接入过程不触碰训练循环。2. 模型接入:组装式复用而非拷贝 modeling
与 FSDP2 路线"拷贝 HF modeling 进仓改造"本质不同:Megatron 后端的模型接入是组装式的——通过查表登记组件,
model.json里的model_id决定组件实现与 layer spec。(a)
model_id查表机制。 两级表都是普通 dict,真实条目如下:# mindspeed_mm/models/vision/vision_model.py L17:组件实现表 VISION_ENCODER_MAPPINGS = { "clip": CLIPViT, "qwen2vit": Qwen2VLViT, # qwen2.5vl 直接复用 qwen2vl 的 ViT 实现 ... } # mindspeed_mm/models/common/module_spec/get_layer_spec.py L13-26:layer spec 表 vit_layer_specs = {'qwen2vit': get_qwen2vl_layer_spec, ...} llm_layer_specs = {'qwen2lm': get_qwen2vl_llm_layer_spec, 'qwen2_5_lm': get_qwen2vl_llm_layer_spec, # 同一函数,零新代码 ...}qwen2_5_lm与qwen2lm指向同一个qwen2vl_layer_spec.py中的函数——qwen2.5vl 的 LLM 侧对 Megatron 而言与 qwen2vl 同构。(b) qwen2.5vl 相对 qwen2vl 的模型侧全部增量。 ViT 的 MLP 从普通 MLP 变为 GLU 结构,这个增量(delta)完全由
examples/qwen2.5vl/model_7b.json的vision_encoder段两个字段表达:"gated_linear_unit": true, "activation_func": "silu",没有新增一行组件代码——Megatron 的 TransformerConfig 原生支持这两个字段。这是"增量迁移"的理想形态:新模型 = 最近的已迁移基线 + 配置化增量。
(c) 全新结构时的接入点。 若目标模型没有可复用的组件:在
VISION_ENCODER_MAPPINGS登记新实现类、在vit_layer_specs/llm_layer_specs登记新 spec 函数,spec 文件以qwen2vl_layer_spec.py为起点复制修改。改动仍收敛于"新组件文件 + 两处 dict 登记"。3. TP/PP 切分与权重转换实践
(a)
pipeline_num_layers语义与 PP 层平衡。model.json中每个模块的pipeline_num_layers数组长度必须等于 PP 数,各 stage 之和必须等于该模块num_layers。7B(TP1/PP2,finetune_qwen2_5_vl_7b.sh)的决策:vit_pp_layers[32, 0]+llm_pp_layers[12, 16]——stage0 承担了整个 ViT,故少分 LLM 层(12 对 16)。VLMModel按此数组推导各 rank 的 pre/post_process(vlm_model.pyL186 附近的判定逻辑)。(b) 转换器跟着模型增量走。
mm-convert即checkpoint/convert_cli.py(pyproject.toml 注册);框架逻辑在checkpoint/vlm_model/hf_to_mm.py/mm_to_hf.py,每模型一个转换器文件。checkpoint/vlm_model/converters/qwen2_5vl.py的注释直接说明了增量与转换器的对应关系:def create_qwen2_5_vl_ops(...) -> List[Operator]: """qwen2.5vl在qwen2vl的基础上vit的mlp变成了glu模式、需要增加合并处理逻辑""" ops = [UpGateMergeOp(...), ...] # 仅新增 gate/up 合并 ops += create_qwen2vl_ops(...) # 其余全部复用 qwen2vl return ops # qwen2.5vl的tp切分在qwen2vl的tp切分基础上,修改了vit中mlp的tp切分逻辑,适应glu结构 qwen2_5_vl_tp_patterns = {**qwen2vl_tp_patterns, **{r"...mlp.linear_fc1.weight": GLUSplit, ...}} # L96-100模型侧增量是 ViT MLP GLU 化,转换器增量就是一组合并 op 加两条 GLUSplit 切分 pattern,其余整体继承。
(c) 转换命令实操。 7B PP2 的离线转换(
examples/qwen2.5vl/README.md"权重转换"节):mm-convert Qwen2_5_VLConverter hf_to_mm \ --cfg.mm_dir "ckpt/mm_path/Qwen2.5-VL-7B-Instruct" \ --cfg.hf_config.hf_dir "ckpt/hf_path/Qwen2.5-VL-7B-Instruct" \ --cfg.parallel_config.llm_pp_layers [[12,16]] \ --cfg.parallel_config.vit_pp_layers [[32,0]] \ --cfg.parallel_config.tp_size 1注意:
llm_pp_layers/vit_pp_layers/tp_size三参数必须与训练脚本的 TP/PP 及model.json的pipeline_num_layers一致。训练后如需重新切分权重,使用mm-convert Qwen2_5_VLConverter resplit(不支持 VPP 场景,README 原文)。(d) 踩坑记录(实测踩中):
bridge_patch: true)目前仅支持 TP 切分方式(README 原文),PP=2 时必须先mm-convert。model.json不一致时,或加载失败,或静默错位。pipeline_num_layers之和错误是静默故障:各 stage 之和与该模块num_layers不一致时不报错,会构建出缺 post_process(最终 layernorm/输出头)的错误结构,到下游才暴露。4. 数据接入:零新代码
qwen2.5vl 数据侧三个组件(数据集构建、对话模板、collator)全部复用,纯
data.json配置:dataset_type: huggingface(通用多模态数据集)+template: qwen2vl+collate_param.model_name: qwen2vl。若模板/collator 需要新增,登记点分别是mindspeed_mm/data/data_utils/func_utils/template.py的_register_template()(qwen2vl 模板在 L325-326 登记)与mindspeed_mm/data/dataloader/data_collator.py的DATA_COLLATOR字典(L681-694)。真实数据管线:COCO2017 train2017(118,287 张图)+ LLaVA-Instruct-150K 标注,经
mindspeed_mm/fsdp/tools/data_tool/llava_instruct_2_mllm_demo_format.py(无 shuffle/random,输出确定性保序)转出mllm_format_llava_instruct_data.json,实测得到 157,712 个样本,无样本被跳过。数据转换踩坑:
5. 配置、启动与跑通(4×910B3 实测)
先用 3B + mock 数据打通全链路(4×Atlas 910B3 64GB):
mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py(用法见docs/zh/features/building_data_for_VLModel.md"使用虚构数据"节),tokenizer 用 3B 原始权重,448×448 单图、文本长 1024、32 样本python3 -m checkpoint.convert_cli Qwen2_5_VLConverter hf_to_mm,llm_pp_layers [[18,18]](36 层均分)、vit_pp_layers [[32,0]]、tp_size 1,产物 7.6GBmodel_3b.json的pipeline_num_layers[32]→[32,0]、[36]→[18,18];脚本 NPUS_PER_NODE 8→4、PP 1→2、GRAD_ACC_STEP 32→8(GBS=16)、train-iters=20,即 4 卡 TP1/PP2/DP2[t 1/1, p 1/2]分片保存成功跑通判定口径与指南 §8 一致:日志按 iteration 持续打印、loss 有限且无 NaN/Inf、grad norm 稳定、无 skipped iteration、checkpoint 能按 TP/PP 分片落盘。
6. 精度对齐记录(方法与结论口径)
结论:保持 TP2/PP2 拓扑不变,4 卡复刻与 8 卡 ST 基线前 2 步 loss 6 位有效数字完全一致(而非仅落入下文 CI 门禁的 1% 容差内);iter3=9.436471 正常下降,grad norm 142.082/115.587/55.825。方法与配置如下。
方法:不与 GPU loss 曲线对比,而是复现仓库 ST 的 loss 基线,以验证训练链路数学等价。ST 用例(
tests/st/shell_scripts/finetune_qwen2_5_vl_7b.sh)为 8 卡 TP2/PP2/CP1、MBS=1、GRAD_ACC_STEP=2(GBS=4)、--train-iters 3、--seed 42、无--load(随机初始化),且开着--use-distributed-optimizer/--use-fused-rmsnorm/--use-fused-swiglu/--use-flash-attn;数据配置max_samples: 20、shuffle: False。复现成立依赖三点:
shuffle: False+ 保序数据保证每步 global batch 内容一致;日常回归由 CI 门禁量化把关(
tests/st/test_tools/test_ci_st.pyL10-12:margin_loss = 0.01、margin_time_percent = 0.05、margin_memory_percent = 0.1)。本文为开发过程实践的方法沉淀,具体 loss 数值以tests/st/baseline_results/当前基线为准。7. 经验总结
llm_pp_layers/vit_pp_layers/tp_size、训练脚本 TP/PP、model.json的pipeline_num_layers三处必须一致;pipeline_num_layers之和错误还是静默故障,排障先查它。参考
docs/zh/features/megatron_developer_migration_guide.md)examples/qwen2.5vl/README.md(权重下载、转换命令、启动步骤)docs/zh/pytorch/weight_conversion.md、docs/zh/features/building_data_for_VLModel.md