Files
LLaMA-Factory/docs/zh/advanced/distributed/fsdpturbo-ep-efsdp.md

12 KiB
Raw Blame History

FSDPTurbo EP/EFSDP 与 LlamaFactory FSDP2/CP 设计说明

English version: FSDPTurbo EP/EFSDP and LlamaFactory FSDP2/CP Design

本文描述 fsdpturbo distributed plugin 的当前实现。核心原则是保持两侧职责清晰:

  • FSDPTurbo 负责专家并行EP、专家参数分片EFSDP和设备算子注册。
  • LlamaFactory 负责进程初始化、基础 DeviceMesh、外层 FSDP2、CP、模型初始化与权重加载。
  • LlamaFactory 的集成层负责把两套参数布局组合起来,并处理跨 Mesh 的梯度范数。

1. 配置边界

公共并行拓扑放在 TrainingArguments 顶层FSDPTurbo 私有参数保留在 dist_config

cp_size: 1

dist_config:
  name: fsdpturbo
  ep_size: 16
  ep_dispatcher: eager

最小示例中的字段职责如下:

  • ep_size:专家并行组大小。
  • ep_dispatcherFSDPTurbo EP dispatcher默认为 eager

dp_sizecp_sizecp_modemp_replicate_sizemp_shard_sizedist_timeout 属于公共拓扑字段,继续放在顶层。dist_config 会被严格解析为 FSDPTurboParams;如果把公共拓扑 字段误放进去,会直接报错,而不是静默忽略。

顶层训练参数 bf16 同时控制 FSDPTurbo 的参数存储和计算 dtype。backend 会在 FSDP materialization 前完成模型 dtype 转换,因此 ModelEngine 不需要读取 distributed backend 配置。

以下高级字段为可选项,因此没有写入上面的最小 YAML 示例:

  • fsdp_ignored_modules:额外排除在 LlamaFactory 外层 FSDP2 之外的模块。模型规格选中的专家参数 会被集成层自动加入忽略集合,普通配置无需重复填写。
  • hook_modulesFSDPTurbo EFSDP hook 的可选模块模式,默认为空列表。
  • fsdp_implementationFSDPTurbo EFSDP 实现,可选 nativecustom,默认为 native

EFSDP 的目标由模型规格决定。Attention、Embedding、LM Head 等非专家参数不进入 FSDPTurbo EFSDP plan而是继续由 LlamaFactory 外层 FSDP2 管理。

模型相关的模块路径和准备逻辑统一由 FSDPTurboEPModelSpec 注册表管理。当前内置 qwen3_moeqwen3_5_moe;未注册的模型会明确报错。ep_modulesep_fsdp_modules 不属于 YAML 接口,严格参数解析会拒绝这两个字段,避免用户配置与模型实际结构失配。

2. Mesh 初始化

LlamaFactory 的 DistributedInterface 只初始化自身原有的 model/data mesh。它不感知 EP、EFSDP 也不为 distributed plugin 提供额外 mesh 注册接口。FSDPTurbo 的专家拓扑由插件文件内的 FSDPTurboParallelState 独立创建和持有:

run_sft / run_dpo / run_rm
  -> DistributedInterface(training_args)
     -> 初始化 LlamaFactory model/data mesh
  -> DistributedPlugin("fsdpturbo").shard_model(...)
     -> FSDPTurboFSDP2Engine.__init__()
        -> FSDPTurboParallelState.initialize()
           -> 初始化并保存 expert parent mesh 及其子 mesh

FSDPTurboParallelState 创建专家侧四维父 Mesh

(edp, efsdp, ep, expert_cp)

当前尺寸计算为:

dp_size       = world_size / cp_size
ep_fsdp_size  = dp_size / ep_size
edp_size      = dp_size / (ep_size * ep_fsdp_size)
mesh_shape    = (edp_size, ep_fsdp_size, ep_size, cp_size)

状态对象保存 edp_meshefsdp_meshep_meshexpert_cp_mesh。插件内部模型切分和梯度范数 都从这个状态对象读取专家通信域LlamaFactory 其他 backend 不需要实现或感知这些接口。状态初始化 会校验 ep_size 为正数且能够整除 dp_size,重复初始化时也会拒绝拓扑发生变化。

3. 模型切分顺序

模型包装顺序必须保持为“专家侧优先,外层 FSDP2 随后”:

