已关闭
【实践文档】InternVL3.5 pure FSDP2 迁移与性能优化 #625
cartes1us创建于  8月16日关闭于  11 天前
cartes1us
cartes1us
8月16日 创建

关联内容


【实践文档】InternVL3.5 接入 pure FSDP2 后端开发实践

本文介绍 InternVL3_5-30B-A3B-Instruct 接入 MindSpeed MM pure FSDP2 的实现与验证。

1. 迁移目标

  • 保留原有 Megatron-FSDP2 训练路径,新增 pure FSDP2 模型、数据和示例配置;
  • 复用 InternVL 原始 remote code 和仓库已有 DCP 转换、加载能力;
  • 按 Issue 371 的统一配置完成 2000 步精度、性能对比。

代码落点如下:

examples/internvl3.5/README.md
examples/internvl3.5/finetune_internvl3_5.sh
examples/internvl3.5/internvl3_5_config.yaml
mindspeed_mm/fsdp/data/datasets/internvl3_5/dataset.py
mindspeed_mm/fsdp/models/internvl3_5/modeling_internvl3_5.py
mindspeed_mm/fsdp/models/internvl3_5/npu_patch.py

2. 模型接入

模型插件注册为 internvl3_5。
InternVL 主体直接从本地完整 HF 目录加载 remote code,在模型构建期间将 Qwen3-MoE decoder layer 替换为 pure FSDP2 所需的融合专家布局,构建结束后恢复原类,复用上游模型实现。

融合专家参数布局如下:

gate_up_proj: [num_experts * hidden_size, 2 * expert_dim]
down_proj:    [num_experts * expert_dim, hidden_size]

前向计算调用仓库已有的融合 MoE 实现,保持 128 个 experts 的路由和计算语义。

3. 权重准备

使用仓库已有 ExpertMergeDcpConverter 将完整 HF checkpoint 转为标准 DCP:

mm-convert ExpertMergeDcpConverter hf_to_dcp \
  --hf_dir ckpt/hf_path/InternVL3_5-30B-A3B-Instruct \
  --save_dir ckpt/convert_path/InternVL3_5-30B-A3B-Instruct

YAML 中 model.model_name_or_path 指向原始 HF 目录,用于读取配置和 remote code;
training.load 指向 DCP 目录,用于加载模型权重。

4. 数据接入

新增 internvl3_5 dataset plugin,完成以下处理:

  • 读取 InternVL ShareGPT JSONL 标注及单图样本;
  • 将 <image> 展开为视觉上下文 token;
  • 按 InternVL 对话模板构造 input_ids 和 labels;
  • 图像直接缩放至 448×448;文本序列右侧补齐后拼接 batch。

训练配置使用 max_dynamic_patch: 1、序列长度 1024 和 20,000 条 ShareGPT4V 标注池。
pure FSDP2 与 Megatron 路径采用一致的 input_ids、labels、attention_mask、pixel_values、image_flags 及 collated batch。

5. 配置与启动

核心配置如下:

parallel:
  fully_shard_parallel_size: auto
  fsdp_plan:
    param_dtype: bf16
    reduce_dtype: fp32
    num_to_backward_prefetch: 2

model:
  model_id: internvl3_5
  num_hidden_layers: null

training:
  train_iters: 2000
  load_format: dcp
  load_strict: true

model.num_hidden_layers: null 表示沿用 checkpoint 的完整层数。

准备好 YAML 中的模型、DCP 和数据路径后执行:

bash examples/internvl3.5/finetune_internvl3_5.sh

5.1 特性支持

通过 YAML 配置以下特性:

特性 配置字段 说明
Ulysses Context Parallel parallel.ulysses_parallel_size 把长序列拆开给多卡并行算:省激活显存、提速度
Expert Parallel parallel.expert_parallel_size、parallel.ep_plan 把 MoE 专家分摊到多卡:省专家参数显存、加快 MoE 计算
激活重计算 features.recompute、features.recompute_plan 不存中间激活,反向时重算:省激活显存
ChunkLoss features.enable_chunk_loss、features.chunkloss_plan logits 分块计算、算完即释放:避免超大 logits 占显存
激活值卸载 features.enable_activation_offload、features.activation_offload_plan 暂时不用的激活转移到 CPU:省峰值显存

