2026-05-15LLM推理优化GPU并行计算

LLM推理的"投机取巧":推测解码如何榨干GPU算力

当你用 ChatGPT 或 Claude 生成一段文字时,有没有注意到输出是"一个字一个字"蹦出来的?这不是产品设计选择,而是 Transformer 架构的本质约束——**自回归解码(Autoregressive Decoding)** 的串行特性让每一步都在等上一步。

biluo·6889 words

为什么LLM生成这么慢?

当你用 ChatGPT 或 Claude 生成一段文字时,有没有注意到输出是"一个字一个字"蹦出来的?这不是产品设计选择,而是 Transformer 架构的本质约束——自回归解码(Autoregressive Decoding) 的串行特性让每一步都在等上一步。

让我们先理解这个问题有多严重。假设用 A100 GPU 运行 Llama-3-70B:

阶段 操作 耗时占比
Prefill 并行处理 prompt ~5%
Decode 逐 token 生成 **~95%**

Decode 阶段之所以慢,是因为 Transformer 的核心是 Self-Attention:生成第 N 个 token 时,需要attend到前面 N-1 个 token。这意味着:

1. 第 N 步的计算量 ≈ 第 N-1 步的计算量(KV Cache 帮助下)

2. 但每次计算只能产生 1 个 token

3. GPU 的矩阵乘法单元(Tensor Core)严重吃不饱——它们设计用来处理大批量并行运算,现在却被绑在单个 token 上

这就是为什么 A100 跑 70B 模型时,GPU 利用率常常低于 30%

推测解码:用一个便宜模型预测,多个贵模型验证

推测解码(Speculative Decoding)的核心思想来自一个简单观察:

> 与其每次让大模型猜 1 个字,不如让小模型先猜 一串字,然后让大模型 并行验证

数学直觉

假设:

  • 大模型 M_big 生成 1 token 需要时间 T_big
  • 小模型 M_small 生成 1 token 需要时间 T_small,且 T_small ≈ T_big / k(k 通常 5-20x)
  • 小模型预测的接受率为 α(约 70-90%)

传统自回归方式:T_big per token

推测解码

1. 小模型一次生成 γ 个 token:时间 ≈ γ × T_small

2. 大模型并行验证这 γ 个 token:时间 ≈ T_big(矩阵乘法,并行处理)

3. 实际被接受的 token 数 ≈ α × γ

有效每 token 时间 ≈ (γ × T_small + T_big) / (α × γ + 1)

当 T_big >> T_small, α > 0.7, γ = 32-64 时,加速比可达 2-5x

实际算法流程

`

大模型 M_big (目标模型)

小模型 M_small (推测模型,与 M_big 通常同结构)

温度 T > 0 用于生成

# 阶段1:小模型"猜测"

draft_tokens = []

for i in range(gamma):

token = M_small.forward(draft_tokens)

draft_tokens.append(token)

if token == EOS: break

# 阶段2:大模型"验证"(KVCache 复用,并行)

logits_big = M_big.forward_parallel(draft_tokens) # 一次性 forward

# 阶段3:自适应接受

accepted = 0

for i, token in enumerate(draft_tokens):

# 方法1:Greedy - 只看最大概率 token

if i == 0:

accepted_token = argmax(logits_big[i])

else:

accepted_token = argmax(logits_big[i])

if token == accepted_token or random.random() < alpha:

accepted += 1

else:

# 拒绝,从这里重新采样

draft_tokens = draft_tokens[:accepted+1]

draft_tokens.append(sampling_from(logits_big[accepted]))

break

return draft_tokens # accepted + 1 tokens

`

实现细节:为什么你的推测解码跑不起来?

KVCache 的陷阱

大多数推测解码实现会在这里翻车。小模型生成的 token 序列,在大模型眼里是 全新的 token,没有 KVCache 可用。这意味着大模型的验证阶段仍然需要计算 attention——但好消息是,所有 γ 个位置的 attention 可以并行计算,而不是串行。

`python

# 伪代码:大模型并行验证(关键优化)

def verify_large_model(batch_draft_tokens: List[Token], kv_cache: KVCache):

"""

一次性处理所有 draft tokens,利用矩阵并行的优势

不同于自回归的单 token forward,这里是批量矩阵乘法

"""

# 输入形状: [batch_size=gamma, seq_len]

# 输出形状: [batch_size=gamma, vocab_size]

logits = large_model(batch_draft_tokens, kv_cache=kv_cache)

# 注意:这里 large_model 需要支持 packed batch inference

return logits

`

如何选择小模型?

不是随便找个小模型就行。关键要求:

1. 分布对齐:小模型和大模型的预测分布要足够接近

2. draft 长度:γ 越大,加速比越高,但接受率会下降

3. 延迟差距:T_small / T_big 越大,整体收益越高

最佳实践:

  • **同结构不同 size**:如 Llama-3-70B + Llama-3-8B,接受率高
  • **同 size 不同量化**:Q4 大模型 + FP16 小模型
  • **蒸馏模型**:用大模型数据微调过的小模型,接受率可到 95%+

拒绝采样策略

最简单的 Greedy(只接受最大概率 token)效果一般。更好的方法:

方法1:基于温度的接受

