Prhub

#6325 [veomni] feat: add MoE router replay (R2/R3) support

原始 PR 作者 hjshi84 合并时间 2026-05-27 11:49 文件变更 17 提交数 2 评论 3 代码增减 +1829 / -39

执行摘要

为 VeOmni 引擎添加 MoE 路由器重放支持

在MoE模型中,rollout和actor计算log_prob时路由器产生的top-k索引可能因浮点计算顺序而不同,导致策略梯度的污染。通过记录rollout阶段的决策并在actor中重放(R3),或在actor计算log_prob时记录并重放(R2),可消除这种不匹配,提高训练一致性和效率。PR描述中强调:"Removes the floating-point drift between rollout and actor router decisions that otherwise contaminates the RL policy gradient on MoE models."

推荐阅读verl/utils/veomni/router_replay.py中的注释和设计决策(id键索引、R3掩码、回退机制)。对于专注于VeOmni或MoE训练的研究者,理解RR的工作原理有助于排查策略梯度偏差。PR中的测试设计也值得参考,特别是模拟recompute的测试。

讨论亮点

审查中主要涉及三点:

  • 性能优化: gemini-code-assist[bot] 建议在构造布尔掩码时使用torch.repeat替代Python列表,以减少转换开销。
  • 配置解耦: wuxibin89 建议将router_replay的检查逻辑从engine_workers.py移至VeOmniActorConfig.__post_init__,保持工作者层不感知后端细节。
  • 依赖版本: Luosuu 指出可以升级veomni版本至0.1.11,以支持必要的钩子。
    最终PR合并前应已解决这些问题(版本已更新)。

实现拆解

  1. 核心控制器 (VeOmniRouterReplay): 在 verl/utils/veomni/router_replay.py 中实现状态机。通过id键位置索引(使用id(module))分配层位置,支持激活检查点下的反向重计算。提供begin_record/begin_replay/on_router_forward/collect_recorded/clear等生命周期方法。
  2. 引擎集成 (transformer_impl.py): 在VeOmniEngineWithLMHead中集成控制器。在__init__中解析配置并初始化控制器;在initialize()中执行防御性检查(要求use_remove_padding=True)并安装钩子;在prepare_model_inputs中记录侧通道信息(pad_size、cu_seqlens)并构建R3掩码;在forward_backward_batch中组织RECORD/REPLAY生命周期,使用try/finally确保状态清理。
  3. 工作者配置 (engine_workers.py, actor.py): 在ActorRolloutRefWorker.__init__中读取actor.veomni.router_replay.mode,原只支持megatron,现扩展支持veomni。在VeOmniActorConfig中添加配置字段。
  4. 配置和示例: 修改veomni.yaml_generated_ppo_veomni_trainer.yaml暴露router_replay配置块;添加examples/router_replay/run_qwen3_5_35b_a3b_veomni.sh演示R2/R3使用。
  5. 测试覆盖: 新增两个CPU测试文件(共29个测试),分别覆盖控制器状态机(包括per-layer reverse recompute)和引擎胶水代码(侧通道、NestedTensor断言、严格错误路径)。
文件 模块 状态 重要度
verl/utils/veomni/router_replay.py 重放控制 added 9.36
tests/utils/veomni/test_router_replay_on_cpu.py 控制器测试 added 8.42
tests/workers/test_router_replay_engine_helpers_on_cpu.py 引擎集成测试 added 8.42
verl/workers/engine/veomni/transformer_impl.py 引擎集成 modified 8.31
verl/utils/veomni/__init__.py 包初始化 added 6.35
verl/workers/engine_workers.py 工作者配置 modified 6.34
verl/workers/config/actor.py 配置定义 modified 6.01
examples/router_replay/run_qwen3_5_35b_a3b_veomni.sh 示例脚本 added 5.97
verl/trainer/config/engine/veomni.yaml 引擎配置 modified 4.4
verl/trainer/config/_generated_ppo_veomni_trainer.yaml 生成配置 modified 3.87

关键符号

