假设要训练一个能在沙箱里修复小程序的助手。一次合格的修复需要提交补丁,并实际跑过要求的回归用例。拿基座做一轮 rollout,发现它有时只解释报错,有时改了代码却没验证,就直接说「修好了」。验证器只判最终任务是否成功,给每条轨迹打 0 或 1。给同一个问题多采样几次,可能还是一组 0。冷启动 SFT 值不值得做,可以先算一笔采样账。
一组回答到底有没有奖励信号
在结果监督的 GRPO 里,同一道题生成多条轨迹。每条轨迹的奖励减去组内均值,再按组内标准差缩放。用四条轨迹来算,假如奖励为 [0, 0, 0, 1],均值是 0.25。用总体标准差约 0.433、忽略数值稳定项,得到的优势约为 [-0.58, -0.58, -0.58, 1.73]。唯一一次成功提供了清楚的组内比较信号。
[0, 0, 0, 0] 和 [1, 1, 1, 1] 都没有组内奖励差异,实践中可以把这类组的优势设为零或过滤掉。如果同样答错的几条轨迹拿到了不同的部分分,组内仍有比较信号。带参考策略 KL 正则的 GRPO 在优势为零时仍可能因 KL 项产生梯度,消失的是奖励驱动的那一项。DeepSeekMath 的 GRPO 目标
用有效组比例算 rollout 账
假设同一批 100 道题,每题采样 8 次。基座产生 10 个奖励有差异的组,冷启动后产生 40 个。两个批次都生成 800 条轨迹,平均每得到一个有效组,分别消耗 80 条和 20 条轨迹。在有效组比例近似稳定的条件下,攒够 40 个有效组,预计分别需要约 3,200 条和 800 条轨迹。这是算例,实际训练还要比较生成 token、工具耗时、显卡小时和冷启动本身的成本。DAPO 的动态采样也会过滤并补采二值奖励下全对或全错的组。DAPO 动态采样
做小规模试跑时,可以用同一批题、相同组大小和采样设置,分别统计全对、全错、混合组占比。SFT 可能让原本全错的组出现成功轨迹,也可能让原本混合的简单题变成全对,所以最好按题目难度分别记录。基座若已经能稳定采到一部分成功样本,奖励可自动核验,接口和输出格式也够稳定,就可以先用等预算试跑直接 RL。DeepSeek-R1-Zero 展示了基座在可验证推理任务上直接做 RL 的一种路径。DeepSeek-R1 技术报告
奖励先保成功,再比较成本
如果只奖励少调用工具,模型可能通过提前结束拿到高分。可以先由环境判定修复是否通过,要求实际跑过指定用例,再给成功轨迹按成本打分。下面以调用次数为例,n 由执行器记录,失败的调用和重试也算进去。
python
def reward(success: bool, n: int) -> float:
if not success:
return 0.0
return 1.0 - 0.2 * min(n / 4, 1.0)
两次调用做对得 0.90,四次做对得 0.80,零次调用直接声称修好得 0。成功分始终高于失败分。同组全对时,如果调用次数不同,模型也能区分哪条成功路线更省。GRPO 会按组内差异缩放优势,所以 0.2 划定的是原始奖励区间。想知道成本信号在更新中有多强,还得观察各类组的优势分布。
如果只根据回答里写的「测试通过」评分,模型就可能学会伪造测试结果。把测试日志和调用计数放到环境侧,使用受保护的隐藏测试,再留一批题覆盖工具报错、超时和必须多次验证的情况。
验收时先看端到端修复成功率和回归通过率,再比较成功任务的平均调用数和 P95 延迟,并一起报告全部请求的 token 和工具成本。训练侧记录每千条 rollout 有多少有效组,以及到达同一验证成功率用了多少 GPU 小时。这样才能算清冷启动省下的 rollout,是否覆盖了准备示范和训练 SFT 的开销。