一个修 bug 的 agent 跑了 3 轮:读文件,改代码并跑测试,提交。最后隐藏测试全部通过,奖励(reward) 1。训练时马上有两个问题:这 1 分该算到哪些 token 头上?工具返回的上千个 token,要不要也算进 loss?目前最常见的做法是把整条轨迹当作一个样本:同一任务跑一组轨迹,用组内结果算一个 episode 级 advantage,铺到这条轨迹里模型自己生成的每个 token 上;工具输出留在上下文里,但从 loss 里 mask 掉。想把成败分得更细(按状态分组比较、训练 turn 级 critic、给每一轮单独奖励),要么依赖环境状态会重复出现,要么多训一个模型,要么得有可信的中间信号。

待审核草稿。 这一篇还没有经过站主审阅,事实、取舍和措辞都可能修改;引用前请回到文中链接的一手资料核对。

一条轨迹进了训练器,长什么样

多轮 agent 的一条轨迹,拼起来就是一个长 token 序列:任务描述,模型的第 1 个动作 $a_1$,工具返回的观察 $o_1$,模型的第 2 个动作 $a_2$,……一直到提交或预算用完。动作由策略采样;观察来自环境,模型没有“选择”它们。后训练基础设施已经用一个 20 个位置的例子讲过 mask 和分母,这里只写出多轮版本的目标。省略 PPO 的概率比、clipping 和 KL 之后(见 PPO clipping),一组 $G$ 条轨迹的 loss 是:

$$ \mathcal L(\theta)=-\frac{1}{\sum_{i,t} m_{i,t}}\sum_{i=1}^{G}\sum_{t} m_{i,t}\,\hat A_{i,t}\,\log\pi_\theta\big(y_{i,t}\mid y_{i,<t}\big) $$

$y_{i,t}$ 是第 $i$ 条轨迹的第 $t$ 个 token,$y_{i,<t}$ 是它前面的全部内容,包括工具输出;$m_{i,t}$ 在模型生成的 token 上取 1,在 prompt、工具输出和 padding 上取 0;$\hat A_{i,t}$ 是这个 token 分到的 advantage。分母这里取全部有效动作 token;也可以先在每条轨迹内平均、再对轨迹平均,两种做法给长轨迹的权重不同,见 DAPO 的两种平均方式。

Search-R1(Jin 等,2025-03,论文 §3.1)把这个 mask 写成指示函数 $I(y_t)$,让 policy gradient 只在模型生成的 token 上计算。verl 的 Agent Loop 文档里,response_mask 对模型生成的 token 记 1,对工具返回的 token 记 0。被 mask 的 token 仍然留在上下文里,后面的动作是看着它们生成的;mask 只决定梯度落在哪里。多 agent 系统里也有同样的划分:Kimi K2.5(论文,2026-02)训练并行 agent 时冻结子 agent,把它们的输出当作环境观察,只用 RL 更新协调者,理由是避免信用分配不清和训练不稳。

算一次:3 轮回合,4 条轨迹

下面全是教学假设。任务是修好 utils.py 里 parse_date 的一个 bug。每条轨迹最多 3 轮,第 3 轮提交后环境跑隐藏测试:全过得 1,否则得 0。同一任务采 $G=4$ 条轨迹:

轨迹 第 1 轮 第 2 轮 第 3 轮 奖励 $R_i$
τ1 读 utils.py 改对,跑可见测试 提交 1
τ2 读 utils.py 改错一处,可见测试没发现 提交 0
τ3 搜索 parse_date 的调用点(call site) 改对,跑可见测试 提交 1
τ4 先跑一遍测试 改对,跑可见测试 提交 1

按组内总体标准差做标准化(和站内 GRPO 算例同一约定):均值 $\bar R=0.75$,标准差 $\sqrt{0.1875}\approx0.433$,

$$ \hat A_i=\frac{R_i-\bar R}{\operatorname{std}(R)}\quad\Rightarrow\quad \hat A=[+0.577,\;-1.732,\;+0.577,\;+0.577] $$

PyTorch 的 torch.std 默认用 $G-1$ 作分母(文档),按它算是 $[+0.5,-1.5,+0.5,+0.5]$:比例不变,尺度不同。DeepSWE(Agentica 与 Together AI,2025-07-02,博客)在它的 GRPO 变体里去掉了除以标准差这一步,并改用 leave-one-out 基线(baseline):每条轨迹减去其余 3 条的平均,得到 $[+0.333,-1,+0.333,+0.333]$。三种约定的符号和相对大小一样,加起来都是 0。Episode 级做法里 $\hat A_{i,t}=\hat A_i$,同一条轨迹里每个动作 token 拿到同一个数。

