r6 训崩复盘 与 r7 修复配置对照(SAO / Qwen3.5-35B-A3B / SWE 任务)

日期:2026-08-31 · 运行:sao-qwen35-35b-a3b-3nodes(3 节点,单 rollout GAE + value model) 配置文件:scripts/train/configs/sao_3nodes_qwen35_35b_a3b_dynbsz_warmcritic.env(r7) 对照:r6 = sao_3nodes_qwen35_35b_a3b_dynbsz.env 直跑,step ~130 崩溃


一、r6 为什么训崩

因果链(一句话版)

critic 始终没有收敛 → 单 rollout GAE 退化成无基线的 REINFORCE → 一个对奖励中性的 "think 工具调用"习惯被迷信式强化 → 滚雪球至 72% 的工具调用都是 think,策略瘫痪。

逐环拆解

  1. critic 被饿死。 r6 的 CRITIC_GRAD_CLIP=1.0,而实际 critic/grad_norm 常年在 7-68 之间——每一次更新都被缩小 7-70 倍,配置的 CRITIC_LR 形同虚设。结果: vf_explained_var 在 130 步里始终 0~0.3,价值基线从未成型。
  2. 基线失效 = 优势失真。 SAO 的设计是"critic 提供基线、无需组内采样"(n=1)。 critic 不准时,优势 ≈ 原始回报,且 r6 还关了 batch 白化 (GAE_WHITEN_ADVANTAGES=False,2026-08-13 的决定,当时假设 critic 会提供基线)。 未白化的批优势均值在 ±0.25 之间大幅漂移——整批全正/全负的更新时有发生。
  3. 压垮骆驼的三根稻草。 steps 119-121 出现连续三个全负优势批,紧接着 think-spam 起飞。全负批对所有行为无差别打压,唯独"多调 think(不产生 token 消耗奖励差异的空操作)"这类中性行为相对受罚最轻,于是被相对强化。
  4. 迷信强化的指数轨迹。 think 调用占全部工具调用的比例:
  5. steps 5-50:0.06-0.08(基线)
  6. steps 80-100:0.17-0.18
  7. step 115:0.29
  8. step 130:0.73 ← 崩溃确认
  9. 期间 val 从未突破:step 20-120 一直卡 0.62-0.66,说明这 130 步没有积累任何 已验证的能力——回滚不损失任何东西(这是 r7 敢从 step 0 重来的依据)。

崩溃的外在表现

模型在轨迹中反复、连续地调用 think 工具(一步多次、无实质内容),挤掉真实的 编辑/执行动作;num_turns 上升而有效动作密度下降,最终任务通过率崩塌。


二、r7 相对 r6 的配置差异(四个 delta,各断一环)

# 配置项 r6 r7 针对哪一环
1 GAE_WHITEN_ADVANTAGES False True 批白化重新居中优势,杜绝"全负批";即使 critic 弱,也有批相对基线兜底
2 CRITIC_GRAD_CLIP 1.0 10 停止饿死 critic:让典型梯度(5-30)不再被缩,critic LR 恢复真实含义
3 CRITIC_MODEL_PATH 随 actor 初始化(value head 随机) r6@step100 的暖启动 critic(合并后的 HF 检查点) 价值冷启动是主要瓶颈(论文 3.2 节);r6 critic 的训练数据(样本 0-6400)完全早于 spam 爆发,"弱而不歪"
4 CRITIC_WARMUP 0 20 actor 冻结 20 步,让暖启动 critic 先在基础策略上重新校准,校准期 actor 不可能被伤害

白化(delta 1)具体是怎么做的

实现在 verl/trainer/ppo/core_algos.py 的 compute_gae_advantage_return (GAE_WHITEN_ADVANTAGES 映射到其 whiten_advantages 参数),两步:

  1. 先按标准 GAE 逐 token 算原始优势:δ_t = r_t + γ·V_{t+1} − V_t, A_t = δ_t + γλ·A_{t+1}(observation token 被 response_mask 跳过,V 和 TD 误差 都只在模型自己生成的 token 上传播);
  2. 再对整个 batch 的全部 response token 做一次 masked_whiten: A ← (A − μ_batch) / √(σ²_batch + ε),其中 μ/σ² 是 64 条轨迹 × 各自数千 response token 汇总的标量均值/方差(带 Bessel 修正),输出严格零均值、 单位方差。

