Prhub

#6029 [fully_async] fix: replace routed_experts on partial rollout resume i…

verl-project/verl · 作者 NoonePauseferg · 合并时间 2026-04-17 10:42

分析状态 已生成
文件变更 1提交数 1 · 评论 8
代码增减 +3 / -5
fully_async rollout experimental megatron

执行摘要

修复完全异步策略中部分 rollout 恢复时 MoE 路由专家重复拼接导致的训练不稳定问题。

根据PR body描述,sglang引擎在每次调用时返回的routed_experts覆盖整个序列(提示词+所有生成token),而部分rollout恢复时输入为提示词+已生成token,sglang会重新处理整个输入并返回完整路由数据。原代码使用torch.cat拼接新旧数据,导致路由专家重复(如提示词+A B C + 提示词+A B C + D E),进而破坏MoE专家重放,引发actor/ppo_kl指标异常。相关Issue包括#4348(部分rollout RFC)、#4101(R3路由器重放)和#5344(完全异步中的R3)。

该PR值得精读,它揭示了在异步训练中处理路由专家数据时的关键设计决策:直接替换而非拼接,以确保路由与权重版本一致。关注作者与reviewer关于路由切片方案的讨论,以及实验结果的权衡。

讨论亮点

reviewer wuxibin89提出替代方案:建议提取与output.token_ids对应的路由专家切片进行拼接,以反映不同模型版本。作者NoonePauseferg回应:直接替换更优,因为actor更新始终使用最新权重(v2),如果使用v1的路由与v2的权重重放,计算出的logprob既不是pi_v1也不是pi_v2,而是不一致的混合;直接替换可确保v2路由+v2权重=干净的pi_v2,使重要性比率定义明确。作者进一步通过实验比较,显示记住路由的版本训练结果更稳定,并创建了PR #6046进行后续探索。最终结论:先合并本PR作为基线,后续再实验每迭代切片方案。

实现拆解

  1. 移除冗余导入:在verl/experimental/fully_async_policy/agent_loop/agent_loop.py中删除import torch,因为路由专家处理不再需要拼接操作。
  2. 修改路由专家合并逻辑:将FullyAsyncLLMServerManager.generate()方法中处理routed_experts的代码从条件拼接改为直接赋值。原逻辑为:如果output.routed_experts不为None,则检查final_output.routed_experts是否为None,若是则赋值,否则使用torch.cat沿第0维拼接。新逻辑为:如果output.routed_experts不为None,则直接final_output.routed_experts = output.routed_experts
  3. 添加注释说明:在修改处添加注释,解释sglang返回完整序列路由专家数据,因此在部分rollout恢复时新输出已覆盖所有位置,无需拼接。
  4. 无测试配套改动:由于这是分布式异步训练中的bug,涉及多节点sglang+Megatron设置和MoE模型,难以在CI中复现,因此未添加单元或端到端测试。
文件 模块 状态 重要度
verl/experimental/fully_async_policy/agent_loop/agent_loop.py 异步策略 modified 6.01
verl/experimental/fully_async_policy/agent_loop/agent_loop.py core-logic

唯一变更文件,包含完全异步策略中 LLM 服务器管理器的核心生成逻辑,修复了路由专家处理 bug。

async def generate(
    self,
    request_id: str,
    prompt_ids: List[int],
    sampling_params: Dict[str, Any],
    image_data: Optional[List[ImageType]] = None,
    video_data: Optional[List[VideoType]] = None,
) -> TokenOutput:
    """
    生成token的异步方法,支持部分rollout恢复。
    """
    # ... 初始化final_output等代码 ...
    while True:
        # 1. 生成tokens
        output = await super().generate(
            request_id=request_id,
            prompt_ids=prompt_ids + final_output.token_ids, # 部分rollout恢复时,输入为提示词+已生成token
            sampling_params=sampling_params,
            image_data=image_data,
            video_data=video_data,
        )
