W20_Speculative_Decoding_投机解码的智慧.md

Speculative Decoding:投机解码的智慧

一句话理解

Speculative Decoding(投机解码) 是"用小聪明换大效率"的艺术——用一个又快又小的大模型猜测接下来几个词,再用大模型一次性验证,快速又省力。

传统方式:大模型一个字一个字憋 → 慢,但准确
投机解码:小模型猜3个词 → 大模型一次验证3个 → 快,且准确

为什么需要投机解码?

大模型推理的瓶颈:

方案 每步计算 每步时间 生成100字总耗时
大模型(70B) 1次完整Transformer 100ms 100 × 100ms = 10秒
小模型(7B) 1次完整Transformer 10ms 100 × 10ms = 1秒(但质量差)
投机解码 小模型猜5个 → 大模型验证 120ms 100 ÷ 5 × 120ms = 2.4秒

核心洞察:验证比生成快很多!小模型猜5个,大模型一次验证,只需额外20ms。

核心洞察:验证比生成快很多!


工作原理

三步走

第一步:小模型"投机"猜测

  • 输入:“今天天气”
  • 小模型生成:["很", "不错", "啊", ",", "适合"](5个Token,一次性并行生成)

第二步:大模型"验证"

  • 输入:“今天天气很不错啊,适合”
  • 大模型验证每个位置:
    • 位置1 “很”:✓ 接受
    • 位置2 “不错”:✓ 接受
    • 位置3 “啊”:✗ 拒绝 → 改为 “很”
    • 位置4 “,”:需要重新采样
    • 位置5 “适合”:需要重新采样

第三步:输出结果

  • 最终接受:["很", "不错", "很", ",", "出"]
  • 最终文本:“今天天气很不错很,出…”

数学原理

# 投机解码的核心逻辑
def speculative_decoding(draft_tokens, large_model, small_model):
    """
    draft_tokens: 小模型猜测的Token序列
    """
    # 大模型验证所有位置
    large_probs = large_model.predict_proba(
        input_ids + draft_tokens  # 一次输入
    )
    
    accepted = []
    for i, draft_token in enumerate(draft_tokens):
        # 大模型在第i个位置的概率分布
        q = large_probs[i]  # 大模型认为每个词的概率
        # 小模型在第i个位置的概率分布  
        p = small_model.probs[i]  # 小模型猜测的概率
        
        # 比较概率
        if q[draft_token] >= p[draft_token]:  # 小模型猜对了
            accepted.append(draft_token)
        else:  # 小模型猜错了
            # 从修正后的分布采样一个新Token
            corrected_prob = softmax(q - p)  # 重新归一化
            new_token = sample(corrected_prob)
            accepted.append(new_token)
            break  # 后续全部重新生成
    
    return accepted

为什么有效?

关键假设

验证结果 概率
0个对 5%
1个对 30%
2个对 40%
3个对 20%
4个对 4%
5个对 1%

平均接受:约3个 ✓
加速比:3倍!(假设小模型"猜对"的概率 ≈ 70%)

理论加速比

公式:加速比 ≈ N / (1 + (N - E))

其中:
- N = 小模型一次猜的Token数(Draft数)
- E = 期望接受的Token数

例如:
- N=5, E=3 → 加速比 = 5 / (1 + 2) ≈ 1.67倍
- N=10, E=7 → 加速比 = 10 / (1 + 3) ≈ 2.5倍
- N=20, E=15 → 加速比 = 20 / (1 + 5) ≈ 3.3倍

2026年最新演进

1. EAGLE系列(自回归投机解码)

版本 时间 特点
EAGLE-1 早期 首个实用的自回归投机解码
EAGLE-2 2025年 接受率提升至85%+
EAGLE-3 2026年 稳定版发布

EAGLE的核心改进:

  • 顶层Token预测:只猜最后一个位置
  • 层次化验证:从粗到细
  • 多假设分支:同时保留多个可能的路径
  • 自适应Draft长度:动态调整猜测数量

2. LongSpec(长上下文投机解码)

2026年针对长上下文场景的新方案:

问题:长上下文下,大模型验证也变慢

传统投机解码:
- 小模型猜5个 → 大模型验证
- 但大模型在长上下文下也很慢

LongSpec方案:
- 分段验证:只验证最近N个Token的注意力
- 历史用近似:远处的Token用压缩表示
- 结果:长上下文也能保持2-3倍加速

3. Medusa(多Token同时预测)

对比 方式
传统 1个 → 1个 → 1个 → 1个 → 1个
Medusa 1个 → [3个并行预测] → [接受/拒绝]

每个"头"预测一个位置:

  • Head 1:预测第1个Token
  • Head 2:预测第2个Token
  • Head 3:预测第3个Token

优势:不需要额外的小模型!


代码实战

基础实现(概念版)

import torch
import torch.nn.functional as F

