Prhub

#1829 fix: require explicit off-policy correction for async PPO training

原始 PR 作者 Shi-Dong 合并时间 2026-07-29 09:26 文件变更 3 提交数 1 评论 1 代码增减 +48 / -1

执行摘要

强制异步 PPO 显式选择 off-policy 校正,防止静默错训

PR body 明确指出:use_rollout_logprobs=False 使 trainer 用当前 actor 重算 log_probs 并作为 PPO 比率分母,Clipping is then anchored to a policy that never generated the trajectory,且 The weight_versions recorded on samples are only a metric — nothing enforces consistency。框架虽已提供三种显式校正方案,但默认配置下这种陈旧性会被静默忽略,因此需要一个强制契约来防止用户无意中运行错误目标函数的 PPO 训练。

值得精读。该 PR 以极小改动(48 行)解决了一个隐蔽且影响训练正确性的问题,设计上选择了快速失败而非自动默认,避免替用户做可能错误的策略选择。重点关注三点:一是错误信息直接列出三个可选方案的可操作性;二是将校验放在 train_async.py::train 最前而非参数解析阶段,确保 GPU 等资源分配前失败;三是用 use_critic 简单判别 PPO 估计器的取舍。建议同时审查仓库内所有异步 PPO 示例是否有显式配置这三个 flag,以免被新契约打断。

讨论亮点

PR 无实质技术讨论。唯一评论是 gemini-code-assist[bot] 的服务终止通告:The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.。reviewer Zhichenzzz 直接提交了 APPROVED,未留下审查意见。

实现拆解

  1. 新增校验函数:在 miles/utils/arguments.py 中新增 validate_async_off_policy_correction(args),先判断 args.use_critic(PPO 风格估计器的判别标志),非 PPO 估计器直接返回;否则断言 use_rollout_logprobsuse_tiskeep_old_actor 三者至少一个为真,不满足则抛出包含完整方案说明的 AssertionError,错误信息直接列出三种 flag 的语义。
  2. 入口接线:在 train_async.pytrain() 函数顶部(configure_logger 之前、GPU 分配与 rollout 引擎创建之前)调用该校验,保证在任何资源占用发生前快速失败;同时更新 import。
  3. 测试配套:在 tests/fast/utils/test_arguments.py 中新增 _make_async_ppo_args 辅助构造 SimpleNamespace 参数,并通过 TestValidateAsyncOffPolicyCorrection 的三个测试方法覆盖:默认拒绝路径、三种校正方案各自通过、use_critic=False 的非 PPO 估计器不受影响。作者在 PR body 中说明,因该测试文件 import 了 miles.utils.arguments 而依赖 sglang,故在分支上直接执行了提取出的函数体验证相同用例,CI 中运行真实测试文件。
  4. 无配置/部署变更:未涉及 YAML、Docker 或 CI 配置,纯参数契约与入口校验改动。
文件 模块 状态 重要度
miles/utils/arguments.py 参数校验 modified 6.67
train_async.py 异步训练 modified 4.99
tests/fast/utils/test_arguments.py 参数 modified 6.45

关键符号

validate_async_off_policy_correction train

关键源码片段

miles/utils/arguments.py core-logic

新增 validate_async_off_policy_correction 校验函数,定义异步 PPO 必须显式选择行为策略校正方案的契约,是本 PR 的核心逻辑。

