Beat the Long Tail: Distribution-Aware Speculative Decoding for RL Training

Together AI · UIUC · UCSD · Zelei Shao & Vikranth Srivatsa et al. · 2025-11-17 · arXiv:2511.13841
关键词: RL post-training · speculative decoding · suffix tree · long-tail · VeRL · vLLM · model-free drafter

速读卡片 (TL;DR)

一句话:把 draft model 整个扔掉,改用滑动窗口 suffix tree 当 drafter,再按 trajectory 长度差异化分配 draft budget,在不改 RL 训练语义的前提下把 rollout 时间砍掉 30–50%。

~70%
RL step 中 rollout 占比
≤50%
rollout time 减少 (math)
0 params
drafter 是非参数

立场:系统侧"工程优雅"的极致——drafter 完全 model-free、可在任何已有推理栈(vLLM/SGLang)上嵌入,不引入第二个模型、不需重训、不改 reward loop。


1 · 动机:为什么 RL rollout 配 EAGLE 很难

1.1 历史脉络:从 serving 到 rollout 的迁移

Speculative decoding 起家于 LLM serving 场景:固定权重的 target,小巧 draft model 提议多 token,target 一次 verify。EAGLE / Medusa / SpecInfer 这条线在 serving 上做到 2–3× wall-clock 加速。

2024 起,RL post-training (RLHF / GRPO / DAPO) 占总训练时间的比例急速上升:reasoning 模型 trajectory 从 ~512 token 拉到 16k–32k,DeepSeek-R1 进一步把 long-CoT 推到几万 token。论文里的硬数据:rollout 占总训练时间 > 70%。这是 Amdahl 视角下"该被攻击的部分"。

把 spec decoding 直接搬过来?——三个 insight 决定不能简单照搬:

1.2 别的方案为什么不够 (alternatives table)

方案思路在 RL 里的死穴
EAGLE-2/3 (NeMo-RL 路线)训练 draft head,跟随 target 周期性蒸馏policy 每 step 漂移 → 要么接受率塌,要么需频繁重训 draft head,工程复杂
Self-speculative (layer skip)用 target 自身浅层做 draftRL 里 target 还在变,layer-skip 选择策略也得重调;对 long context 不友好
Prompt Lookup (PLD/PLD+)从 input 里找重复 n-gram只用单个 request 内 prompt,忽略跨 epoch 的 trajectory 复用 (Insight-2)
SPEC-RL用历史 trajectory 直接当 draft,引入 lenience 接受参数改了输出分布,牺牲严格 lossless;不区分 request 长度
FastGRPO训练 draft model 与 target 同步更新显存预算大;扩展性差;仍是参数化 drafter
RhymeRL利用 trajectory 时序相似 + batch skew没有 problem-difficulty 与 sliding-window 概念

1.3 为什么这事不平凡

表面上"用历史 trajectory 当 draft 来源"听起来一句话能讲清,但要在生产 RL 系统里落地,有三个非平凡点:

  1. 策略漂移 vs. 历史质量。一开始的 trajectory 反映早期 policy,后期已经"过期"。直接用全部历史 → 接受率掉。需要某种 sliding window + recency weighting。
  2. 跨 problem 干扰。同一个 domain 的不同问题,token-level 模式不互通。一条全局 suffix tree 在统计上稀释信号,在系统上还吃 query 时间。需要 per-problem sharding。
  3. 一刀切的 budget 是错的。短序列对 makespan 没贡献,给它发 draft tokens 反而增加 verification overhead;长序列才是 makespan 的 bottleneck。需要根据预测长度动态分配 budget。

这三点叠加起来才是 DAS 的论文价值。如果只换数据结构(suffix tree),会被 SuffixDecoding (Oliaro 2025) 截胡;如果只做 budget 分配,会被 SpecServe / Liu 2024 截胡。这三个 component 的耦合+针对 RL 训练循环的工程化才是新的。


