263 秒一步的 RL 后训练:权重同步与投机解码的实测账本

Miles v0.1 把 1T 模型的权重同步从 53 秒压到 7 秒,NVIDIA 把 rollout 生成加速 2.10× 却只换来 1.85× 净收益。两份九月技术报告给出的参数名、数字表与边界条件。

工程实践 已修订 RL 后训练 推理优化 投机解码 权重同步 SGLang MoE 异步训练 长上下文

一步 263 秒,先弄清这 263 秒花在哪

九月初两份技术报告把 RL 后训练的工程账摊开了。RadixArk 的 Miles v0.1(arXiv:2609.08368,9 月 8 日)给出一个端到端案例:GLM-5.2 744B-A40B 模型跑终端编码任务的全异步 agentic RL,64 张 NVIDIA GB300,前 30 个测量步的中位步时 263 秒。NVIDIA 那份投机解码报告(arXiv:2609.07108,9 月 7 日)补上了另一半——在多轮负载里,rollout 只占 step time 的 55.8%,工具执行和环境延迟都藏在这一段内。

这个 55.8% 是动手前必须先测的数。你以为在优化解码,实际一半时间在等沙箱返回。半年前站内那篇《Agentic RL 的真实瓶颈》把 rollout 判为基础设施问题,现在这个判断有了可查的参数名和可比的数字。

关于这个中位数有两个容易被忽略的口径:它只统计前 30 个测量步,而且 step 0 那个 1042 秒的 warm-up 值被从图里裁掉了。把它当成稳态吞吐去估总时长会严重高估,反过来,用它对比不同方案的相对收益是稳的。

权重同步:58 秒压到 8 秒,单节点却慢 70%

权重同步是 rollout 与 trainer 之间最容易量化的一段。Miles 提供三档传输,实测数据(H100,1 GB transfer bucket,两侧 fleet 等规模,稳态平均):

模型每侧节点数BroadcastP2P变化
Qwen3-30B-A3B22.67 s2.16 s−19.1%
GLM-5 744B-A40B1658.30 s8.48 s−85.5%
Kimi K2 1T-A32B3253.28 s7.23 s−86.4%

Kimi K2 那一行含每次传输后约 884 毫秒的 on-GPU 重量化。真正要记住的判据是:收益随 fleet 宽度增长,而不是随模型参数量增长。2 节点时只有 19.1%,16 节点才跳到 85.5%。

反过来的坑更值钱。单节点部署时 P2P 比 broadcast 最多慢约 70%——单节点没有额外的聚合带宽可赚,却照样要付 host 侧 re-sharding 和 pinned memory staging 的开销。所以 broadcast 才是默认值,只有 trainer fleet 与 rollout fleet 各自跨越多于一个节点时切 P2P 才划算。P2P 的模型覆盖也有限,它要求 Megatron 与 SGLang 之间存在统一的权重名映射,当前覆盖 Qwen2/Qwen3 dense、Qwen3-MoE、GLM4-MoE 系列、DeepSeek-V3 与 V3.2 衍生版。

第三档 disk-delta 只要求双方能看到同一个文件系统,不需要 NCCL 或 RDMA fabric。它有一个足够阴的实现细节:默认的 XOR 增量编码是对合运算,对同一 base 连续 apply 两次会静默退回旧权重,不会报错。需要幂等就换 overwrite 编码,代价是写入字节更多。Miles 对结果状态的每个 tensor 做 checksum 而非对 delta 做,base 不匹配就在任何 engine reload 之前停住。另外 disk-delta 只支持 Megatron 后端,与 colocation、LoRA、prefill–decode 分离任一项组合都会被拒绝启动。

投机解码进 rollout:单轮 1.88×,多轮缩到 1.43×

既然 rollout 生成占大头,把投机解码搬进 RL 循环是自然的动作。NVIDIA 的实测覆盖 8B 到 122B:

目标模型Draft接受长度Rollout 加速端到端加速
Qwen3-8BEAGLE-32.281.63×1.50×
Qwen3-8BDFlash3.452.23×1.88×
Qwen3-8BDSpark3.632.18×1.83×
Qwen3.5-35B-A3BDFlash4.581.50×1.46×
Nemotron-3.5-Lightning-30B-A3BDSpark2.651.19×1.16×
Qwen3.5-122B-A10BDFlash4.781.72×1.35×
GPT-OSS-120BDFlash3.801.48×1.19×