# 新增于 miles/utils/arguments.py 的异步 PPO 策略契约校验函数
def validate_async_off_policy_correction(args) -> None:
    """强制为异步 PPO 训练显式选择行为策略校正方案。    异步训练循环中,下一轮 rollout 会在当前权重更新发布前启动,
    样本可能来自落后于 actor 的陈旧策略(stale policy)。默认情况下
    log_probs 由当前 actor 重算并作为 PPO 比率分母,导致裁剪与
    KL 优势计算锚定在一个从未生成过该轨迹的策略上。
    """
    # 非 PPO 估计器(use_critic=False,如 GRPO)没有该问题,直接放行
    if not args.use_critic:
        return
    # 三个内置方案必须至少显式启用其一,否则视为配置错误
    assert args.use_rollout_logprobs or args.use_tis or args.keep_old_actor, (
        'Async PPO training requires an explicit behavior-policy correction, because rollouts are '
        'generated before the current weight update while log probs are recomputed by the current '
        'actor by default. Pass one of: --use-rollout-logprobs (use the rollout engine\'s log probs '
        'as the ratio denominator), --use-tis (truncated importance sampling correction), or '
        '--keep-old-actor (recompute the denominator with the weights the rollout engines used).'
    )
tests/fast/utils/test_arguments.py test-coverage

覆盖三种校正方案放行、默认拒绝与非 PPO 放行三个分支,防止校验逻辑回归;是 PR 唯一测试配套。

# 构造带默认值的异步 PPO 训练参数,便于测试覆盖不同组合
# 默认 use_critic=True 表示 PPO 风格估计器,三个校正方案均未开启
def _make_async_ppo_args(**overrides) -> SimpleNamespace:
    defaults = dict(
        use_critic=True,
        use_rollout_logprobs=False,
        use_tis=False,
        keep_old_actor=False,
    )
    defaults.update(overrides)
    return SimpleNamespace(**defaults)
​
​
class TestValidateAsyncOffPolicyCorrection:
    # 三个校正方案均未设置时,必须触发 AssertionError 并提示可选方案
    def test_ppo_without_correction_is_rejected(self):
        with pytest.raises(AssertionError, match='behavior-policy correction'):
            validate_async_off_policy_correction(_make_async_ppo_args())
​
    # 任一校正方案单独启用即可满足契约
    @pytest.mark.parametrize('flag', ['use_rollout_logprobs', 'use_tis', 'keep_old_actor'])
    def test_ppo_with_any_correction_passes(self, flag):
        validate_async_off_policy_correction(_make_async_ppo_args(**{flag: True}))
​
    # GRPO 等非 PPO 估计器(use_critic=False)不受新契约影响
    def test_non_ppo_estimators_are_unaffected(self):
        validate_async_off_policy_correction(_make_async_ppo_args(use_critic=False))

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 破坏性变更风险:所有未设置任一校正 flag 的异步 PPO 训练会在启动时立即失败。这是有意行为,但现有训练脚本和配置若未及时补上 flag 会被中断,需要迁移现有命令。
  2. 校验粒度不足validate_async_off_policy_correction 只检查 flag 是否存在,并不验证校正机制本身是否真正生效(例如 --keep-old-actor 的旧 actor 权重是否确实与 rollout 引擎版本对齐),也无法检测样本 weight_versions 的一致性,属于“配置契约”而非“运行时正确性保障”。
  3. 判别符依赖:以 use_critic 作为 PPO 估计器的判别标志,若未来其他估计器(如带 critic 的非 PPO 算法)也启用 use_critic=True,可能被该校验误拦截;当前与框架内 --advantage-estimator ppo 的语义一致。
  4. 测试覆盖受限tests/fast/utils/test_arguments.py 依赖 sglang,在未安装 sglang 的轻量 CI 环境下可能无法执行新增测试,需依赖作者手工验证或等待 CI 完整环境。

异步 PPO 训练的所有现有用户都会受到影响:默认命令将不再可用,必须显式选择三种校正方案之一,否则训练在资源分配前即失败,避免了在错误目标函数下浪费算力。同步入口 train.py、非 PPO 估计器(如 GRPO)以及使用 use_critic=False 的配置完全不受影响。对团队而言,需要同步更新异步 PPO 相关文档、示例和默认配置模板,明确标注该契约;对系统而言,这次防御性校验降低了训练结果不可信的风险,但对架构和运行性能无影响。

异步 PPO 默认配置破坏性失败 校验只查 flag 存在性 测试依赖 sglang

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论