把 τ1 展开成 token,token 数是编的:

片段 内容 token 数 谁产生 $m$ 每个 token 的 advantage
prompt 任务描述、工具说明 400 环境 0 不进 loss
$a_1$ 调用 read_file("utils.py") 30 模型 1 +0.577
$o_1$ 文件内容 900 工具 0 不进 loss
$a_2$ 一段思考、一次修改、调用测试 150 模型 1 +0.577
$o_2$ 可见测试的日志 300 工具 0 不进 loss
$a_3$ 提交 20 模型 1 +0.577

1,800 个 token 里只有 200 个有梯度,占九分之一。假设 4 条轨迹的动作 token 都是 200 个,上面公式的分母就是 800;τ2 的 200 个动作 token 各乘 −1.732,被压低,其余三条被抬高。

不 mask 工具输出会怎样

τ1 的 900 个文件内容 token 和 300 个测试日志 token 也会拿到 +0.577:训练会抬高“模型自己写出这段文件内容、这段日志”的概率,可这些文字是环境给的,不是模型的决定。τ2 的日志被乘上 −1.732,等于教模型“别预测出这段真实的报错”。在这个例子里,工具输出是动作 token 的 6 倍(1,200 对 200),梯度的大头会落在和决策无关的地方。Search-R1 的消融(§5.4,Table 4,Qwen2.5-7B base,PPO):加 mask 时平均 exact match 是 0.431,不加是 0.343。

整条轨迹一个分数,哪里不够

Episode 级 advantage 不区分轨迹里哪一步做得好。τ2 错在第 2 轮那处修改,可它第 1 轮读文件的动作也拿到 −1.732。

这个读文件动作 τ1 和 τ2 都做了,上下文也相同,两份梯度方向一样,会直接相加:$0.577-1.732\approx-1.155$,净效果是被压低。统计上这不算错:这一组里先读文件的两条轨迹成功率 1/2,低于组平均 0.75。但只有 4 个样本,这个判断基本是噪声;episode 级方法靠大量任务和采样把它平均掉。反过来,如果 4 条轨迹第一步完全相同,减去均值后的 advantage 加起来正好是 0:所有轨迹都做的那一步,既不被奖励也不被惩罚,被拉开的只有轨迹之间不同的地方。

按状态再分一次组:GiGPO

GiGPO(Feng 等,2025-05,NeurIPS 2025)保留 episode 级的组相对 advantage $A^E$,再找组内重复出现的环境状态(anchor state),把从同一状态出发的动作放进一个小组,用各自之后的折扣回报比较(§4.2–4.3,Eq. 5、7、8):

$$ R_t^{(i)}=\sum_{k=t}^{T}\gamma^{\,k-t}r_k^{(i)},\qquad A(a_t^{(i)})=A^E(\tau_i)+\omega\,A^S(a_t^{(i)}) $$

$r_k^{(i)}$ 是第 $i$ 条轨迹第 $k$ 步的奖励,这里只有最后一步非零;$A^S$ 是在同一 anchor state 的小组里对 $R_t$ 做的组内标准化。论文取 $\omega=1$、$\gamma=0.95$(附录 E.1)。例子里 τ1 和 τ2 读完同一个文件后处在同一状态,第 2 轮的两次修改组成一个两元素小组:回报 $[0.95\times1,\;0]$,标准化后 $A^S=[+1,-1]$。τ1 那次修改的 token 合计 $0.577+1=1.577$,τ2 那次是 $-1.732-1=-2.732$。这次比较只发生在“从同一处出发的两个选择”之间,不受 τ3、τ4 走了别的路影响。

限制也要算清楚。4 条轨迹都从同一个初始状态出发,所以第 1 轮的小组就是整组;回报只差一个公共因子 $\gamma^2$,按标准差归一化后 $A^S$ 和 $A^E$ 完全相同,等于把 $A^E$ 加了两遍。论文的局限一节说,方法依赖状态匹配,复杂环境里很难认出相同状态;没有状态重复时,它退化成 GRPO。训练细节里,他们把最长公共子序列相似度超过 0.9 的两个状态并为一组(§5.1)。代码仓库里两条轨迹走到同一状态的机会,可能比 ALFWorld 这类文字环境少:这是推测,论文没有在代码任务上测。

其他做法:turn 级 critic 与逐轮奖励