启动脚本固定使用 internvl3_5_config.yaml,即 2 卡全特性组合(CP=2、EP=2加三种显存特性)。

6. 精度与性能对比

八组实验均完成 2000 步。实验使用两张 64 GiB Ascend NPU,语言模型前 8 层、24 层视觉编码器及 128 个 experts。各组使用相同初始权重、数据顺序、优化器和学习率:seed=42、global batch=2、序列长度 1024、2000 步 cosine 调度、200 步 warmup、BF16 参数和 FP32 梯度规约。

图像预处理统一为 RGB → BICUBIC resize 448×448 → ImageNet 归一化,不补边。CP 与全特性组使用 CP2/DP1/micro batch2,其余组使用 CP1/DP2/micro batch1,有效全局 batch 均为 2。两种基线关闭重计算,重计算消融和全特性组开启;分片模块、预取、数据加载线程及运行时设置一致。

以 Megatron-FSDP2 为基线,使用 TrainingLogParser 的相对绝对误差:mean(abs(FSDP2 loss - Megatron loss) / abs(FSDP2 loss)) × 100%,统计全部 2000 步。耗时取第 100–200 步(含端点,共 101 步)的平均值。显存从首次前向到最后一次优化器更新全程测量,取两张卡峰值的最大值;已分配显存为张量实际占用,预留显存包含内存分配器缓存。每组运行一次。

组别 末步 loss 平均相对绝对误差 最大单步误差 平均耗时(ms/step) 峰值已分配显存(GiB) 峰值预留显存(GiB)
① Megatron-FSDP2 2.015499 — — 658.153 49.72 55.88
② FSDP2 无特性 2.004347 0.273431% 1.520336% 605.616 48.91 56.37
② + CP 2.007337 0.424580% 3.037344% 671.273 48.91 56.46
② + EP 2.012444 0.274241% 1.763618% 430.756 47.19 51.89
② + 激活重计算 2.012961 0.269524% 1.268152% 666.531 48.04 54.07
② + ChunkLoss 2.003631 0.269253% 1.463872% 613.474 48.72 55.61
② + 激活值卸载 2.021225 0.271989% 1.610306% 623.256 48.86 56.37
③ FSDP2 全特性 2.006621 0.423846% 2.873473% 624.042 45.88 50.30

七组 FSDP2 的平均误差均低于 0.5%。CP 和全特性组分别有 4、5 步的单步误差超过 2%,其余组没有单步超过 2%。

① vs ②:

Megatron vs FSDP2 无特性

① vs ③:

Megatron vs FSDP2 全特性

6.1 两种后端的计算设置

  • loss:先计算每个样本有效 token 的平均 loss,再对样本求平均,避免回答较长的样本占更大权重。
  • 重计算:关闭视觉编码器默认启用的内部重计算,由框架特性开关统一控制。

7. 单特性消融

五个消融分别在②基础上单独开启 CP、EP、重计算、ChunkLoss 或激活值卸载;③同时开启全部特性。以下误差均相对 Megatron,完整结果见第 6 节。

无特性组平均误差为 0.273431%。单独开启 EP、重计算、ChunkLoss 和激活值卸载后,平均误差分别为 0.274241%、0.269524%、0.269253% 和 0.271989%,均与无特性组接近。本轮未观察到这四项特性使平均误差明显增大。

CP 组平均误差为 0.424580%,比无特性组高约 0.151 个百分点,最大单步误差为 3.037344%,有 4 步超过 2%。CP 改变了序列切分、通信和每卡批次组织;本轮结果表明,CP 组与 Megatron 的差异较大,但这些结果尚不能确定各因素的具体贡献。

全特性组平均误差为 0.423846%,与 CP 单项接近,最大单步误差为 2.873473%,有 5 步超过 2%。本轮未出现开启更多特性后平均误差逐项累加的现象。七组 FSDP2 的平均误差均低于 0.5%;每组运行一次,小幅差异不作为某项特性能改善精度的结论。

