训练后端:FSDP / Megatron 两条路
6.1 verl 给你两条训练后端
verl 通过 actor_rollout_ref.actor.strategy(或顶层 model_engine)选择训练后端:
| 后端 | 对应 Hydra 配置 | 主战场 |
|---|---|---|
| FSDP | actor: fsdp_actor | ≤ 70B 模型,灵活快速 |
| FSDP2 | actor: fsdp2_actor | PyTorch 2.x 原生新版 |
| Megatron-Core | actor: megatron_actor | 千亿级模型 + TP/PP |
| TorchTitan | actor: torchtitan_actor | 实验性 |
| VeOmni | actor: veomni_actor | 多模态 / 大型 MoE |
在启动脚本里通过 model_engine 一次切换:
python -m verl.trainer.main_ppo \
model_engine=fsdp \
... 其他 override
等价于 actor / critic / ref 全部用 FSDP。
6.2 FSDP 路径
什么时候用
- 模型 ≤ 70B;
- 不熟 Megatron,希望"装好 PyTorch 就能跑";
- 常规科研 / 业务训练;
- 多模态、新模型架构(FSDP 不需要为模型重写代码)。
关键配置(fsdp_actor.yaml)
| 字段 | 含义 |
|---|---|
actor.fsdp_config.fsdp_size | FSDP shard 数量,通常 = DP world size |
actor.fsdp_config.param_offload | 参数卸载到 CPU |
actor.fsdp_config.optimizer_offload | 优化器卸载到 CPU |
actor.fsdp_config.wrap_policy | FSDP 包模块的策略:transformer_auto_wrap_policy 等 |
actor.fsdp_config.forward_prefetch | 前向 prefetch |
actor.fsdp_config.backward_prefetch | 反向 prefetch |
actor.fsdp_config.mixed_precision | bf16 / fp16 |
actor.use_torch_compile | 是否包 torch.compile |
actor.use_remove_padding | 变长序列去 padding,省算力 |
actor.enable_gradient_checkpointing | 激活重算 |
FSDP vs FSDP2
| 差别 | FSDP1 | FSDP2 |
|---|---|---|
| PyTorch 版本 | ≥ 1.11 | ≥ 2.4 |
| API 风格 | module wrapper | tensor 级(DTensor) |
| 显存碎片 | 偶有问题 | 更小 |
| 对自定义模型友好度 | 中等 | 更好 |
| verl 支持 | 稳定 | 推荐用 |
6.3 Megatron 路径
什么时候用
- 模型 ≥ 30B(特别 MoE);
- 需要 TP × PP × DP × EP × CP 多维并行;
- 已经有 Megatron 经验,能容忍 NVIDIA-only 的约束;
- 训 DeepSeek-V3 / Qwen3-235B-A22B / 千亿 MoE 这种"非 Megatron 训不动"的模型。
关键配置(megatron_actor.yaml)
| 字段 | 含义 |
|---|---|
actor.megatron.tensor_model_parallel_size | TP 度 |
actor.megatron.pipeline_model_parallel_size | PP 度 |
actor.megatron.virtual_pipeline_model_parallel_size | interleaved PP |
actor.megatron.expert_model_parallel_size | EP 度(MoE 专用) |
actor.megatron.context_parallel_size | CP 度(长上下文) |
actor.megatron.sequence_parallel | 是否开 SP |
actor.megatron.use_distributed_optimizer | 类似 ZeRO-1,把优化器切到 DP |
actor.megatron.use_mcore_models | 用 Megatron-Core 新版模型实现 |
Megatron-Bridge:HF 模型 ↔ Megatron 互转
Megatron 路径有个挑战:HF 模型权重不能直接 load 进 Megatron,要转格式。verl 推荐用 NVIDIA 出的 Megatron-Bridge:
# 训练前一次性把 HF checkpoint 转 Megatron 格式
python -m megatron_bridge.tools.convert_hf_to_megatron \
--hf_model Qwen/Qwen3-30B-A3B \
--output ckpt/qwen3_30b_a3b_mcore \
--tp 4 --pp 2 --ep 8
# 训完用反向工具转回 HF
python -m megatron_bridge.tools.convert_megatron_to_hf \
...
6.4 怎么选:FSDP 还是 Megatron
| 指标 | FSDP | Megatron |
|---|---|---|
| 上手难度 | 简单 | 陡峭 |
| 显存效率 | 中 | 高(TP+PP 精细切) |
| 训练吞吐(70B 内) | 很好 | 相当或略低 |
| 训练吞吐(≥ 100B) | 受限 | 明显领先 |
| 支持模型范围 | 广(任何 HF 模型) | 需要适配模型层 |
| 多模态 | 原生支持 | 需要额外适配 |
| MoE 训练 | 支持但低效 | 专家并行加持,吞吐高 |
| checkpoint 互转 | 原生 HF 格式 | 要走 Megatron-Bridge |
经验法则:
- 4B / 7B / 14B / 32B GRPO:FSDP 一定够;
- 70B 稠密 GRPO:FSDP 也能跑,但 ZeRO-3 风格通信开销大,可考虑 Megatron + TP=2/4;
- MoE(Mixtral 8x22B / Qwen3-235B / DeepSeek-V3):必须 Megatron + EP。
6.5 hybrid: FSDP actor + Megatron critic?
verl 允许 actor / critic / ref 走不同后端。但常见做法是统一——除非有强需求,不建议 mix。配置举例:
actor_rollout_ref.actor.strategy=fsdp2
critic.strategy=megatron # 不推荐这样混,除非你知道在干什么
6.6 模型并行尺寸推荐表(Megatron 路径)
| 模型 | TP | PP | EP | DP | 总 GPU |
|---|---|---|---|---|---|
| Qwen3-7B | 2 | 1 | 1 | 4 | 8 |
| Qwen2.5-32B | 4 | 2 | 1 | 2 | 16 |
| Qwen3-30B-A3B (MoE) | 2 | 1 | 4 | 1 | 8 |
| Qwen3-235B-A22B (MoE) | 4 | 4 | 8 | 1 | 128 |
| DeepSeek-V3 671B | 4 | 8 | 16 | 1 | 512 |
关系仍然是:
$$W = \mathrm{TP} \times \mathrm{PP} \times \mathrm{DP} \times (\mathrm{EP}/\mathrm{TP\text{ for MoE}})$$
6.7 训练后端调优清单
| 问题 | FSDP 解 | Megatron 解 |
|---|---|---|
| OOM | 开 param_offload + optimizer_offload | 调大 TP / PP;开 SP |
| throughput 低 | 开 use_remove_padding,开 torch.compile | 升 virtual_pipeline_model_parallel_size |
| 反向慢 | backward_prefetch = backward_pre | 开 distribute_saved_activations |
| 长上下文 | FSDP + flash-attn | 开 CP(context parallel) |
| 多模态 | 用 FSDP;vision tower 不切 | 需要 Megatron 多模态分支 |
6.8 EngineRegistry:两级 dispatch
讲了这么多后端,verl 怎么从一个 strategy: fsdp 字段最终拿到正确的实现类?看 verl/workers/engine/base.py:267 的 EngineRegistry:
class EngineRegistry:
_registry: Dict[str, Dict[str, Dict[str, Type[BaseEngine]]]] = {}
# model_type backend device → class
@classmethod
def register(cls, model_type, backend, device):
def decorator(engine_cls):
cls._registry[model_type][backend][device] = engine_cls
return engine_cls
return decorator
@classmethod
def new(cls, model_type, backend, ...):
return cls._registry[model_type][backend][device](...)
三个维度:
| 维度 | 取值 | 来源 |
|---|---|---|
model_type | "transformer" / "value_head" / "lm_head" | worker 构造时根据角色决定(actor → lm_head, critic → value_head) |
backend | "fsdp" / "megatron" / "torchtitan" / "veomni" / "mindspeed" / "automodel" | YAML strategy: 字段 |
device | "cuda" / "npu" / "rocm" | auto_set_device(config) 在 main_ppo.py:46 自动判定 |
真实注册表(来自 workers/engine/__init__.py:14–65):
| 类 | 典型用途 |
|---|---|
FSDPEngine | 纯 backbone(ref policy 用) |
FSDPEngineWithLMHead | 带 LM head(actor / rollout) |
MegatronEngine | backbone |
MegatronEngineWithLMHead | 带 LM head |
MegatronEngineWithValueHead | 带 value head(critic) |
TorchTitanEngine | PyTorch 官方 titan 路径(实验性) |
VeOmniEngine | VeRL-Omni 多模态后端 |
AutomodelEngine | HF AutoModelForCausalLM 直跑(vexact / 调试) |
MindspeedEngine | 华为昇腾 |
6.9 EngineConfig 字段全景
每种 backend 的 YAML 都继承自共通的 EngineConfig(workers/config/engine.py:77)。下面这些字段不分后端都有,是调试时第一档要查的:
| 字段 | 含义 |
|---|---|
strategy | 选 backend(fsdp / megatron / ...) |
use_dynamic_bsz | 按 token 预算自动调 micro batch size(变长序列必开) |
max_token_len_per_gpu | 训练时单卡 token 预算(一般 8192 / 16384) |
micro_batch_size_per_gpu | dynamic_bsz=False 时的固定 micro |
infer_max_token_len_per_gpu | 推理(log_prob / ref)时的 token 预算 |
infer_micro_batch_size_per_gpu | 同上 micro batch |
param_offload | 参数 offload 到 CPU(hybrid engine sleep level=2 时配合) |
optimizer_offload | 优化器 offload |
grad_offload | 梯度 offload |
forward_only | ref / reward model 设 True,不建 optimizer,省一半显存 |
router_replay | MoE 路由确定性,下节展开 |
6.10 MoE 的 Router Replay:R2 / R3
MoE 模型的训练和 rollout 之间有一个特别讨厌的不一致来源——router 输出。同一个 token 在训练侧路由到 expert A、rollout 侧路由到 expert B,importance ratio 就完全失真。verl 在 workers/config/engine.py 提供了 EngineRouterReplayConfig 字段管这个:
| mode | 做什么 | 代价 |
|---|---|---|
"disabled" | 不管,训练和 rollout 各路由各的 | importance ratio 噪声大 |
"R2" | 训练侧 replay rollout 的 router decision;training routing 强制和 rollout 对齐 | 训练侧不能学到新的路由策略 |
"R3" | 双向 replay:rollout 也用 training 的最新 router | 需要每步同步 router 权重;带通信开销 |
实战经验:
- 纯 PPO 训练:R2 足够,吞吐损失小;
- 训练 / rollout 频繁切的 long CoT:R3 更稳;
- DeepSeek-V3 671B / Qwen3-235B 这类 MoE,README 推荐默认 R2。
6.11 训练 → rollout 之间的 weight extraction
这是连接本章和 ch07 的核心点。每个 engine 必须实现 get_per_tensor_param()(engine/base.py:150):
| 后端 | 实现做的事 |
|---|---|
| FSDP / FSDP2 | 对每个 sharded param 调 summon_full_params() 临时 all-gather;按 layer 分组 yield,yield 完立刻释放 |
| Megatron | 每个 PP stage 持有自己 layer 的权重;先 PP broadcast 全 stage 数据,再按 TP / DP 维度选择性发给 rollout |
| TorchTitan | 类似 FSDP2 的 DTensor 路径 |
| Automodel (vexact) | HF 模型直接 state_dict(),对每个 tensor yield;最简单也最慢 |
对 LoRA 情形,peft_merge=True 时会先 merge adapter 到 base 再 yield;peft_merge=False 时分两条路 yield,base 一次性同步后只增量推 adapter(base_sync_done flag 控制)。这就是 LoRA RL 训练能比 full-finetune 快得多的关键——base 权重只需要在 step 0 同步一次。
6.12 切换后端的 YAML 长什么样
| backend | 关键 YAML 片段 |
|---|---|
| FSDP(默认) | actor_rollout_ref.actor.strategy: fsdpfsdp_config: { wrap_policy: ..., param_offload: false } |
| FSDP2 | 同上但 strategy: fsdp2,自动用 PyTorch 2.x 的 DTensor 路径 |
| Megatron | strategy: megatronmegatron: { tensor_model_parallel_size: 4, pipeline_model_parallel_size: 2, virtual_pp_size: 2 } |
| TorchTitan | strategy: torchtitan(实验性,关注 examples/grpo_trainer/run_*_torchtitan.sh) |
| VeOmni | strategy: veomni,配合 verl-omni 子项目 |
| Mindspeed (NPU) | strategy: mindspeed,硬件昇腾 NPU |
| Automodel (vexact) | strategy: automodel,HF 直跑,调试用 |
换 backend 不需要改算法代码,YAML 一行字段就够。这是 verl 把 engine 抽象做对的回报。
6.13 这章你需要带走什么
- 知道 verl 训练后端有 FSDP / FSDP2 / Megatron / TorchTitan / VeOmni 五条;
- 清楚 FSDP 和 Megatron 的适用边界;
- MoE 几乎必须走 Megatron;
- 知道
actor.fsdp_config.*和actor.megatron.*两套字段在哪里查; - 切换后端只是一个
model_engine=...。