def speculative_decode(
    large_model,
    small_model,
    input_ids,
    max_draft=5,
    temperature=0.0
):
    """
    投机解码的简化实现
    """
    outputs = []
    draft_cache = []  # 小模型猜测的Token
    
    while len(outputs) < max_new_tokens:
        # 第一步:小模型"投机"
        with torch.no_grad():
            small_logits = small_model(input_ids + draft_cache)
            small_probs = F.softmax(small_logits / temperature, dim=-1)
            
            # 采样N个Token
            draft_tokens = torch.multinomial(
                small_probs[-1], max_draft
            ).tolist()
        
        # 第二步:大模型验证
        large_logits = large_model(input_ids + outputs + draft_tokens)
        large_probs = F.softmax(large_logits / temperature, dim=-1)
        
        # 第三步:接受/拒绝
        accepted = []
        for i, draft_tok in enumerate(draft_tokens):
            q = large_probs[i]  # 大模型概率
            p = small_probs[i] if i < len(small_probs) else None
            
            if p is not None:
                # 使用概率阈值或接受准则
                accept_prob = min(1.0, q[draft_tok].item() / max(p[draft_tok].item(), 1e-10))
            else:
                accept_prob = q[draft_tok].item()
            
            if torch.rand(1).item() < accept_prob:
                accepted.append(draft_tok)
            else:
                # 拒绝:从大模型分布采样
                new_tok = torch.multinomial(q, 1).item()
                accepted.append(new_tok)
                break  # 后续全部重新生成
        
        outputs.extend(accepted)
        
        if len(accepted) < len(draft_tokens):
            # 被拒绝,需要更新draft_cache
            draft_cache = []
        else:
            draft_cache = []
    
    return outputs

vLLM中使用投机解码

from vllm import LLM, SamplingParams

# vLLM自动启用投机解码
llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    speculative_model="meta-llama/Llama-3.1-8B-Instruct",  # 用同模型
    num_speculative_tokens=5,  # 一次猜5个
    tensor_parallel_size=1,
)

sampling_params = SamplingParams(temperature=0)
outputs = llm.generate(prompts, sampling_params)

适用场景与限制

适用场景

场景 效果 说明
流式输出 ⭐⭐⭐⭐⭐ 接受即输出,响应更快
长文本生成 ⭐⭐⭐⭐ 加速比稳定
批量推理 ⭐⭐⭐ 有额外开销
短对话 ⭐⭐ 开销可能大于收益

不适用场景

场景 原因
小Batch 小模型推理有固定开销
低延迟要求极高 投机解码有额外延迟波动
资源受限 需要同时加载两个模型

常见问题

Q1:投机解码会影响输出质量吗?

不会。投机解码的输出与大模型自回归生成完全一致

数学保证:
P(接受第i个Token) = min(1, P_大模型(该Token))

→ 被接受的Token分布 = 大模型原始分布
→ 输出质量 = 大模型质量

Q2:小模型和大模型差距多大合适?

不是越大越好,也不是越小越好

差距 问题
太大(小模型太弱) 猜对率低,加速比下降
太小(小模型太强) 两个模型太接近,浪费资源

最佳差距

  • 参数量比:10:1(7B猜,70B验)
  • 能力差距:BLEU差5-15%最合适

Q3:GPU资源不够怎么办?

# 方案1:使用量化小模型
small_model = AutoModelForCausalLM.from_pretrained(
    "tinyllama-1b",
    load_in_4bit=True  # 4位量化,大幅降低显存
)

# 方案2:使用CPU小模型
small_model = AutoModelForCausalLM.from_pretrained("gpt2")  # 124M

# 方案3:使用投机解码的蒸馏版本(Medusa)
# 不需要额外小模型

性能对比

测试环境:LLaMA-2-70B on A100 80GB

方案 吞吐量 提升
自回归解码 45 tokens/s 基准
投机解码(EAGLE-2) 120 tokens/s ↑ 167%
vLLM + 投机解码 150 tokens/s ↑ 233%
延迟对比(生成1000 tokens) 时间
自回归解码 22秒
投机解码 8秒

总结

Speculative Decoding的核心智慧:

  • 核心洞察:验证比生成快(10倍差距)
  • 方法:小模型猜,大模型验
  • 效果:2-3倍加速,保证输出质量
  • 本质:用并行换串行,用小错换大效

2026年进化:

  • EAGLE系列:接受率突破85%+
  • LongSpec:长上下文支持
  • Medusa:无需额外小模型
  • 自适应:动态调整猜测策略

"投机"不丢人,省力才是真聪明!


延伸阅读

相关文章 说明
W19 KV Cache 另一个加速推理的技术
W13 Transformer 了解自注意力机制
W07 上下文窗口 与长上下文的关系

本文收录于「AI词汇专栏」
相关阅读:W19 KV Cache · W07 上下文窗口

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