`python

def accept_with_temperature(logits_draft, logits_big, temperature=0.8):

# 计算小模型和大模型在 draft token 上的概率比

p_small = softmax(logits_draft / temperature)

p_big = softmax(logits_big)

for i, token in enumerate(draft_tokens):

ratio = p_big[token] / (p_small[token] + 1e-8)

if ratio > 1.0 or random.random() < ratio:

accepted.append(token)

else:

# 拒绝,重新采样

accepted.append(sample_from(p_big))

break

return accepted

`

方法2:树状验证(Tree Verification)

一次验证多个 draft 路径,而不是线性序列。Google 的 Medusa 和 HuggingFace 的 EDSD 都用了这个思路:

`python

# Medusa 风格:多个 draft head 同时预测

class MedusaHead(nn.Module):

def __init__(self, hidden_size, vocab_size, depth=5):

super().__init__()

self.layers = nn.ModuleList([

nn.Sequential(

nn.Linear(hidden_size, hidden_size),

nn.ReLU(),

nn.Linear(hidden_size, vocab_size)

) for _ in range(depth)

])

def forward(self, hidden_states):

# 同时预测 5 个未来位置的 token

return [layer(hidden_states) for layer in self.layers]

`

实战:HuggingFace Speculative Decoding 详解

HuggingFace Transformers 从 4.36 开始支持推测解码:

`python

from transformers import AutoModelForCausalLM, AutoTokenizer

from transformers.generation import SpeculativeDecoding

model_id = "meta-llama/Llama-3.1-70B-Instruct"

small_model_id = "meta-llama/Llama-3.1-8B-Instruct"

tokenizer = AutoTokenizer.from_pretrained(model_id)

big_model = AutoModelForCausalLM.from_pretrained(

model_id,

device_map="auto",

torch_dtype=torch.bfloat16

)

small_model = AutoModelForCausalLM.from_pretrained(

small_model_id,

device_map="auto"

)

# 关键参数

speculative_decoding = SpeculativeDecoding(

main_model=big_model,

speculative_model=small_model,

num_speculative_tokens=32, # γ,越大越省但越挑模型

threshold=0.8, # 接受率阈值

)

prompt = "解释为什么天空是蓝色的,用物理原理说明:"

inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

with SpeculativeDecoding.speculative_decoding_context(speculative_decoding):

outputs = big_model.generate(

**inputs,

max_new_tokens=200,

do_sample=True,

temperature=0.7,

)

result = tokenizer.decode(outputs[0], skip_special_tokens=True)

`

Benchmark 数据(Llama-3-70B + Llama-3-8B,A100 80GB):

方式 tokens/sec 加速比
自回归(70B only) 28 1.0x
推测解码(γ=32, α=0.85) 67 **2.4x**
推测解码(γ=64, α=0.72) 89 **3.2x**

超越推测解码:未来方向

推测解码只是 LLM 推理优化的冰山一角。更激动人心的方向:

1. 投石机解码(Rockpile Decoding)

用 KVCache 预测下一个 token 的位置,直接跳转到那里计算,避免无意义的 attention。

2. 前向验证(Forward Verification)

不仅是预测下一个 token,而是预测下一个 N 个 token 的完整 KV 向量,大模型直接用这些预计算的 KV 进行验证。

3. 混合推测(Hybrid Speculation)

根据内容难度自适应选择:小模型负责简单句子的预测,遇到复杂推理时自动退化为大模型直接生成。

`python

class AdaptiveSpeculativeDecoder:

def __init__(self, big_model, small_models: List):

self.big = big_model

self.smalls = small_models # 多级模型:8B, 3B, 1B

def generate(self, prompt, difficulty_hint=None):

if difficulty_hint == "complex":

return self.big.generate(prompt) # 直接用大模型

small = self.smalls[0] # 默认用最大的小模型

return self.speculative_decode(prompt, small)

`

4. 推测解码 + 量化协同

Q4 量化的大模型 + INT8 的小模型,减少内存带宽压力,进一步放大加速效果。

总结:什么场景适合推测解码?

优点:

  • 显著提升 token 生成吞吐量(2-4x)
  • 不改变模型输出分布(数学上等效)
  • 易于集成到现有推理框架

缺点:

  • 增加了内存占用(小模型也要在显存里)
  • 对小模型质量要求高
  • 不适合流式输出场景(需要等 γ 个 token 才能开始验证)

最佳场景:

  • 批量推理(batch inference)
  • 对延迟要求不高但对吞吐要求高的场景(客服机器人、文案生成)
  • 部署时显存足够放下两个模型

不太适合:

  • 实时交互流式输出(每次等 γ 个 token 才开始吐字)
  • 单个请求延迟敏感场景(首 token 时间不变)

推测解码不是银弹,但它聪明地利用了"小模型预测 + 大模型验证"的范式,在不损失质量的前提下榨干 GPU 并行算力。如果你正在优化 LLM 推理服务,值得把它加入工具箱。

---

*附:主流实现参考*

  • HuggingFace Transformers: `generation.SpeculativeDecoding`
  • vLLM: `--speculative-decoding` flag
  • TensorRT-LLM: `SpeculativeDecodingPlugin`
  • Medusa (多 draft head): https://github.com/FasterDecoding/Medusa