8. 特性的运行时调用树

modeling_internvl3_5.py 定义模型与专家适配,npu_patch.py 安装模型前向及 NPU/CP attention。以下按初始化、前向和反向的实际顺序列出五项特性的主要调用路径;框架包装函数按 YAML 开关生效。

8.1 初始化与训练入口

Trainer 初始化
├─ 导入模型/数据插件,建立 CP、EP 和 FSDP 并行组 [框架]
├─ 构建 InternVLChatModel [modeling + 上游 HF]
│  └─ apply_internvl35_npu_patch:安装模型前向与 NPU/CP attention [npu_patch]
├─ 按 YAML 应用特性与并行包装 [框架]
│  ├─ pre_fully_shard_apply:重计算 → 激活值卸载 → ChunkLoss
│  ├─ ParallelApplier:EP、FSDP2 分片及参数预取
│  └─ post_fully_shard_apply:配置需要的辅助损失捕获
└─ 创建优化器、数据加载器和 TrainEngine,随后进入训练循环 [框架]

8.2 CP(上下文并行)

训练循环 → InternVLChatModel.forward [modeling]
├─ 文本 embedding [上游 HF]
├─ extract_feature(pixel_values) [上游 HF]
│  ├─ 视觉 embeddings
│  ├─ _internvl3_5_vision_encoder_forward [npu_patch]
│  │  ├─ split_forward_gather_backward_with_cp:切分图内 token [框架]
│  │  ├─ 逐层视觉 encoder
│  │  │  └─ _internvl3_5_vision_attention_forward [npu_patch]
│  │  │     ├─ all_to_all:切分头、聚合图内序列 [框架]
│  │  │     ├─ npu_fusion_attention:BNSD、非因果 [NPU 算子]
│  │  │     └─ all_to_all:还原 token 分片与完整头 [框架]
│  │  └─ gather_forward_split_backward_with_cp:还原完整图内序列 [框架]
│  └─ pixel_shuffle → mlp1
├─ 用视觉特征替换 <IMG_CONTEXT> 对应的文本 embedding [modeling]
├─ language_model.model.forward → _internvl3_5_qwen_model_forward [npu_patch]
│  ├─ 在完整序列上构建 causal mask [上游 HF]
│  ├─ 切分 position_ids 和 inputs_embeds,保留全局序列长度 [框架 + npu_patch]
│  ├─ 逐层 _internvl3_5_moe_decoder_layer_forward [npu_patch]
│  │  ├─ input_layernorm → self_attn → residual
│  │  │  └─ _internvl3_5_moe_attention_forward [npu_patch]
│  │  │     └─ flash_attention_2 的 NPU 实现:Ulysses 通信与全局 mask [框架]
│  │  └─ post_attention_layernorm → MLP/MoE → residual
│  │     └─ InternVL3_5MoeSparseMoeBlock.forward [modeling]
│  │        ├─ gate → FP32 softmax → top-k → 权重归一化
│  │        └─ experts:按 EP 开关走下节对应路径
│  └─ final norm
└─ lm_head / ChunkLoss → _aggregate_cp_loss [modeling + npu_patch]
   └─ gather_forward_split_backward 汇集各 CP rank 的局部贡献后求和 [框架]

8.3 EP 与非 EP 的 MoE 路径

InternVL3_5MoeSparseMoeBlock.forward [modeling]
├─ router:gate → softmax → top-k → 权重归一化
└─ self.experts(hidden_states, routing_weights, router_indices)
   ├─ EP 关闭:InternVL3_5MoeTextExperts.forward [modeling]
   │  ├─ _view_expert_weights:将 DCP 参数视为三维专家权重
   │  └─ fused_moe_forward_npu [框架]
   │     └─ permute → grouped_matmul → SwiGLU → grouped_matmul → unpermute
   └─ EP 开启:框架将 experts.forward 绑定到模型 ep_forward
      ├─ _view_expert_weights:取本 rank 的专家权重 [modeling]
      └─ ep_dispatcher.ep_forward(默认 alltoall)[框架]
         └─ token 分发 → 本地 grouped GEMM 专家计算 → token 回收