2 · 背景速查 (术语 / 数据结构)

术语含义 (本文)
rolloutRL 训练的一步里,actor 用当前 policy 生成 trajectory 的阶段
makespan一个 batch 里最慢一条 trajectory 决定的总耗时
drafter提议候选 token 的组件——本文里它不是模型而是 suffix tree
suffix tree对字符串 (token 序列) 所有后缀建索引的压缩 trie,Ukkonen 算法 O(n) 在线构造
capacity factor kidrafter 对 request i 的最大可达接受比例 (∈(0,1])
αirequest i 的接受效率(单位 draft token 转化为接受 token 的速率)
pirequest i 分到的总提议 token 数 (draft budget)
lirequest i 的目标生成长度
Nfwd有效 forward pass 数(speculative 后)

Suffix tree 30 秒回顾

给定字符串 S = "banana$",suffix tree 把 所有后缀 banana$, anana$, nana$, ana$, na$, a$, $ 压成一棵 trie,公共前缀合并到同一节点上。从 root 到任意叶子的路径就是某个 suffix。Ukkonen 1995 给出在线、单次扫描、O(n) 时间构造的算法——这正好契合 RL 里"每 epoch 进来一批新 trajectory,要 incremental 维护"的场景。

查询:给定当前 decode 已生成的 prefix q(长度 m),从 root 沿 q 指引的边下降,落到的节点的子树就是所有以 q 结尾的历史延续。在子树里取频次最高 / 路径最长的分支,就是 draft proposal。查询时间 O(m)。


3 · 方法 A — Suffix Tree Drafter

3.1 核心思路

放弃训练 draft model,改用一个对最近 N 个 epoch 的 trajectory 增量维护的 suffix tree。每次 target 即将解码下一段时:

  1. 取当前 decode prefix 末尾 m 个 token (m 一般 8–32) 作为 query;
  2. 在 suffix tree 里 longest-match,定位到一个内部节点;
  3. 从该节点出发沿"最高频 child"路径走 d 步 (draft length),拿到 d 个 draft tokens;
  4. target 一次 forward 把 (q, draft1..d) 全 verify,接受最长 prefix。

3.2 SVG: 一棵小的 suffix tree

假设过去两条 rollout 拼起来是 S = "the cat sat on the mat$" (只为示意,实际是 BPE token 序列)。下图把它的 (示意性) suffix tree 画出来,并展示 query "the " 如何 longest-match:

root "the " "cat " "sat " "on "/"mat$" A "cat " "mat$" cat→ "sat on the mat$" L1 mat$ L2 Query: "the " step1: root --"the "--> A step2: 在 A 下 freq("cat")=1, freq("mat$")=1 → 选最长路径 draft = "cat sat on the mat" target 一次 forward verify, 接受最长 prefix(可能 4 token)
S = "the cat sat on the mat$" 建的 (压缩示意) suffix tree。Query "the " 落在节点 A,子树里的所有"以 'the ' 开头的延续"都是合法 draft 候选;按频次/长度选一条沿路径走 d 步即可拿到 d-token draft。Ukkonen 算法保证整棵树是 O(n) 在线构造,每条新 rollout 进来可 O(Δn) 增量更新。

3.3 Worked example: 一个 token 的命运

假设当前 epoch t,policy πt 正在为问题 P3 生成第 1273 个 token。decode prefix 末尾 16 token 是 "...therefore the answer is "

  1. 查询:取末 8 token "the answer is " 走 P3 的 suffix tree。
  2. 命中:P3 在过去 16 个 epoch 共 256 条 trajectory 里 (sliding window),有 47 条以 "the answer is " 开头的延续;最频繁的一条是 "\\boxed{42}\\n\\nLet me verify"
  3. 提议 d=8:draft = "\\boxed", "{", "4", "2", "}", "\\n", "\\n", "Let"
  4. Verify:target 一次 forward,前 5 个被接受,第 6 个 "\\n" 处 logits argmax 是空格——拒绝。
  5. 结果:1 次 forward 推进了 5 token (而不是 1),省 4 次 forward。Tree 也用刚刚生成的这条 trajectory online update。

