WAR:Agent RL 的 Rollout 为什么需要两层负载感知
把同步训练的长尾、历史后缀草稿与 KV 局部性放在同一轮轨迹生成中,理解接受长度为何不等于加速
本文目录
一个编程 Agent 的 rollout,不是让模型连续生成一段文字就结束。它可能先读取文件,再执行测试,把报错加入上下文,继续修改代码。每次返回模型时,历史更长了,原来的 KV 也未必仍留在 GPU;同一批任务中,有的两轮就完成,有的却要反复与环境交互。
如果训练要等本批所有轨迹完成才更新,最后几条轨迹就会决定整个 rollout 阶段何时结束。此时“让每个推理副本尽量满载”与“让剩下的长轨迹尽快完成”,可能需要不同的优化方式。
2026 年 7 月 19 日公开的预印本 WAR: Workload-Aware Rollouts for Synchronous Agentic Reinforcement Learning将 cache-aware 调度与 SuffixDecoding 组合起来:前者处理请求之间的资源竞争,后者利用低负载时的计算余量减少串行解码轮数。本文依据其 v1 方法与实验,并结合 SuffixDecoding v3解释机制;文中的小规模数值都是原理算例,不是论文 GPU 结果的复现。
同步屏障等待的是整条轨迹,不是一次模型调用
先固定一个简化的同步训练周期。第 k 轮 actor 参数为 \(\theta_k\),它为选定 prompts 生成多条完整轨迹;收集奖励和所需统计,再完成本轮更新得到 \(\theta_{k+1}\)。多轮工具交互发生在轨迹内部:
策略 θk
├─ 轨迹 A:模型 → 工具 → 模型 → 完成
├─ 轨迹 B:模型 → 工具 → 模型 → 工具 → 模型 → 完成
└─ 轨迹 C:模型 → 完成
↓ 全部完成后越过本轮屏障
奖励/训练更新 → θk+1
这是用于理解同步依赖的图,不表示所有奖励计算必须等到最后一刻才开始;可重叠的具体阶段取决于框架。更完整的模型与数据流见 RLHF/GRPO。
从 rollout 开始计时,若各轨迹实际完成时刻为 \(C_i\),本轮都必须完成时,结束时刻由 \(\max_i C_i\) 决定,而不是平均值。四条抽象轨迹在 2、2、2、10 秒结束,平均完成时刻只有 4 秒,屏障却要等到第 10 秒。这个例子没有模拟 continuous batching,也不能据此读取 GPU 利用率。
同一轮开始时可能有大量请求争用 KV,临近结束却只剩几条长尾请求。训练 prompt batch size 没变,推理侧的活跃 batch 却已经完全不同。把一个固定开关施加到整轮,容易在最需要低延迟时继续使用面向满载吞吐的配置。
三种 batch,只有一种直接描述当前副本的解码压力
至少要区分训练选了多少 prompts、全局还有多少待服务请求,以及某个推理副本当前实际执行多少条序列。一个 prompt 还可能生成多条 rollout,因此它们连计数单位都不相同。
WAR 的控制结构不是“低负载启用 A,高负载启用 B”的全局二选一:全局调度器按请求规模选择放置策略,各副本则按本地 effective batch 独立决定是否使用后缀推测解码。System Design将这两层分开。
| 控制层 | 观察什么 | 改变什么 |
|---|---|---|
| 全局调度 | 排队请求、目标副本缓存与负载、轨迹进度 | 哪条请求先去哪个副本 |
| 副本解码 | 本地 effective batch 与推测成本 | 当前是否值得多 token 验证 |
| 训练循环 | 本轮完整轨迹与策略版本 | 何时完成更新并发布新参数 |
因此,全局仍在采用缓存感知放置时,某个暂时低负载的副本可以开启推测解码;最后几条轨迹也可能重新进入适合推测的区间。反之,全局队列短并不保证某个副本没有积压,不能用全局平均负载代替本地信号。
历史后缀能给出草稿,但它不是一份可直接复用的 KV
SuffixDecoding §3在当前请求与历史生成文本的 token 序列中寻找重复模式。假设历史包含:
历史 token IDs:[10, 20, 30, 40, 50]
当前序列末尾:[..., 20, 30]
可提出的草稿:[40, 50]
这里匹配的是离散 token 子串,不是语义相似度,也没有证明当前模型接下来一定会生成 40、50。Suffix tree 用来快速找到模式及其可能后继,再构造较小的 speculation tree 供目标模型验证。
原始算法分别维护历史输出的全局树与当前请求的本地树。候选扩展使用历史分支频数及路径统计,较长匹配允许尝试更长草稿;没有合适匹配时退回普通生成。这些统计是提出候选的启发式,不是当前 actor 的精确条件概率。历史中某分支出现过 90%,不能据此绕过当前模型,把它当作 90% 的策略概率。
这与 Prefix Cache 的复用条件不同:
| 状态 | 保存内容 | 可以复用的原因 |
|---|---|---|
| Suffix history | token 模式及后继结构 | 它只提供候选,仍由当前模型验证 |
| KV Cache | 特定前缀经过模型计算的中间张量 | 完整合法前缀、模型版本及执行条件兼容 |
两个请求虽然都以 [20, 30] 结尾,但更早的上下文可能不同。后缀模式能给它们同一份草稿,对应位置的 KV 却通常不同。不能把“匹配相同后缀”误解成可以直接拼接历史 KV。
策略更新后也一样。旧文本仍可作为新模型的提议来源,但旧权重算出的 KV 不能只因 token 没变就继续使用。在正确验证、token ID 空间一致等前提下,旧提议主要影响命中与接受效率;它不是直接把旧策略生成的训练样本当作新策略样本。
WAR 在 RL step 结束后向各副本共享 token 历史,通过异步接口更新 suffix tree。共享的不是 GPU KV tensor;异步更新缓存,也不等于 rollout 与下一版 actor 的训练异步重叠。它仍然是一项同步 RL 优化。
“目标模型验证过”需要说明验证的是哪种采样
如果任务采用贪心生成,可以用相应的确定性验证规则;随机采样则要保持目标分布。WAR 的实验并不是纯贪心,因此只比较 draft 是否等于 target argmax,不足以支持“无损采样”的结论。
经典 speculative sampling 提供了一个清楚的检查基准。令 p 为目标分布,q 为草稿的实际采样分布,两者都已应用温度、截断与重新归一化;对从 q 抽到、因而 \(q(x)>0\) 的 token,接受概率是:
\[a(x)=\min\left(1,\frac{p(x)}{q(x)}\right)\]拒绝时不能随便再从 p 抽一次,而应从正残差的归一化分布抽样:
\[r(x)=\frac{\max(p(x)-q(x),0)}{\sum_y\max(p(y)-q(y),0)}\]这条残差公式仅用于发生拒绝的分支;若 p=q,拒绝概率为零,不执行除以零的归一化。Leviathan 等人的 Algorithm 1 与 §2.3给出了该协议。
用三元素分布复算:\(p=[0.6,0.3,0.1]\),\(q=[0.2,0.5,0.3]\)。接受后落在各元素上的无条件概率质量为:
\[\min(p,q)=[0.2,0.3,0.1]\]总接受率是 0.6,拒绝概率是 0.4,残差条件分布是 [1,0,0]。两部分相加恰好回到 p。如果拒绝后错误地改为从原 p 重采,最终变成:
所有输出都仍是合法 token,程序也不必报错,但策略分布已经被改写。对 RL 来说,这不是普通的微小性能差异。
这只是经典线性草稿协议的原理检查,不是在声称 WAR 的树验证器恰好使用这张 q 表。Suffix tree 的历史频率不一定等于实际 proposal 分布,树形验证还涉及祖先 mask、兄弟分支隔离与提交规则;本文未取得足以审计 WAR 适配细节的实现,不能据论文的高层“无损”描述替它完成证明。分布保持也不意味着同一随机种子必然得到逐 token 相同的轨迹。
接受六个 token,可以比接受三个更慢
Model-free 仅表示没有额外 draft network 的参数与前向计算,不表示零开销。CPU 查树、传递候选、构造 tree mask、目标模型验证、临时 KV 和模式切换仍需要时间。
在一个固定本地 batch B 的简化比较中,普通 decode 每轮为每条活跃序列提交一个 token,耗时 \(T_1(B)\)。推测轮平均每条提交 τ 个 token,总耗时为 \(T_{spec}(B)\),包含本轮必要的草稿、验证与管理工作。忽略请求加入/退出时,批次吞吐比为:
\[\frac{B\tau/T_{spec}}{B/T_1} =\frac{\tau T_1}{T_{spec}}\]τ 要数最终提交的全部 token,包括算法可能生成的修正或 bonus token,不能与口径未说明的 draft acceptance length 混用。若各请求提交数不同,应先求整批实际提交数,再除以迭代时间。
两组自制毫秒算例:
| 场景 | 普通整批一轮耗时 | 推测整批一轮耗时 | 每条平均提交 τ | 相对吞吐 |
|---|---|---|---|---|
| 低负载 | 4 | 6 | 3 | \(3\times4/6=2\) 倍 |
| 高负载 | 6 | 42 | 6 | \(6\times6/42=6/7\) 倍 |
每一行内部都比较同一 B 的两种路径;两行代表不同负载下的人为成本假设,不能跨行据此推断绝对 GPU 吞吐。第二行提交长度翻倍,却因为验证变贵而退化。接受率回答“草稿有多好”,吞吐还要回答“这些草稿占用了多少本可服务其他请求的资源”。
原始 SuffixDecoding 的 batch 控制讨论也将工作负载作为重要边界。切换点必须针对模型、上下文、硬件和树预算校准,不能把某篇实验中的 batch=16 当作普遍常数。调度还会不断改变每个副本的 B,因此解码层应持续观察本地状态。
请求先给谁、放在哪里,缓存命中率不能单独回答
多轮请求等待工具期间,原副本的 KV 可能被新请求驱逐。Sticky session 记录“上次在这里运行”,却不保证“这次仍然命中”。同组其他 rollout 又可能在另一副本留下共同前缀,因此位置偏好应结合真实 cache 状态。
命中率也不是完整成本。命中 90% 的 2,000-token 请求剩 200 个 token,命中 70% 的 20,000-token 请求剩 6,000 个;即使比例更低,后者避免重算的绝对 token 更多,而其后缀 Attention 工作也可能更大。选择目标副本时还要纳入排队与容量,不能仅按一个百分比排序。
WAR 将等待时间、同组已完成轨迹提供的 assistant-turn 估计、目标副本缓存命中和 inflight 请求数组合为优先级:临近 sandbox TTL 的请求获得强提升,较短轨迹和较轻负载倾向优先,缓存命中提供位置亲和性。Cache-Aware Scheduling给出的是经验打分,不是最优调度定理。
同组 turns 可用指数移动平均更新:
\[\widehat T_{new}=\alpha T_{observed}+(1-\alpha)\widehat T_{old}\]其中 \(0<\alpha\leq1\) 是新观测的平滑权重;越大越重视最近一次完成的轨迹,取 1 时只保留最新观测。
但它估计的是相似任务的总轮数,不是精确剩余 token 或剩余秒数。一次读取文件与一次长时间测试的工具成本不同;已经生成十轮,也不意味着总共十二轮的估计就可靠地预测“只剩两轮”。优先短任务有机会更早释放 KV,但也要防止长任务长期排不到资源。
Sandbox TTL 让这一问题越过了性能边界。请求等得太久,环境可能被回收,轨迹因此提前结束。把等待保护拿掉后,总完成时间下降,可能只是少生成了 tokens、少执行了工具,而不是同一任务更快。Deadline 提升只是降低这种风险;若环境不可用或资源不足,一个较大的打分并不能形式化保证所有请求及时恢复。
并发窗口同样需要独立的硬性边界。论文尝试用 cache-hit 反馈调整窗口,但其写法不是常见的“显存压力高则减小”规则:它在低命中区间做加法增加,在高命中区间做乘法调整,不能擅自反转后仍称为原算法。更具体地,其最终上下界表达式在 inflight=0、queue=0 时会得到 0;只有一个排队请求和四个空闲副本时,上界为 0.25,与文字“至少 1”并不一致。
这些情况需要明确空闲语义、整数取整与 admission 规则。本文保留这一未充分说明的实现边界,不把预印本公式默认为可直接上线的控制器,更不凭猜测补一段代码来声称复现作者行为。
一条草稿通过验证之后,才可以跨到真实 Agent 世界
把两层控制装回系统时,可以按状态边界检查,而不是只看吞吐曲线:
- 当前逻辑前缀属于哪版 actor,哪些 KV 已提交,哪些只是候选分支的临时状态。
- Tree mask 是否只允许每个候选看见自己的祖先,拒绝分支的 KV 是否正确回收。
- EOS、停止条件与 tool-call 边界是否在提交序列上判断,未验证草稿是否绝不触发工具副作用。
- 从推测切回普通 decode 后,位置、采样参数和随机状态是否仍符合约定。
- 轨迹在等待工具与等待 GPU 时是否仍有有效 sandbox,超时与取消是否被纳入完成率。
这些是本文提出的实现审核问题,不代表已检查过 WAR 的对应源码。尤其是工具调用:一个最终被拒绝的候选 token 可以安全丢弃,已经执行过的文件修改或网络操作却不能简单按“丢弃 draft”撤回。模型输出的提交点必须先于真实副作用的执行点。
读懂加速结果,也要读懂它没有测什么
WAR 使用 Qwen3-32B、16 张 H100、500 条 SWE prompts,每个 prompt 生成 8 条 rollout,每种配置运行 5 个训练 steps。论文报告低负载约 1.4 倍、高负载最高约 1.6 倍的 rollout throughput;这些是其固定模型与负载下的报告,不是完整 RL 训练或最终模型质量的普遍倍数。Evaluation
例如原 rollout 用 60 秒,其他奖励/更新合计 40 秒;rollout 快 1.5 倍后,整轮变成 \(60/1.5+40=80\) 秒,总体只快 \(100/80=1.25\) 倍。这仍是人为边界算例,目的是区分局部阶段与整体训练,不是重算论文图表。
更关键的是比较工作量。作者只保留 prefill tokens、decode tokens 和 assistant turns 相对基线差异均在 5% 以内的 runs。这个筛选减少了明显工作量差异,但不证明采样分布相同,也不代表包含所有运行的无筛选性能分布。五个连续 steps 不是五个独立随机种子,更不足以证明长程收敛等价。
论文的 TTL 消融更直接地揭示了风险:training batch=64 时,关闭等待保护,相对启用该保护的配置,rollout 时间减少 46.9%,但 decode tokens 也减少 41.3%、assistant turns 减少 38.9%。这些数字对应轨迹提前终止,不能当成另一个更快方案。这里的比较对象是启用等待项的配置,不是任意其他基线。
因此,评估这类系统应同时保留完整轨迹比例、sandbox 超时、有效生成量、rollout wall time 与整轮 update 时间;接受长度、cache hit rate 只是解释性指标。还要分别测试冷 suffix tree、重复任务、分布变化、长尾阶段与高并发,避免只在重复最多的局部窗口证明一个全局结论。
本文的分布校正、吞吐盈亏、屏障与边界反例可在仓库中复算:
node scripts/reviews/sep30-war-examples.mjs
这些 CPU 检查不运行 veRL/SGLang,不证明 WAR 树验证器、真实 sandbox 调度或 RL 收敛正确。WAR 提供的有价值视角,是把同步 rollout 当作随时间改变形态的工作负载:拥挤时要减少无谓重算与争抢,尾部要降低剩余轨迹的延迟;无论采用哪种加速,策略分布、完整轨迹与工具提交边界都不能被当成可省略的成本。
觉得有帮助?
分享给同样关注系统性能的人。