VeOmniRouterReplay.__init__ VeOmniRouterReplay.install VeOmniRouterReplay.uninstall VeOmniRouterReplay.on_router_forward VeOmniRouterReplay.begin_record VeOmniRouterReplay.begin_replay VeOmniRouterReplay.collect_recorded VeOmniRouterReplay.set_microbatch_targets VeOmniEngineWithLMHead._maybe_push_router_replay_state VeOmniEngineWithLMHead.forward_backward_batch VeOmniEngineWithLMHead.prepare_model_inputs

关键源码片段

verl/utils/veomni/router_replay.py entrypoint

新增的核心控制器文件,实现 VeOmniRouterReplay 状态机,包含 RECORD/REPLAY 生命周期、id 键位置索引、跨 rank 聚合、安装 / 卸载等全部核心逻辑。

class VeOmniRouterReplay:
    """Router replay controller for VeOmni (FSDP2 + optional Ulysses SP)."""
    def __init__(self, sp_group: dist.ProcessGroup | None = None) -> None:
        self._sp_group = sp_group
        self._action: RouterReplayAction = RouterReplayAction.DISABLED
        # id(router_module) -> position. Populated lazily on first sight
        # of each router; stable across the lifetime of the controller
        # Key: uses `id(module)` so per-layer activation checkpointing
        # backward recompute (reverse order) still maps to the correct
        # position — monotonic cursor would fail here.
        self._id_to_pos: dict[int, int] = {}
        # RECORD: per-layer-position list of [local_nnz, topk] tensors
        # One entry per micro-batch; outer list grows as new routers fire
        # on the first micro-batch.
        self._recorded: list[list[torch.Tensor]] = []
        # REPLAY: per-layer-position list of target indices for current micro-batch
        self._targets: list[torch.Tensor] = []
        ...
    def install(self, model: nn.Module) -> None:
        """Set this controller as the active replay target in the VeOmni
        moe_router_replay module-level singleton."""
        import veomni.utils.moe_router_replay
        veomni.utils.moe_router_replay.set_active_replay(self)

评论区精华

布尔掩码构造优化 性能

gemini-code-assist[bot] 指出在 `prepare_model_inputs` 中从 Python 列表构建掩码张量效率低,建议使用 `torch.repeat`。

结论:未明确是否采纳,但在合并前应有机会优化。 · 待处理

配置检查移至配置类 设计

wuxibin89 建议将 `engine_workers.py` 中的 `router_replay` 模式检查移至 `VeOmniActorConfig.__post_init__`,以保持工作者层不感知后端。

结论:PR 合并前已调整,当前代码中配置检查已部分移到 `VeOmniActorConfig`。 · 已解决

VeOmni 版本升级 other

Luosuu 建议安装 `veomni==0.1.11` 以支持新钩子。

结论:PR 中已更新 CI 配置使用 0.1.10,可能后续再升级。 · 已解决

风险与影响

  1. 核心逻辑复杂: 状态机在激活检查点下的正确性依赖id键索引,但任何未预期的模块对象创建(如FSDP2 rebuild)可能破坏稳定性。
  2. 配置约束: 要求use_remove_padding=True,若用户在非rmpad路径下启用RR会抛出运行时错误,但文档说明不足可能导致困惑。
  3. 外部依赖: 依赖VeOmni侧set_active_replay钩子,若VeOmni版本不匹配将导致安装失败。
  4. 性能开销: RECORD需要跨SP ranks做all-gather,可能增加通信开销;R3模式的内存占用随序列长度增加。
  5. 测试覆盖局限: CPU测试覆盖了状态机和引擎胶水,但端到端GPU测试仅通过手动脚本验证,可能在多卡/分布式环境下有未暴露的问题。

影响范围: 直接影响VeOmni引擎上的qwen3_moe/qwen3_5_moe模型训练,通过减小策略梯度方差可能提升训练效果。程度: 中。由于引入可选功能,默认disabled,不会影响现有工作流。但用户如需启用,需满足use_remove_padding=True及特定VeOmni版本。团队需保持对VeOmni钩子演进的同步。

核心路径变更 外部依赖 VeOmni 配置约束 use_remove_padding 缺少分布式端到端测试

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论