3.4 反向论证:不用 suffix tree 会怎样


4 · 方法 B — Per-problem sharding & sliding window

4.1 为什么 per-problem 而不是全局一棵树

论文 Figure 6 的结论:per-problem suffix tree 比 global 一棵树 接受率更高查询更便宜。原因:

4.2 SVG: per-problem shard + sliding window

Epoch 轴 → e=t-32e=t-16 e=t-8e=t-2e=t (now) stale (drop) Sliding window (N=16 epochs) Per-problem shards: Tree(P1) "求积分..." ~12k tokens avg accept = 5.3 Tree(P2) "组合数学..." ~8k tokens avg accept = 4.1 Tree(P3) "几何证明..." ~22k tokens avg accept = 6.8
每个 problem 维护独立 suffix tree,树只摄入过去 N 个 epoch(典型 16/32) 的 trajectory。每过一个 iteration:旧 trajectory 移出窗口、新 trajectory online insert。窗口大小是 bias–stability trade-off:窗太短覆盖不够,窗太长 stale 严重(早期 policy 的 token 已经不像现在的 policy 了)。论文经验值 16–32。

4.3 实证细节

Figure 6 显示三种 history 配置的平均接受 token 数:

配置avg accepted tokens / roundspec latency (ms/tok)
global + per-request偏低最高 (querying 大全局索引)
per-problem + per-request最高中等 (有 prefix trie 路由开销)
per-problem only次高最低 (适合 7B 以下小模型)

对小模型禁用 prefix trie 的 routing,因为 routing 的 CPU 开销已经接近 draft 收益本身。


5 · 方法 C — Length-aware speculation budget

5.1 直觉

RL batch 里的 trajectory 长度分布严重偏斜(Figure 1):前 100 步后 effective batch size 已经塌缩到 <30%,只剩几条 long tail 在跑。这意味着:

所以"per-request 平均分 budget"是错的。budget 应该向 long-tail 倾斜。

5.2 SVG: budget allocation 概念图

Baseline: 平均分配 (浪费在 short) 短 r1, l=200, draft=20 中 r2, l=2k, draft=20 长 r3, l=12k, draft=20 (不够!) DAS: length-aware (向长倾斜) 短 r1: 不发 draft (skip) 中 r2: medium budget = 8 长 r3: aggressive budget = 32, 把 makespan 压下去 原 trajectory tokens 额外 draft tokens (verify cost)
Baseline 给所有 request 同等 draft budget,会在短 request 上白白付出 verification cost,而长 request 拿到的 budget 又不够压低 makespan。DAS 把 budget 偏移给长 request,短的直接 skip,中等的给中等。

5.3 Length class policy (运行时)

因为 generation 长度在线不可精确预测(同一 prompt 不同 epoch 长度 spread 巨大,见 Figure 9),论文用一个 hierarchical heuristic:

  1. Init from history:对每个 prompt,从历史 trajectory 长度分布算一个 {Long, Medium, Short} 多数类作为初始预测。
  2. Online update:解码到第 l 个 token 时,根据 P(class | l, init) 把当前 request 重新分类。比如初始预测 Medium,但已经生成了 8k 还没结束,就上调为 Long,加大 draft budget。
  3. Class → budget 表是 offline tune 出来的(典型: Long=32, Medium=8, Short=0)。

6 · 公式推导:最优 draft budget

6.1 Per-pass latency 模型

profiling 显示 (DeepSeek-R1-Distill-7B, Figure 8):

tfwd = cbase + ctok · ntoks

cbase 是 per-pass 固定开销 (kernel launch、DRAM→SRAM 搬权重);ctok 是每 token 边际成本。Mean relative error ≈ 12%。

