263 秒一步的 RL 后训练:权重同步与投机解码的实测账本
Miles v0.1 把 1T 模型的权重同步从 53 秒压到 7 秒,NVIDIA 把 rollout 生成加速 2.10× 却只换来 1.85× 净收益。两份九月技术报告给出的参数名、数字表与边界条件。
一步 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 等规模,稳态平均):
| 模型 | 每侧节点数 | Broadcast | P2P | 变化 |
|---|---|---|---|---|
| Qwen3-30B-A3B | 2 | 2.67 s | 2.16 s | −19.1% |
| GLM-5 744B-A40B | 16 | 58.30 s | 8.48 s | −85.5% |
| Kimi K2 1T-A32B | 32 | 53.28 s | 7.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-8B | EAGLE-3 | 2.28 | 1.63× | 1.50× |
| Qwen3-8B | DFlash | 3.45 | 2.23× | 1.88× |
| Qwen3-8B | DSpark | 3.63 | 2.18× | 1.83× |
| Qwen3.5-35B-A3B | DFlash | 4.58 | 1.50× | 1.46× |
| Nemotron-3.5-Lightning-30B-A3B | DSpark | 2.65 | 1.19× | 1.16× |
| Qwen3.5-122B-A10B | DFlash | 4.78 | 1.72× | 1.35× |
| GPT-OSS-120B | DFlash | 3.80 | 1.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 s | 1.00× | 31.5 s | — | — | 1.00× |
| EAGLE-3 | 1.89 | 238.7 s | 1.38× | 42.4 s | +34.3% | 0.37 s | 1.31× |
| DFlash | 2.84 | 229.2 s | 1.44× | 35.8 s | +13.6% | 0.57 s | 1.37× |
| DSpark | 3.37 | 156.8 s | 2.10× | 36.1 s | +14.6% | 0.54 s | 1.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 而变慢。
上手顺序
- 先测 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 成了瓶颈。 - 两侧 fleet 都跨多节点再考虑 P2P,单节点保持 broadcast。
- 投机解码按接受长度、update 开销、净加速三步验,别跳步。
- MoE 上 R3 按 recipe 决定,不设成默认开启。
- 全异步要求 rollout 与 trainer 分离放置,colocated 下 Miles 会直接拒绝启动;in-flight 请求默认 retract(FSDP 后端则总是 retract,没有别的选择),启用 session server 时 abort 模式同样被拒——因为 session server 无法容忍一条轨迹的 token 历史在半途被拆掉。
- 后端选择和传输方式选择都由启动 flag 控制,但技术报告里没给出这两个 flag 的字符串,别照着猜——回官方文档查。
半年前把 rollout 判成基础设施问题的那个结论没变。变化在于,它现在是一组能对着抄的参数名、一张能拿来比的数字表,以及几条踩过才知道的边界条件。
参考:Miles v0.1 技术报告 arXiv:2609.08368,代码 github.com/radixark/miles;NVIDIA 投机解码协同训练 arXiv:2609.07108。