本文档记录在 MindSpeed-MM 上为 Qwen3-VL 系列模型 (Qwen3-VL-8B / Qwen3-VL-32B Dense 模型与 Qwen3-VL-235B-A22B MoE 模型) 新增纯 FSDP2(native FSDP2)后端全参 SFT 支持的完整实践过程,包括开发模型实践(模型代码迁移、配置开发、权重转换、精度对齐、Ulysses CP / Ring Attention 序列并行开发、EP 专家并行测试)与性能优化实践(显存触顶导致步时波动的定位与解决、loss 计算 logits 显存尖刺定位与 Chunk Loss 优化、单步性能拆解)两部分。其中 235B 为 MoE 模型,复用仓上已有的 MoE 模型实现,无模型代码改动,开发工作为脚本/配置开发与精度对齐。
本次适配充分复用纯 FSDP2 后端仓上现有特性——Ulysses CP / Ring Attention 序列并行、EP 专家并行、Chunk Loss、激活重计算(recompute)、异步激活卸载(async activation offload)——在 Qwen3-VL 系列模型上全部完成适配:开启后均取得显存/性能优化收益,且精度对齐全部验证达标(loss/norm 相对误差 <1%)。逐项汇总见下文「特性支持与效果总览」。
对标实现为仓上已有的 Megatron + FSDP2 后端:
examples/qwen3vl/finetune_qwen3vl_8B.sh
examples/qwen3vl/finetune_qwen3vl_8B_v1.sh
examples/qwen3vl/qwen3vl_full_sft_8B.yaml
examples/qwen3vl/qwen3vl_8B_config_v1.yaml
examples/qwen3vl/finetune_qwen3vl_32B.sh
examples/qwen3vl/finetune_qwen3vl_32B_v1.sh
examples/qwen3vl/qwen3vl_full_sft_32B.yaml
examples/qwen3vl/qwen3vl_32B_config_v1.yaml
examples/qwen3vl/finetune_qwen3vl_235B.sh
examples/qwen3vl/finetune_qwen3vl_235B_v1.sh
examples/qwen3vl/qwen3vl_full_sft_235B.yaml
examples/qwen3vl/qwen3vl_235B_config_v1.yaml
各模型的其余维度一致:训练入口 pretrain_transformers.py → mindspeed_mm/fsdp/train/trainer.py;模型实现 mindspeed_mm/models/transformers/qwen3vl/ → mindspeed_mm/fsdp/models/qwen3vl/(8B/32B 共用同一 Dense 实现;235B 复用已有 MoE 实现 mindspeed_mm/fsdp/models/qwen3vl_moe/);配置风格 Megatron 参数 + YAML(gpt_args)→ 纯六段式 YAML(parallel/model/data/features/training/tools)。
pretrain_transformers.py
mindspeed_mm/fsdp/train/trainer.py
mindspeed_mm/models/transformers/qwen3vl/
mindspeed_mm/fsdp/models/qwen3vl/
mindspeed_mm/fsdp/models/qwen3vl_moe/
本文档的适配工作以「复用纯 FSDP2 后端仓上现有特性」为原则:CP、EP、Chunk Loss、重计算、异步激活卸载等特性均不为 Qwen3-VL 新增框架机制,模型侧只做接入点适配、配置侧开关即用。六个特性在 Qwen3-VL 系列上全部适配完成,开启后均体现显存/性能优化,且精度对齐全部验证达标,一览如下:
features.recompute
recompute_plan
enable_chunk_loss
chunkloss_plan
enable_activation_offload
activation_offload_plan
ulysses_parallel_size
ring_attention_size
expert_parallel_size
ep_plan
精度对齐统一方法:与对标基线(1.7 为 Megatron + FSDP2 后端,1.8.5 / 1.9 为特性关闭配置)用 TrainingLogParser 按 ["loss:", "norm:"] 逐 step 对比,均满足相对误差 <1% 的对齐要求。
["loss:", "norm:"]
本次开发的核心改动如下:
mindspeed_mm/fsdp/models/qwen3vl/modeling_qwen3_vl.py
qwen3_vl
qwen3_vl_moe
qwen3vl/
mindspeed_mm/fsdp/models/qwen3vl/npu_patch.py
finetune_qwen3vl_32B_v1.sh
finetune_qwen3vl_235B_v1.sh
qwen3vl_32B_config_v1.yaml
qwen3vl_235B_config_v1.yaml
目录拆分的原因:此前 mindspeed_mm/fsdp/models/qwen3vl/ 目录下只有 MoE 版实现(modeling_qwen3_vl_moe.py,注册 id qwen3_vl_moe),仅覆盖 Qwen3-VL-30B-A3B/235B-A22B 等 MoE 模型。Qwen3-VL-8B/32B 为 Dense 结构,不含 MoE 专家层,无法复用 MoE 版实现,因此新增 Dense 版 modeling_qwen3_vl.py,并将 MoE 版迁至独立的 qwen3vl_moe/ 目录,使两个目录各自对应一种模型结构:
modeling_qwen3_vl_moe.py
modeling_qwen3_vl.py
qwen3vl_moe/
mindspeed_mm/fsdp/models/ ├── qwen3vl/ # Dense 版(Qwen3-VL-8B/32B) │ ├── modeling_qwen3_vl.py # @model_register.register("qwen3_vl") │ └── npu_patch.py # apply_qwen3vl_npu_patch() └── qwen3vl_moe/ # MoE 版(Qwen3-VL-30B-A3B/235B-A22B) ├── modeling_qwen3_vl_moe.py# @model_register.register("qwen3_vl_moe") └── npu_patch.py # apply_qwen3vl_moe_npu_patch()
mindspeed_mm/fsdp/models/qwen3vl/modeling_qwen3_vl.py 参考 HuggingFace transformers 的 Qwen3-VL 官方实现(commitid 保持与 examples/qwen3vl/README_v1.md 中要求一致),改造为适配纯 FSDP2 后端,8B/32B 共用,关键开发点如下。Qwen3-VL-235B-A22B(MoE)复用已就绪的 mindspeed_mm/fsdp/models/qwen3vl_moe/ 实现(含下文的 CP 适配与融合 MoE 算子 patch),无模型代码改动:
examples/qwen3vl/README_v1.md
注册机制:顶层模型类通过装饰器注册到 FSDP2 后端的模型注册表,注册 id 与 YAML 中 model.model_id 对应:
model.model_id
from mindspeed_mm.fsdp.utils.register import model_register @model_register.register("qwen3_vl") class Qwen3VLForConditionalGeneration(Qwen3VLPreTrainedModel, GenerationMixin): ...
注册装饰器只有在对应包被 import_plugin 导入时才会执行,因此 YAML 的 training.plugin 中必须列出 mindspeed_mm/fsdp/models/qwen3vl。
import_plugin
training.plugin
mindspeed_mm/fsdp/models/qwen3vl
模块结构:实现为自包含文件,包括:
Qwen3VLVisionPatchEmbed
Qwen3VLVisionAttention
Qwen3VLVisionBlock
Qwen3VLVisionPatchMerger
Qwen3VLVisionRotaryEmbedding
Qwen3VLTextAttention
Qwen3VLTextMLP
Qwen3VLTextDecoderLayer
Qwen3VLTextRMSNorm
Qwen3VLTextRotaryEmbedding
Qwen3VLModel
Qwen3VLForConditionalGeneration
重计算支持:Qwen3VLVisionBlock 和 Qwen3VLTextDecoderLayer 继承 transformers 的 GradientCheckpointingLayer,使 YAML 中 features.recompute_plan.apply_modules 可以按模块粒度开启激活重计算。
GradientCheckpointingLayer
features.recompute_plan.apply_modules
异步激活卸载(activation offload)支持:模型结构按异步激活卸载特性的接入约定组织,使 YAML 中 features.enable_activation_offload: true + activation_offload_plan 可以直接生效:
features.enable_activation_offload: true
model.visual.blocks
model.language_model.layers
nn.ModuleList
activation_offload_plan.apply_modules
{*}
FeaturesApplier
block_idx
depth
hidden_states
with_async_save_on_cpu
特性原理、参数与典型收益详见 异步激活卸载。
forward 接口对齐数据pipeline:forward 接收 data collator 产出的 batch keys(input_ids、attention_mask、position_ids、labels、pixel_values、image_grid_thw 等),当 labels 非空时在模型内部通过 loss_function 计算 loss,配合 YAML 中 features.loss_cfg 使用。
forward
input_ids
attention_mask
position_ids
labels
pixel_values
image_grid_thw
loss_function
features.loss_cfg
NPU 融合算子 patch(npu_patch.py::apply_qwen3vl_npu_patch):将热点小算子替换为昇腾融合实现,减少算子数、提升执行效率(替换清单与 MoE 版的专家层融合详见 2.4 NPU 融合算子适配):
npu_patch.py::apply_qwen3vl_npu_patch
apply_rotary_pos_emb_vision
npu_fused_operator.apply_transformers_vision_rope_half_npu
apply_rotary_pos_emb
npu_fused_operator.apply_transformers_rope_half_npu
Qwen3VLTextRMSNorm.forward
npu_fused_operator.rms_norm_forward_npu
MoE 版目录的 patch 额外包含 Qwen3VLMoeTextExperts.forward 的融合 MoE 替换,Dense 版无专家层,因此不需要。
Qwen3VLMoeTextExperts.forward
纯 FSDP2 后端由单一六段式 YAML 驱动(examples/qwen3vl/qwen3vl_8B_config_v1.yaml / qwen3vl_32B_config_v1.yaml / qwen3vl_235B_config_v1.yaml),三个模型的配置结构一致,各段关键配置与设计考虑如下:
(1)parallel —— 并行与分片策略
parallel: tensor_parallel_size: 1 # TP当前代码未实现,令TP=1 fully_shard_parallel_size: auto # 自动按设备数确定分片组大小 fsdp_plan: apply_modules: # 需与 model.named_modules() 实际名称严格一致 - model.visual.blocks.{*} - model.visual.merger - model.visual.deepstack_merger_list.{*} - model.visual - model.language_model.embed_tokens - model.language_model.layers.{*} - model.language_model - lm_head param_dtype: bf16 # 参数 bf16 分片 reduce_dtype: fp32 # 梯度规约 fp32,保证数值精度
分片粒度按「视觉 block / merger、文本单层、embedding、lm_head」划分,通信桶大小适中,有利于通信与计算 overlap。32B 模型参数量更大(text 64 层、hidden 25600),FSDP 参数分片 + bf16 参数/fp32 规约是其能在 8 卡上训练的基础。
(2)data —— 数据模块
data: dataset_param: dataset_type: huggingface # 复用已有 huggingface 数据集插件 attr: { images: images, messages: messages, role_tag: role, ... } basic_parameters: cutoff_len: 16384 # 序列长度 16k template: qwen3_vl_nothink # 非思考模式模板 enable_thinking: false dataloader_param: dataloader_mode: sampler sampler_type: BaseRandomBatchSampler num_workers: 8 pin_memory: true collate_param: { model_name: qwen3vl, ignore_pad_token_for_loss: true } enable_preload: true # 数据预取,掩盖cpu get_batch和H2D拷贝
数据侧基本完全复用库上现有代码,无需新增 dataset/template/collator;attr 将 ShareGPT 风格原始字段映射到框架字段。
attr
(3)model / features / training / tools
model: model_id: qwen3_vl # 对应 @model_register.register("qwen3_vl") attn_implementation: flash_attention_2 freeze: [model.visual] # SFT 冻结视觉编码器 features: loss_cfg: { loss_type: default } recompute: true # 配置重计算 recompute_plan: apply_modules: - model.visual.blocks.{*} - model.language_model.layers.{*} enable_chunk_loss: true # chunk loss,消除 loss 计算的 logits 显存尖刺(见二、性能优化实践) chunkloss_plan: apply_module: lm_head chunk_size: 512 enable_activation_offload: true # 配置异步激活卸载 activation_offload_plan: apply_modules: - model.visual.blocks.{*} - model.language_model.layers.{*} training: micro_batch_size: 1 optimizer: adamw adam_fused: true # 昇腾融合 AdamW init_model_with_meta_device: true # meta 初始化,避免全量权重驻留 CPU 内存 load: <Qwen3-VL-*-Instruct-dcp> # DCP 权重路径(release 的上一级目录) plugin: # 导入插件式注册的model和data - mindspeed_mm/fsdp/models/qwen3vl - mindspeed_mm/fsdp/data/datasets/huggingface tools: profile: # 采集 profiling,用于性能分析 enable: true profile_type: static ranks: [0] static_param: { level: level1, with_memory: true, record_shapes: true, start_step: 10, end_step: 11, aic_metrics_type: PipeUtilization }
(4)Qwen3-VL-235B(MoE)配置差异
235B 为 MoE 模型,复用已就绪的 qwen3vl_moe 模型实现,其配置(qwen3vl_235B_config_v1.yaml)相对 8B/32B 的差异点如下:
qwen3vl_moe
mindspeed_mm/fsdp/models/qwen3vl_moe
parallel.expert_parallel_size
1
parallel.ep_plan.apply_modules
model.language_model.layers.{*}.mlp.experts
parallel.ep_plan.dispatcher
alltoall
mc2
allgather
training.use_deter_comp
true
纯 FSDP2 后端使用 DCP(PyTorch Distributed Checkpoint)格式权重。meta 初始化(init_model_with_meta_device: true)训练前,需将 HuggingFace 权重转换为 DCP:
init_model_with_meta_device: true
source /usr/local/Ascend/ascend-toolkit/set_env.sh # Qwen3-VL-8B mm-convert GenericDCPConverter hf_to_dcp \ --hf_dir ckpt/hf_path/Qwen3-VL-8B-Instruct \ --dcp_dir ckpt/dcp_path/Qwen3-VL-8B-Instruct-dcp # Qwen3-VL-32B / Qwen3-VL-235B-A22B 同理,替换为对应路径 mm-convert GenericDCPConverter hf_to_dcp \ --hf_dir ckpt/hf_path/Qwen3-VL-32B-Instruct \ --dcp_dir ckpt/dcp_path/Qwen3-VL-32B-Instruct-dcp mm-convert GenericDCPConverter hf_to_dcp \ --hf_dir ckpt/hf_path/Qwen3-VL-235B-A22B-Instruct \ --dcp_dir ckpt/dcp_path/Qwen3-VL-235B-A22B-Instruct-dcp # 转换后目录结构: # Qwen3-VL-*-Instruct-dcp/ # ├── release/ # └── latest_checkpointed_iteration.txt
YAML 中 training.load 配置为 release 文件夹的上一级目录。训练完成后如需导出 HF 权重用于推理,使用 dcp_to_hf 子命令反向转换。
training.load
release
dcp_to_hf
mllm_format_llava_instruct_data.json
mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py
启动脚本 examples/qwen3vl/finetune_qwen3vl_8B_v1.sh(32B 为 finetune_qwen3vl_32B_v1.sh,235B 为 finetune_qwen3vl_235B_v1.sh)核心内容:
export NON_MEGATRON=true # 必须:启用纯 FSDP2 所需的算子适配 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export MULTI_STREAM_MEMORY_REUSE=2 export TASK_QUEUE_ENABLE=2 export CPU_AFFINITY_CONF=1 export ASCEND_LAUNCH_BLOCKING=0 export ACLNN_CACHE_LIMIT=100000 export HCCL_CONNECT_TIMEOUT=7200 torchrun $DISTRIBUTED_ARGS mindspeed_mm/fsdp/train/trainer.py \ examples/qwen3vl/qwen3vl_8B_config_v1.yaml # 32B/235B 分别替换为 qwen3vl_32B_config_v1.yaml / qwen3vl_235B_config_v1.yaml
两个后端的环境变量差异:纯 FSDP2 后端必须设置 NON_MEGATRON=true,且不再需要 Megatron 侧的 CUDA_DEVICE_MAX_CONNECTIONS。
NON_MEGATRON=true
CUDA_DEVICE_MAX_CONNECTIONS
各模型数据集均使用 README 中提供的 COCO 数据集(mllm_format_llava_instruct_data.json),分别运行 Megatron + FSDP2 后端基线脚本与本次新增的纯 FSDP2 后端脚本;其中 8B/32B 在平台申请 8 卡,各长跑 2000 步。
对齐方法:使用精度对齐工具 TrainingLogParser,选取默认关键词 ["loss:", "norm:"] 解析两份训练日志并逐 step 对比。
8B 不减层运行全层模型,基线为 finetune_qwen3vl_8B.sh,本次新增为 finetune_qwen3vl_8B_v1.sh。对齐结果:
finetune_qwen3vl_8B.sh
finetune_qwen3vl_8B_v1.sh
结论:loss 相对误差保持在 1% 以内,纯 FSDP2 后端与 Megatron + FSDP2 后端达到精度对齐,满足精度对齐要求。
由于 32B 模型尺寸较大,全层跑测 8 卡必现 OOM,因此精度对齐采用减层训练,两个后端使用相同的减层配置(text 32 层 + vision depth 13,全层为 text 64 层 + vision depth 27),基线为 finetune_qwen3vl_32B.sh,本次新增为 finetune_qwen3vl_32B_v1.sh:
finetune_qwen3vl_32B.sh
# 纯 FSDP2 后端:mindspeed_mm/fsdp/models/modelhub.py transformer_config.text_config.num_hidden_layers = 32 transformer_config.vision_config.depth = 13 # Megatron + FSDP2 后端:mindspeed_mm/models/transformers_model.py self.transformer_config.text_config.num_hidden_layers = 32 self.transformer_config.vision_config.depth = 13
对齐结果:
平台申请 8 卡,基线为 finetune_qwen3vl_235B.sh,本次新增为 finetune_qwen3vl_235B_v1.sh。由于 235B 模型尺寸较大,全层跑测 8 卡必现 OOM,因此精度对齐采用减层训练,两个后端使用相同的减层配置(text 2 层 + vision depth 1):
finetune_qwen3vl_235B.sh
# 纯 FSDP2 后端:mindspeed_mm/fsdp/models/modelhub.py transformer_config.text_config.num_hidden_layers = 2 transformer_config.vision_config.depth = 1 # Megatron + FSDP2 后端:mindspeed_mm/models/transformers_model.py self.transformer_config.text_config.num_hidden_layers = 2 self.transformer_config.vision_config.depth = 1
两者长跑 2000 步。对齐结果:
结论:loss 相对误差小于 1%,绝对误差小于 1%,纯 FSDP2 后端与 Megatron + FSDP2 后端达到精度对齐,满足精度对齐要求。
高分辨率图片/视频输入使 VLM 的序列长度快速增长,注意力计算的显存开销随序列长度近似按 O(S²) 上升,长序列训练需要序列并行(Context Parallel,CP)。仓上 Qwen3-VL-MoE(30B)已在纯 FSDP2 后端打通 Ulysses CP(头维 All-to-All)与 Ring Attention(序列切分 + ring 组 P2P)及其混合模式,本次将该能力扩展到 Dense 系列(8B/32B)。两个特性的原理、框架侧实现与通信原语详见 Ulysses / Ring Attention 特性实现梳理,本节聚焦 Dense 模型的开发过程。
CP 的实现分为框架侧与模型侧两部分:
parallel
parallel_args.py
parallel_state.py
communication.py
ops/flash_attn/flash_attn.py
flash_attention_2
loss_func.py
mindspeed_mm/fsdp/models/qwen3vl_moe/modeling_qwen3_vl_moe.py
Modification
get_seq_len
set_seq_len
total
visual
per_visual
total_seq_len
rotary_pos_emb
packed_data_split_forward_gather_backward_with_cp
cu_seqlens
seq_split_lens
ring_in_bnsd=False
gather_forward_split_backward
masked_scatter
ring_in_bnsd=True
is_causal=True
attention_mask=None
set_seq_len("total")
text_position_ids
inputs_embeds
_deepstack_process
visual_embeds
其中第 9 点在 Dense 版顶层 forward 中已具备,本次补齐第 1–8 点。移植完成后与 MoE 参考实现做了 CP 原语使用量的逐项比对,除 MoE 特有的路由辅助损失适配(Dense 无专家层,不需要)外完全一致。
qwen3vl_8B_config_v1.yaml / qwen3vl_32B_config_v1.yaml 的 parallel 段已包含开关(默认 1,即关闭),按需调大即可:
qwen3vl_8B_config_v1.yaml
parallel: tensor_parallel_size: 1 fully_shard_parallel_size: auto # 需满足 >= ring_attention_size × ulysses_parallel_size ring_attention_size: 2 # >1 开启 Ring Attention(仅 NPU 支持) ulysses_parallel_size: 2 # >1 开启 Ulysses CP;两者同时 >1 即混合 CP
关键约束:
world_size
ring_attention_size × ulysses_parallel_size
2 × ring_attention_size
num_query_heads % ulysses_parallel_size == 0
序列长度 / cp_size > 8k
测试方案:使用 COCO 数据集,分别运行 CP 关闭基线(ring_attention_size: 1、ulysses_parallel_size: 1)与开启 CP 的纯 FSDP2 配置,使用 TrainingLogParser 选取默认关键词 ["loss:", "norm:"] 逐 step 对比 loss/norm 曲线。各模型均采用减层配置、跑测 100 步:8B/32B 减层 LLM 16 层 + ViT 7 层、GBS=4,基线为 4 卡(4 die)、开启 CP 的配置为 8 卡(8 die);235B 沿用 1.7 精度对齐的减层配置(LLM 2 层 + ViT 1 层)、GBS=8,基线为 8 卡(8 die)、开启 CP 的配置为 16 卡(16 die)。
ring_attention_size: 1
ulysses_parallel_size: 1
1. Ulysses CP 精度对比
Qwen3-VL-8B 减层(LLM 16 层、ViT 7 层),GBS=4,跑测 100 步:
Qwen3-VL-32B 减层(LLM 16 层、ViT 7 层),GBS=4,跑测 100 步:
Qwen3-VL-235B 减层(LLM 2 层、ViT 1 层),GBS=8,跑测 100 步:
2. Ring Attention 精度对比
结论:Ulysses CP 与 Ring Attention 两种配置下,8B/32B/235B 的 loss/norm 曲线均与 CP 关闭基线对齐,满足精度对齐要求。
235B 为 MoE 模型,专家层(model.language_model.layers.{*}.mlp.experts)在纯 FSDP2 后端支持专家并行(Expert Parallel,EP):expert_parallel_size > 1 时,框架按 ep_plan.apply_modules 将专家参数切分至 ep 组内各 rank(每 rank 仅持有部分专家),token 按路由结果经 ep_plan.dispatcher 指定的通信方式在 ep 组内分发,专家计算完成后汇聚回原 rank。交付配置 qwen3vl_235B_config_v1.yaml 的 parallel 段已包含 EP 入口(默认 expert_parallel_size: 1,即关闭),调大即可启用:
expert_parallel_size > 1
ep_plan.apply_modules
ep_plan.dispatcher
expert_parallel_size: 1
parallel: expert_parallel_size: 1 # >1 开启 EP ep_plan: apply_modules: - model.language_model.layers.{*}.mlp.experts dispatcher: alltoall # 可选 alltoall / mc2 / allgather
测试方案:沿用 1.7 精度对齐的减层配置(LLM 2 层 + ViT 1 层),GBS=8,跑测 100 步,使用 TrainingLogParser 选取默认关键词 ["loss:", "norm:"] 与 EP 关闭基线逐 step 对比 loss/norm 曲线:
结论:expert_parallel_size=2 的 loss/norm 曲线与 EP 关闭基线对齐,满足精度对齐要求。与 1.8.5 中 CP 测试(开启后卡数翻倍至 16 die)不同,EP 是在原设备网格内对专家参数重新划分,world_size 保持不变,EP=2 与基线同样在 8 卡上跑测。
expert_parallel_size=2
性能测试采用减层模型 + mock 数据集控制变量,排除真实数据加载波动与全层模型资源占用对问题定位的干扰。之所以不使用 COCO 数据集做性能测试:COCO 序列长度较短(通常在 1k 以下),无法对 16k 长序列场景施压。
减层配置:在模型构建处修改 transformer_config(测试完成后需还原):
transformer_config
# 纯 FSDP2 后端:mindspeed_mm/fsdp/models/modelhub.py transformer_config.text_config.num_hidden_layers = 32 # 8B 全层为 36,32B 全层为 64 transformer_config.vision_config.depth = 13 # 全层为 27 # Megatron + FSDP2 后端:mindspeed_mm/models/transformers_model.py(如需对比测试) transformer_config.text_config.num_hidden_layers = 32 transformer_config.vision_config.depth = 13
mock 数据集:使用库上数据生成脚本 mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py 生成 16k 序列长度数据集(8B/32B 性能测试共用):
source /usr/local/Ascend/ascend-toolkit/set_env.sh SAVE_DIR=/home/data/datasets/ mkdir -p $SAVE_DIR python mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py \ --tokenizer_path /usr/local/weights/Qwen3-VL-8B-Instruct/ \ --pic_width 1024 \ --pic_height 1024 \ --num_pics 10 \ --text_length 16384 \ --num_samples 128 \ --save_dir $SAVE_DIR
生成文件的实际命名与 --num_pics/--text_length/--num_samples 取值相关,请与 YAML 中 data.dataset_param.basic_parameters.dataset 路径保持一致(本次测试使用 mock_data_pic_num_10_text_len_16384_sample_num_128.json)。
--num_pics
--text_length
--num_samples
data.dataset_param.basic_parameters.dataset
mock_data_pic_num_10_text_len_16384_sample_num_128.json
本次测试使用的优化特性配置与训练配置如下(以 Qwen3-VL-8B 测试期配置为例,chunk loss 为优化过程中开启,见 2.2.5;32B 显存压力更大,其交付配置默认开启 chunk loss 与覆盖视觉 block 的激活 offload,并开启确定性计算 use_deter_comp: true):
use_deter_comp: true
# 优化特性配置 features: loss_cfg: loss_type: default # 使用模型自带 loss 计算 router_aux_loss_coef: 0.0 # Dense 模型无路由辅助损失 recompute: true # 开启激活重计算 recompute_plan: apply_modules: - model.visual.blocks.{*} - model.language_model.layers.{*} enable_chunk_loss: false # 本阶段未开启 chunk loss chunkloss_plan: apply_module: lm_head chunk_size: 1024 enable_activation_offload: true # 开启激活 offload activation_offload_plan: # 激活 offload 作用范围:文本 decoder 层 apply_modules: - model.language_model.layers.{*} # 训练配置 training: micro_batch_size: 1 gradient_accumulation_steps: 1 seed: 42 lr: 1.0e-5 lr_decay_style: cosine lr_warmup_ratio: 0.1 weight_decay: 0 train_iters: 2000 clip_grad: 0.0 init_model_with_meta_device: true # meta 初始化,配合 DCP 权重加载 optimizer: adamw adam_fused: true # 昇腾融合 AdamW save_interval: 10000 use_deter_comp: false plugin: # 必须包含所有带 @register 装饰器的包 - mindspeed_mm/fsdp/models/qwen3vl - mindspeed_mm/fsdp/data/datasets/huggingface
减层配置初始采用 text 32 层 + vision depth 13,训练过程中观察到:
单步时间变化:
采集 rank0 单步 profiling(YAML tools.profile,static 模式、level1、with_memory: true),在 timeline 中发现一段较长的 Free 空隙:
tools.profile
with_memory: true
向上追溯该 Free 段对应的 runtime 调用,发现是 aclrtFreePhysical:
aclrtFreePhysical
原因分析:aclrtFreePhysical 接口用于释放通过 aclrtMallocPhysical 申请的物理内存。显存触顶后,ACL 触发了物理内存的重整/回收管理,该过程在训练主流程中表现为同步的 Free 等待,从而导致单步时间出现约 1000 ms 的波动。
aclrtMallocPhysical
措施:为显存预留余量,将减层配置从 32/13 调整为 text 24 层 + vision depth 13:
transformer_config.text_config.num_hidden_layers = 24 transformer_config.vision_config.depth = 13
效果:单步时间波动从约 1000 ms 降至约 40 ms,训练恢复平稳:
对 24/13 配置采集 rank0 单步 profiling:
单步总耗时拆解为 Free + Communication(Not Overlapped) + Computing = 4463.33 ms:
结论:显存余量充足后,aclrtFreePhysical 引起的长 Free 消失(Free 占比仅 1.54%),FSDP2 通信基本被计算掩盖(未掩盖通信仅 1.39%),任务呈计算密集型特征,NPU 利用率极高。当前配置下性能瓶颈在计算本身,通信与内存管理均非瓶颈。
减层虽然规避了显存触顶,但也限制了可训练的模型规模。要进一步支持全层训练,需要找到并消除显存占用的大头。
现象:查看内存快照发现,中间部分存在明显的显存尖刺:
尖刺位置正好处于前向和反向的中间位置,推测是计算 loss 时产生了较大的中间变量。如果能消除此尖刺,即可在 8 卡上全层运行 qwen3vl-8b。
定位:查看 loss 计算代码发现,在计算 loss 之前会产生一个临时张量 logits:
logits
在此处打断点查看其 shape:
logits 的 shape 达到 [1, 16384, 151936],即 (B, S, V):16k 序列长度下,单个 logits 张量元素数约 24.9 亿(bf16 约 4.6 GiB,计算 loss 时若升为 float32 占用翻倍),这正是显存尖刺的来源。
优化措施:对照仓上 qwen3vl MoE 模型的代码实现,并参考 Chunk Loss 特性文档,在模型代码中适配 chunk loss:
并在 YAML 中开启:
features: enable_chunk_loss: true chunkloss_plan: apply_module: lm_head chunk_size: 512
其原理是将 tensor 在序列维度切分成长度为 512 的 sub_seq 子序列分段进行 loss 计算,避免一次性生成大张量 logits。
优化效果:开启 chunk loss 后重新实验,不减层也可以在 8 卡上很好地跑下 qwen3vl-8b 全层对 16k 数据集的训练:
结论:chunk loss 消除了 loss 计算产生的大 logits 中间张量,峰值显存大幅下降,使 qwen3vl-8b 无需减层即可在 8 卡上进行 16k 序列长度的全层训练,足见 chunk loss 带来的显存优化作用巨大。
32B 性能测试复用 8B 生成的 16k mock 数据集。由于模型更大,使用 16k 数据集需要进一步减层:经测试当 language_model 层数置为 12、visual 层数置为 7(12/7)时可勉强跑下。
在 12/7 减层配置下训练,仍出现了在 Qwen3-VL-8B 中遇到过的内存重整问题:
采集 rank0 单步 profiling,可以观察到存在多段 Free:
向上追溯这些 Free 段对应的 runtime 调用,发现是 aclrtFreePhysical 和 aclrtGetMemInfo:
aclrtGetMemInfo
图:Free 段对应 aclrtFreePhysical 和 aclrtGetMemInfo 调用(qwen3vl_32B_fsdp2_12_7_16k_aclrtFree.png)
原因分析:由 8B 的实践已知 aclrtFreePhysical 是在释放物理内存;查阅 CANN 接口文档,aclrtGetMemInfo 用于获取 Device 上应用可用内存的空闲大小和总大小。两者交替出现,说明显存再次触顶后 ACL 反复进行内存重整管理,原因与 8B 相同。
措施:进一步下调减层配置为 text 8 层 + vision depth 3:
transformer_config.text_config.num_hidden_layers = 8 transformer_config.vision_config.depth = 3
效果:单步时间相较之前变得非常均匀:
对 8/3 配置采集 rank0 单步 profiling:
单步总耗时拆解为 Free + Communication(Not Overlapped) + Computing = 3771.3 ms:
结论:任务呈计算密集型特征,NPU 利用率极高。与 8B 相比,32B 未掩盖通信占比略高(3.24% vs 1.39%),符合模型更大、单层通信量更多的预期,但整体仍以计算为主,通信与内存管理均非瓶颈。
8/3 虽然稳定,但可训练层数被压得过低。查看内存快照发现,与 8B 相同,中间部分同样存在明显的显存尖刺:
其根因与 8B 一致:loss 计算前产生的临时张量 logits 在 16k 序列长度下达到 (B, S, V) 量级的大张量(定位过程详见 2.2.5)。
优化措施:应用 chunk loss 并在 YAML 中开启(配置同 2.2.5:apply_module: lm_head、chunk_size: 512),将 tensor 在序列维度切分成长度为 512 的 sub_seq 子序列分段进行 loss 计算,避免一次性生成大张量 logits。
apply_module: lm_head
chunk_size: 512
优化效果:开启 chunk loss 后重新实验,将 language_model 层数置为 32、visual 层数置为 13(32/13),即可在 8 卡上很好地跑下 qwen3vl-32b 对 16k 数据集的训练:
结论:从在 8 卡上只能跑下 8 层 llm 和 3 层 vit,到应用 chunk loss 后可以跑下足足 32 层 llm 和 13 层 vit,可训练层数提升约 4 倍,足见 chunk loss 带来的显存优化作用巨大。
除上述显存类优化外,计算侧对模型中的热点小算子做了 NPU 融合算子适配:将原始 PyTorch 实现替换为昇腾融合算子,减少算子数量与 kernel 启动开销,提升执行效率。适配位于各模型目录的 npu_patch.py,modeling 文件末尾在检测到 NPU 环境(IS_NPU_AVAILABLE)时自动调用 apply_*_npu_patch() 完成模块级函数替换,模型前向代码无需改动,随 training.plugin 导入模型包时生效。
npu_patch.py
IS_NPU_AVAILABLE
apply_*_npu_patch()
Dense 版(qwen3vl/npu_patch.py::apply_qwen3vl_npu_patch,8B/32B 共用):
qwen3vl/npu_patch.py::apply_qwen3vl_npu_patch
torch_npu.npu_rms_norm
MoE 版(qwen3vl_moe/npu_patch.py::apply_qwen3vl_moe_npu_patch,30B/235B):在上述三类算子(对应 MoE 类 Qwen3VLMoeTextRMSNorm)替换的基础上,额外将专家层前向 Qwen3VLMoeTextExperts.forward 替换为 npu_fused_operator.fused_moe_forward_npu,把 permute → grouped matmul(gate/up)→ SwiGLU → grouped matmul(down)→ unpermute 的 MoE 计算链路整体以融合算子执行。
qwen3vl_moe/npu_patch.py::apply_qwen3vl_moe_npu_patch
Qwen3VLMoeTextRMSNorm
npu_fused_operator.fused_moe_forward_npu
fsdp_plan
num_to_forward_prefetch
num_to_backward_prefetch
新增: examples/qwen3vl/finetune_qwen3vl_8B_v1.sh examples/qwen3vl/finetune_qwen3vl_32B_v1.sh examples/qwen3vl/finetune_qwen3vl_235B_v1.sh examples/qwen3vl/qwen3vl_8B_config_v1.yaml examples/qwen3vl/qwen3vl_32B_config_v1.yaml examples/qwen3vl/qwen3vl_235B_config_v1.yaml mindspeed_mm/fsdp/models/qwen3vl/modeling_qwen3_vl.py # Dense 实现,注册 id qwen3_vl,同时支持 8B/32B mindspeed_mm/fsdp/models/qwen3vl_moe/(modeling_qwen3_vl_moe.py、npu_patch.py,由 qwen3vl/ 迁出) 修改: mindspeed_mm/fsdp/models/qwen3vl/npu_patch.py # patch 切换为 Dense 版 参考(未改动): examples/qwen3vl/finetune_qwen3vl_8B.sh / qwen3vl_full_sft_8B.yaml # Megatron + FSDP2 对标实现 examples/qwen3vl/finetune_qwen3vl_32B.sh / qwen3vl_full_sft_32B.yaml # Megatron + FSDP2 对标实现 examples/qwen3vl/finetune_qwen3vl_235B.sh / qwen3vl_full_sft_235B.yaml # Megatron + FSDP2 对标实现
Qwen3-VL 模型 FSDP2 后端训练开发实践
本文档记录在 MindSpeed-MM 上为 Qwen3-VL 系列模型 (Qwen3-VL-8B / Qwen3-VL-32B Dense 模型与 Qwen3-VL-235B-A22B MoE 模型) 新增纯 FSDP2(native FSDP2)后端全参 SFT 支持的完整实践过程,包括开发模型实践(模型代码迁移、配置开发、权重转换、精度对齐、Ulysses CP / Ring Attention 序列并行开发、EP 专家并行测试)与性能优化实践(显存触顶导致步时波动的定位与解决、loss 计算 logits 显存尖刺定位与 Chunk Loss 优化、单步性能拆解)两部分。其中 235B 为 MoE 模型,复用仓上已有的 MoE 模型实现,无模型代码改动,开发工作为脚本/配置开发与精度对齐。
本次适配充分复用纯 FSDP2 后端仓上现有特性——Ulysses CP / Ring Attention 序列并行、EP 专家并行、Chunk Loss、激活重计算(recompute)、异步激活卸载(async activation offload)——在 Qwen3-VL 系列模型上全部完成适配:开启后均取得显存/性能优化收益,且精度对齐全部验证达标(loss/norm 相对误差 <1%)。逐项汇总见下文「特性支持与效果总览」。
对标实现为仓上已有的 Megatron + FSDP2 后端:
examples/qwen3vl/finetune_qwen3vl_8B.shexamples/qwen3vl/finetune_qwen3vl_8B_v1.shexamples/qwen3vl/qwen3vl_full_sft_8B.yamlexamples/qwen3vl/qwen3vl_8B_config_v1.yamlexamples/qwen3vl/finetune_qwen3vl_32B.shexamples/qwen3vl/finetune_qwen3vl_32B_v1.shexamples/qwen3vl/qwen3vl_full_sft_32B.yamlexamples/qwen3vl/qwen3vl_32B_config_v1.yamlexamples/qwen3vl/finetune_qwen3vl_235B.shexamples/qwen3vl/finetune_qwen3vl_235B_v1.shexamples/qwen3vl/qwen3vl_full_sft_235B.yamlexamples/qwen3vl/qwen3vl_235B_config_v1.yaml各模型的其余维度一致:训练入口
pretrain_transformers.py→mindspeed_mm/fsdp/train/trainer.py;模型实现mindspeed_mm/models/transformers/qwen3vl/→mindspeed_mm/fsdp/models/qwen3vl/(8B/32B 共用同一 Dense 实现;235B 复用已有 MoE 实现mindspeed_mm/fsdp/models/qwen3vl_moe/);配置风格 Megatron 参数 + YAML(gpt_args)→ 纯六段式 YAML(parallel/model/data/features/training/tools)。目录
特性支持与效果总览
本文档的适配工作以「复用纯 FSDP2 后端仓上现有特性」为原则:CP、EP、Chunk Loss、重计算、异步激活卸载等特性均不为 Qwen3-VL 新增框架机制,模型侧只做接入点适配、配置侧开关即用。六个特性在 Qwen3-VL 系列上全部适配完成,开启后均体现显存/性能优化,且精度对齐全部验证达标,一览如下:
features.recompute+recompute_plan)enable_chunk_loss+chunkloss_plan)enable_activation_offload+activation_offload_plan)ulysses_parallel_size)ring_attention_size)expert_parallel_size+ep_plan)一、开发模型实践
1.1 改动总览
本次开发的核心改动如下:
mindspeed_mm/fsdp/models/qwen3vl/modeling_qwen3_vl.pyqwen3_vlmindspeed_mm/fsdp/models/qwen3vl_moe/qwen3_vl_moe)从qwen3vl/目录迁出,独立成目录,代码逻辑不变;235B 直接复用,无模型代码改动mindspeed_mm/fsdp/models/qwen3vl/npu_patch.pyexamples/qwen3vl/finetune_qwen3vl_8B_v1.sh、finetune_qwen3vl_32B_v1.sh、finetune_qwen3vl_235B_v1.shexamples/qwen3vl/qwen3vl_8B_config_v1.yaml、qwen3vl_32B_config_v1.yaml、qwen3vl_235B_config_v1.yaml目录拆分的原因:此前
mindspeed_mm/fsdp/models/qwen3vl/目录下只有 MoE 版实现(modeling_qwen3_vl_moe.py,注册 idqwen3_vl_moe),仅覆盖 Qwen3-VL-30B-A3B/235B-A22B 等 MoE 模型。Qwen3-VL-8B/32B 为 Dense 结构,不含 MoE 专家层,无法复用 MoE 版实现,因此新增 Dense 版modeling_qwen3_vl.py,并将 MoE 版迁至独立的qwen3vl_moe/目录,使两个目录各自对应一种模型结构:1.2 模型代码开发
mindspeed_mm/fsdp/models/qwen3vl/modeling_qwen3_vl.py参考 HuggingFace transformers 的 Qwen3-VL 官方实现(commitid 保持与examples/qwen3vl/README_v1.md中要求一致),改造为适配纯 FSDP2 后端,8B/32B 共用,关键开发点如下。Qwen3-VL-235B-A22B(MoE)复用已就绪的mindspeed_mm/fsdp/models/qwen3vl_moe/实现(含下文的 CP 适配与融合 MoE 算子 patch),无模型代码改动:注册机制:顶层模型类通过装饰器注册到 FSDP2 后端的模型注册表,注册 id 与 YAML 中
model.model_id对应:from mindspeed_mm.fsdp.utils.register import model_register @model_register.register("qwen3_vl") class Qwen3VLForConditionalGeneration(Qwen3VLPreTrainedModel, GenerationMixin): ...注册装饰器只有在对应包被
import_plugin导入时才会执行,因此 YAML 的training.plugin中必须列出mindspeed_mm/fsdp/models/qwen3vl。模块结构:实现为自包含文件,包括:
Qwen3VLVisionPatchEmbed(Conv3d 分块)、Qwen3VLVisionAttention、Qwen3VLVisionBlock、Qwen3VLVisionPatchMerger(含 deepstack merger)、Qwen3VLVisionRotaryEmbedding;Qwen3VLTextAttention、Qwen3VLTextMLP、Qwen3VLTextDecoderLayer、Qwen3VLTextRMSNorm、Qwen3VLTextRotaryEmbedding;Qwen3VLModel(视觉特征与文本 embedding 融合、3D RoPE position_ids 处理)与Qwen3VLForConditionalGeneration(lm_head + loss)。重计算支持:
Qwen3VLVisionBlock和Qwen3VLTextDecoderLayer继承 transformers 的GradientCheckpointingLayer,使 YAML 中features.recompute_plan.apply_modules可以按模块粒度开启激活重计算。异步激活卸载(activation offload)支持:模型结构按异步激活卸载特性的接入约定组织,使 YAML 中
features.enable_activation_offload: true+activation_offload_plan可以直接生效:model.visual.blocks与model.language_model.layers均为同构 block 堆叠(nn.ModuleList),activation_offload_plan.apply_modules通过{*}模式逐 block 匹配,框架侧FeaturesApplier为每个 block 分配全局编号(block_idx/depth)并包装其 forward,按 block 组织激活的生命周期与反向预取顺序;Qwen3VLVisionBlock与Qwen3VLTextDecoderLayer的 forward 第一个位置参数统一为hidden_states张量,满足包装器with_async_save_on_cpu的约定(取第一个位置参数作为 hidden_states,仅卸载与其共享数据指针的保存张量,即 block 入口激活);GradientCheckpointingLayer,offload 可与重计算叠加——offload 将重计算入口的 block 激活异步卸载到 host 侧(D2H/H2D 走独立 swap 流,被计算掩盖,反向时 prefetch 提前加载),重计算丢弃 block 内部中间激活,共同降低峰值显存。特性原理、参数与典型收益详见 异步激活卸载。
forward 接口对齐数据pipeline:
forward接收 data collator 产出的 batch keys(input_ids、attention_mask、position_ids、labels、pixel_values、image_grid_thw等),当labels非空时在模型内部通过loss_function计算 loss,配合 YAML 中features.loss_cfg使用。NPU 融合算子 patch(
npu_patch.py::apply_qwen3vl_npu_patch):将热点小算子替换为昇腾融合实现,减少算子数、提升执行效率(替换清单与 MoE 版的专家层融合详见 2.4 NPU 融合算子适配):apply_rotary_pos_emb_vision(视觉 RoPE)npu_fused_operator.apply_transformers_vision_rope_half_npuapply_rotary_pos_emb(文本 RoPE)npu_fused_operator.apply_transformers_rope_half_npuQwen3VLTextRMSNorm.forwardnpu_fused_operator.rms_norm_forward_npuMoE 版目录的 patch 额外包含
Qwen3VLMoeTextExperts.forward的融合 MoE 替换,Dense 版无专家层,因此不需要。1.3 训练配置开发
纯 FSDP2 后端由单一六段式 YAML 驱动(
examples/qwen3vl/qwen3vl_8B_config_v1.yaml/qwen3vl_32B_config_v1.yaml/qwen3vl_235B_config_v1.yaml),三个模型的配置结构一致,各段关键配置与设计考虑如下:(1)parallel —— 并行与分片策略
parallel: tensor_parallel_size: 1 # TP当前代码未实现,令TP=1 fully_shard_parallel_size: auto # 自动按设备数确定分片组大小 fsdp_plan: apply_modules: # 需与 model.named_modules() 实际名称严格一致 - model.visual.blocks.{*} - model.visual.merger - model.visual.deepstack_merger_list.{*} - model.visual - model.language_model.embed_tokens - model.language_model.layers.{*} - model.language_model - lm_head param_dtype: bf16 # 参数 bf16 分片 reduce_dtype: fp32 # 梯度规约 fp32,保证数值精度分片粒度按「视觉 block / merger、文本单层、embedding、lm_head」划分,通信桶大小适中,有利于通信与计算 overlap。32B 模型参数量更大(text 64 层、hidden 25600),FSDP 参数分片 + bf16 参数/fp32 规约是其能在 8 卡上训练的基础。
(2)data —— 数据模块
data: dataset_param: dataset_type: huggingface # 复用已有 huggingface 数据集插件 attr: { images: images, messages: messages, role_tag: role, ... } basic_parameters: cutoff_len: 16384 # 序列长度 16k template: qwen3_vl_nothink # 非思考模式模板 enable_thinking: false dataloader_param: dataloader_mode: sampler sampler_type: BaseRandomBatchSampler num_workers: 8 pin_memory: true collate_param: { model_name: qwen3vl, ignore_pad_token_for_loss: true } enable_preload: true # 数据预取,掩盖cpu get_batch和H2D拷贝数据侧基本完全复用库上现有代码,无需新增 dataset/template/collator;
attr将 ShareGPT 风格原始字段映射到框架字段。(3)model / features / training / tools
model: model_id: qwen3_vl # 对应 @model_register.register("qwen3_vl") attn_implementation: flash_attention_2 freeze: [model.visual] # SFT 冻结视觉编码器 features: loss_cfg: { loss_type: default } recompute: true # 配置重计算 recompute_plan: apply_modules: - model.visual.blocks.{*} - model.language_model.layers.{*} enable_chunk_loss: true # chunk loss,消除 loss 计算的 logits 显存尖刺(见二、性能优化实践) chunkloss_plan: apply_module: lm_head chunk_size: 512 enable_activation_offload: true # 配置异步激活卸载 activation_offload_plan: apply_modules: - model.visual.blocks.{*} - model.language_model.layers.{*} training: micro_batch_size: 1 optimizer: adamw adam_fused: true # 昇腾融合 AdamW init_model_with_meta_device: true # meta 初始化,避免全量权重驻留 CPU 内存 load: <Qwen3-VL-*-Instruct-dcp> # DCP 权重路径(release 的上一级目录) plugin: # 导入插件式注册的model和data - mindspeed_mm/fsdp/models/qwen3vl - mindspeed_mm/fsdp/data/datasets/huggingface tools: profile: # 采集 profiling,用于性能分析 enable: true profile_type: static ranks: [0] static_param: { level: level1, with_memory: true, record_shapes: true, start_step: 10, end_step: 11, aic_metrics_type: PipeUtilization }(4)Qwen3-VL-235B(MoE)配置差异
235B 为 MoE 模型,复用已就绪的
qwen3vl_moe模型实现,其配置(qwen3vl_235B_config_v1.yaml)相对 8B/32B 的差异点如下:model.model_idqwen3_vl_moetraining.pluginmindspeed_mm/fsdp/models/qwen3vl_moeqwen3vl_moe包parallel.expert_parallel_size1parallel.ep_plan.apply_modulesmodel.language_model.layers.{*}.mlp.expertsparallel.ep_plan.dispatcheralltoallalltoall/mc2/allgathertraining.use_deter_comptrue1.4 权重转换
纯 FSDP2 后端使用 DCP(PyTorch Distributed Checkpoint)格式权重。meta 初始化(
init_model_with_meta_device: true)训练前,需将 HuggingFace 权重转换为 DCP:source /usr/local/Ascend/ascend-toolkit/set_env.sh # Qwen3-VL-8B mm-convert GenericDCPConverter hf_to_dcp \ --hf_dir ckpt/hf_path/Qwen3-VL-8B-Instruct \ --dcp_dir ckpt/dcp_path/Qwen3-VL-8B-Instruct-dcp # Qwen3-VL-32B / Qwen3-VL-235B-A22B 同理,替换为对应路径 mm-convert GenericDCPConverter hf_to_dcp \ --hf_dir ckpt/hf_path/Qwen3-VL-32B-Instruct \ --dcp_dir ckpt/dcp_path/Qwen3-VL-32B-Instruct-dcp mm-convert GenericDCPConverter hf_to_dcp \ --hf_dir ckpt/hf_path/Qwen3-VL-235B-A22B-Instruct \ --dcp_dir ckpt/dcp_path/Qwen3-VL-235B-A22B-Instruct-dcp # 转换后目录结构: # Qwen3-VL-*-Instruct-dcp/ # ├── release/ # └── latest_checkpointed_iteration.txtYAML 中
training.load配置为release文件夹的上一级目录。训练完成后如需导出 HF 权重用于推理,使用dcp_to_hf子命令反向转换。1.5 数据集准备
mllm_format_llava_instruct_data.json,参考 针对VL模型的数据构造 · 使用真实数据集。mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py,示例命令见 2.1 测试配置。1.6 启动训练
启动脚本
examples/qwen3vl/finetune_qwen3vl_8B_v1.sh(32B 为finetune_qwen3vl_32B_v1.sh,235B 为finetune_qwen3vl_235B_v1.sh)核心内容:export NON_MEGATRON=true # 必须:启用纯 FSDP2 所需的算子适配 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export MULTI_STREAM_MEMORY_REUSE=2 export TASK_QUEUE_ENABLE=2 export CPU_AFFINITY_CONF=1 export ASCEND_LAUNCH_BLOCKING=0 export ACLNN_CACHE_LIMIT=100000 export HCCL_CONNECT_TIMEOUT=7200 torchrun $DISTRIBUTED_ARGS mindspeed_mm/fsdp/train/trainer.py \ examples/qwen3vl/qwen3vl_8B_config_v1.yaml # 32B/235B 分别替换为 qwen3vl_32B_config_v1.yaml / qwen3vl_235B_config_v1.yaml两个后端的环境变量差异:纯 FSDP2 后端必须设置
NON_MEGATRON=true,且不再需要 Megatron 侧的CUDA_DEVICE_MAX_CONNECTIONS。1.7 精度对齐
各模型数据集均使用 README 中提供的 COCO 数据集(
mllm_format_llava_instruct_data.json),分别运行 Megatron + FSDP2 后端基线脚本与本次新增的纯 FSDP2 后端脚本;其中 8B/32B 在平台申请 8 卡,各长跑 2000 步。对齐方法:使用精度对齐工具 TrainingLogParser,选取默认关键词
["loss:", "norm:"]解析两份训练日志并逐 step 对比。Qwen3-VL-8B(不减层)
8B 不减层运行全层模型,基线为
finetune_qwen3vl_8B.sh,本次新增为finetune_qwen3vl_8B_v1.sh。对齐结果:结论:loss 相对误差保持在 1% 以内,纯 FSDP2 后端与 Megatron + FSDP2 后端达到精度对齐,满足精度对齐要求。
Qwen3-VL-32B(减层 32/13)
由于 32B 模型尺寸较大,全层跑测 8 卡必现 OOM,因此精度对齐采用减层训练,两个后端使用相同的减层配置(text 32 层 + vision depth 13,全层为 text 64 层 + vision depth 27),基线为
finetune_qwen3vl_32B.sh,本次新增为finetune_qwen3vl_32B_v1.sh:# 纯 FSDP2 后端:mindspeed_mm/fsdp/models/modelhub.py transformer_config.text_config.num_hidden_layers = 32 transformer_config.vision_config.depth = 13 # Megatron + FSDP2 后端:mindspeed_mm/models/transformers_model.py self.transformer_config.text_config.num_hidden_layers = 32 self.transformer_config.vision_config.depth = 13对齐结果:
结论:loss 相对误差保持在 1% 以内,纯 FSDP2 后端与 Megatron + FSDP2 后端达到精度对齐,满足精度对齐要求。
Qwen3-VL-235B(MoE,减层 2/1)
平台申请 8 卡,基线为
finetune_qwen3vl_235B.sh,本次新增为finetune_qwen3vl_235B_v1.sh。由于 235B 模型尺寸较大,全层跑测 8 卡必现 OOM,因此精度对齐采用减层训练,两个后端使用相同的减层配置(text 2 层 + vision depth 1):# 纯 FSDP2 后端:mindspeed_mm/fsdp/models/modelhub.py transformer_config.text_config.num_hidden_layers = 2 transformer_config.vision_config.depth = 1 # Megatron + FSDP2 后端:mindspeed_mm/models/transformers_model.py self.transformer_config.text_config.num_hidden_layers = 2 self.transformer_config.vision_config.depth = 1两者长跑 2000 步。对齐结果:
结论:loss 相对误差小于 1%,绝对误差小于 1%,纯 FSDP2 后端与 Megatron + FSDP2 后端达到精度对齐,满足精度对齐要求。
1.8 Ulysses CP 与 Ring Attention 序列并行开发
1.8.1 开发背景
高分辨率图片/视频输入使 VLM 的序列长度快速增长,注意力计算的显存开销随序列长度近似按 O(S²) 上升,长序列训练需要序列并行(Context Parallel,CP)。仓上 Qwen3-VL-MoE(30B)已在纯 FSDP2 后端打通 Ulysses CP(头维 All-to-All)与 Ring Attention(序列切分 + ring 组 P2P)及其混合模式,本次将该能力扩展到 Dense 系列(8B/32B)。两个特性的原理、框架侧实现与通信原语详见 Ulysses / Ring Attention 特性实现梳理,本节聚焦 Dense 模型的开发过程。
1.8.2 开发思路:框架复用、对标 MoE 参考实现移植
CP 的实现分为框架侧与模型侧两部分:
parallel段参数校验(parallel_args.py)、5 维 device mesh 与进程组构建(parallel_state.py)、通信原语(communication.py的 all_to_all / split / gather / packed 系列)、CP 感知的 flash attention 算子(ops/flash_attn/flash_attn.py,已替换 transformers 的flash_attention_2实现,内含 ulysses-only / ring-only / 混合三条路径)、loss 的 CP 切分(loss_func.py)。这些组件均与模型结构无关,Dense 模型可直接使用。mindspeed_mm/fsdp/models/qwen3vl_moe/modeling_qwen3_vl_moe.py中这些位置均以Modification注释标记且已验证,因此 Dense 版采取对标 MoE 参考实现逐项移植的方式,在mindspeed_mm/fsdp/models/qwen3vl/modeling_qwen3_vl.py中完成适配。1.8.3 模型侧适配点
get_seq_len/set_seq_len(total/visual/per_visual三类),在模型 forward 与注意力算子间传递序列形状信息(注意力内部依赖total_seq_len决定 All-to-All 聚合尺寸)hidden_states与rotary_pos_emb经packed_data_split_forward_gather_backward_with_cp按每张图片独立切分(ring 逐样本切、ulysses 整体切);cu_seqlens移至 CPU 避免 NPU 上 host-device 同步;开启 ring 时按本 ring rank 重算cu_seqlenstotal_seq_len(视觉 token 总长)与seq_split_lens(per-image 的 ring 非均匀切分尺寸)、ring_in_bnsd=Falsegather_forward_split_backward聚合为完整视觉嵌入(后续按 image_token 掩码masked_scatter进文本 embedding 需要全局序列视图)ring_in_bnsd=True、is_causal=True、total_seq_lenattention_mask=None;ulysses 的cu_seqlens参数须在序列切分前由完整 position_ids 生成;set_seq_len("total")后对position_ids/text_position_ids/inputs_embeds做 CP 切分(先 ring zigzag 负载均衡切分,后 ulysses 切分)_deepstack_process先 gathervisual_embeds与hidden_states→ 按掩码位置叠加视觉特征 → 再切分回去gather_forward_split_backward聚合后求和(补偿 FSDP 在含 CP ranks 的分片组上的梯度平均)其中第 9 点在 Dense 版顶层 forward 中已具备,本次补齐第 1–8 点。移植完成后与 MoE 参考实现做了 CP 原语使用量的逐项比对,除 MoE 特有的路由辅助损失适配(Dense 无专家层,不需要)外完全一致。
1.8.4 使用方式
qwen3vl_8B_config_v1.yaml/qwen3vl_32B_config_v1.yaml的parallel段已包含开关(默认 1,即关闭),按需调大即可:parallel: tensor_parallel_size: 1 fully_shard_parallel_size: auto # 需满足 >= ring_attention_size × ulysses_parallel_size ring_attention_size: 2 # >1 开启 Ring Attention(仅 NPU 支持) ulysses_parallel_size: 2 # >1 开启 Ulysses CP;两者同时 >1 即混合 CP关键约束:
world_size须为ring_attention_size × ulysses_parallel_size的整数倍;flash_attention_2(CP 逻辑在被替换的注意力实现中);2 × ring_attention_size的整数倍(zigzag 负载均衡切分要求);num_query_heads % ulysses_parallel_size == 0(8B 为 32 头、32B 为 64 头,需整除);序列长度 / cp_size > 8k以获得最佳效果,序列过短时通信可能无法被计算掩盖。1.8.5 精度对齐
测试方案:使用 COCO 数据集,分别运行 CP 关闭基线(
ring_attention_size: 1、ulysses_parallel_size: 1)与开启 CP 的纯 FSDP2 配置,使用 TrainingLogParser 选取默认关键词["loss:", "norm:"]逐 step 对比 loss/norm 曲线。各模型均采用减层配置、跑测 100 步:8B/32B 减层 LLM 16 层 + ViT 7 层、GBS=4,基线为 4 卡(4 die)、开启 CP 的配置为 8 卡(8 die);235B 沿用 1.7 精度对齐的减层配置(LLM 2 层 + ViT 1 层)、GBS=8,基线为 8 卡(8 die)、开启 CP 的配置为 16 卡(16 die)。对齐结果:
1. Ulysses CP 精度对比
Qwen3-VL-8B 减层(LLM 16 层、ViT 7 层),GBS=4,跑测 100 步:
Qwen3-VL-32B 减层(LLM 16 层、ViT 7 层),GBS=4,跑测 100 步:
Qwen3-VL-235B 减层(LLM 2 层、ViT 1 层),GBS=8,跑测 100 步:
2. Ring Attention 精度对比
Qwen3-VL-8B 减层(LLM 16 层、ViT 7 层),GBS=4,跑测 100 步:
Qwen3-VL-32B 减层(LLM 16 层、ViT 7 层),GBS=4,跑测 100 步:
Qwen3-VL-235B 减层(LLM 2 层、ViT 1 层),GBS=8,跑测 100 步:
结论:Ulysses CP 与 Ring Attention 两种配置下,8B/32B/235B 的 loss/norm 曲线均与 CP 关闭基线对齐,满足精度对齐要求。
1.9 EP 专家并行测试(Qwen3-VL-235B)
235B 为 MoE 模型,专家层(
model.language_model.layers.{*}.mlp.experts)在纯 FSDP2 后端支持专家并行(Expert Parallel,EP):expert_parallel_size > 1时,框架按ep_plan.apply_modules将专家参数切分至 ep 组内各 rank(每 rank 仅持有部分专家),token 按路由结果经ep_plan.dispatcher指定的通信方式在 ep 组内分发,专家计算完成后汇聚回原 rank。交付配置qwen3vl_235B_config_v1.yaml的parallel段已包含 EP 入口(默认expert_parallel_size: 1,即关闭),调大即可启用:parallel: expert_parallel_size: 1 # >1 开启 EP ep_plan: apply_modules: - model.language_model.layers.{*}.mlp.experts dispatcher: alltoall # 可选 alltoall / mc2 / allgather测试方案:沿用 1.7 精度对齐的减层配置(LLM 2 层 + ViT 1 层),GBS=8,跑测 100 步,使用 TrainingLogParser 选取默认关键词
["loss:", "norm:"]与 EP 关闭基线逐 step 对比 loss/norm 曲线:结论:
expert_parallel_size=2的 loss/norm 曲线与 EP 关闭基线对齐,满足精度对齐要求。与 1.8.5 中 CP 测试(开启后卡数翻倍至 16 die)不同,EP 是在原设备网格内对专家参数重新划分,world_size保持不变,EP=2 与基线同样在 8 卡上跑测。二、性能优化实践
2.1 测试配置
性能测试采用减层模型 + mock 数据集控制变量,排除真实数据加载波动与全层模型资源占用对问题定位的干扰。之所以不使用 COCO 数据集做性能测试:COCO 序列长度较短(通常在 1k 以下),无法对 16k 长序列场景施压。
减层配置:在模型构建处修改
transformer_config(测试完成后需还原):# 纯 FSDP2 后端:mindspeed_mm/fsdp/models/modelhub.py transformer_config.text_config.num_hidden_layers = 32 # 8B 全层为 36,32B 全层为 64 transformer_config.vision_config.depth = 13 # 全层为 27 # Megatron + FSDP2 后端:mindspeed_mm/models/transformers_model.py(如需对比测试) transformer_config.text_config.num_hidden_layers = 32 transformer_config.vision_config.depth = 13mock 数据集:使用库上数据生成脚本
mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py生成 16k 序列长度数据集(8B/32B 性能测试共用):source /usr/local/Ascend/ascend-toolkit/set_env.sh SAVE_DIR=/home/data/datasets/ mkdir -p $SAVE_DIR python mindspeed_mm/fsdp/tools/data_tool/generate_mock_data_for_vlmodel.py \ --tokenizer_path /usr/local/weights/Qwen3-VL-8B-Instruct/ \ --pic_width 1024 \ --pic_height 1024 \ --num_pics 10 \ --text_length 16384 \ --num_samples 128 \ --save_dir $SAVE_DIR本次测试使用的优化特性配置与训练配置如下(以 Qwen3-VL-8B 测试期配置为例,chunk loss 为优化过程中开启,见 2.2.5;32B 显存压力更大,其交付配置默认开启 chunk loss 与覆盖视觉 block 的激活 offload,并开启确定性计算
use_deter_comp: true):# 优化特性配置 features: loss_cfg: loss_type: default # 使用模型自带 loss 计算 router_aux_loss_coef: 0.0 # Dense 模型无路由辅助损失 recompute: true # 开启激活重计算 recompute_plan: apply_modules: - model.visual.blocks.{*} - model.language_model.layers.{*} enable_chunk_loss: false # 本阶段未开启 chunk loss chunkloss_plan: apply_module: lm_head chunk_size: 1024 enable_activation_offload: true # 开启激活 offload activation_offload_plan: # 激活 offload 作用范围:文本 decoder 层 apply_modules: - model.language_model.layers.{*} # 训练配置 training: micro_batch_size: 1 gradient_accumulation_steps: 1 seed: 42 lr: 1.0e-5 lr_decay_style: cosine lr_warmup_ratio: 0.1 weight_decay: 0 train_iters: 2000 clip_grad: 0.0 init_model_with_meta_device: true # meta 初始化,配合 DCP 权重加载 optimizer: adamw adam_fused: true # 昇腾融合 AdamW save_interval: 10000 use_deter_comp: false plugin: # 必须包含所有带 @register 装饰器的包 - mindspeed_mm/fsdp/models/qwen3vl - mindspeed_mm/fsdp/data/datasets/huggingface2.2 Qwen3-VL-8B 性能优化
2.2.1 问题:显存触顶导致单步时间大幅波动
减层配置初始采用 text 32 层 + vision depth 13,训练过程中观察到:
单步时间变化:
2.2.2 Profiling 定位
采集 rank0 单步 profiling(YAML
tools.profile,static 模式、level1、with_memory: true),在 timeline 中发现一段较长的 Free 空隙:向上追溯该 Free 段对应的 runtime 调用,发现是
aclrtFreePhysical:原因分析:
aclrtFreePhysical接口用于释放通过aclrtMallocPhysical申请的物理内存。显存触顶后,ACL 触发了物理内存的重整/回收管理,该过程在训练主流程中表现为同步的 Free 等待,从而导致单步时间出现约 1000 ms 的波动。2.2.3 优化措施与效果
措施:为显存预留余量,将减层配置从 32/13 调整为 text 24 层 + vision depth 13:
transformer_config.text_config.num_hidden_layers = 24 transformer_config.vision_config.depth = 13效果:单步时间波动从约 1000 ms 降至约 40 ms,训练恢复平稳:
2.2.4 单步性能拆解
对 24/13 配置采集 rank0 单步 profiling:
单步总耗时拆解为 Free + Communication(Not Overlapped) + Computing = 4463.33 ms:
结论:显存余量充足后,aclrtFreePhysical 引起的长 Free 消失(Free 占比仅 1.54%),FSDP2 通信基本被计算掩盖(未掩盖通信仅 1.39%),任务呈计算密集型特征,NPU 利用率极高。当前配置下性能瓶颈在计算本身,通信与内存管理均非瓶颈。
2.2.5 loss 计算显存尖刺定位与 Chunk Loss 优化
减层虽然规避了显存触顶,但也限制了可训练的模型规模。要进一步支持全层训练,需要找到并消除显存占用的大头。
现象:查看内存快照发现,中间部分存在明显的显存尖刺:
尖刺位置正好处于前向和反向的中间位置,推测是计算 loss 时产生了较大的中间变量。如果能消除此尖刺,即可在 8 卡上全层运行 qwen3vl-8b。
定位:查看 loss 计算代码发现,在计算 loss 之前会产生一个临时张量
logits:在此处打断点查看其 shape:
logits的 shape 达到 [1, 16384, 151936],即 (B, S, V):16k 序列长度下,单个 logits 张量元素数约 24.9 亿(bf16 约 4.6 GiB,计算 loss 时若升为 float32 占用翻倍),这正是显存尖刺的来源。优化措施:对照仓上 qwen3vl MoE 模型的代码实现,并参考 Chunk Loss 特性文档,在模型代码中适配 chunk loss:
并在 YAML 中开启:
features: enable_chunk_loss: true chunkloss_plan: apply_module: lm_head chunk_size: 512其原理是将 tensor 在序列维度切分成长度为 512 的 sub_seq 子序列分段进行 loss 计算,避免一次性生成大张量 logits。
优化效果:开启 chunk loss 后重新实验,不减层也可以在 8 卡上很好地跑下 qwen3vl-8b 全层对 16k 数据集的训练:
结论:chunk loss 消除了 loss 计算产生的大 logits 中间张量,峰值显存大幅下降,使 qwen3vl-8b 无需减层即可在 8 卡上进行 16k 序列长度的全层训练,足见 chunk loss 带来的显存优化作用巨大。
2.3 Qwen3-VL-32B 性能优化
32B 性能测试复用 8B 生成的 16k mock 数据集。由于模型更大,使用 16k 数据集需要进一步减层:经测试当 language_model 层数置为 12、visual 层数置为 7(12/7)时可勉强跑下。
2.3.1 问题:内存重整问题复现,训练时间大幅波动
在 12/7 减层配置下训练,仍出现了在 Qwen3-VL-8B 中遇到过的内存重整问题:
2.3.2 Profiling 定位
采集 rank0 单步 profiling,可以观察到存在多段 Free:
向上追溯这些 Free 段对应的 runtime 调用,发现是
aclrtFreePhysical和aclrtGetMemInfo:图:Free 段对应 aclrtFreePhysical 和 aclrtGetMemInfo 调用(qwen3vl_32B_fsdp2_12_7_16k_aclrtFree.png)
原因分析:由 8B 的实践已知
aclrtFreePhysical是在释放物理内存;查阅 CANN 接口文档,aclrtGetMemInfo用于获取 Device 上应用可用内存的空闲大小和总大小。两者交替出现,说明显存再次触顶后 ACL 反复进行内存重整管理,原因与 8B 相同。2.3.3 优化措施与效果
措施:进一步下调减层配置为 text 8 层 + vision depth 3:
transformer_config.text_config.num_hidden_layers = 8 transformer_config.vision_config.depth = 3效果:单步时间相较之前变得非常均匀:
2.3.4 单步性能拆解
对 8/3 配置采集 rank0 单步 profiling:
单步总耗时拆解为 Free + Communication(Not Overlapped) + Computing = 3771.3 ms:
结论:任务呈计算密集型特征,NPU 利用率极高。与 8B 相比,32B 未掩盖通信占比略高(3.24% vs 1.39%),符合模型更大、单层通信量更多的预期,但整体仍以计算为主,通信与内存管理均非瓶颈。
2.3.5 Chunk Loss 优化
8/3 虽然稳定,但可训练层数被压得过低。查看内存快照发现,与 8B 相同,中间部分同样存在明显的显存尖刺:
其根因与 8B 一致:loss 计算前产生的临时张量
logits在 16k 序列长度下达到 (B, S, V) 量级的大张量(定位过程详见 2.2.5)。优化措施:应用 chunk loss 并在 YAML 中开启(配置同 2.2.5:
apply_module: lm_head、chunk_size: 512),将 tensor 在序列维度切分成长度为 512 的 sub_seq 子序列分段进行 loss 计算,避免一次性生成大张量 logits。优化效果:开启 chunk loss 后重新实验,将 language_model 层数置为 32、visual 层数置为 13(32/13),即可在 8 卡上很好地跑下 qwen3vl-32b 对 16k 数据集的训练:
结论:从在 8 卡上只能跑下 8 层 llm 和 3 层 vit,到应用 chunk loss 后可以跑下足足 32 层 llm 和 13 层 vit,可训练层数提升约 4 倍,足见 chunk loss 带来的显存优化作用巨大。
2.4 NPU 融合算子适配
除上述显存类优化外,计算侧对模型中的热点小算子做了 NPU 融合算子适配:将原始 PyTorch 实现替换为昇腾融合算子,减少算子数量与 kernel 启动开销,提升执行效率。适配位于各模型目录的
npu_patch.py,modeling 文件末尾在检测到 NPU 环境(IS_NPU_AVAILABLE)时自动调用apply_*_npu_patch()完成模块级函数替换,模型前向代码无需改动,随training.plugin导入模型包时生效。Dense 版(
qwen3vl/npu_patch.py::apply_qwen3vl_npu_patch,8B/32B 共用):apply_rotary_pos_emb_visionnpu_fused_operator.apply_transformers_vision_rope_half_npuapply_rotary_pos_embnpu_fused_operator.apply_transformers_rope_half_npuQwen3VLTextRMSNorm.forwardnpu_fused_operator.rms_norm_forward_nputorch_npu.npu_rms_normMoE 版(
qwen3vl_moe/npu_patch.py::apply_qwen3vl_moe_npu_patch,30B/235B):在上述三类算子(对应 MoE 类Qwen3VLMoeTextRMSNorm)替换的基础上,额外将专家层前向Qwen3VLMoeTextExperts.forward替换为npu_fused_operator.fused_moe_forward_npu,把 permute → grouped matmul(gate/up)→ SwiGLU → grouped matmul(down)→ unpermute 的 MoE 计算链路整体以融合算子执行。2.5 经验总结
aclrtMallocPhysical/aclrtFreePhysical),引入毫秒级同步等待,表现为单步时间大幅波动。性能测试与正式训练均建议通过减层、降 batch、开重计算/激活 offload/chunk loss 等方式预留显存余量,而不是贴着上限运行。aclrtFreePhysical/aclrtGetMemInfo),即可定位到显存触顶引发的 ACL 内存重整,避免在计算/通信层面盲目排查;该路径在 8B 与 32B 上均得到验证。apply_module: lm_head,按序列维度分块计算 loss)可消除该尖刺——8B 借此在 8 卡上支撑了 16k 全层训练,峰值显存降至 20 GB 出头。fsdp_plan分片粒度与num_to_forward_prefetch/num_to_backward_prefetch等预取配置。附录
A. 本次改动文件清单
B. 参考资料