DistributedPlugin("fsdpturbo")
  -> FSDPTurboFSDP2Engine.shard_model(model)
     -> prepare_model_ep(model)
        -> expert_parallelize_modules(model, ep_mesh, ep_plan)
        -> expert_fully_shard_modules(model, efsdp_mesh, ep_plan, fsdp_plan)
        -> 收集专家参数作为 ignored_params
     -> FSDP2Engine.prepare_model(model, ignored_params=...)
        -> 对剩余 Transformer Layer 和根模块执行 outer fully_shard

这样可以避免同一专家参数同时被 EFSDP 和外层 FSDP2 管理。外层 FSDP2 仍复用 LlamaFactory 原有的初始化、checkpoint 和保存流程。

LlamaFactory 集成层接受 eagerfusedmc2domino,并将选项原样传给 FSDPTurbo。这四种模式的实现边界和当前验证状态不同

Dispatcher 主要路径 额外要求 本 PR 验证状态
eager 使用 PyTorch 实现 permute、unpermute 和 grouped matmul张量仍在当前加速设备上通过标准 AllToAll 完成 token dispatch/combine 依赖最少,用作参考实现 已在 A3 上完成精度和性能验证
fused 保持相同的 AllToAll 拓扑,将 permute、unpermute 和 grouped matmul 切换为设备融合算子 需要对应的设备算子、dtype 和 layout 支持;存在空专家时可回退到 eager 局部算子 已在 A3 上完成精度和性能验证
mc2 使用专用算子融合 AllToAllV 和 grouped matmul减少通信与计算之间的中间开销 依赖 MC2 NPU 算子、HCCL communicator 以及对应的 shape/dtype 约束 FSDPTurbo 提供实现,本 PR 未做端到端验证
domino 将专家模块输入的第一维分成两片,使用独立通信流和 event 重叠 AllToAll 与专家计算 需要异步 stream/event 支持,且两个分片都要有足够的 token 工作量才能覆盖调度开销 FSDPTurbo 提供实现,本 PR 未做端到端验证

当前只验证 eagerfused,是因为它们分别覆盖参考实现和 A3 常用设备融合路径,可用于隔离并验证 LlamaFactory 与 FSDPTurbo 之间的 EP/EFSDP 集成正确性。本次实验矩阵没有继续扩展到 mc2domino:它们还引入了额外的算子、通信调度和输入形状约束,需要独立比较数值、长步稳定性和 profiler 结果。 因此,它们在配置接口上可选,但不应从本 PR 的实验结果推断为已达到相同的稳定性、精度或性能水平。

4. FSDPTurbo 依赖入口

LlamaFactory 从各功能的定义模块直接导入所需对象:

from fsdp_turbo.distributed.expert_parallel.expert_fully_shard_parallel import (
    expert_fully_shard_modules,
)
from fsdp_turbo.distributed.expert_parallel.expert_parallel import expert_parallelize_modules
from fsdp_turbo.fsdp_turbo_config import EPPlanConfig, FSDPPlanConfig
from fsdp_turbo.utils.str_match import module_name_match

导入发生在 prepare_model_ep() 内,因此没有安装 FSDPTurbo 时,其他 distributed backend 仍可正常导入。 这里不通过 fsdp_turbo.distributed.__init__ 聚合导出,避免 package 初始化期间的额外依赖和潜在循环导入。

5. 梯度范数

外层参数和专家参数可能属于不同 DTensor Mesh不能直接放入一次标准 clip_grad_norm_()fsdpturbo plugin 按参数所属 Mesh 分组计算局部 p 次方和:

  • 非专家参数沿 DP 和 CP group 汇总。
  • 专家参数沿 FSDPTurboParallelState 保存的 EFSDP、EP 和 expert-CP group 汇总。
  • 汇总得到全局范数后,对所有本地梯度应用同一个 clipping coefficient。

启动阶段会执行一次零梯度 warmup使相关 collective 在正式训练前完成初始化。 当前这是 fsdpturbo backend 的专用实现;其他 backend 继续保留原有梯度范数路径,等待上游 distributed plugin 解耦后再统一公共接口。

6. 权重加载

LlamaFactory 保留 init_on_meta 和 safetensors 加载流程。父类 FSDP2Engine 的加载器通过 self._copy_weights(...) 动态调用 FSDPTurbo engine 的覆写实现,因此该方法不是未使用代码。 它支持包含多个 Shard placement 的 DTensor按各 Mesh 维度依次计算当前 rank 对应的本地切片。 模型保存和 checkpoint 接口继续复用 LlamaFactory FSDP2 实现。

7. Kernel plugin