关键细节:critic 的回归目标(returns = 原始 A + V)在白化之前构建,白化只作用于 喂给 actor 的优势——所以它不污染价值学习,只改变策略梯度的中心和尺度。

为什么有效(对 r6 崩溃链的针对性)

代价与回退条件(为什么 2026-08-13 曾把它关掉):单 rollout 下没有组内对照, 白化的"批相对基线"叠在 critic 的价值基线之上,理论上是重复居中——批里其他 prompt 的难度会混进本 prompt 的优势里,引入跨任务噪声。当时假设 critic 会承担基线职责, r6 用实测(ev 常年 <0.3)推翻了这个假设。配置里写明的交还条件:ev 连续 ~20 步 保持 >0.4 后,后续 run 可以再试 False——即"critic 证明自己之前,白化不下岗"。

配套但非本质的:CRITIC_PPO_EPOCHS=2(critic 比 actor 更新快)、 ROLLOUT_IS_THRESHOLD=0.2_4.0、故意不加 scaffold 侧 think 限制(隔离 RL 侧修复 的因果贡献,用监控线 think-frac>0.15 兜底代替)。

r7 的结果验证


三、r7 的行为模式变化(轨迹级测量)

按训练阶段各采样 N=80 条轨迹做的分类统计(早期 vs 后期):

行为指标 早期 后期 判读
编辑后回读该文件(read-back-after-edit) ~65-70% ~65-70% 稳定,验证习惯保持
每轨迹编辑次数 4.7 8.0 上升:更多的迭代修改
每轨迹运行完整测试套件(pytest 等) 88.8% 57.0% 下降,但见下行
python -c 内联执行(次数,全采样窗口) 691 1018 大幅上升
任意代码执行(测试套件 ∪ 内联执行)占轨迹比 97.5% 93.7% 基本持平

核心结论:验证行为没有消失,而是换了形态——从"跑整套 pytest"迁移为"用 python -c / heredoc 做快速定点验证"。这是效率优化方向的漂移,不是验证习惯的 退化。唯一的关注项:最后四分之一阶段测试套件运行率降到 46.8%,如果 val 再次 进入平台期,这是第一个要检查的假设(是否在丢失回归测试的习惯)。

与 GRPO 训练同款模型的对照

GRPO 腿的表现:num_turns 与 response length 双双上涨,CoT 长度 +30%, read-back 和测试套件运行比例上升。r7(SAO)的对照:


四、附:r7 运行期的三个工程教训(与训崩无关但代价高昂)

  1. resume 会静默覆盖配置里的 LR 改动。 verl 恢复时加载 optimizer + lr_scheduler 状态,checkpoint 里的旧 LR 直接钉死新配置(r7 曾把 CRITIC_LR 写成 2.5e-6,实际 119 步全部跑在 5e-6)。修复:checkpoint.load_contents='[model,optimizer]' 跳过 scheduler,新建的 scheduler 会按配置 LR 生效(已验证:权重增量比值 1.954≈2)。
  2. critic 在 5e-6 下的"肥尾"不是不稳定。 grad_norm 偶发 30-70(甚至一次 233, 源于 sandbox pod 集体消失产生的空壳批),但窗口均值/中位数平、vf_loss 下行、 从未连续 3 步越线;clip=10 是真正的减震器。判据要用"连续 3 步 >30",单发不动作。
  3. actor LR 1e-6→2e-6 的 A/B:同 checkpoint 160 出发,2e-6 腿 val@180=0.646, 1e-6 腿 val@180=0.682。2e-6 的位移确实翻倍(物理验证),但没有换来 val;差值 3.6pt ≈ 1.2σ,方向上支持保守 LR + 健康 critic 的组合。