做法 信号从哪来 买到什么 代价与前提 例子
Episode 级回报 整条轨迹的最终结果;组相对时和同一任务的其他轨迹比 不训 critic,实现简单 组相对要为每个任务跑多条完整轨迹;轨迹内所有动作同一个分 DeepSWE(2025-07-02)、GLM-5(2026-02)用组相对;Kimi-Researcher(2025-06-20)用 REINFORCE,并给正确轨迹乘一个 γ 衰减因子,鼓励更短的路径
按状态分组 组内重复出现的状态 不加模型,rollout 不变 状态要能匹配;不重复时退化为 GRPO GiGPO(2025-05)
Turn 级 critic 学出来的、按轮次估计的价值 每一轮都有估计,可以复用旧数据 多一个模型,估值有误差 ArCHer(2024-02):句子级 off-policy critic 带动 token 级策略
训练时多给 critic 信息 critic 能看到策略看不到的参考答案等 中间步的打分更准 只在训练时有这种信息的任务上可行 SWEET-RL(2025-03),ColBench 上比其他多轮 RL 方法高 6 个百分点
逐轮奖励 环境或规则给的中间奖励 信号更密 中间奖励要设计,也可能被钻空子 Wei、Zeng 等(2025-05):在搜索和游戏任务上,逐轮奖励在 GRPO 和 PPO 下都好过只有终局奖励
长任务用 critic PPO value model 估计片段之后的回报 能处理长度、数量不同的训练片段 critic 的训练成本和误差 GLM-5.2(官方博客,2026-06)从组相对改成从单条 rollout 学习的 critic PPO,见接到 LLM;SAO(2026-07),见异步 RL

多步 critic 怎么用 TD 和 GAE 把终局奖励往回传,接到 LLM 里有一个 3 步算例,MC 与 TD 讲了终止和截断的区别,这里不重复。RAGEN(Wang 等,2025-04,论文)在多轮 RL 里观察到一种反复出现的崩溃,作者叫它 Echo Trap:奖励方差(variance)突然塌下去、梯度出现尖峰;他们的稳定版 StarPO-S 用了轨迹过滤、加入 critic 和梯度稳定三类改动。

回合变长以后:上下文、截断和 token 对齐

上下文越来越长,大头是工具输出。 例子里 3 轮就有 1,800 个 token,其中 1,200 来自工具。这些 token 不进 loss,却决定了每一轮的 prefill 有多贵、什么时候撞上上下文上限。

撞上限的轨迹怎么记分? 常见选择有三种。记为失败(0 分),模型会学到“早点交差”,长任务被系统性地压低;整条从 loss 里去掉,少了一部分负样本(negative example),但不再惩罚“还没做完”;把它当作截断、用价值估计接上后续回报,需要一个 critic。DeepSWE(博客,2025-07-02)选了第二种,叫 compact filtering:撞上最大上下文、最大步数或生成超时的轨迹,从 loss 里整条 mask 掉。作者给的理由是,只在 agent 主动提交时才给奖励,能鼓励它在提交前认真测试,也能推迟奖励崩溃。GLM-5(论文 §4.1.2)把因沙盒崩溃而失败的样本排除在训练之外,免得模型替环境的问题挨罚。单轮生成里的同类问题见 DAPO 的超长样本处理,终止和截断的区别见 MC 与 TD。

压缩历史改变的是观察。 上下文装不下时,系统会摘要或丢掉旧内容,之后的动作是看着压缩后的历史做出的。由谁来写摘要也有区别:如果是策略模型自己写,摘要就是它的动作,可以进 loss;如果是另一个模块写,它就是观察。把一条长任务切成几段训练时,片段边界不等于任务结束,见接到 LLM 的切片一节。

训练用的 token 必须是生成时的那些 token。 常见的 agent 框架会把模型输出解析成消息和工具调用,下一轮再用对话模板重新编码。verl 的 Agent Loop 文档指出,解析后的消息再编码回去,不一定等于原来生成的 token ids;这对推理服务问题不大,对 RL 训练却很关键,所以它让推理引擎(inference engine)直接收发 token ids。GLM-5(§4.1.2)出于同样的原因加了一个 token-in-token-out 网关,记录每条轨迹实际采样的 token ids:重新分词会让 token 边界出现细小错位,动作就和奖励、advantage 对不齐了。

训练系统:环境比模型慢的时候

单轮 RLVR 里,一次 rollout 主要是 GPU 在解码;agent RL 里,相当一部分时间花在 GPU 之外:起容器、执行命令、跑测试、等网页返回。

