ReSpec: Towards Optimizing Speculative Decoding in Reinforcement Learning Systems
速读卡片 (TL;DR)
一句话:把 EAGLE-3 直接接进 RL 不仅不加速,反而会让 reward 崩盘——ReSpec 用三件事把 SD 救回来:动态 SD 配置、在线 KD 让 drafter 跟上 actor、用 rollout reward 给 KD 加权防止 drafter 把 actor 带偏。
立场:这条线里"最系统"的一篇——同时把 G1(batch-size 收益消失)、G2(drafter staleness)、G3(SD 把 actor 带歪) 三个坑列出来并各给一个对应机制。Reward-weighted KD 是真正新的部分,其它两块更偏工程。
1 · 动机:naïve SD 在 RL 里为什么不工作
1.1 历史脉络
Speculative decoding 在 serving 系统里已经被打磨得非常成熟:SGLang / TensorRT-LLM / OpenAI predicted-outputs / AWS SageMaker 都默认带 SD。EAGLE-3 把 lossless SD 的 acceptance length 推到 ~4–6 tokens,1.5–2× 的加速基本是即插即用的。
把这套搬进 RL post-training 听上去很顺:RL 一个 step 里 generation 占 75–86% 时间(Table 1),减半就是端到端减半。GRPO/DAPO 这类 group sampling 又把每个 prompt 复制成 8/16 个 rollout,token 量再翻几倍。看起来 SD 是天造地设的优化。
但作者把 EAGLE-3 直接挂在 VeRL+SGLang 上一跑,事情立刻不对劲(Figure 5):Qwen2.5-7B 在 math 数据集上,带 EAGLE-3 跑 200 步,reward 不仅没涨,反而从 0.3 跌到 0.2;不带 SD 的 baseline 同期能涨到 0.4。SD 不是没加速,而是把 actor 训坏了。
1.2 别的方案为什么不够
在 ReSpec 之前,RL × SD 这条线已经有几个方向,但都各有缺口:
| 方案 | 核心想法 | 为什么不够 |
|---|---|---|
| Static EAGLE-3 (NVIDIA NeMo-RL 风格) | 每隔若干步用 actor 重新蒸馏 drafter,中间冻结 | G2 staleness 在 100 步内就发作 (Figure 4: accept length 从 4.0 掉到 2.5);批大且温度高时 G1 反向劣化(Figure 3 在 batch=64 时 speedup < 1) |
| Online SD for serving (DistillSpec, Liu23) | 用流式数据持续蒸馏 drafter,跟随 request 分布漂移 | 它们的 teacher 是固定的、漂移在 input 侧;RL 的 teacher (actor) 自己在变,且没有把 reward 信号利用起来 |
| SPEC-RL (并行同期工作) | 把 rollout 切成 SD-eligible 区段,只在合适处触发 SD | 仍然是 static drafter;不解决 G2;也没有 G3 这个"SD 让 policy 退化"的诊断 |
| Async RL (AReaL, RhymeRL) | generation/training 异步重叠,根本不修 SD 本身 | 正交问题——可以叠加,不替代 |
| 更大的 draft tree / Medusa | 用更激进的 draft 提高 acceptance length | 放大 G3:multi-token 接受概率方差按 (1+δ)^|T| 指数膨胀,被偷换的 trajectory 进 replay buffer 后污染 gradient |
1.3 为什么这事不平凡
把上面的故事聚成三个 research gap(论文里 G1/G2/G3,这是文章的骨架):
三个 gap 中,G3 是最反直觉的:经典 SD 的 acceptance test (Eq.1) 数学上保证每个 token 的 marginal 分布等于 target——很多人因此以为 SD 是 lossless 的、不影响训练。但 RL 的 return 是非线性 trajectory function:第 5 个 token 不一样,后面 100 个 token 的奖励可能差出一个数量级。Eq.3 给出的 multi-token 方差是 (1+δ)^|T| − 1,指数膨胀。drafter 偏爱的"看起来合理"的短语会反复出现在 replay buffer 里(论文那个例子:drafter 老说 "the key to success",而 actor 真正高 reward 的续写是 "the key of life"),于是 gradient 就被这些 lower-reward continuation 反复"投票"。这是 distribution bias,不是 variance,根本无法靠多采样消除。
另外两个 opportunity 让方案有抓手:① generation 工作流是高度倾斜的(Figure 6:active batch 在一个 rollout window 内从 32 衰减到 1),意味着 SD 配置不能 static,得跟 active batch 走;② SD 的 verification 步骤已经天然在算 target logits,这正是 KD 想要的 soft target——白送的监督信号,只要顺手存下来就能拿来 distill。
2 · 背景速查
| 术语 | 含义 |
|---|---|
| target / actor | RL 里的策略模型,即 SD 里的"大模型 verifier"。这两个角色在 RL × SD 里同一份权重 |
| drafter / draft model | EAGLE-3 风格的小 head + 一两层 transformer,提议 k 个候选 token |
| acceptance length | 一次 verify 中被接受的连续 token 数;直接决定 speedup |
| (s, t, n) | SD 三个超参:speculative rounds / target branching factor / draft length per round |
| active batch size | 当前还没结束的 sequence 数。RL rollout 中从满 batch 衰减到 1 |
| replay buffer (Q) | ReSpec 自己的 buffer,存 (x, y, log p, log q, r),用于 reward-weighted KD |
SD acceptance test (one-shot 版)
其中 q 是 drafter,p 是 target。被拒时从 residual r(x) = max(0, p−q) / Σ max(0, p−q) 重采样。marginal 分布无偏是 Leviathan'23 的核心定理。
SD cost model (Eq.4)
Cq: drafter 单 token 成本;Cp: target 单 token 成本;α: 并行验证效率(0<α≤1);r: 接受率。当 batch 已经把 GPU 喂饱时 α→1 但 Cq 反而成了纯开销 → SD 退化为净亏损。
3 · G1 解法:Adaptive Speculative Decoding Server
核心观察(Figure 7 那张热力图):同一份 SD 配置 (s=5, t=8, n=32) 在 batch=2 时 1.46× speedup,在 batch=32 时变成 0.76×(亏 24%)。所以"挑一个最好的 (s,t,n) 用一辈子"这条路根本走不通。
ReSpec 的方案分两层:
- Solver (offline):训练前在目标硬件上跑一轮 profiling,枚举 (s,t,n) × batch size 的吞吐表,拟合一个 lookup model。这一步 lightweight,只做一次。
- Scheduler (runtime):每个 decoding step 看当前 active batch,查表选最优配置;如果连 SD 本身都不划算(预测 speedup < 1),直接降级到纯 target decode。
难点在状态切换:从 spec 切回 non-spec,或反过来,KV cache 状态不一样。ReSpec 用一个 per-request 的 flag,把 decoding 抽象成两个状态:
- Non-spec → Spec:复用 prefill 接口,把当前 decode batch "promote" 成 spec-enabled batch,不改 decoding kernel
- Spec → Non-spec:直接丢掉 speculative metadata(候选树等),继续普通 decode
Worked example
假设 H100 上 Qwen2.5-7B + EAGLE-3,profiler 测到下表(简化版):
| active batch | (s,t,n)=(3,5,8) | (5,8,16) | (5,8,32) | Solver 选 |
|---|---|---|---|---|
| 2 | 1.24× | 1.36× | 1.46× | (5,8,32) |
| 16 | 1.20× | 1.21× | 1.06× | (3,5,16)→(3,8,32)≈1.46× |
| 32 | 1.16× | 1.04× | 0.76× | 关 SD,纯 target |
追踪一个 prompt 的生命周期:rollout 开始时 batch=32 → Scheduler 选 non-spec(虽然 EAGLE-3 已经加载,但不调用 draft path);跑了 200 token 后 28 个 sequence 已经 EOS,active=4 → 切到 spec,先用一次 prefill-style forward 把 KV cache 补到当前位置(这是论文里 "Extend Once" 那一步,Figure 9b 中间的灰条),之后正常 spec decode。这样大批和长尾两段都吃到甜头。
反向论证:不做 Adaptive Server 会怎样?
静态选 (5,8,32):大 batch 段亏 24%,小 batch 段赚 46%,两边正负相抵后端到端只剩 ~10%。论文的 ablation(Figure 14)显示:在已经有 reward-weighted KD 的基础上,Adaptive Server 又加 12%(1.48× → 1.66×)。这 12% 几乎全靠"在大 batch 阶段不要开 SD"这个简单决策。
4 · G2 解法:Drafter Evolution via on-policy KD
EAGLE-3 在 serving 里只蒸馏一次就稳稳跑;在 RL 里 actor 每 step 在变,所以训练 100 步后,Figure 4 里 acceptance length 已经从 4.0 滑到 ~2.5。修复思路简单又巧妙:SD 的 verification 已经在算 target logits 了,顺手存下来当 KD 的 soft target,反过来更新 drafter。
具体流程(Algorithm 1 简化):
- 每个 rollout sample 产生
(x, y, log p, log q, r)五元组,其中log p是 verification 自动给出的 target logits - 这些五元组存进 replay buffer Q
- 每 I 步训练一次 drafter,目标是
KL(softmax(log p) ‖ q_θ(·)),梯度只对当前 q_θ 反传(不对 log q 反传——log q 只是诊断) - 更新完的 drafter 推回 inference engine,下一个 RL step 用
Worked example: 一条 rollout 的生命周期
假设 prompt = "求 ∫₀¹ x²dx",actor 输出 100 个 token。其中第 17 个位置 SD verify 同时产出长度 V=151936(Qwen2.5 vocab) 的 logits 张量 log p_17 ∈ ℝ^V。存盘时只存 top-K=32(节省存储,论文默认这么做),其它位置 mask 掉。
| 位置 t | token y_t | log p (top-3) | log q (drafter) | KL 贡献 |
|---|---|---|---|---|
| 17 | "x²" | {x²:-0.4, x:-2.1, t²:-3.2} | {x²:-0.9, x:-1.5, t²:-2.0} | 0.32 |
| 18 | "dx" | {dx:-0.1, dt:-3.5} | {dx:-0.3, dt:-1.8} | 0.18 |
| 19 | "=" | {=:-0.05} | {=:-0.06} | ~0 |
句子级 r=0.85(答对了 1/3)。Loss = 0.85 · (0.32 + 0.18 + 0) = 0.425,反向更新到 drafter 的 token-mlp head。下一个 step,drafter 在"x²"位置的概率会被推高,acceptance length 期望提升 ~5%。
反向论证:不让 drafter 更新会怎样?
等价于 NeMo-RL / SPEC-RL 的做法。短期 OK,长期(>100 step)acceptance length 退化到 2.5,SD 的物理增益降到 1.2× 以下;再叠加 G3(下面),reward 直接崩。Figure 12 中"EAGLE-3"那条曲线就是这个状态:Qwen-3B 在第 400 步 val score 降到 0.15。
5 · G3 解法:Reward-Weighted KD 核心创新
这是 ReSpec 区别于其它 RL × SD 工作的真正新东西。前两个 gap 的解法多少有先例(adaptive SD 是 serving 里的常识,on-policy KD 是 DistillSpec 改 RL),但 G3 第一次被诊断为"SD 不仅没加速,还在偷换梯度",并且解法是把 RL 的 reward 信号注回到 SD 的 KD 损失里。
问题精确化
朴素 KD(标准 KL,所有样本平权)在 RL 里有个反馈环路:
- drafter 学习全部 rollout(包括 reward=0 的失败 trajectory)
- drafter 学到的"平均"分布偏向 actor 的 noisy 行为
- 下一轮 SD 用这个 drafter 提议,acceptance test 在marginal上无偏,但在 multi-token 上把 actor 朝 drafter 的偏置方向"拉过去"
- 更多 noisy rollout 进 buffer → drafter 更偏 → ...
论文给出的修正非常直接——给每个 sample 的 KD 损失加一个 reward 权重 w(r):
默认 w(r) = r(实际实现里 normalize + clip 防止极端值)。直觉:reward 高的 trajectory 是"actor 的优秀样本",让 drafter 重点学这类;reward 低的 trajectory 是 actor 的失败案例,drafter 不该去模仿。
Worked example: GRPO group of 5
同一道数学题 GRPO 采 5 个完成,reward = [1.0, 0.8, 0.3, 0.3, 0.0]。每条算自己的 KL_t = Σ_t KL(p_t ‖ q_t),假设都接近 0.4。
| 样本 | KL_raw | 朴素 KD 贡献 | Reward-weighted 贡献 (w=r) |
|---|---|---|---|
| r=1.0 (best) | 0.40 | 0.40 | 0.40 ← 主导 |
| r=0.8 | 0.40 | 0.40 | 0.32 |
| r=0.3 | 0.40 | 0.40 | 0.12 |
| r=0.3 | 0.40 | 0.40 | 0.12 |
| r=0.0 (failed) | 0.40 | 0.40 ← 污染 | 0.00 |
| 总和 | — | 2.00 | 0.96 |
朴素 KD 的更新方向是 5 条样本的平均;reward-weighted 的更新方向 ≈ 0.42 · ∇top1 + 0.33 · ∇top2,实际把 drafter "拉"向 reward=1.0 那条 trajectory 的预测分布。failed sample 完全不传梯度。
消融对比 (Figure 10)
| 策略 | step ~125 | step ~150 | step ~175 | step ~200 |
|---|---|---|---|---|
| EAGLE-3 only (无更新) | 0.30 → ↓ | 0.20 | 0.07 | ~0 |
| No-reward KD (平权) | 0.30 → ↓ | ~0 | ~0 | ~0 |
| Reward-weighted KD | 0.32 | 0.36 | 0.40 | 0.42 |
| W/O EAGLE-3 (baseline) | 0.32 | 0.36 | 0.40 | 0.42 |
这张表是论文最重要的数据:平权 KD 比"完全不更新"还坏,因为它主动把 drafter 训成了"actor 的均值",而 actor 均值充满失败 trajectory。Reward-weighted 几乎完美追上 baseline 曲线。
反向论证:为什么不能用 advantage 替代 r?
GRPO 里其实有 normalized advantage A = (r - mean_group) / std_group。论文用了原始 r 而不是 A,理由(我推断的):drafter 是generative model,需要绝对的"高质量分布"信号;A 包含负值,clip 后等价于"比平均差就丢掉",会丢一半样本而且不稳定。直接用 r 并 clip 到 [0, ∞) 是最简洁的工程化选择。这个权重函数的最优形式应该有研究空间(例如温度缩放),论文没深入。
6 · Async Update Overlap (G2 系统侧补丁)
drafter 也是个模型,反向传播要 GPU 时间。如果每个 RL step 都同步训一次 drafter,generation 必须等更新完成 → pipeline bubble。ReSpec 用两个机制摊薄这个开销:
- Replay buffer 累积:每 I 步才更新一次 drafter,且每次只用 buffer 的 1/I,把"频繁小更新"摊成"低频次但 batch 大"
- Async overlap:drafter 训练塞进 RL pipeline 的 idle slot——actor weights sync / reward inference 等阶段,GPU 有空,顺势跑 drafter forward+backward
worked example: I=1 时(每步更新),Qwen-7B reward 涨到 0.42;I=3 时降到 0.35;I=5 时降到 0.30 甚至低于 sync。所以默认 I=1 + async,既频繁又不阻塞。这是系统级 vs 算法级 freshness的小心 trade-off。
7 · 关键公式与数值敏感性
7.1 Multi-token 接受概率方差(Eq.3)
把 χ² divergence 近似为常数 δ,方差按序列长度指数增长。这给 G3 提供了理论:即使每个 token 单步 marginal 无偏,接受 5-token 块时方差是 (1+δ)⁵ − 1。
| δ (drafter 偏离度) | |T|=2 | |T|=4 | |T|=8 |
|---|---|---|---|
| 0.05 (drafter 新鲜) | 0.10 | 0.22 | 0.48 |
| 0.20 (100 步未更新) | 0.44 | 1.07 | 3.30 |
| 0.50 (严重 stale) | 1.25 | 4.06 | 24.6 |
物理直觉:drafter 越 stale (δ↑),长 draft block (|T|↑) 的方差爆炸越快。这就是为什么 ReSpec 必须同时做 G2(让 δ 小)和 G3(用 reward 选好的 trajectory),仅其一不够——只解 G2 也会因为剩余 δ 在长 block 下放大;只解 G3 不让 drafter 更新,δ 会从 0.05 飙到 0.5,把 1.25 的方差变成 24.6。
7.2 SD cost (Eq.4) 在 RL 下的退化
r 是接受率(acceptance rate)。当 r → 0,cost → ∞,SD 比裸 decode 慢。把 G2 staleness 写进去:r 从 0.7 (新鲜) 降到 0.3 (stale 100 步) 时,假设 C_q=0.1 C_p,α=0.7:
| r | Cost (单位 C_p) | vs 裸 decode (C_p) |
|---|---|---|
| 0.7 | (0.1 + 1.43)/0.7 = 2.18 | 但每次 verify k 个 token → 实际 0.45 → 2.2× speedup |
| 0.5 | (0.1 + 1.43)/0.5 = 3.06 | 0.61 → 1.6× speedup |
| 0.3 | (0.1 + 1.43)/0.3 = 5.10 | 1.02 → ~1×, SD 收益归零 |
8 · 实验关键结果
8.1 端到端 reward stability (Figure 12)
| 模型 | baseline (no SD) | EAGLE-3 naïve | ReSpec |
|---|---|---|---|
| Qwen2.5-3B (400 步) | 0.600 | 0.500 | 0.550 |
| Qwen2.5-7B (200 步) | 0.400 | 0.327 | 0.427 |
| Qwen2.5-14B (200 步) | 0.609 | 0.586 | 0.632 |
读法:naïve EAGLE-3 在 3B/7B 上明显伤 reward;ReSpec 不仅追平 baseline,有时还略高(可能 reward-weighted KD 起到了"选择性放大优秀样本"的隐性正则)。14B 上差距小,符合"大模型对 stale drafter 更鲁棒"的直觉。
8.2 端到端 speedup (Figure 13)
| 模型 | avg speedup | max speedup |
|---|---|---|
| Qwen-3B | 1.84× | 4.53× |
| Qwen-7B | 1.69× | 2.41× |
| Qwen-14B | 1.50× | 2.33× |
3B 的 4.5× max speedup 是瞬时峰值(应该在 long-tail 单 sequence 阶段);avg 1.84× 是更可靠的数。
8.3 消融 (Figure 14, Qwen-14B)
| 累加 | speedup | 增量 |
|---|---|---|
| baseline (no SD) | 1.00× | — |
| + Reward-weighted KD (Online Learner) | 1.48× | +48% |
| + Adaptive Server | 1.66× | +12% |
| + Async Update Overlap | 1.78× | +7% |
读法:贡献排序是 RW-KD ≫ Adaptive Server > Async overlap。RW-KD 是 main lifter——这跟我前面说的"它是真正的算法创新"一致。注意 RW-KD 既包含 G2 修复(让 SD 不退化)又包含 G3 修复(让 actor 不被带偏),所以 1.48× 里其实是"让 SD 在 RL 里能用"这个基本能力的全部。
9 · 与同类工作对比
| 系统 | G1 自适应 | G2 drafter 更新 | G3 reward 注入 | 核心立场 |
|---|---|---|---|---|
| ReSpec (本文) | ✓ Solver+Scheduler | ✓ on-policy KD | ✓ reward-weighted | 三个 gap 都解,系统级 |
| NeMo-RL × EAGLE-3 (NVIDIA, 2604.26779) | ✗ static (s,t,n) | 定期重蒸馏 | ✗ 没识别出 G3 | 系统集成,不改 SD 算法 |
| SPEC-RL (并行同期) | ~ 选择性触发 | ✗ static drafter | ✗ | 切片 + 区段 SD,不更新 drafter |
| DAS (Draft-Adapted Sampling, RL 方向) | ✗ | ~ 偶尔重蒸 | ✗ | 聚焦 sampling 策略,SD 是手段 |
| DistillSpec (Zhou'23, serving 方向) | — | ✓ online KD | ✗ teacher 是固定的 | SD 在 serving 里追踪 input 漂移 |
定位:ReSpec 在三件事上都打了卡,而其它工作每件至少缺一件。如果说 NeMo-RL 是 "把 SD 装进 RL pipeline 的工程示范",SPEC-RL 是 "把 SD 用得更聪明",那 ReSpec 是 "在 RL 训练动态下重新设计 SD 算法"。Reward-weighted KD 是其它工作目前都没碰的角度。
10 · 局限 / 个人 take / 待验证问题
- 实验规模有限:只到 14B,没碰 MoE 或更大稠密模型。Qwen3-32B / DeepSeek-V3 671B 上 reward-weighted KD 是否仍有这么大边际收益未知。
- w(r)=r 太朴素:reward 在 GRPO 里通常 ∈ [0,1],但绝对值受 reward model scale 影响。换 task(代码 / agent / preference)后 clip 阈值需要重调,论文没给 sensitivity。
- Adaptive Server profiling 离线一次:模型一变(actor 蒸馏 / LoRA / quantize)profiling 表就需要重做。社区里需要一个轻量的 online profiler。
- 4.5× 是峰值,avg 1.5–1.8×;真正落到生产线的口径应该是 avg。Qwen-14B 才 1.50×,跟 NeMo-RL 报的 1.4×(8B sync)接近。
- Reward-weighted KD 与 RLHF 偏好奖励的耦合:如果 reward model 本身有偏(过拟合/sycophancy),drafter 会放大这个偏差;论文没讨论 reward model robustness 的 downstream 影响。
- EAGLE-3 限定:整套设计基本绑死 EAGLE-3。如果换成 Medusa / Lookahead / Cascade,接口和 KD 形式都要改。
待验证问题清单
- 把 w(r) 换成 normalized advantage 或 quantile-rank,在 GRPO 里是否更稳?
- Reward-weighted KD 对 reward hacking 是否敏感?如果 reward model 被 hack,drafter 会不会把 hack 放大?
- 14B 上 RW-KD 增益缩小到 ~0,是否 ReSpec 在更大模型上的核心贡献会被稀释?
- Async I=1 vs sync 在 64+ GPU 大集群上的 weight broadcast 成本如何?(论文是 16 GPU 测的)
- Drafter staleness 与 actor learning rate 的耦合:大 LR 下 δ 增长更快,KD 更新频率是否需要随 LR 自动 scale?
- 把 Adaptive Server 的 (s,t,n) 用 RL bandit 在线学,而不是 offline profile,是否更适应数据集 / horizon 漂移?