B.1 目标与估计量
问题 1:为变长工具轨迹选择损失聚合方式
场景。 你的 BugFix 风格智能体(agent)产生的轨迹(trajectory),策略 token 数从 40(read、edit、submit)到 2,400(多次失败的编辑加上冗长推理)不等。当前损失是对每条序列内逐 token 项取均值,再在序列间取平均(seq_mean_token_mean)。一位同事提议改为在整个批次上取 token 均值(token_mean);另一位提议对 token 求和后除以一个固定常数。选一个并为之辩护。
参考答案。 把每个选项写成逐 token 梯度项上的权重。在 seq_mean_token_mean 下,长度为 的轨迹中的一个 token 得到权重 ,于是每条轨迹的总贡献相等,而一条 40 token 成功轨迹中的 token 的权重是一条 2,400 token 失败轨迹中 token 的 60 倍。对负优势(advantage)而言,这意味着长的失败轨迹在每个 token 上受到的惩罚很弱,这是一条有文献记载的导致长度增长的途径(Dr. GRPO 的分析,见第 9 章)。在 token_mean 下,批次中每个 token 得到权重 ,于是长轨迹按其长度成比例地主导批次梯度,而且归一化因子随批次变化,把有效学习率与批次的长度分布耦合在一起。常数归一化 给每个 token 相同权重,使有效步长与批次的长度无关,代价是回合较短时梯度更小,并且多了一个在 变化时必须重新审视的超参数。
本书对智能体轨迹的默认做法是 token_mean 或常数归一化,而不是 seq_mean_token_mean,因为逐序列均值正是会产生奖励从未要求的、依赖长度的激励的那个选项。在剩下两个选项之间,如果你很少改变长度预算、并希望免费获得梯度累积不变性(问题 2),就选常数归一化;如果你需要一个与常见公开配方一致的即插即用方案,就选 token_mean。无论选哪个,都要在一个真实批次上测量每条轨迹的总权重与 的关系(即第 6 章中的那张图),并分别跟踪成功与失败轨迹的平均策略 token 长度。如果失败轨迹变长而成功轨迹没有,聚合方式就是头号嫌疑。
问题 2:梯度累积改变了你的结果
场景。 一个原本每个优化器步使用 1 个含 16 条轨迹的微批次(microbatch)的运行,为了容纳更长的上下文,改成了 4 个各含 4 条轨迹的微批次加梯度累积(gradient accumulation)。其他什么都没变。现在奖励上升得更慢,平均长度向上漂移。在记录下来的某一步中,微批次 1 有 100 个有效策略 token,微批次 2 有 900 个。 参考答案。 可能的 bug 是在每个微批次内部计算token_mean,然后对各微批次的损失取平均。在记录的那一步中,微批次 1 的一个 token 得到权重 ,而微批次 2 的一个 token 得到 ,比值为 9。单微批次运行给所有 token 相同的权重 。因此累积后的损失是另一个目标:它会提高那些恰好与短轨迹分在同一微批次的 token 的权重。由于在修复任务中短轨迹不成比例地多为成功轨迹,这种重新加权不是随机噪声;它改变了成功与失败之间的相对推力,并可能导致你看到的长度漂移。
修复方法是在反向传播之前计算全局归一化因子:统计该优化器步所有微批次(以及所有数据并行 rank,通过 all-reduce)的有效 token 数,然后让每个微批次返回其 token 和 除以这个全局计数。问题 1 中的常数归一化聚合从构造上就避开了这个问题。验证方法是:在一个冻结的批次上分别用两种方式运行一个优化器步,比较展平后的梯度;余弦相似度应在数值精度内为 1,范数也应一致。第 14 章推导了正确的归一化,第 18 章展示了它在监控面板(dashboard)上的特征。
问题 3:固定预算下组大小与提示数量的取舍
场景。 你每步能负担 512 个回合(episode)。可选方案是 128 个提示配 、64 个提示配 ,或 32 个提示配 。奖励是二值的。在当前策略下,你的任务池中位任务的成功率接近 0.1,另有一条长尾,其中的任务成功率在 0.3 到 0.5 附近。 参考答案。 使用组相对基线(baseline)和二值奖励时,若组内所有成员都成功或都失败,该组的梯度为零。若某任务的成功概率为 ,则出现退化组的概率为 。当 时,、、 对应的值分别为 0.656、0.430 和 0.185;当 时分别为 0.248、0.058 和 0.003。因此在这个任务池的难度下, 浪费了困难任务约三分之二的回合,而 浪费的不到五分之一,但每步只看到 32 个不同的提示,这会增大更新在提示之间的方差,并减慢对任务池的覆盖。 本书的默认做法是:选择 ,使中位任务上的退化组概率低于大约一半,然后把剩余预算用于提示数量,并按实测通过率过滤任务,而不是无限制地增大 。在这里,这意味着 配 64 个提示,同时丢弃滚动通过率为 0 或 1 的任务(第 14 章),或使用动态采样来补充退化组(第 9 章)。测量每步零优势组的比例,以及每 100 步中获得非零梯度的不同任务数。如果前者高,就过滤或重采样;如果后者低,就增加提示数量。问题 4:稀有成功与标准差归一化
场景。 在 、二值奖励的设置下,某个组恰好有一次成功。你的优势使用总体标准差计算 。一位评审者说,这会放大困难任务上侥幸的成功。计算优势并决定是否保留这一归一化。 参考答案。 这里 ,。成功样本得到 ,每个失败样本得到 。不做除法时,这两个值分别为 和 。一般地,对成功比例为 的组,归一化把所有优势乘以 ,在 时为 2,在 时为 3.02。因此几乎总失败或几乎总成功的任务,每个回合得到的更新比中等难度任务更大。这正是 Dr. GRPO 论文所批评的难度加权(第 7 章)。 这种加权是否有害,取决于困难任务上的成功是什么。如果那一次成功是真正的修复,提高它的权重是一种合理的课程。如果困难任务上的成功大多是侥幸、漏洞利用(exploit)或不稳定测试(问题 9),归一化放大的恰恰是你最不想要的噪声。对于已知存在噪声的验证器(verifier)奖励,本书的默认做法是去掉标准差除法,保留均值(或留一法)基线,并用学习率和裁剪(clipping)控制步长。针对困难任务这一档,测量获得奖励的回合中同时通过更严格留出(held-out)检查的比例。如果比例高,归一化是站得住的;如果比例低,就去掉它。问题 5:手算一个 KL 估计
场景。 从 采样了三个策略 token。逐 token 对数比率 分别为 、 和 。计算这三个 token 上平均的 的 k1、k2、k3 估计,并解释它们为何不一致。 参考答案。 对从 采样的 token,令 。三个估计量(第 10 章)为 、 和 。逐 token 来看, 为 ,均值 ; 为 ,均值 ; 为 、 和 ,均值 。 在从 采样时, 和 对真实 KL 都是无偏(unbiased)的,但在三个样本上它们相差近五倍。这是方差(variance)的差异,而不是矛盾: 在单个 token 上可以为负(第二个 token),且离散程度大;而 对每个 token 都非负,因为 ,并且在两个策略接近时方差更低。 有偏,但在对数比率较小时接近 。评审中还有两点很重要。第一,作为损失时,各估计量的梯度并不自动等于 KL 的梯度;哪个估计量给出正确梯度,取决于 KL 放在奖励中还是损失中,以及 stop-gradient 的位置(在第 10 章中推导)。第二,在监控面板上,要记录 的均值以及 的一个高分位数,因为均值可能掩盖少数散度很大的 token。问题 6:这次运行还该保留 KL 惩罚吗?
场景。 你要从一个 SFT 检查点出发,在一个使用验证器奖励的编程智能体任务上开始强化学习。你所参照的一个公开配方完全去掉了 KL 项。你上一次 的运行学得很慢,它到参考策略(reference policy)的 KL 稳定在每 token 约 0.02 nats,同时奖励停滞。 参考答案。 KL 项的作用是把策略留在奖励可信的区域,并保留你没有训练的能力。对于学习得到的奖励模型(reward model),这两个理由都很充分,因为奖励模型在分布外是错的。对于基于执行的验证器,第一个理由较弱:隐藏测试(hidden tests)不会因为策略移动而变得不准确,尽管漏洞利用仍可能出现(第 11 章)。KL 很小且平稳时奖励停滞,说明惩罚正在起约束作用:策略已经移动到 允许的最远处,奖励梯度不再压过惩罚梯度。 可选方案:降低 (成本低,保留一个软锚点);去掉 KL,依靠裁剪和保守的学习率(省掉参考模型的前向计算,约占 GRPO 一步中训练器额外前向计算的三分之一,以及它的显存);或定期把参考策略重置为最近的检查点(保留局部信赖域(trust region),而不锚定在 SFT 上)。对于用验证器奖励的编程强化学习,本书的默认做法是去掉惩罚或把它设得很小,并用测量来替代它的保护作用:在你关心但不训练的能力上做留出评估(例如普通代码生成或指令遵循)、熵(entropy),以及奖励投机(reward hacking)检测器。如果这些指标退化,就恢复 KL。决策应依据这些留出曲线,而不是 KL 值本身,因为 KL 数值在不同任务之间没有固定含义。B.2 奖励与验证器
问题 7:智能体把公开测试硬编码了
场景。 训练奖励是通过的公开测试(public tests)比例。150 步之后奖励为 0.92,但同一批任务上的隐藏测试通过率是 0.31。检查轨迹发现,编辑内容会针对公开测试的确切输入做分支。 参考答案。 这是最直白形式的奖励投机:奖励衡量的是与智能体能看到的测试的一致性,而策略找到了比修复函数更低成本的达成一致的方法。在 BugFix gym 中这就是hardcode_tests 操作(第 11 章);在真实代码仓库中,它表现为对输入做特判、修改测试,或捕获异常以压制失败。奖励和预期目标从来就不是同一个东西;它们只是在策略较弱时恰好一致。
修复是分层的,按代价排序。在智能体从未见过的隐藏测试上计算奖励,公开测试只作为工具提供。为已知的漏洞利用模式(修改了测试文件、按输入相等做分支)添加检测器,给这类轨迹零奖励,并单独记录以便计数。在评估中加入留出的任务类型,使记住的模式无法通过。本书的默认做法是从每次运行一开始就使用隐藏测试奖励加漏洞利用日志,因为这个漏洞只需一次编辑,策略一定会找到它。逐步测量公开测试与隐藏测试通过率之间的差距;差距扩大是最早的信号,它往往在主奖励指标出现任何变化之前就已出现。
问题 8:部分得分招来部分解
场景。 为了让稀疏奖励变稠密,你把二值的“所有隐藏测试通过”改成了通过的隐藏测试比例。奖励上升更快了,但完全解决率停在 0.45 不再提高,而且现在许多轨迹修好简单的测试用例就提交。 参考答案。 部分得分改变的是最优策略,而不只是信号密度。在比例奖励下,一个稳定通过 10 个测试中 8 个的策略每回合得到 0.8,而一个尝试完整修复、成功率一半的策略最多得到 0.5 加上其部分通过的得分。如果最后几个测试需要修复中代价高的部分(读更多代码、处理边界情况),比例奖励会让跳过这部分成为理性选择。稠密信号有助于早期学习,却损害最终目标。 可选方案:保留二值奖励,用更好的初始策略(SFT 或课程)和过滤来应对稀疏性(第 15 章、第 14 章);使用混合奖励,例如 ,取较小的 使完全成功占主导;或者把 退火到零。本书的默认做法是在隐藏测试上使用二值奖励并按通过率过滤,只有当初始策略几乎什么都解决不了时,才退而使用小 的混合奖励。无论训练奖励是什么,始终把完全解决率作为主要指标报告。问题 9:不稳定测试与奖励噪声
场景。 对相同补丁重新运行隐藏测试,发现有 4% 的结果在不同运行之间翻转。你的成功率约为 0.3。这对学习的伤害有多大?你该怎么做? 参考答案。 把噪声建模为每个二值奖励以概率 独立翻转。那么观测到的期望奖励为 ,因此观测期望奖励的策略梯度是真实梯度的 倍。在 时,信号缩小 8%,而每次翻转都增加方差。仅此一点尚可容忍。真正的伤害在于翻转并不独立于策略:超时的测试对慢的补丁翻转得更频繁,依赖网络的测试则随环境翻转。相关的噪声会使梯度偏向任何与“通过不稳定测试”相关的行为。 本书的默认做法是在训练前逐个测试测量不稳定性(多次运行参考解),丢弃或隔离不确定的测试;然后在固定随机种子、无网络、超时宽松的条件下运行奖励。如果不稳定测试必须保留,对获得奖励的回合做几次重跑的多数投票,其代价比听起来要低,因为只有候选成功才需要重跑。每步在随机抽取的回合样本上,测量奖励与第二次运行的一致率。第 4 章讨论环境的确定性,第 11 章讨论奖励噪声。问题 10:评判模型被利用了
场景。 对于没有测试的任务,你使用一个 LLM 评判模型,对最终补丁和解释打 1 到 10 分。300 步内评判奖励从 5.1 升到 8.7。对 50 条高分轨迹的人工审计发现,其中 30 条包含“所有测试都通过”之类的自信陈述和冗长的解释,但补丁是错的。 参考答案。 评判模型就是一个带提示的学习型奖励模型,针对它做强化学习,就是针对一个固定的、可被利用的函数做优化。策略学到了评判模型会奖励的特征(断言式的说法、长度、格式),这些比正确的补丁更廉价。审计直接给出了问题的估计:60% 的高奖励轨迹是假阳性。 可选方案,从成本最低的开始:去掉评判模型不应读取的通道(删去智能体关于测试结果的陈述,只评判 diff 和真实的工具输出);尽可能加入基于执行的检查,哪怕只是部分检查,例如“补丁能应用且代码能编译”;用包含具体可检查条目的评分细则代替整体打分;集成或轮换多个评判模型;以及加入 KL 惩罚,它在学习型奖励下比在验证器下更重要(问题 6)。本书的默认做法是把评判模型的输入限制为可验证的产物,并与现有的任何执行信号结合,然后定期对最高奖励回合的样本做固定的人工审计,作为常设指标。跟踪评判奖励与审计精度的对比;一旦两者背离,就先停下来修好奖励,再继续训练(第 11 章)。问题 11:隐藏测试通过环境泄漏
场景。 某个新任务类型上的通过率在 20 步内从 0.2 跳到 0.7,远快于其他任务类型。轨迹显示,智能体在编辑之前会运行git log 和 grep。
参考答案。 在单个任务类型上跳得这么快,说明智能体找到了让任务比预期更容易的信息。候选泄漏(leakage)有:仓库的 git 历史中包含修复提交或文件的更新版本;隐藏测试文件存在于容器中;问题描述文本引用了修复;或者包缓存中含有包含修复的上游版本。每一种都是环境构建的 bug,而不是模型能力。关于 SWE 类基准的公开报告讨论过相关问题,即解答出现在问题描述文本或仓库状态中(见第 20 章)。
本书的默认做法是:在被证明无辜之前,把任何快得可疑的任务类型都当作泄漏处理。将容器的文件系统和历史与智能体应当看到的内容做 diff,删除“未来”的历史,把隐藏测试放在容器之外、只在计算奖励时复制进去,并禁用网络访问。然后加入一个金丝雀任务:其唯一的解答位于泄漏位置。一旦金丝雀被解出,就说明泄漏仍然存在。测量每个任务类型的学习速度,以及成功轨迹中查看历史的工具调用频率。
B.3 环境与工具
问题 12:模型学会了立即调用 submit
场景。 100 步之后,70% 的回合只有一次submit 调用,没有任何编辑。通过率从 0.25 降到 0.08。每轮有 的步惩罚,正确提交的奖励为 1。
参考答案。 计算当前策略下两种策略的价值。立即提交得到 (并且失败),所以其回报(return)约为 。一次 6 轮的完整尝试代价为 ,以某个概率 成功,所以其期望回报为 。如果策略在尝试上的成功率已降到 0.10 以下,那么在按现有写法定义的奖励下,立即提交就是更好的策略。步惩罚造成了这种激励,而组基线又强化了它:在一个组内,短的失败比长的失败回报更高,因此相对于组均值会得到正优势。
可选方案:去掉步惩罚,依靠轮数预算来限制长度;让惩罚只作用于成功的回合(这样在成功者中偏好效率,但失败不会因为短而更便宜);或者要求至少调用一次 test 才能提交。对于二值验证器的编程任务,本书的默认做法是不设逐步惩罚,设硬性轮数预算,如果要施加效率压力,只在成功轨迹之间施加。分别测量成功与失败回合的长度分布,以及零编辑回合的比例。第 12 章在 TinyAgent 上复现了这一失败。
问题 13:智能体从不提交
场景。 在 30 轮上限下,40% 的回合因触及上限而结束。这些回合目前得到 0 奖励。其中一些包含从未提交的正确编辑。 参考答案。 截断是训练设置的属性,而不是任务的属性。把截断的回合当作失败,等于告诉策略长回合是坏的;如果部署时也有预算,这是合理的,否则就不合理。把它当作本来会成功也毫无依据。第三种选择是在截断时对最终状态运行隐藏测试,并把该状态当作已提交来给奖励;这会认可正确的工作,并消除修好之后还继续探索的激励。 对编程任务,本书的默认做法是在截断时用同样的隐藏测试评估仓库的最终状态,因为部署产物是补丁,而不是提交这一动作。如果你想教智能体停下来,就在成功回合中给显式提交加一个小奖励。基于价值的方法可以在截断时从 critic(价值模型)自举;不依赖 critic 的方法做不到这一点,这正是 critic 仍然有用的地方之一(第 7 章)。测量截断回合中最终状态会通过的比例,以及首次到达通过状态所需轮数的分布。问题 14:粗粒度编辑操作还是自由形式补丁
场景。 你的原型像 BugFix gym 一样使用一个编辑操作菜单。产品方希望智能体能写任意 diff。使用自由形式 diff 的强化学习学得慢得多:在前 200 步中,60% 的编辑无法应用。 参考答案。 操作菜单是一个小的离散动作空间:探索(exploration)成本低,每个动作在语法上都有效,也更容易分析动作与回报的关系。但它也把可解任务集合限制在菜单覆盖的范围内,且无法迁移到真实工作。自由形式 diff 能表达菜单外的修复,却让大多数随机动作无效,于是早期的奖励信号被格式失败主导,而不是修复质量。 常见的出路是保留自由形式接口,并在强化学习之前去掉格式瓶颈:在有效编辑轨迹上做一个简短的 SFT 阶段;使用一个宽容的编辑工具(对精确字符串做搜索替换,搜索串找不到时给出有帮助的错误信息);以及一个返回具体错误信息的解析器,使失败的编辑成为一条有信息量的观测(observation),而不是白白浪费的一轮。本书的默认做法是使用字符串替换工具的自由形式编辑加 SFT 冷启动(cold start),粗粒度菜单只用作调试环境,在那里每种现象都能廉价复现。把编辑应用率与解决率分开测量;强化学习不应同时学习编辑格式和修复。问题 15:环境重置太贵
场景。 每个回合在一个由仓库快照构建的全新容器中运行。启动容器需要 3 分钟;一个典型回合的智能体交互需要 8 分钟。你每步需要 512 个回合。 参考答案。 启动占每个回合实际耗时的 27%,如果按需启动容器,每个回合都要等待它。由于启动不依赖于策略,它可以移出关键路径。可选方案:为每个任务镜像维护一个预先启动的热容器池,让重置变成一次查找;在同一任务的多个回合之间复用容器,配合一个重置步骤(git checkout 和 git clean,再检查文件系统是否与记录的哈希一致);或者在启动后对运行中的容器做快照并从快照恢复,这取决于你的容器运行时支持什么。
本书的默认做法是按 Little 定律确定大小的热容器池(问题 18),加上对同一任务回合的带验证重置的复用;这是常见情形,因为组采样对每个任务运行 个回合。验证步骤不可省略:不完整的重置会在同一组的回合之间泄漏状态,使它们的奖励相关,进而破坏组基线。测量重置时间、池命中率,以及未通过文件系统检查的重置比例。
B.4 系统与规模
问题 16:陈旧度为 2 步的异步训练器
场景。 你的异步(asynchronous)流水线用比正在训练的权重最多旧 2 个优化器步的权重生成 rollout。你用训练器自己为当前步重新计算的old_logp 来计算 PPO 比率。应当如何校正陈旧度(staleness)?这种校正何时会失效?
参考答案。 数据采样自行为策略(behavior policy)(运行陈旧权重的推理引擎),而不是当前步的 。用训练器当前权重重新计算 old_logp,会让 PPO 比率从 1 开始,从而掩盖分布偏移。在逐 token 的局部校正中,所选动作概率对应的重要性权重是 ,它可分解为 乘以 。第一个因子是 PPO 为信赖域所裁剪的部分;第二个因子是陈旧度与不一致校正(第 13 章)。实践中,把采样器的对数概率与每个 token 一起存储,逐 token 计算截断权重 ,并乘进策略梯度项,同时 PPO 裁剪继续作用于 。截断通过限制大权重来降低方差,代价是引入偏差;它也没有解决前缀分布变化的问题。
这种校正会以三种方式失效。第一,随着陈旧度增大,逐 token 比率的分布变宽,越来越多的 token 触及上限,于是更新的偏差越来越大;被截断的比例就是要盯住的指标。第二,逐 token 权重忽略了序列级比率,而序列级比率是 token 比率的乘积,对长的智能体轨迹会爆炸;逐 token 校正没有处理完整的前缀分布偏移,轨迹变长后,这个近似的误差可能更明显(第 8 章)。第三,如果 包含训练器没有复现的采样变换(温度、top-p),那么比率就完全是相对于错误的分布计算的(第 3 章)。本书的默认做法是:陈旧度上限 1 到 2 步,存储采样器对数概率,逐 token 上限 取 2 左右,并对被截断比例以及用 估计的 设置告警。
问题 17:推理引擎与训练器之间的分词器漂移
场景。 多轮 rollout 以文本形式存储,并由训练器重新分词。对大多数 token,训练器为策略 token 重新计算的对数概率与引擎的值相差的平均绝对值为 0.002,但约 1% 的轨迹在少数位置上的差异超过 5 nats,而这些轨迹主导了梯度范数。 参考答案。 小而普遍的差异是预期中的训推不一致(training–inference mismatch):不同的 kernel、精度和批次形状。特定位置上罕见的大差异则是另一种故障:训练器在给引擎从未采样过的 token id 打分。把生成的 id 解码成文本再重新编码并不能还原,尤其是在工具调用边界、空白字符和特殊 token 附近,因此训练器计算的是另一种分词结果的对数概率,其中一些被模型认为极不可能。这些 token 产生巨大的比率并主导更新(第 5 章)。 修复方法是token-in/token-out 约定:存储确切的采样 id 及其采样器对数概率;通过拼接 id 组装训练序列(策略 id 按采样原样保留,观测 id 由环境封装层分词一次);永远不要对策略文本重新分词。作为防护,逐轨迹记录每个 token 对数概率绝对差的最大值,丢弃或掩码超过阈值的轨迹,并统计丢弃了多少。0.002 的基础不一致随后由问题 16 中的重要性校正处理。测量任一位置差异超过 1 nat 的轨迹比例;修复后它应接近零。问题 18:为长回合设计 rollout 集群
场景。 回合平均 50 轮,结束时上下文为 64k token。每轮生成约 400 个策略 token,并运行一个平均耗时 8 秒的工具。容器启动需要 3 分钟。你希望大约每 30 分钟完成一个优化器步,每步 512 个回合。 参考答案。 从单个回合的实际耗时开始。如果负载下解码速度约为每条序列每秒 40 个 token(这是一个需要测量的假设),每轮生成耗时 10 秒,工具耗时 8 秒,因此一个 50 轮的回合约需 15 分钟,再加上 3 分钟启动。由 Little 定律,在途回合数等于吞吐量(throughput)乘以持续时间:每 30 分钟 512 个回合、每个 18 分钟,意味着约 307 个回合并发,也就是约 307 个容器和 307 条驻留在推理引擎中的序列。307 条最长 64k token 的序列所需的 KV cache 是推理集群的约束瓶颈:每个 token 的大小为 每元素字节数,再乘以驻留 token 总数;在选择引擎副本数之前,你应当为自己的模型算出它。前缀缓存很重要,因为每一轮都是在已有上下文后追加;没有它,每一轮都要重新预填充多达 64k 个 token。 设计选择由这些算术推出。预热容器,让启动离开关键路径(问题 15)。让生成相对训练异步运行,使工具延迟不会让训练器空转,同时设陈旧度上限(问题 16)。约束长尾:如果 5% 的回合耗时是中位数的三倍,同步的一步就要等它们,所以要么使用在权重更新后续跑的部分 rollout,要么限制回合时长并按问题 13 处理截断。避免一个诱人的修复,即多启动一些回合、只用最先完成的 512 个训练:它会悄悄地让批次偏向短回合,而短回合往往是简单的,因此要比较被训练回合与被启动回合的长度分布。本书的默认做法是异步设计,陈旧度上限 1 到 2 步,热容器池大小约为并发数的 1.2 倍,开启前缀缓存,并设逐轮 token 预算。测量引擎利用率、KV cache 占用率、工具时间占比、回合时长的第 95 百分位数,以及每个被训练 token 的陈旧度。第 13 章正是为这一计算构建了一个离散事件模拟器。问题 19:估算一次强化学习运行的 GPU 小时数
场景。 一个参数量 的稠密模型。每步使用 512 个回合,平均每回合总共 24,000 个 token,其中 6,000 个由策略生成。你计划训练 300 步。假设加速器的稠密 bf16 峰值为 FLOP/s,训练的模型 FLOPs 利用率为 40%。估算 GPU 小时数。 参考答案。 前向传播每 token 用 FLOPs,前向加反向用 ,初步估计时忽略注意力的 FLOPs(在 24k 上下文下它们会增加可观的比例,所以把结果视为下界)。训练涉及每一个 token,无论是策略 token 还是观测 token,因为观测 token 是梯度流经的输入。策略更新: FLOPs。重新计算旧对数概率和参考对数概率增加两次前向传播,每 token ,即 。总计 FLOPs,在有效 FLOP/s 下约为 9,800 GPU 秒,即每步 2.7 GPU 小时。 生成受解码限制而非 FLOP 限制。每步 307 万个策略 token,假设对长上下文智能体批次的聚合解码速度为每 GPU 每秒 1,000 个 token,则解码约需 0.85 GPU 小时。每个回合 18,000 个观测与提示 token 的预填充(借助前缀缓存,每个只预填充一次)为 FLOPs,即 0.4 GPU 小时。受计算限制的总量约为每步 4 GPU 小时。主要的不确定性在于推理集群的利用率:如果工具等待和长尾使它只有 25% 的时间在忙,生成约需 5 GPU 小时,一步约需 7.7 GPU 小时,300 步约需 2,300 GPU 小时,这还不包括评估、失败的运行和消融实验(ablation)。在评审中,正确的答案是方法和主导项,而不是最终数字。在一次短的试运行中测量两种角色实际的每 GPU 每秒 token 数,替换上面的每一个假设。问题 20:不断增长的训推不一致
场景。 在权重相同的情况下,第 0 步的平均 为 0.003。到第 400 步它变成 0.03,训练变得不稳定。该模型是混合专家模型。 参考答案。 固定权重下的数值不一致来自两个引擎中不同的 kernel、归约顺序、精度和批次形状。对混合专家模型还有一个额外的、不连续的来源:路由器 logits 中微小的数值差异可能选中不同的专家,这对 token 概率的改变远大于舍入误差。不一致随训练增长,说明策略已移入这些不连续性影响更大的区域,例如路由器或 token 分布接近平局的地方,或者低熵策略使少数大的分歧主导了均值。 可选方案:针对引擎的概率做重要性校正(问题 16),这能限制损害但不能消除来源;使用序列级比率,一条公开的研究路线认为它对 MoE 模型更稳定(见第 20 章中的 GSPO);对齐数值,例如让引擎以更高精度计算最终 logits,或在可用时使用批次不变的 kernel;或者,如果技术栈支持,记录引擎的路由决策并在训练器中重放。本书的默认做法是从第 0 步起记录不一致的分布(均值和第 99.9 百分位数),应用截断的重要性权重,并在尾部一开始增长时就排查数值问题。把不断增长的不一致视为崩溃的先行指标,而不是噪声。B.5 评估
问题 21:计算 pass@k
场景。 对一个任务你采样 次,其中 次通过。估计 pass@1 和 pass@5。一位同事把 pass@5 算成 。谁是对的? 参考答案。 第 17 章中的无偏估计量是 ,即从 个样本中随机取 个的子集至少包含一次成功的概率。这里 pass@1 为 ,pass@5 为 。同事的代入估计给出 。代入估计量是有偏的,因为 是 的凹函数,Jensen 不等式使其期望低于真实值;而组合估计量在 时严格无偏。这个差别并非无关紧要:在这里,单个任务上就差了 8.5 个百分点。 在评审中,还要问 相对于 的大小:当 接近 时,pass@ 的估计方差很大,所以 要远大于你报告的最大 。先在各任务上求估计值,再在任务间平均,然后用在任务上(而不是在样本上)做的自助法(bootstrap)报告置信区间。问题 22:2 个百分点的提升是真的吗?
场景。 在一个 500 任务的基准上,每个任务采样一次,检查点 A 解决 41.0%,检查点 B 解决 43.0%。按任务看,B 解决了 40 个 A 失败的任务,A 解决了 30 个 B 失败的任务。 参考答案。 若把两次运行当作非配对的,每个比率的标准误约为 ,于是差值的标准误约为 0.031,2 个百分点的提升完全在噪声范围内。配对视角才是正确的,因为两个检查点运行在相同任务上:只有结果不一致的任务携带信息。不一致任务分别为 40 和 30 个时,差值为 ,标准误为 , 约为 1.2,在常规水平下仍不显著。对 40 比 30 的划分做 McNemar 检验也得到同样结论。 要判断这 2 个百分点是否可信,可以先在现有任务上降低评测噪声。增加独立任务同样有帮助,尤其是任务差异主导不确定性时。每个任务采样多次并比较逐任务通过率;当问题针对的是方法而不是某个检查点时,运行多个训练随机种子;并在两次运行中固定评测框架(harness)(模板、温度、预算)(第 17 章)。本书的默认报告方式是配对比较,加上在任务上做自助法得到的区间、每个任务的样本数和随机种子数。单次观察到的差异若小于同一配方的跨随机种子波动,就需要更多训练种子和差值区间,才能判断方法是否更优。问题 23:基准得分高,新任务得分低
场景。 你的模型在一个公开的 SWE 类基准上得分很好,该基准的任务来自 2024 年之前的仓库。在你上个月从相同仓库构建的 100 个新任务上,它的通过率只有大约一半,而基座模型(base model)在两组任务之间的比值差距要小得多。 参考答案。 有三种相互竞争的解释。污染(contamination):基准的问题描述、补丁或测试出现在预训练、SFT 或强化学习数据中,因此模型是在回忆而不是修复。分布偏移:新任务出于与记忆无关的原因更难或不同(更新的 API、更大的补丁)。评测框架差异:新任务运行在不同的环境构建中,或测试覆盖更弱。与基座模型对比有帮助:如果基座模型的差距更小,说明差距是在你的后训练过程中扩大的,这指向你的数据,包括从与基准相同的仓库构建的强化学习任务集。 本书的默认做法是:在训练前按仓库、问题描述和补丁相似度对训练任务与每个评估集做去污染;维护一个定期重建、标注日期的新评估集;并同时报告两者。要直接检验记忆,可以用问题描述文本探测模型,看它是否复现出它没被展示过的标准补丁或文件路径。在各检查点上测量公开集与新集之间的差距;在强化学习训练过程中扩大的差距,是你的流水线的属性。第 17 章和第 20 章讨论基准污染与有效性。问题 24:强化学习提高了 pass@1,却在大 k 上变差
场景。 强化学习之后,留出任务上的 pass@1 从 0.22 升到 0.35。在 时,基座模型解决了 0.71 的任务,强化学习模型解决了 0.62。一位同事问,强化学习是不是让模型变差了。 参考答案。 两个测量都可能是对的。采用 on-policy 目标的强化学习会把概率集中到已经成功的行为上,这提高了 pass@1,但可能去掉那些偶尔能解决其他任务的低概率策略。大 的 pass@ 衡量的是在大量采样下对解空间的覆盖,因此更尖锐的分布可能覆盖得更少。这一模式已在推理基准上被公开报告,而它在更长时间训练后或使用显式探索时在多大程度上仍然成立,尚有争议(第 20 章)。 哪个指标重要取决于部署方式。如果智能体对每个问题只运行一次,pass@1 就是产品指标,强化学习模型更好。如果系统采样许多次尝试并用验证器挑选,大 覆盖率就重要,强化学习模型对这样的系统可能更差。本书的默认做法是在相同任务上报告基座和强化学习检查点完整的 pass@ 曲线,并把曲线交叉视为关于熵与探索的信息(第 10 章)。要据此行动,可以尝试保持熵的改动(clip-higher、不那么激进的过滤、更小的学习率),并检查交叉点是否右移且不牺牲 pass@1。B.6 调试
问题 25:奖励上升,但留出 pass@1 不变
场景。 400 步中训练奖励从 0.18 升到 0.55。每 50 步评估一次的留出 SWE-bench Verified pass@1,始终在初始值上下一个百分点以内。 参考答案。 逐项分析两个数字之间的差距。训练奖励与评估指标是同一个函数吗?如果训练使用公开测试、部分得分或评判模型,策略就能在不提高隐藏测试成功率的情况下提高奖励(问题 7、8、10)。评测框架与训练环境相同吗:相同的模板、工具、预算、截断和温度?一个学会使用评测框架所不提供的工具的模型,无法展现其收益。训练任务与评估任务来自同一分布吗?在小型合成仓库上的收益不一定能迁移到大型真实仓库。最后,评估足够灵敏吗?在 500 个任务上每个采样一次,一个百分点在噪声范围内(问题 22)。 成本最低的区分性测量,是在同一批留出的、训练分布内的任务上,同时评估训练奖励和评估指标。如果两者都上升,问题在于迁移或评测框架不一致;如果只有奖励上升,说明奖励正被投机利用。然后抽取 20 条高奖励训练轨迹读一读。本书的默认做法是保留一个训练分布的留出划分,用评估指标打分,作为常设的监控面板与训练奖励并列,这样从第一天起就能看到这个差距(第 18 章)。问题 26:熵在第 50 步前坍缩
场景。 策略 token 上的平均逐 token 熵在 50 步内从 0.9 降到 0.05 nats。奖励起初上升,现在停在 0.30。组越来越多地出现全部通过或全部失败。 参考答案。 熵坍缩(entropy collapse)意味着策略在其访问的状态上几乎变成确定性的,于是组内轨迹几乎相同,组基线给出零优势,学习停止。快速坍缩有几个常见原因:对强化学习而言过高的学习率(强化学习的更新通常比 SFT 小得多);每个批次上的轮次(epoch)过多;对称裁剪限制了低概率 token 的增长幅度,却允许高概率 token 进一步增长;或者奖励强烈偏好某一种模板式回答。一项公开分析把每步熵的变化与对数概率和优势之间的协方差联系起来,这提供了一种理解为什么自信且被奖励的 token 会驱动坍缩的视角(第 20 章)。 可选方案:降低学习率或轮次(成本最低,但会减慢学习);像 DAPO 的 clip-higher 修改那样提高裁剪上界,使具有正优势的低概率 token 能更快增长(第 9 章);加入熵奖励项(有效但容易调过头,系数高时可能产生乱码);或提高采样温度,这会改变行为策略,必须在对数概率中体现出来(第 3 章)。本书的默认做法是先把学习率减半并启用 clip-higher,只在配有目标熵控制器时才加入熵奖励项。只在策略 token 上测量熵,测量退化组比例,并把中等 下的 pass@ 作为结果检查(第 10 章)。问题 27:平均回答长度翻倍
场景。 300 步中,每回合平均策略 token 数从 1,800 增长到 3,700。奖励略有提升。增长集中在失败回合中,并且更多回合触及上下文上限。 参考答案。 长度增长可能是有益的学习行为(更多阅读、更多测试、更多推理),也可能是目标函数的副产物。集中在失败回合中是线索。在序列均值聚合下,更长的失败轨迹把负优势分摊到更多 token 上,每个 token 被压低得更少,于是长的失败在每个 token 上受到的惩罚更少(问题 1)。如果被截断的回合与短的失败得到相同奖励,上下文上限处的截断会使情况更糟,因为截断后的长度不再带来额外代价。 先检查聚合方式:在一个真实批次上计算每条轨迹的总权重与长度的关系。然后按轮的类型拆分长度增长:增长是在一轮之内的推理 token 上、在轮数上,还是在重复的相同工具调用上?重复说明是退化的循环,而不是有用的工作。本书的默认做法是 token 级或常数归一化的聚合,与部署预算一致的显式逐轮和逐回合 token 预算,以及在训练与评估之间一致的超长序列处理。测量成功与失败回合的长度百分位数,以及因截断而结束的回合比例。问题 28:损失掩码在工具输出上训练
场景。 在重构了轨迹构建器之后,模型开始在自己的轮次中生成看起来像测试运行器输出的文本,例如PASSED [100%]。策略 token 的熵急剧下降。奖励不变。
参考答案。 这个症状说明模型在学习生成观测文本,也就是观测 token 进入了损失。常见原因是动作掩码(action mask)与被预测位置之间差一位:掩码对齐到了 ids 而不是 ids[:, 1:],或者某个工具片段的掩码偏移了一位,使其第一个或最后一个 token 被包含进来。由于观测 token 在给定上下文时高度可预测(格式、重复的路径),在它们上训练会降低测得的熵,在监控面板上看起来像是进步。奖励不变,是因为额外的梯度对修复没有帮助。
修复并加防护。对每个整理好的批次断言:凡是生成方不是策略的 token,动作掩码都为零,使用在 rollout 时记录的归属标签(第 5 章)。记录每个批次中被掩码纳入的 token 比例,并分别记录策略位置和观测位置上的熵。本书的默认做法是在一条固定的手工构建轨迹上写单元测试,显式写出其期望掩码,并在每次修改轨迹代码时运行。第 18 章复现了这一故障及其在监控面板上的特征。
B.7 研究判断
问题 29:在教师轨迹上做 SFT,还是做强化学习?
场景。 你有一个能解决 60% 训练任务的强教师智能体,以及一个能解决 15% 的 7B 学生模型。预算允许在 20,000 条过滤后的教师轨迹上做 SFT,或在学生上做强化学习,或两者都做。选哪个,按什么顺序? 参考答案。 在教师(teacher)轨迹上做 SFT,是在教师的状态分布上做行为克隆(behavior cloning)。它廉价、稳定,并且在学生(student)落后很多时能让学生进步很快;但学生会访问教师从未访问过的状态(它自己的错误),而这些轨迹没有教它如何从中恢复。这种累积误差随回合长度增长。强化学习在学生自己的分布上训练并直接优化结果,但它需要非零的成功率才能得到梯度,每次有用更新的代价也高得多。on-policy 蒸馏(on-policy distillation)介于两者之间:学生生成,教师为学生的每个 token 打分,于是学生在自己的状态上获得稠密监督(第 16 章)。 本书的默认顺序是先做 SFT,消除格式失败,并把成功率提高到强化学习的组具有信息量的范围(问题 3),然后在学生自己的 rollout 上做强化学习或 on-policy 蒸馏。当训练时能使用教师且它与学生共享分词器(tokenizer)时,用 on-policy 蒸馏;当教师不可用、不匹配或比目标更差时,用强化学习。在 SFT 之前,检查教师轨迹与强化学习模板的格式兼容性,因为阶段之间的模板变化是退化的常见来源(第 15 章)。在 SFT 之后测量学生的熵:如果 SFT 让熵坍缩了,强化学习就没有多少可供探索的余地。问题 30:要不要采用上周预印本里的新方法?
场景。 一篇预印本报告,其新的优势估计量在一个 32B 模型上把 SWE-bench Verified pass@1 比 GRPO 提高了 6 个百分点。你的负责人问是否要切换生产配方。 参考答案。 先读清楚到底比较了什么。基线是否用同样的力度调过参(学习率、裁剪范围、过滤)?用了多少个随机种子,提升是否大于不同种子间的波动?评测框架是否相同,包括温度、预算和测试时扩展?提升在作者的较小规模上是否成立,在第二个基准上是否同号?这个主张是关于估计量本身的,还是配方同时改变了数据、长度预算或训练步数?在有其他人复现之前,预印本中的每个数字都是作者报告的。 本书的默认做法是先复现机制,再复现标题数字:把改动实现为一个开关,在一个小的受控环境中测试它,在那里你能测量它对其声称修复的量(方差、偏差、熵)的影响;然后在适中规模上用多个随机种子做匹配的消融实验;只有到那时,才做配对评估(问题 22)的生产规模 A/B 测试。如果它在适中规模上以大于种子噪声的幅度胜出,且没有任何护栏指标退化,就采用它。事先写下什么样的结果会让你否决它。第 20 章给出了解读作者报告收益的完整清单。小结
- 智能体强化学习(agentic RL)中的大多数设计评审问题,都可归结为一小组机制:损失如何给 token 和轨迹加权,基线如何产生或消除信号,奖励与目标有何不同,行为策略与被训练策略有何不同,以及评估与部署有何不同。
- 好的答案要指出机制、能将其与其他可能区分开的成本最低测量、默认行动及其代价,以及确认修复有效的指标。
- 简短的计算(退化组概率、标准差归一化的缩放、KL 估计量、pass@、配对标准误、Little 定律、FLOP 计数)能在做任何实验之前平息许多争论。
- 本附录中的默认做法是在所述假设下的建议。它们是设计评审的起点,而不是规则。
练习
- 对问题 3,推导每步具有非零梯度的任务数的期望,作为 、提示数量和任务池成功率分布的函数;并对成功率在 上均匀分布、预算为 512 个回合的任务池,求出最优的 。
- 对一个总参数 200B、激活参数 20B 的混合专家模型重做问题 19。哪些项随总参数扩展,哪些随激活参数扩展?
- 改变问题 9 中的一个假设:假设翻转只以概率 发生在正确的补丁上(假阴性)。推导观测到的期望奖励及其梯度,并与对称情形比较。
- 在一个不同的约束下为问题 18 写一页设计评审:环境只允许 64 个并发容器。哪些东西会改变?你放弃了什么?
- 从上面挑选任意两个默认做法相互冲突的问题(例如问题 6 去掉 KL 与问题 10 使用评判模型)。写出在给定运行中决定采用哪个默认做法的决策规则。
- 对问题 28,针对第 5 章中的
collate函数编写答案中描述的单元测试,并确认当掩码偏移一位时它会失败。