122B 那行接受长度最高(4.78)而端到端加速最低(1.35×),因为每次验证 forward 都要触发稀疏路由和更多专家计算。接受长度高不等于省时间。全表的区间是:接受长度 2.28–4.78、rollout 加速 1.19–2.23×、端到端 1.16–1.88×——最差的一档只快 16%,这个下界比最好那档更适合当容量规划的基准。

配置也值得记:单轮实验在 H100 上跑 GRPO + DAPOMath-17K,4,096 输入 / 16,384 响应,AIME 2024 上评;多轮实验换成 NeMo Gym Workplace Assistant,序列长 32,768 带工具反馈。两张表不是同一套并行配置,直接横向减会得出错误结论。

PP 配置下的分解表更适合当模板用。Qwen3-8B 基线 rollout 329.2 秒、更新 31.5 秒:

Draft接受长度Rollout加速Update开销Tap 等待端到端
无—329.2 s1.00×31.5 s——1.00×
EAGLE-31.89238.7 s1.38×42.4 s+34.3%0.37 s1.31×
DFlash2.84229.2 s1.44×35.8 s+13.6%0.57 s1.37×
DSpark3.37156.8 s2.10×36.1 s+14.6%0.54 s1.85×

DSpark 把 rollout 压到 156.8 秒(2.10×),更新涨到 36.1 秒(+14.6%),净得 1.85×。EAGLE-3 因为 TTT 需要多次前向,更新开销涨 34.3%,接受长度只有 1.89,净加速掉到 1.31×。只看 rollout 加速会系统性高估收益,必须把 draft 协同训练摊进 update 时间再算净值。注意这张表的接受长度与上一张不同(EAGLE-3 是 1.89 而非 2.28)——同一 draft 在 PP=4 切片下的接受率会变,跨表引用数字前先确认是不是同一配置。

多轮场景要再打一次折。同样的 draft,单轮端到端 1.50×–1.88×,换到 NeMo Gym Workplace Assistant 这类多轮负载就只剩 1.25×–1.43×,原因就是前面那个 55.8%——更快的解码盖不住工具执行。

长上下文下的两个具体障碍

把 draft 放进大规模长上下文训练会撞上两个结构性问题,值得完整记下来,因为它们不是调参能绕开的。

第一个是 branch attention 在标准因果上下文并行(CP)下不被支持。标准 CP 假设只有一条主序列,按因果掩码分片后在 rank 之间 ring 循环 K/V。而 draft 的每个 branch query,无论是 EAGLE-3 的 TTT 位置还是 DFlash/DSpark 的 block 位置,都要 attend 两组东西:分布在各 CP rank 上的主序列因果前缀,以及留在 anchor rank 本地、既不进主序列分片也不参与 ring 通信的 branch-local KV。解法是把 branch attention 拆开——主序列走 packed、负载均衡的 zigzag ring attention,branch 部分做 rank-local 计算,两个结果按 online softmax 合并。

收益是实的。相对 SpecForge 用的 USP 实现,CP=2/4/8 的注意力延迟分别快 2.9×/2.3×/1.5×,每卡峰值显存降到 1/2.7——USP 会把 batch pad 到真实 token 数的 2.25 倍,packed 实现省掉了这份 padding。256K token 上从 CP=1 扩到 CP=8,TTT 注意力延迟 17.7 秒降到 2.35 秒(7.5×,并行效率 94%),每卡显存 53.2 GB 降到 7.5 GB。

第二个是流水线并行下 target features 跨 stage。TapChannel 走一条独立路径搬中间特征,不动 pipeline schedule 本身,单边写带宽 27–39 GB/s,fan-in 传输比经 pinned host memory 中转快 4.5×–8.5×,接收侧 draft stage 只引入 1.6% 的 HBM 争用。反面数据更有说服力:走 host staging 会让每个 rank 慢 83%–88%。Tap 等待每次更新 0.37–0.57 秒,占优化时间 1.5%–2.2%,基本被正常流水调度吸收掉了。

数值一致性:三层修正与 60 MB 的账

即便完全同步执行,SGLang 与 Megatron 用不同 kernel、不同精度、不同 batching,对同一条轨迹的同一个 token 也会给出不同概率,importance ratio r = exp(log π_train − log π_rollout) 就此偏离 1。Miles 把这件事拆成三层处理。