8.4 激活重计算

YAML features.recompute + recompute_plan.apply_modules
└─ FeaturesApplier → recompute_modules [框架]
   ├─ 包装 vision_model.encoder.layers.{*} 与 language_model.model.layers.{*}
   ├─ 前向通过 checkpoint 执行被包装层
   └─ 反向按需要重算层内前向,恢复求梯度所需的激活

8.5 ChunkLoss

YAML enable_chunk_loss + chunkloss_plan
└─ FeaturesApplier.apply_chunkloss → 包装 language_model.lm_head [框架]
   └─ InternVLChatModel.forward [modeling]
      ├─ build_loss_func:传入 labels、loss_type 和 chunk_size [框架]
      └─ lm_head(hidden_states, loss_func)
         └─ chunkloss_forward → loss_func(hidden_states, weight, bias, labels)
            └─ 分块计算投影与交叉熵,汇总 loss [框架]

8.6 激活值卸载

YAML enable_activation_offload + activation_offload_plan.apply_modules
└─ FeaturesApplier.apply_activation_offload_modules [框架]
   └─ async_offload_modules → with_async_save_on_cpu
      └─ 被包装层的 forward 在 saved_tensors_hooks 上下文执行
         ├─ pack:检查 autograd 要保存的张量
         │  ├─ 跳过参数、参数视图和空存储
         │  ├─ 仅匹配与该层输入 hidden_states 相同 data_ptr 的张量
         │  ├─ 跳过所选模块中的最后一块
         │  └─ 异步 D2H;后续块推进时等待传输并释放对应设备存储
         └─ unpack:反向取用时 H2D 恢复并等待完成,同时预取前一块

CP 中的切分、聚合和 all-to-all 通过 autograd 回传梯度。全特性组同时使用 CP、EP、重计算、ChunkLoss 和卸载包装;重计算与卸载复用框架实现。

9. 结论

InternVL3.5 通过独立插件接入 pure FSDP2,支持 CP、EP、重计算、ChunkLoss 和激活值卸载,八组实验均完成精度与性能验证。


【实践文档】InternVL3.5 pure FSDP2 训练性能优化实践

本文以 Megatron 和纯 FSDP2 无特性配置为基线,通过参数对照实验比较训练速度和显存占用,选择适合本模型的运行配置。

1. 目标与验收口径

优化目标是在平均相对绝对 loss 误差小于 2% 的前提下,降低训练耗时和显存占用。

实验配置和结果见第一篇第 6 节。

2. 基础优化

2.1 基于融合的 MoE 实现

模型完成专家权重布局和输入输出适配后,调用仓库通用的融合 MoE 函数。该函数将多个专家的计算分组执行,减少逐个专家调用带来的算子下发开销。

2.2 FSDP 参数分片的模块划分

FSDP2 切分参数时以模块为最小单元。切分单元过粗会失去分片意义,过细则通信次数过多。本模型按 InternVL 的模块树划分切分单元:

parallel:
  fully_shard_parallel_size: auto
  fsdp_plan:
    apply_modules:
      - vision_model.embeddings
      - vision_model.encoder
      - mlp1
      - language_model.model.layers.{*}
      - language_model.lm_head
    reshard_after_forward: true

2.3 参数预取与任务队列

参数预取用于提前准备下一模块的权重,减少计算等待;任务队列用于异步下发 NPU 任务,减少 CPU 下发等待。本轮重点比较这两类配置:

参数 含义
num_to_forward_prefetch / num_to_backward_prefetch 前向、反向提前准备的 FSDP 模块组数。例如 0/2 表示前向沿用默认调度,反向显式预取两个模块组。预取越深,同时驻留的权重越多,显存开销也可能增加。
TASK_QUEUE_ENABLE 0 关闭任务队列;1 完成部分准备后将任务入队;2 将更多准备工作移到下发线程,与主线程并行执行。

预取还依赖模块列表的顺序。本模型按实际执行顺序配置为:视觉 embeddings → 视觉 encoder → mlp1 → 语言层 → lm_head。