6.2 接受 token 的饱和模型

给 request i 提议 pi 个 draft token,接受数:

Ai(pi) = ki · li · (1 − e−αi pi / li)

ki ∈ (0,1] 是 capacity factor (drafter 与 target 的最大 alignment);αi 是接受效率。pi → ∞ 时 Ai → kili(永远不会 100% 因为 drafter ≠ target)。

6.3 总目标

剩余需 forward 的 token 数: li(1 − ki + kie−αipi/li)。Batch 的 forward pass 数由最长那条决定:

Nfwd = maxi [ li(1 − ki + kie−αipi/li) ]

总延迟:

J(p) = cbase·Nfwd + ctok·Σ pi + C

6.4 闭式解

把约束 li(1 − ki + kie−αipi/li) ≤ Nfwd 在最优处取等:

pi* = − (lii) · ln(1 − ki(1 − Nfwd/li)),  当 Nfwd < li
pi* = 0,  否则

6.5 物理直觉

三个观察(直接对应论文 §4.2.2):

  1. pi* ∝ li:越长的 request 应分越大 budget(因为它本就是 makespan 的瓶颈)。
  2. li ≤ Nfwd ⇒ pi* = 0:反正它早完成,不必 speculate。
  3. cbase ≫ ctok:per-pass 主导(典型小 batch 场景)→ 应激进减 Nfwd,即提议更多 token;反之大 batch 时 ctok 主导,应保守。

6.6 数值敏感度小表

取 cbase=20ms, ctok=0.5ms, k=0.6, α=0.4:

liNfwd 假设pi* (近似)解读
200180~25短;接近 skip 边界
20001200~210medium 档
160008000~2500aggressive,~32 token / pass

7 · Worked example: 一个 prompt 的完整命运

设 RL 训练第 t 步,problem P3 = "证明欧拉公式":

  1. 历史信号:过去 16 epoch 它的 generation 长度均值 11k、最大 18k → init class = Long → draft budget per round = 32。
  2. Drafter 状态:P3 的 suffix tree 已吸收最近 16 epoch × 16 sample = 256 条 trajectory,共 ~2.8M token,树节点 ~600k。
  3. Decode 启动:πt 开始生成。每步当前 prefix 末 16 token 进 P3 tree:longest-match → 沿最高频路径取 32 token draft → target 一次 batched verify。
  4. 典型 round:32 提议被接受 18 个 (k≈0.6),Nfwd(此 request) 从 11000 降到 ~6500,直接砍掉 40% forward pass。
  5. Online update:本 round 真正生成的 18 个新 token incrementally insert 到 P3 tree (Ukkonen O(18))。
  6. 跨步:已生成 9k 还没 EOS → online length classifier 把 class 维持 Long;若初始误判为 Medium,这里会 escalate。
  7. 本步结束:P3 trajectory 总长 13k,贡献最大 makespan。Tree 把 epoch t-16 的某条 trajectory 移出窗口、把 epoch t 的 16 条 sample 全部加进。
  8. 下一步:πt+1 ← πt(GRPO 更新),但 P3 tree 几乎不变(因为 policy 漂移是渐变的,recent window 仍 representative)→ 接受率保持。

8 · 实验关键结果

8.1 Math RL (DeepSeek-R1-Distill-Qwen-7B, DSR-sub)

指标VeRL baselineDAS变化
Generation time / step~400ms (示意)~200ms-50%+
Reward (30 步内)0.65 → 0.75同曲线无下降

8.2 Code RL (Qwen3-8B, DeepCoder)

指标baselineDAS变化
Code generation time~280ms~210ms~25%
Reward1.4 → 1.8同曲线无下降

Math 收益 (-50%) 比 code (-25%) 大,因为 math reasoning 的 trajectory 更长 + 模板化 (\boxed, Let me verify, Therefore...) 更多,suffix tree 命中率高。