​
        # 2. 将output合并到final_output
        final_output.token_ids.extend(output.token_ids)
        if output.log_probs is not None:
            final_output.log_probs.extend(output.log_probs)
        # sglang返回routed_experts用于完整序列(提示词+所有token),
        # 因此在部分rollout恢复时,新输出已覆盖所有位置。
        if output.routed_experts is not None:
            final_output.routed_experts = output.routed_experts # 直接替换,而非拼接
        if output.num_preempted is not None:
            final_output.num_preempted += output.num_preempted
        final_output.stop_reason = output.stop_reason
        # ... 后续更新global_steps和检查停止条件的代码 ...

关键符号

FullyAsyncLLMServerManager.generate

评论区精华

路由专家处理策略:替换 vs 切片拼接 设计

reviewer wuxibin89 建议提取与 output.token_ids 对应的路由专家切片进行拼接,以反映不同模型版本;作者 NoonePauseferg 认为直接替换更优,可确保路由与权重版本一致,避免不一致的混合计算。

结论:先合并直接替换方案作为基线,后续通过 PR #6046 实验切片方案。 · 已解决

风险与影响

  1. 回归风险:修改后路由专家数据不再累积历史,如果后续逻辑依赖完整历史路由(如某些调试或分析工具),可能引发问题。但根据讨论,这符合当前训练设计。
  2. 性能风险:无显著性能影响,仅改变数据赋值方式。
  3. 兼容性风险:与sglang v0.5.9及更高版本行为兼容,但若sglang未来更改routed_experts返回逻辑(如只返回新增token的路由),则需重新适配。
  4. 测试覆盖不足:由于分布式环境复杂性,缺少自动化测试,可能隐藏边缘情况。
  1. 对用户影响:修复了使用完全异步策略、部分rollout和MoE模型时可能出现的训练不稳定问题(actor/ppo_kl异常),提升训练可靠性。
  2. 对系统影响:仅影响FullyAsyncLLMServerManager.generate()方法中的路由专家处理逻辑,不改变其他模块。
  3. 对团队影响:提供了更清晰的路由专家处理模式,为后续优化(如PR #6046)奠定基础。
核心路径变更 缺少测试覆盖

关联 Issue

未识别关联 Issue

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

完整报告

执行摘要

  • 一句话:修复完全异步策略中部分rollout恢复时MoE路由专家重复拼接导致的训练不稳定问题。
  • 推荐动作:该PR值得精读,它揭示了在异步训练中处理路由专家数据时的关键设计决策:直接替换而非拼接,以确保路由与权重版本一致。关注作者与reviewer关于路由切片方案的讨论,以及实验结果的权衡。

功能与动机

根据PR body描述,sglang引擎在每次调用时返回的routed_experts覆盖整个序列(提示词+所有生成token),而部分rollout恢复时输入为提示词+已生成token,sglang会重新处理整个输入并返回完整路由数据。原代码使用torch.cat拼接新旧数据,导致路由专家重复(如提示词+A B C + 提示词+A B C + D E),进而破坏MoE专家重放,引发actor/ppo_kl指标异常。相关Issue包括#4348(部分rollout RFC)、#4101(R3路由器重放)和#5344(完全异步中的R3)。

实现拆解

  1. 移除冗余导入:在verl/experimental/fully_async_policy/agent_loop/agent_loop.py中删除import torch,因为路由专家处理不再需要拼接操作。
  2. 修改路由专家合并逻辑:将FullyAsyncLLMServerManager.generate()方法中处理routed_experts的代码从条件拼接改为直接赋值。原逻辑为:如果output.routed_experts不为None,则检查final_output.routed_experts是否为None,若是则赋值,否则使用torch.cat沿第0维拼接。新逻辑为:如果output.routed_experts不为None,则直接final_output.routed_experts = output.routed_experts
  3. 添加注释说明:在修改处添加注释,解释sglang返回完整序列路由专家数据,因此在部分rollout恢复时新输出已覆盖所有位置,无需拼接。
  4. 无测试配套改动:由于这是分布式异步训练中的bug,涉及多节点sglang+Megatron设置和MoE模型,难以在CI中复现,因此未添加单元或端到端测试。

关键文件:

  • verl/experimental/fully_async_policy/agent_loop/agent_loop.py(模块 异步策略;类别 source;类型 core-logic;符号 FullyAsyncLLMServerManager.generate): 唯一变更文件,包含完全异步策略中LLM服务器管理器的核心生成逻辑,修复了路由专家处理bug。

关键符号:FullyAsyncLLMServerManager.generate

关键源码片段

verl/experimental/fully_async_policy/agent_loop/agent_loop.py

唯一变更文件,包含完全异步策略中LLM服务器管理器的核心生成逻辑,修复了路由专家处理bug。

async def generate(
    self,
    request_id: str,
    prompt_ids: List[int],
    sampling_params: Dict[str, Any],
    image_data: Optional[List[ImageType]] = None,
    video_data: Optional[List[VideoType]] = None,
) -> TokenOutput:
    """
    生成token的异步方法,支持部分rollout恢复。
    """
    # ... 初始化final_output等代码 ...
    while True:
        # 1. 生成tokens
        output = await super().generate(
            request_id=request_id,
            prompt_ids=prompt_ids + final_output.token_ids, # 部分rollout恢复时,输入为提示词+已生成token
            sampling_params=sampling_params,
            image_data=image_data,
            video_data=video_data,
        )
​
        # 2. 将output合并到final_output
        final_output.token_ids.extend(output.token_ids)
        if output.log_probs is not None:
            final_output.log_probs.extend(output.log_probs)
        # sglang返回routed_experts用于完整序列(提示词+所有token),
        # 因此在部分rollout恢复时,新输出已覆盖所有位置。
        if output.routed_experts is not None:
            final_output.routed_experts = output.routed_experts # 直接替换,而非拼接
        if output.num_preempted is not None:
            final_output.num_preempted += output.num_preempted
        final_output.stop_reason = output.stop_reason
        # ... 后续更新global_steps和检查停止条件的代码 ...

评论区精华

reviewer wuxibin89提出替代方案:建议提取与output.token_ids对应的路由专家切片进行拼接,以反映不同模型版本。作者NoonePauseferg回应:直接替换更优,因为actor更新始终使用最新权重(v2),如果使用v1的路由与v2的权重重放,计算出的logprob既不是pi_v1也不是pi_v2,而是不一致的混合;直接替换可确保v2路由+v2权重=干净的pi_v2,使重要性比率定义明确。作者进一步通过实验比较,显示记住路由的版本训练结果更稳定,并创建了PR #6046进行后续探索。最终结论:先合并本PR作为基线,后续再实验每迭代切片方案。

  • 路由专家处理策略:替换 vs 切片拼接 (design): 先合并直接替换方案作为基线,后续通过PR #6046实验切片方案。

风险与影响

  • 风险:1. 回归风险:修改后路由专家数据不再累积历史,如果后续逻辑依赖完整历史路由(如某些调试或分析工具),可能引发问题。但根据讨论,这符合当前训练设计。
    2. 性能风险:无显著性能影响,仅改变数据赋值方式。
    3. 兼容性风险:与sglang v0.5.9及更高版本行为兼容,但若sglang未来更改routed_experts返回逻辑(如只返回新增token的路由),则需重新适配。
    4. 测试覆盖不足:由于分布式环境复杂性,缺少自动化测试,可能隐藏边缘情况。
  • 影响:1. 对用户影响:修复了使用完全异步策略、部分rollout和MoE模型时可能出现的训练不稳定问题(actor/ppo_kl异常),提升训练可靠性。
    2. 对系统影响:仅影响FullyAsyncLLMServerManager.generate()方法中的路由专家处理逻辑,不改变其他模块。
    3. 对团队影响:提供了更清晰的路由专家处理模式,为后续优化(如PR #6046)奠定基础。
  • 风险标记:核心路径变更, 缺少测试覆盖

关联脉络

  • PR #6046 [fully_async] experiment: per-iteration routed_experts slicing for partial rollout resume: 由本PR讨论引发,进一步探索路由专家切片方案,以比较训练稳定性。
  • PR #5997 [trainer,algo] feat: Support On-Policy Distillation in main_ppo_sync: 涉及在线策略蒸馏和路由专家重放(R3),与本PR的MoE专家重放相关。
  • PR #5989 [megatron] fix: add missing FP8 padding for router replay: 修复Megatron路由器重放路径问题,与本PR的路由专家处理同属路由重放领域。

参与讨论