以下以纯 FSDP2 无特性配置为基线。每组训练 200 步、独立运行两次,保持 2000 步学习率调度;耗时取第 100~200 步的均值,再对两次运行求平均。显存取完整 200 步、两张卡和两次运行中的最高值。

配置变化 平均耗时(ms/步) 峰值已分配显存(GiB) 峰值预留显存(GiB)
原基线:预取 0/2,队列 1 624.84 50.48 56.70
队列改为 0 653.93 50.48 56.70
队列改为 2 631.46 50.48 56.70
预取改为 0/0 636.05 48.42 56.13
预取改为 0/1 638.05 49.31 56.13
预取改为 1/2 639.26 50.48 57.52
预取改为 1/1 651.49 49.31 57.52
将 mlp1 排到语言层之前,保持预取 0/2、队列 1 608.43 48.89 56.13

调整模块顺序后,平均耗时从 624.84 降至 608.43 ms/步,下降 2.63%,峰值已分配显存减少 1.59 GiB。其余预取和队列设置的耗时均高于原基线,因此正式实验采用上述模块顺序,保留预取 0/2 和队列 1。

3. 性能分析

3.1 并行特性与显存优化

性能对比只基于 Megatron 训练路径和纯 FSDP2 无特性两条基线。以下结果来自八组 2000 步实验,比较 CP、EP、重计算、ChunkLoss 和激活值卸载对耗时和显存的影响;各组均采用融合 MoE;FSDP2 非 EP 组调用通用融合函数,EP 组调用框架 dispatcher,Megatron 使用其训练路径中的融合实现。

以 FSDP2 无特性组为参照,其平均耗时为 605.616 ms/step,峰值已分配显存为 48.91 GiB,峰值预留显存为 56.37 GiB。下述比较均来自本轮 2000 步实验,完整精度、耗时和显存表见第一篇第 6 节。

EP 是当前负载下收益最大的单项特性。 平均耗时降至 430.756 ms/step,比无特性组下降 28.87%;峰值已分配显存减少 1.72 GiB,峰值预留显存减少 4.47 GiB。EP1 中每卡处理不同样本,FSDP 在专家计算前收集完整专家权重;EP2 中每卡保留一半专家,接收两卡路由到这些专家的 token,以 token 分发与回收通信替代这部分权重收集。在固定全局 batch 下,平均每卡的专家计算量不会因此直接减半。这里的加速是通信方式与计算组织变化后的整体结果,不能解释为专家参数或 FLOPs 简单减半。

CP 在当前短序列负载下没有表现出提速或显存下降。 耗时为 671.273 ms/step,比无特性组增加 10.84%,峰值已分配显存基本持平。CP 组由 DP2、每卡 1 个样本改为 CP2/DP1、每卡 2 个样本,再按序列切分,有效全局 batch 仍为 2。因此每卡激活量不能简单按 CP 数量除以二;当前配置下,切分与通信开销也没有被计算收益抵消。

重计算体现了时间与显存的交换。 耗时增加 10.06%,峰值已分配显存减少 0.87 GiB,峰值预留显存减少 2.30 GiB。它减少前向激活的保存,反向时再计算这些激活;当前结果适合说明其显存收益,而不是提速收益。

ChunkLoss 和激活值卸载的单项收益较小。 ChunkLoss 使峰值已分配显存减少 0.19 GiB,耗时增加 1.30%;卸载组减少约 0.04 GiB,耗时增加 2.91%。ChunkLoss 作用于 logits 与 loss 计算,卸载作用于保存的激活;如果这些内存不决定整步的最高峰,局部节省就不会等量反映在全程峰值中。本轮序列长度和 batch 下,两项特性的显存收益有限,尚不能据此推断更长序列的表现。

全特性组合进一步降低了最大显存,但耗时高于无特性组。 平均耗时为 624.042 ms/step,比无特性组增加 3.04%;峰值已分配显存为 45.88 GiB,减少 3.03 GiB(6.19%);峰值预留显存为 50.30 GiB,减少 6.07 GiB(10.77%)。相对 Megatron 的 658.153 ms/step,耗时下降 5.18%。全特性比 EP 单项更慢、但显存更低,说明组合兼顾计算并行与显存压缩,各项收益不能直接相加。每组运行一次,尚未评估重复运行的波动。