8.3 关键 ablation

8.4 与 EAGLE 的接受率曲线

论文 Figure 4: EAGLE 静态 drafter 的 avg accepted tokens 在训练全过程基本持平(因为它没法跟随 policy 漂移);DAS 的 nonparametric drafter 随训练步骤上升——因为 sliding window 里的 trajectory 越来越像当前 policy。这是"online drafter"的核心价值。


9 · 与同类工作对比

方法drafter 类型需训练?tail-aware?lossless?嵌入难度
DAS (本文)suffix tree (model-free)是 (length class)低 (vLLM plugin)
NVIDIA NeMo-RL × EAGLE-3EAGLE head (neural)是 (周期蒸馏)中-高
SPEC-RL历史 trajectory + lenience accept否 (改分布)
ReSpecpolicy 自身 (self-spec) + replay部分近似
FastGRPOneural draft, 与 target 同步训是 (在线)
RhymeRLtrajectory 复用 + skew-aware batching部分 (batch skew)

9.1 DAS 的"工程优雅"在哪

  1. Drafter 没参数。不用第二个 GPU model,显存占用 ~MB 级 CPU 内存。
  2. 不改 reward loop。VeRL/OpenRLHF 不用动,只在 vLLM rollout call 上挂一个 hook。
  3. Lossless。verification 是严格 rejection sampling,输出分布与无 SD 的 baseline 完全一致——SPEC-RL 的 lenience 路线就放弃了这条。
  4. O(m) 查询 + O(Δn) 更新。所有数据结构开销在 CPU,target 永远是 GPU bottleneck。
  5. 可与 EAGLE 正交叠加。理论上 DAS 的 budget 模型可以套在 neural drafter 之上(只是论文没做这组实验)。

个人 take:DAS 是 spec-decoding-for-RL 这条线里最容易在已有生产栈上"今晚就上"的方案。NVIDIA 那篇要训 EAGLE-3 + 还得做 capture/replay;FastGRPO 还要管 draft model 显存;DAS 只要一个 suffix tree 库 + 一个 budget 调度器。


10 · 局限 / 个人 take / 待验证问题

10.1 局限

10.2 个人 take

10.3 待验证问题

  1. cold-start 阶段(第 1–2 epoch)实测加速率是多少?有没有 fallback 到 PLD?
  2. 把 sliding window 大小自适应到 KL(πt‖πt-N) 上能不能进一步提收益?
  3. 多模态 / agentic RL(tool 调用穿插)里 trajectory 不连续,suffix tree 怎么 segment?
  4. 把 DAS budget 模型套在 EAGLE-3 drafter 上,长 trajectory 上能不能拿到比 DAS+suffix 更高的 k?
  5. ki 和 αi 在线如何估计?Paper 说"profiling"但没给在线估计的算法。
  6. Off-policy / async RL 下,policy lag 大,sliding window 还成立吗?

立场 Drafter 不必是模型;在 RL 场景里 suffix tree + sliding window 是更对路的非参数替代。
关键洞察 RL rollout 的 makespan 由长 trajectory 决定 → speculation budget 应当向长偏斜,而不是 per-request 平均。
公式 pi* = −(lii) ln(1 − ki(1 − Nfwd/li)),长 request 大 budget,短 request skip。
数据结构 Per-problem suffix tree (Ukkonen O(n) 在线构造) + 16/32 epoch sliding window;查询 O(m),更新 O(Δn)。
数字 Math -50%, Code -25%; unlimited-budget 比 DAS 慢 15%(verification cost 反噬)。
工程优雅 0 参数 drafter / lossless / 不动 reward loop / vLLM hook 即可上线 — 是 spec-RL 这条线里最易部署的。
硬限制 首 epoch 冷启动 + 非模板化 domain (open-ended dialogue) 收益弱;问题数极多时 sharding 内存上升。