TITO(token-in-token-out)消掉第一个结构性成因:session server 掌管 tokenization,精确保存 token IDs、log-probs 与路由专家,rollout 与训练之间不做 detokenize/retokenize 往返。R3(rollout routing replay)消掉第二个:MoE 里 rollout 可能把某 token 送给专家 {2,7},trainer 在另一套 kernel 下选成 {2,8},梯度就更新到没贡献过这个 token 的专家上。开关是 --use-rollout-routing-replay,代价按 (tokens−1) × layers × k 个 32 位整数算,32K 序列、60 层、k=8 大约 60 MB 每条轨迹。

R3 别当默认项开。它是 per-recipe 选择,dense 模型没有路由可 replay,而前面那个 GLM-5.2 参考运行本身就关掉了 R3。第三层 true-on-policy alignment 走的是改数值不改算法的路子,让两侧对每个采样 token 给出完全相同的概率,处理前两层之后剩下的数值残余。

没被覆盖的偏差交给兜底:TIS 把 r clamp 到区间(默认 [0,2])后当作 per-token 权重,clip-or-pop 则把区间外 token 的权重直接置 0。

[0,2] 看着是双侧截断,实际只压上尾:r 是非负的,下界 0 永远不会生效,真正起作用的是把大于 2 的 ratio 拉下来。所以 TIS 是阻尼(极端 token 权重变小但仍在梯度里),clip-or-pop 是剔除(权重直接归零),两者对有效 batch 大小的影响完全不同,选哪个要看你更怕方差还是更怕样本浪费。

两种修正都报三个指标——pre-clamp ratio、clipped fraction、mean |r−1|,这三个数是判断 recipe 是否在悄悄跑偏的第一手证据。

显存腾挪的两个开关

--offload-train 在训练步之间把整个 training process 搬出 GPU,权重、梯度缓冲、优化器状态作为一整块迁移。Megatron 走 allocator 级别迁移,默认目的地是 pinned host memory,模型大到 host RAM 装不下时经固定大小 staging buffer 流式写到 node-local disk;FSDP 后端只能搬到 host memory,落不了盘。默认策略也不同:colocated 下默认 evict,disaggregated 下默认不 evict。

--stream-optimizer-state-to-disk 按参数 bucket 把优化器状态流式落盘。优化器状态是 FP32 master weights 加两个 Adam moment,合计 12 字节每参数,Adam moment 可以降到 BF16,master weights 必须留 FP32。Qwen3-30B-A3B 上实测 offload 从 24 秒降到 5.2 秒、reload 从 8.9 秒降到 1.3 秒。代价有三条:只能以相同并行布局恢复;无法恢复非流式写出的 checkpoint——Miles 选择拒绝 resume 而不是静默从 step 0 重启 Adam,这个设计值得抄;checkpoint 保存因为走同步 copy 而变慢。

上手顺序

  1. 先测 rollout 占 step time 的比例。全异步模式下 Miles 在 rollout/fully_async/ 前缀下报 7 个指标:queue_size、avg_staleness、max_staleness(这三个描述本步取到的组)、buffer_avg_staleness、buffer_max_staleness(这两个描述仍在队列里等着的组)、aborted_groups_filtered(生成阶段就放弃、到达即丢弃)、stale_groups_filtered(收集时因超出 staleness 上限被丢弃)。staleness 定义为当前 trainer 权重版本减去组内出现的最旧版本,口径故意偏悲观。 queue_size 是最快的定位信号:长期贴着 0 说明 rollout 引擎供不上,是生成侧卡住了;持续堆积则反过来,是 trainer 成了瓶颈。
  2. 两侧 fleet 都跨多节点再考虑 P2P,单节点保持 broadcast。
  3. 投机解码按接受长度、update 开销、净加速三步验,别跳步。
  4. MoE 上 R3 按 recipe 决定,不设成默认开启。
  5. 全异步要求 rollout 与 trainer 分离放置,colocated 下 Miles 会直接拒绝启动;in-flight 请求默认 retract(FSDP 后端则总是 retract,没有别的选择),启用 session server 时 abort 模式同样被拒——因为 session server 无法容忍一条轨迹的 token 历史在半途被拆掉。
  6. 后端选择和传输方式选择都由启动 flag 控制,但技术报告里没给出这两个 flag 的字符串,别照着猜——回官方文档查。

半年前把 rollout 判成基础设施问题的那个结论没变。变化在于,它现在是一组能对着抄的参数名、一张能拿来比的数字表,以及几条踩过才知道的边界条件。

参考:Miles v0.1 技术报告 arXiv:2609.08368,代码 github.com/radixark/miles;NVIDIA 投机解码协同训练 arXiv:2609.07108。