副标题: 大模型每生成一个 token 都要搬一次 KV Cache------瓶颈在搬数据,不在算。投机解码做的事就是:用更快的方式"猜"出几个 token 凑成一批,大模型一次验证,用"批处理"省掉重复搬运。
一、为什么大模型一次只能生成 1 个 token?
上篇博客推理引擎的结论中有一个反直觉的事实:
Decode 阶段(每步生成 1 个 token)是 memory-bound 的。 GPU 的张量核心大部分时间在等显存搬运数据。
具体来说,大模型每生成一个 token,要做两件事:
1. 把历史所有 token 的 KV Cache 从 HBM(显存)搬到计算单元
耗时:~95ms ← 瓶颈!memory-bound
2. 用当前 token 的 Query 和所有历史 KV 做矩阵乘法
耗时:~5ms ← 算力大部分闲着
总耗时:~100ms,产出 1 个 token
95% 的时间在搬数据,只有 5% 在真正计算。 举个例子:H100 的理论 FP16 算力是 1,979 TFLOPS。但在 decode 阶段跑 Llama 2 7B,实际只用了不到 5% 的算力------剩下的全在等显存。
那为什么大模型不能一次生成 3 个 token?答案是自回归的架构决定了它不能。token N 依赖 token N-1------你没生成 "A",就不知道 "A" 后面的 token 应该是什么。所以每一步必须等上一步的结果出来,这是串行的。
Step 1: prompt → 前向传播 → 输出 "A"
Step 2: prompt + "A" → 前向传播 → 输出 "B"
Step 3: prompt + "A" + "B" → 前向传播 → 输出 "C"
每一步都要单独搬一次 KV Cache。搬 3 次 = 3 × 95ms = 285ms。
投机解码问了一个问题:能不能把搬 KV Cache 的次数从 3 次减到 1 次,同时产出 3 个 token?
二、核心直觉:把小模型当"助手",帮大模型凑够一批 token
2.1 先看两个关键事实
事实一:大模型"验证"不比"生成"贵多少。
大模型一次前向传播,不管输入是 1 个 token 还是 3 个 token,KV Cache 都要全部搬一次------这就是那 95ms 的固定成本。输入 3 个 token 时多出来的矩阵乘法只增加几毫秒。
| 操作 | 搬 KV Cache | 计算 | 总耗时 |
|---|---|---|---|
| 生成 1 个 token | 95ms | 5ms | 100ms |
| 验证 3 个 token | 95ms | 10ms | 105ms |
验证 3 个只比生成 1 个多了 5ms。搬 KV Cache 才是天花板,不是计算量。
事实二:大模型不能自己"出题给自己批"。
大模型的因果注意力(causal attention)让每个位置的 token 只能看到它之前的 token。所以它能验证"给定 A 之后,B 合不合理"------但它不能自己凭空生成 A、B、C 然后一起验证,因为要生成 B 必须先知道 A,这就回到串行了。
2.2 小模型的作用:做那个"出题的人"
小模型(draft model)虽然不够准,但它很快。让它串行生成 A→B→C,每步只要 ~2ms:
小模型草稿生成(串行,但很快):
prompt → [小模型 step1: 2ms] → A
prompt+A → [小模型 step2: 2ms] → B
prompt+A+B → [小模型 step3: 2ms] → C
草稿耗时: 3 × 2ms = 6ms
现在大模型拿到 A、B、C,把它们当作输入来做一次前向传播------就像 prefill 阶段处理 prompt 一样:
大模型验证(一次前向传播):
输入: prompt + A + B + C
耗时: 搬 KV Cache (95ms) + 验证 3 个位置 (10ms) = 105ms
注意:A、B、C 在这里是大模型的输入,不是它要生成的输出。 大模型不需要知道 A、B、C 是怎么来的------它只是像处理任何输入一样,在每个位置都算出"下一个 token 应该是什么"。
2.3 收益在哪里?
大模型串行生成 3 个 token:
Step1: 100ms → "A"
Step2: 100ms → "B"
Step3: 100ms → "C"
总耗时: 300ms,每 token: 100ms
投机解码:
小模型草稿: 6ms
大模型验证: 105ms
总耗时: 111ms,产出 3 个 token ← 全部猜对的情况
每 token: 37ms,加速 2.7×
即使只猜对 2 个:
111ms / 2 tokens = 55ms/token,仍然加速 1.8×
小模型的作用不是"替代大模型的计算"------是帮大模型把原本必须串行的 3 次前向传播,压缩成 1 次。每次前向传播的固定成本(搬 KV Cache)从 3 次变成 1 次,这才是收益的来源。
一句话总结:小模型"出题",大模型"批改"。批改 1 道题和批改 3 道题耗时差不多,因为改卷子的时间都在翻页,不在看题。
三、验证怎么做的?需要额外训练模型吗?
3.1 不需要任何额外训练
这是投机解码最优雅的地方。你可能会想:"大模型判断小模型猜得对不对,是不是需要一个额外的'裁判模型'?"
不需要。 大模型在做前向传播时,自然会在每个位置输出"下一个 token 的预测"。这是 Transformer 因果注意力自带的能力------训练时学预测下一个词,推理时仍然保留。
投机验证时的一次前向传播:
输入: prompt + A + B + C
位置 1 的输出: p(下一个token | prompt) ← 大模型认为"prompt 后应该是啥"
位置 2 的输出: p(下一个token | prompt+A) ← 大模型认为"prompt+A 后应该是啥"
位置 3 的输出: p(下一个token | prompt+A+B) ← 大模型认为"prompt+A+B 后应该是啥"
这三个概率分布在同一次前向传播里自然产生,不需要额外计算,不需要额外训练。
3.2 "对答案"
小模型在生成 A 时,内部也有一个概率分布 p_draft(下一个 token | prompt)。大模型在验证时也产生了 p_target(下一个 token | prompt)。比较这两个分布:
对位置 A:
小模型: p_draft(A | prompt) = 0.3
大模型: p_target(A | prompt) = 0.4 ← 大模型更确定,接受 ✓
对位置 B:
小模型: p_draft(B | prompt+A) = 0.5
大模型: p_target(B | prompt+A) = 0.2 ← 小模型高估了,有风险
→ 以概率 min(1, 0.2/0.5) = 0.4 接受
→ 假设被拒绝 ✗
对位置 C:
B 被拒绝了,C 失去意义
大模型从修正后的分布中采样 B' 替代
这一步叫"拒绝采样"(rejection sampling)。 它保证了投机解码的输出分布和大模型直接自回归解码的分布数学上完全等价------不是近似,不是牺牲质量换速度,是严格无损的。
3.3 即使全被拒绝也不会更慢
如果 A、B、C 全被拒绝:
- 小模型花了 6ms(浪费了)
- 但大模型在验证时至少会采样出一个新 token A'
- 本轮产出:1 个 token(A')
- 相当于大模型正常生成了一步,多花了小模型的 6ms 成本
即使在最坏情况下,也只比标准解码慢了一点点。 而实际使用中接受率通常在 60-80%,所以整体一定加速。
四、K 选多大?甜蜜的 tradeoff
"每次让小模型猜几个 token,是一个需要平衡的数字。"这个直觉完全正确。
4.1 接受率随 K 递减
小模型猜第 1 个 token 可能 80% 准,但猜第 5 个可能就只剩 40% 了。因为每个位置的预测都有误差,误差会累积。
K=3: 猜对 2.2 个(平均)
K=5: 猜对 2.8 个(平均)
K=10: 猜对 3.2 个(平均)
K 从 3 变到 5,多猜 2 个只多收获 0.6 个
K 从 5 变到 10,多猜 5 个只多收获 0.4 个
→ 边际收益急剧递减
4.2 小模型成本随 K 线性增长
小模型生成 K 个 token 是串行的:
K=3: 小模型耗时 3 × 2ms = 6ms
K=5: 小模型耗时 5 × 2ms = 10ms
K=10: 小模型耗时 10 × 2ms = 20ms ← 已经和验证成本一个数量级了
4.3 存在最优 K
真实加速 ≈ (1 + 平均接受数) / (1 + 草稿成本占比 × K)
K 太小 → 接受 token 太少,收益不明显
K 太大 → 小模型成本高 + 后期接受率低,反而拖累
工程上的经验值:
| 场景 | 推荐 K | 原因 |
|---|---|---|
| 草稿模型极小(0.5B) | 5-7 | 成本极低,多猜几个无所谓 |
| 草稿模型中等(1-3B) | 3-5 | 最优平衡 |
| Self-Speculative(跳层) | 3-4 | 草稿质量较低,后面的猜不准 |
| Eagle3(特征预测) | 5-8 | 草稿质量高,可以多猜 |
| 代码补全(高确定性) | 5-10 | 接受率很高,猜的收益大 |
| 创意写作(低确定性) | 2-3 | 猜不准,不如少猜 |
主流推理引擎(vLLM、SGLang)默认 K 都在 5 左右,这是大量实验后的通用经验值。
五、Eagle:不需要独立小模型的投机解码
5.1 原始方案的痛点
最早的做法(2023 年)就是"加载两个模型":用小模型串行生成草稿,大模型验证。问题也很明显:
- 额外显存占用:加载一个完整的小模型(0.5-3B 参数)
- 小模型和靶模型如果不是同一词表,token 映射还有额外开销
- 部署复杂度翻倍
5.2 Eagle 的洞察:直接预测"未来特征",不生成 token
Eagle 系列(2024 年)做了一步关键的跨越。核心想法:
靶模型跑完已经产生了"内部状态"(hidden state)。从这个内部状态出发,可以直接预测未来 token 的特征向量,而不需要走完整的 token 生成流程。
原始方案(独立草稿模型):
靶模型: [Layer1...LayerN] → token₀
小模型: [Layer1...LayerM] → token₁
小模型: [Layer1...LayerM] → token₂
小模型: [Layer1...LayerM] → token₃
↑ 小模型是完整模型,要跑全量层数,串行 3 次
Eagle 方案:
靶模型: [Layer1...LayerN] → hidden_state + token₀
预测头(hidden_state) → 特征₁(代表 token₁ 的语义)
预测头(特征₁) → 特征₂
预测头(特征₂) → 特征₃
↑ 预测头只有 1-2 层 Transformer,极轻量,一次跑出全部
这里的关键差别:
- 不生成 token,生成特征向量。 特征向量比离散 token 携带更多信息(一个 4096 维向量 vs 一个 token ID),预测更难出错。
- 不需要独立模型。 预测头只是靶模型上额外加的 1-2 层,参数量极小(约 1% 靶模型参数量)。
- 不占显存。 预测头几乎不影响显存布局。
5.3 Eagle → Eagle2 → Eagle3 的演进
| 版本 | 改进点 | 加速比 |
|---|---|---|
| Eagle (v1) | 证明了"从 hidden state 预测未来特征"这条路走得通 | ~2.5× |
| Eagle2 | 引入动态草稿树(不只猜一条路,而是多路并行猜测,验证时挑最好的) | 3-4× |
| Eagle3 | 优化训练方式,让预测头更好学习靶模型的"特征空间结构";改进解码树结构 | 4×+ |
Eagle2 的"草稿树"是什么? 简单说:标准投机是猜一条路径 A→B→C,如果 B 被拒绝,C 也没用了。草稿树是同时猜多条路径:
A
/ | \
B D F ← 3 条并行路径
/ |
C E
一次后验证,取通过率最高的那条。一个位置猜 3 个候选比猜 1 个候选,至少翻倍接受率------而多生成 2 个候选的计算成本只增加 10-20%。
5.4 Falcon:最新 SOTA(AAAI 2025)
Falcon 是目前最强的投机解码方案,核心卖点是:
- 草稿模型只有 2 层 Transformer(比 Eagle3 更轻)
- CSGD 蒸馏(Coupled Sequential Glancing Distillation):训练草稿模型时让它"看到"最终答案的一部分,从而让草稿 token 间的依赖更接近真实靶模型
- 自定义解码树:一次草稿生成多路,验证时同时评估
加速比:2.91-3.51×,在同等加速下比所有前代方案更省显存。
五种方案对比
| 方案 | 年份 | 草稿来源 | 额外显存 | 加速比 | 核心特色 |
|---|---|---|---|---|---|
| 独立草稿模型 | 2023 | 同架构小模型 | 大(0.5-3B 参数) | 2-3× | 部署简单 |
| Self-Speculative | 2024 | 靶模型跳过中间层 | 无 | ~2× | 零额外显存 |
| Medusa | 2024 | 靶模型 + 多预测头 | 小 | ~2.5× | 多头并行猜测 |
| Eagle3 | 2024 | 靶模型 hidden state | 极小 | 3-4× | 特征预测 + 解码树 |
| Falcon | 2025 | 2 层轻量模型 | 极少 | 2.9-3.5× | CSGD 蒸馏 |
六、部署流程对比:正常推理 vs 独立草稿 vs Eagle
前面讲的是"投机解码做了什么",这一节讲"框架需要怎么部署它"------和正常推理在显存布局、推理循环、KV Cache 管理上有哪些具体差异。
6.1 正常推理:单模型,单循环
先回顾正常推理的部署形态:
┌─────────────────────────────────────────┐
│ GPU 显存 │
│ ┌─────────────────────────────────────┐ │
│ │ 靶模型权重 (7B, FP16: ~14GB) │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ KV Cache(随生成动态增长) │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ 临时激活值(forward 中间结果) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
推理循环:
while 没到 max_tokens 且没到 EOS:
logits = target_model.forward(last_token) // decode mode: [batch, 1]
next_token = sample(logits / temperature)
kv_cache.append(next_token) // KV Cache 追加
output.append(next_token)
每一步只做 1 次 forward,输入 1 个 token,产出 1 个 token。KV Cache 只增不减。
6.2 独立草稿模型方案:双模型,KV Cache 需要回滚
这是最原始也最直观的投机解码部署方式------同时加载靶模型和草稿模型:
┌─────────────────────────────────────────┐
│ GPU 显存 │
│ ┌─────────────────────────────────────┐ │
│ │ 靶模型权重 (7B, ~14GB) │ │
│ ├─────────────────────────────────────┤ │
│ │ 草稿模型权重 (1.5B, ~3GB) ← 多出来的 │ │
│ ├─────────────────────────────────────┤ │
│ │ 靶模型 KV Cache │ │
│ ├─────────────────────────────────────┤ │
│ │ 草稿模型 KV Cache(短期,可丢弃) │ │
│ ├─────────────────────────────────────┤ │
│ │ 临时激活值 × 2(两个模型各一份) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
推理循环:
while 没到 max_tokens:
# 阶段 1: Draft(小模型串行生成)
draft_tokens = []
draft_input = last_token
for k in range(K): // K=5,草稿模型跑 5 步
logit = draft_model.forward(draft_input) // decode mode
next_draft = sample(logit)
draft_tokens.append(next_draft)
draft_input = next_draft
# 阶段 2: Verify(大模型一次验证)
target_logits = target_model.forward(draft_tokens) // prefill mode: [batch, K]
# 拒绝采样
accepted = []
for i, (draft_token, target_logit) in enumerate(zip(draft_tokens, target_logits)):
if random() < min(1, target_prob / draft_prob):
accepted.append(draft_token) // 接受
else:
accepted.append(sample_fixed(target_logit)) // 拒绝,用靶模型采样
break // 后续草稿全丢弃
# 关键:回滚 KV Cache
# 假设接受了 2 个,第 3 个被拒绝
# 靶模型 KV Cache 要从被拒绝位置回滚
kv_cache.rollback(len(accepted)) // 释放第 3 个及之后的 KV
# 如果草稿 KV 不跨轮复用,丢弃
draft_kv_cache.clear()
output.extend(accepted)
和正常推理的关键差异:
| 差异点 | 正常推理 | 独立草稿方案 |
|---|---|---|
| 加载模型数 | 1 个 | 2 个 |
| 显存额外开销 | 无 | 草稿模型权重 + 草稿 KV Cache |
| KV Cache 回滚 | 不需要 | 需要------被拒绝的草稿 token 对应的 KV 必须丢弃 |
| forward 类型 | 只用 decode | decode(草稿)+ prefill(验证) |
| 每轮产出 token 数 | 1 个 | 1~K+1 个 |
KV Cache 回滚是投机解码对框架最重要的额外要求。好在 PagedAttention 按块管理------回滚就是释放几个 block,不需要移动数据。vLLM 的 KV cache rollback 正是为此设计的。
6.3 Eagle 方案:单模型 + 预测头,不加载第二个模型
Eagle 的最大优势:不需要加载独立草稿模型,只在靶模型上挂一个极轻量的预测头:
┌─────────────────────────────────────────┐
│ GPU 显存 │
│ ┌─────────────────────────────────────┐ │
│ │ 靶模型权重 (7B, ~14GB) │ │
│ ├─────────────────────────────────────┤ │
│ │ Eagle 预测头 (~1% × 7B, ~0.14GB) │ │
│ │ ← 几乎可以忽略 │ │
│ ├─────────────────────────────────────┤ │
│ │ KV Cache(只有靶模型一份) │ │
│ ├─────────────────────────────────────┤ │
│ │ 临时激活值(含 hidden state) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
推理循环:
while 没到 max_tokens:
# 阶段 1: 靶模型 forward → 获取 hidden state
hidden_state, last_token = target_model.forward(last_input,
return_hidden=True)
# 阶段 2: Draft(预测头基于 hidden state 预测未来特征)
draft_features = []
current_feat = hidden_state
for k in range(K):
next_feat = eagle_head.forward(current_feat) // 极轻量,1-2 层
draft_features.append(next_feat)
current_feat = next_feat
# 将特征向量转回 token(预测头自带分类层)
draft_tokens = [head.classify(f) for head, f in zip(eagle_heads, draft_features)]
# 阶段 3: Verify(靶模型一次验证所有草稿 token)
target_logits = target_model.forward(draft_tokens) // prefill mode
# 拒绝采样 + KV Cache 回滚(同上)
accepted = rejection_sample(target_logits, draft_tokens)
kv_cache.rollback(len(accepted))
output.extend(accepted)
和独立草稿方案的关键差异:
| 差异点 | 独立草稿 | Eagle |
|---|---|---|
| 草稿生成方式 | 小模型自回归生成 token | 从 hidden state 预测特征向量 |
| 草稿生成成本 | 每步跑完整小模型 | 每步只跑 1-2 层预测头(极快) |
| 额外显存 | 草稿模型全量权重 | 预测头(~1% 靶模型参数量) |
| KV Cache | 草稿和靶模型各一份 | 只有靶模型一份 |
| 草稿串行步数 | K 步,每步穿一次小模型 | K 步,每步只穿 1-2 层 |
6.4 三者部署一览
正常推理:
GPU: [靶模型]
循环: for i in range(N):
target.forward() → 1 token
KV: 只增不减
独立草稿:
GPU: [靶模型 | 草稿模型] ← 多一个模型
循环: while not done:
draft.generate(K tokens) ← K 步串行
target.verify(K tokens) ← 1 步并行
rollback KV if rejected ← 需要回滚
KV: 增 + 可能回滚
Eagle:
GPU: [靶模型 | 预测头] ← 预测头极小
循环: while not done:
target.forward() → hidden_state
eagle_head(hidden_state) → K 个特征 → K 个 token ← 极快
target.verify(K tokens) ← 同上
rollback KV if rejected
KV: 增 + 可能回滚
| 正常推理 | 独立草稿 | Eagle | |
|---|---|---|---|
| GPU 模型数 | 1 | 2 | 1 + 轻量预测头 |
| 额外显存 | 0 | ~3GB(1.5B 草稿) | ~0.14GB |
| KV Cache 管理 | 只追加 | 追加 + 回滚 | 追加 + 回滚 |
| 需要新算子? | --- | 无(复用 prefill kernel) | 树形需自定义 attention mask |
| 加速比 | 1×(基线) | 2-3× | 3-4× |
| 部署复杂度 | ★☆☆ | ★★☆ | ★★★(需训练预测头) |
七、LLM 推理优化全景:投机解码处于什么位置?
到这里,我们系列已经覆盖了推理优化的好几个层面。把它们放在一起看,会有一个清晰的层次感。
四层优化体系
┌──────────────────────────────────────────────┐
│ 第四层: 生成策略 │
│ Speculative Decoding, Constrained Decoding │
│ "怎么少做几次前向传播?" │
├──────────────────────────────────────────────┤
│ 第三层: 执行优化 │
│ CUDA Graph, 模型并行(TP/PP), 显存offloading │
│ "每次前向传播怎么跑得更快?" │
├──────────────────────────────────────────────┤
│ 第二层: 调度策略 │
│ Continuous Batching, Chunked Prefill, │
│ Prefill-Decode 分离, Priority Scheduling │
│ "多个请求怎么排队最高效?" │
├──────────────────────────────────────────────┤
│ 第一层: 显存管理 │
│ PagedAttention, RadixAttention, KV Cache 量化 │
│ "KV Cache 怎么存、怎么共享?" │
├──────────────────────────────────────────────┤
│ 底层: 算子效率 │
│ FlashAttention, GEMM 优化, Activation 融合 │
│ "每次矩阵乘法怎么算最快?" │
└──────────────────────────────────────────────┘
各层解决的问题不同
| 层级 | 解决的问题 | 博客对应 |
|---|---|---|
| 算子 | 一次矩阵乘法怎么算得最快 | #16(Agent 写算子) |
| 显存管理 | KV Cache 碎片化、重复存储 | #17(PagedAttention、Prefix Caching) |
| 调度 | 多个请求各不等长,GPU 不能等 | #17(Continuous Batching、Chunked Prefill) |
| 执行 | 单卡放不下、kernel 启动开销 | 本文(简介) |
| 生成策略 | decode 每步产 1 个 token,慢 | 本文重点 |
各层的代表性优化
第一层:显存管理
| 优化 | 核心思路 |
|---|---|
| PagedAttention | 分页管理 KV Cache,消除预留碎片(#17 讲过) |
| RadixAttention | 前缀树共享 KV Cache,RAG 场景 prefill 减少 71%(#17 讲过) |
| KV Cache 量化 | KV 存 FP8/INT8 而非 FP16,显存减半,精度损失 < 1% |
第二层:调度策略
| 优化 | 核心思路 |
|---|---|
| Continuous Batching | 每步重新组批,不等长请求不互相等待(#17 讲过) |
| Chunked Prefill | 长 prompt 切成小块,和 decode 交替执行(#17 讲过) |
| Prefill-Decode 分离 | Prefill(compute-bound)和 Decode(memory-bound)用不同 GPU,互不拖累 |
第三层:执行优化
| 优化 | 核心思路 |
|---|---|
| CUDA Graph | 把 decode 所有 kernel 调用一次录制为 graph,后续重放------省掉每次的 CPU 发起开销 |
| 张量并行(TP) | 权重矩阵按列切分到多卡,每卡算一部分然后 gather |
| 流水线并行(PP) | 不同 GPU 负责不同层,数据像流水线一样流动 |
第四层:生成策略
| 优化 | 核心思路 |
|---|---|
| Speculative Decoding | 小模型猜 + 大模型验证,一次产出多 token------本文 |
| Constrained Decoding | 限制输出必须符合 JSON Schema/Regex,避免格式错误重试 |
这些优化能叠加吗?
能。它们解决的是不同层面的问题,互相独立:
- PagedAttention 管 KV Cache 怎么存
- Continuous Batching 管不同请求怎么排队
- Speculative Decoding 管单个请求怎么少跑几次前向传播
- CUDA Graph 管每次前向传播的 kernel 启动怎么更快
一个实际例子:SGLang 在 RadixAttention(显存层)+ Continuous Batching(调度层)+ Eagle3 Spec Dec(生成策略层)叠加下,高并发多轮对话场景的吞吐是纯 vLLM 无投机 baseline 的 5-8×。
八、实际部署效果
主流引擎对投机解码的支持
| 引擎 | 投机方案 | 特点 |
|---|---|---|
| vLLM | 独立草稿 / Eagle | 和 PagedAttention 完全兼容 |
| SGLang | Eagle3(原生支持) | 和 RadixAttention 叠加,多轮场景加速倍增 |
| llama.cpp | 独立草稿 | 本地部署友好,Q4 量化草稿模型 |
SGLang + Eagle3(Llama 3 8B):
- 无投机: 45 tok/s
-
- Eagle3: 110 tok/s(2.4×)
llama.cpp(GTX 1660 Ti,Qwen3-8B Q4_K_M):
- 无投机: 50 tok/s
-
- Qwen3-0.5B draft: 90 tok/s(1.8×)
最佳实践
- 草稿模型选 10-25% 靶模型参数量足够(1-3B draft / 7-13B target),再大边际收益低
- 同架构同词表接受率最高(Llama 2 7B target → Llama 2 1.5B draft > 随机 0.5B)
- 量化不破坏投机------草稿和靶都可以 INT8/INT4,加速叠加
- 低 temperature 投机加速更明显(T=0 时 α 可达 0.9+;T=1 时降到 0.5)
- 用 SGLang 直接开 Eagle3------当前最低配置、最高收益的方案
九、总结
Speculative Decoding 不是在帮大模型省计算------是在帮它省"搬 KV Cache 的次数"。 小模型的唯一作用是串行生成几个 token,凑成一批,让大模型一次验证------把 3 次 memory-bound 的前向传播变成 1 次,省掉 2 次 KV Cache 搬运的固定成本。
三个层面的理解:
表层(现象):小模型猜,大模型验证,一次产出多个 token
中间层(机制):拒绝采样保证质量无损,接受率决定加速比
深层(物理):把 decode 每步 1 个 token 的串行搬运,变成批量验证的并行搬运
核心条件:
草稿成本 << 单次 Target Decode 成本 → 有加速意义
草稿接受率 > 30-40% → 覆盖草稿成本
草稿模型同架构同词表 → 接受率最高
Temperature → 0 时加速最大
投机解码最精妙的地方在于:不需要额外训练、不改变模型结构、不牺牲生成质量------只是换了一种使用模型的方式。小模型凑 token + 大模型批改,本质上是在用系统设计的思路绕开自回归的串行约束。
附录:进一步阅读
- Leviathan et al. (ICML 2023): Fast Inference from Transformers via Speculative Decoding --- 投机解码原始论文
- Zhang et al. (ACL 2024): Draft & Verify: Lossless LLM Acceleration via Self-Speculative Decoding
- Li et al. (2024): EAGLE-2: Faster Inference of Language Models with Dynamic Draft Trees
- Sun et al. (AAAI 2025): Falcon: Faster and Parallel Inference via Enhanced Semi-Autoregressive Drafting
- vLLM Spec Decoding 文档: docs.vllm.ai
- SGLang Eagle 文档: docs.sglang.io
这个专栏专注于 LLM 推理与部署的底层技术------从算子开发、显存管理、调度策略到生成优化,用可运行的代码和可复现的实验,把论文里的"黑科技"变成可以理解、可以动手验证的知识。如果你对"GPU 到底在干什么"和"推理框架做了什么优化"这类问题感兴趣,欢迎来主页翻翻其他文章。