3.2 同等 EP 配置下的两条训练路径

Megatron 和 pure FSDP2 均支持 EP。为比较两条训练路径,两组实验均设置 EP=2,其余训练参数保持一致;下表给出对应的耗时和显存结果。

两条路径均固定 CP1、每卡 micro batch1、global batch2、预取 0/2、队列 1,关闭重计算、ChunkLoss 和激活值卸载;其余权重、数据与数值设置延续前述共同配置。EP2 每卡保留 64 个专家。两条 EP2 路径通过五步验证后,对 EP1/EP2 各运行两次 200 步,计时和显存口径同第 2.3 节。

配置 两次平均耗时(ms/步) 两次均值(ms/步) 峰值已分配显存(GiB) 峰值预留显存(GiB)
Megatron训练路径,EP1 662.98 / 662.43 662.70 49.72 55.84
Megatron训练路径,EP2 420.27 / 430.39 425.33 47.66 51.52
native FSDP2,EP1 624.80 / 629.42 627.11 50.48 56.70
native FSDP2,EP2 429.58 / 429.40 429.49 47.26 51.56

Megatron路径开启EP2后耗时下降35.82%,native FSDP2下降31.51%;两者峰值已分配显存分别减少2.06和3.22 GiB。同为EP2时,两次均值分别为425.33与429.49 ms/步,相差4.16 ms(约0.98%);Megatron自身两次运行相差10.12 ms,当前证据不足以认定哪条EP2路径稳定更快。原八组中的Megatron为EP1、native EP单项为EP2,不能用那两行直接回答同等EP设置下的后端效率。

两条训练路径仍使用各自的框架调度和 EP 实现,因此这里只比较端到端训练表现,不能把差异归因于单个算子。

4. 结论

当前配置下,全特性组合平均耗时为 624.042 ms/step,比 Megatron 降低 5.18%,比 FSDP2 无特性组增加 3.04%;相对无特性组,峰值已分配显存从 48.91 降至 45.88 GiB,减少 3.03 GiB(6.19%),峰值预留显存从 56.37 降至 50.30 GiB,减少 6.07 GiB(10.77%)。

EP 是本轮提速最明显的单项特性;全特性组合进一步降低显存,但耗时高于 EP 单项和无特性组。结果表明,配置选择需要同时考虑训练速度和显存需求,不能只根据单个特性或单次最快结果下结论。

纯 FSDP2 无特性配置的参数对照中,按执行顺序排列模块后,平均耗时从 624.84 降至 608.43 ms/步。正式实验采用这一模块顺序,预取设为 0/2,任务队列设为 1。同等 EP2 设置下,Megatron 和纯 FSDP2 的平均耗时分别为 425.33 和 429.49 ms/步。

likedislike
cartes1uscartes1us
8月16日 修改了issue 的描述
cartes1uscartes1us
8月18日 修改了issue 的描述
cartes1uscartes1us
8月18日 修改了issue 的描述
cartes1uscartes1us
8月18日 修改了issue 的描述
yeqm成员
8月21日 评论:

👋 您好,欢迎向 MindSpeed MM 提交 Issue!
我们已收到您的反馈,感谢你对开源社区的支持。🎉
📅 处理时效: 维护团队将在 24 小时内 查看并回复您的问题(工作日)。
🚨 紧急联系: 如果您的问题非常紧急,可通过微信联系我们。
请确保 Issue 描述清晰,包含复现步骤和日志,这将帮助我们更快定位问题。谢谢!

likedislike
此处折叠了70条消息 查看更多
cartes1uscartes1us
1 天前 修改了issue 的描述
cartes1uscartes1us
1 天前 修改了issue 的描述
cartes1uscartes1us
1 天前 修改了issue 的描述
cartes1uscartes1us
14 小时前 修改了issue 的描述
cartes1uscartes1us
14 小时前 修改了issue 的描述