环节 在等什么 公开报告里的做法 新风险
生成 解码;每轮上下文变长,prefill 变贵 verl 用 sticky session 把同一条轨迹的后续请求发到同一台推理服务器(文档没写原因;常见的考虑是复用这台服务器上的前缀缓存) 负载不均
环境 起容器、执行命令、网络 DeepSWE 最终训练时每次迭代并行起 512 个 Docker 容器(64 个任务、每个 8 次),为此给 R2E-Gym 加了 Kubernetes 调度;Qwen3-Coder 报告能并行跑 2 万个独立环境(博客,2025-07-22) 容器崩溃、超时、外部服务限流
打分 跑测试或调用 judge DeepSWE 跑测试限时 5 分钟(官方 SWE-bench 评测是 30 分钟),超时记 0 超时怎么记分要先定好
训练 凑齐一个 batch 生成和训练完全解耦:AReaL 报告同样 GPU 数下比同步系统最多快 2.77 倍(论文,2025-05,数学和代码推理任务) 数据由旧策略生成,要做修正

ROLL Flash(论文,2025-10)报告,在同样的 GPU 预算下,异步训练在 RLVR 任务上最多快 2.24 倍,在 agent 任务上最多快 2.72 倍。Kimi-Researcher(博客,2025-06-20)用完全异步的 rollout,再加上按轮次的 partial rollout:超出时间预算的任务先存进 replay buffer,之后的迭代用更新过的权重跑完剩下的轮次。它自报 rollout 至少快 1.5 倍;这也意味着同一条轨迹的前后几轮出自不同版本的策略。Kimi K3 的技术报告(2026-07)说训练和评测期间一共创建了 51,219,741 个沙盒、涉及 1,505,678 个镜像,沙盒支持暂停、fork 和快照,fork 出来的副本可以拿去评分而不影响原来的环境。异步之后数据会变旧,旧数据该怎么修正、为什么分母必须是生成时的概率,见异步 RL;每条轨迹该留下哪些字段,见后训练基础设施。

动手时怎么检查

先试什么 应该观察什么 不能据此推出什么
回放(replay)一条轨迹,把 mask 为 1 的 token 解码出来 只剩模型生成的内容,没有文件内容、日志和模板标记 mask 对了,token 就和生成时一致;还要比对 token ids
统计每组全对、全错的比例 多少任务根本没有组内信号 全错组多就是模型差;也可能是环境坏了或测试超时
把撞上限、超时、工具报错分开计数 各自占多少,随训练怎么变 截断率下降就是能力提升;可能只是学会了早交
在同样的 rollout 数下比较 episode 级和更细的做法 独立评估上的成功率和方差 训练奖励涨得快的那个更好

这条线接下来看什么

下面是开放问题,以及什么样的结果会改变现在的判断,不是预测。

  1. 更细的信用分配在大规模上值不值? GiGPO(2025-05)在 ALFWorld、WebShop 和搜索问答上,逐轮奖励的研究(2025-05)在搜索和游戏任务上,都报告了收益,而 DeepSWE(2025-07)和 GLM-5(2026-02)在代码任务上用的都是组相对 advantage。如果出现在长代码任务上、同样 rollout 预算下的公开对比,这一节的取舍就可以写得更具体。
  2. Critic 会不会回到主流? 同一个团队,GLM-5(2026-02)的 agent RL 还是组相对,GLM-5.2(2026-06)在长任务上换成了 critic PPO;SAO(2026-07)在异步单 rollout 里用 value model。若更多团队在长任务上公开类似选择,并给出和组相对方法的对照,就说明任务一长,“省掉 critic”不再划算。
  3. 数据变旧能容忍到什么程度? AReaL(2025-05)和 ROLL Flash(2025-10)报告了加速,但任务越长,轨迹从开始到结束跨越的策略版本越多。按轨迹长度切片(slice)报告修正后效果的结果,会比单个加速倍数更有用。
  4. 压缩历史和信用分配怎么配合? 摘要由谁写、算不算动作、片段怎么接回完整任务的结果,目前公开实现各不相同;出现统一的做法和消融,才能判断哪一种更稳。

下一篇:复习题。

本页术语 9
奖励 reward
RL 里指导策略更新的标量信号
调用点 call site
代码中实际调用某个函数或接口的位置
基线 baseline
用来比较新方法的参考方法或配置
消融 ablation
去掉一个组件再测一次,用来判断它到底有没有贡献
方差 variance
估计量的波动幅度;重要性加权最容易在这里爆炸
负样本 negative example
被训练目标用来与正样本区分的候选;不一定等价于用户明确不喜欢
推理引擎 inference engine
负责高吞吐生成或模型服务的执行系统
回放 replay
用固定输入重新执行一段数据或模型链路,检查结果是否一致
切片 slice
按内容或用户属性划分的子群体,用来检查平均值掩盖了什么