FLA 算子不属于 distributed config。算子选择通过独立的 kernel_config 完成:

kernel_config:
  name: auto, flash-linear-attention
  include_kernels: chunk_gated_delta_rule, fused_recurrent_gated_delta_rule
  chunk_size: 32

调用链如下:

ModelEngine
  -> apply_kernels("auto, flash-linear-attention")
     -> LlamaFactory 当前加速器对应的 auto kernels
     -> KernelPlugin("flash-linear-attention").apply(...)
        -> fsdp_turbo.ops.get_op()
           -> FSDPTurbo device operator registry
        -> fsdp_turbo.utils.patch.patch_model_members()
           -> FLA backend implementation

chunk_size 当前支持 163264,默认值为 64。Kernel plugin 与 distributed plugin 彼此独立。name: flash-linear-attention 只安装所选 FLA 算子;逗号分隔的 name: auto, flash-linear-attention 会在分布式切分前组合 LlamaFactory 当前加速器的 auto kernels 与 FLA plugin。LlamaFactory 负责算子名到模型属性的映射和 chunk_size 参数绑定FSDPTurbo 负责设备 算子注册、选择和通用 callable patch。FLA 依赖可选的外部三方件,因此保持显式选择,不属于内置 auto 集合。FSDPTurbo 随后会替换目标专家模块的 forward,所以专家计算的最终路径由 ep_dispatcher 决定auto 阶段 应用的 MoE kernel 不会作为独立的第二条专家执行路径保留下来。

8. CP 运行约束与验证范围

init_on_meta 构造模型时必须与 from_pretrained 路径一样传递 attn_implementation,否则模型会退回 非 FlashAttention 实现Ulysses CP 无法启动。Ulysses 在调用 Hugging Face FlashAttention 前重建全局 attention mask只有二维 position IDs 才参与 packed-sequence 检测。Qwen3.5 mRoPE 等多轴 position IDs 已经在 rotary embedding 中消费,不应传入 FlashAttention 的 packed-sequence 检测逻辑。

当前实现已在 Atlas 900 A3 SuperPoD 和 Atlas 950 SuperPoD 上用 Qwen3.5-35B-A3B 完成以下 BF16、AdamW full SFT 验证。本次重验证使用 FSDPTurbo 0e96fbcA3 环境为 CANN 9.0.0、 PyTorch 2.7.1 和 torch-npu 2.7.1.post4A5 环境为 CANN 9.1.0-beta.3、PyTorch 2.10.0 和 torch-npu 2.10.0.post2。表中性能按第 1 步至第 100 步的日志时间戳计算,不包含首步前的初始化、 编译和训练后的模型保存时间:

机器型号 CP EP EFSDP Checkpoint Kernel / Dispatcher 步数 Loss首步 -> 末步) 性能 结果
Atlas 900 A3 SuperPoD 1 16 1 关闭 FLAchunk size 16/ eager 100 1.3361 -> 0.0793 2.51 s/it 通过并完成保存
Atlas 900 A3 SuperPoD 1 16 1 关闭 FLAchunk size 16/ fused 100 1.3354 -> 0.1179 2.17 s/it 通过并完成保存
Atlas 900 A3 SuperPoD 2 4 2 关闭 auto + FLAchunk size 64/ fused 100 1.8114 -> 0.5260 7.65 s/it 通过并完成保存
Atlas 900 A3 SuperPoD 2 4 2 关闭 auto + FLAchunk size 64/ eager 100 1.8095 -> 0.5596 5.88 s/it 通过并完成保存
Atlas 950 SuperPoD 1 8 1 关闭 未配置 kernel plugin / eager 100 1.3575 -> 0.4439 2.68 s/it 通过并完成保存

五组训练的 loss 和 grad norm 均保持有限,并完成 100 步及模型保存。同一切分下EP16 eager/fused 的逐步 loss 相关系数为 0.997CP2/EP4/EFSDP2 eager/fused 为 0.977,说明两种 dispatcher 的 优化轨迹一致。性能收益与切分有关EP16 下 fused 比 eager 快约 13%,而加入 CP 和 EFSDP 后 fused 比 eager 慢约 30%,因此不能把 fused 视为所有 mesh 的默认最优选择。

EP16 两组使用 global batch 16 和 cutoff length 256CP2 两组使用 global batch 8 和 cutoff length 128A5 组使用 global batch 8 和 cutoff length 256。因此首末 loss 用于验证各组自身的收敛趋势, 不同切分组之间的绝对 loss 不应直接作为精度等价结论。