跳到主要内容

L4.2 推理加速机制

三维坐标 layer: L4(推理与 Serving)level: Seniorpillar: 训推框架

上一篇我们把推理引擎的「骨架」(PagedAttention、连续 batching)搭了起来。本篇聚焦让同一块卡多榨出 2–5× 吞吐的四类正交加速机制:用投机采样砍 decode 步数、用 Prefix Caching 砍重复计算、用量化砍显存与带宽、用动态 batching + 准入控制在 SLA 约束下把利用率拉满。它们可叠加,是 Senior 工程师做推理性价比治理的核心工具箱。

学习目标

  • 前置知识:读过 L0.2(Roofline / 算力墙·显存墙,知道为什么 decode 卡在显存带宽)、L1.1(FP8 低精度与 E4M3/E5M2 的硬件根因)、以及本层 L4.1(PagedAttention 与连续 batching 的引擎骨架)。会写基础 Python、用过 HuggingFace transformers 跑过一次推理即可。
  • 学完产出:① 能用「draft–verify + 拒绝采样」一句话讲清投机采样为何是无损加速,并用接受率 α 估算加速比;② 能说清 Prefix Caching 的块级哈希与 Copy-on-Write 如何同时省 prefill 算力和 KV 显存,知道「最稳定的内容放最前面」的工程铁律;③ 能区分 AWQ(激活感知)/GPTQ(二阶误差补偿)/FP8(硬件原生)三条量化路线的量化对象、位宽与硬件依赖,给出选型判据;④ 能用排队论 Wq=ρμ(1ρ)W_q=\frac{\rho}{\mu(1-\rho)} 解释「利用率拉满」与「守住尾延迟」为何天然冲突,并用准入控制 + 优先级分队列设计一套 SLA 守护方案;⑤ 亲手对同一小模型做 AWQ / GPTQ 4bit 量化,量出显存、TPOT 与 perplexity 三项指标的真实兑换比。
  • 阅读姿势:盯住一条主线——推理加速的所有机制,本质都在回答「如何让每次从 HBM 搬权重多干有用功」。投机采样把多步 decode 合成一次 verify、Prefix Caching 让重复前缀只搬一次、量化把每次搬的位宽减半、动态 batching 把一次搬运摊给整批请求;四者正交、可叠加,但都受同一道显存带宽墙约束。

背景与现状

LLM 推理的根本矛盾,是 decode 阶段是「访存密集(memory-bound)」而非「算力密集」:每生成一个 token,都要把整个模型权重从 HBM 搬一遍进 SM,但实际只算一个 token 的 GEMV,算力利用率常常低于 5%。这意味着——GPU 的 FLOPS 大量空转,瓶颈在显存带宽。所有推理加速机制,本质都在回答同一个问题:如何让每次「搬权重」干更多有用功

四大机制对应四条不同的「省」路径:

  • 投机采样(Speculative Decoding):让小模型一次猜多个 token,大模型一次性并行验证——把多次访存密集的 decode 合并成一次算力密集的 verify,省的是 decode 步数
  • Prefix Caching:系统 prompt、few-shot、多轮历史的 KV 完全相同,没必要重算——省的是重复 prefill 计算与显存
  • 量化(Quantization):把 FP16 权重压成 INT8/INT4/FP8,省的是显存占用与访存带宽,让 memory-bound 的 decode 直接变快。
  • 动态 batching + 准入控制:把零散请求拼成大 batch 摊薄权重搬运成本,省的是单请求的边际权重读取,同时用排队论守住 SLA。

业界信号:vLLM 把 Automatic Prefix Caching、Speculative Decoding、FP8/AWQ/GPTQ 量化全部内置为开箱即用开关;TensorRT-LLM 把投机采样与 INT4-AWQ 作为核心卖点;DeepSeek、Qwen 等模型发布时直接附带官方 AWQ/GPTQ/FP8 权重。这说明加速机制已从「论文 trick」沉淀为推理框架的标准配置层,Senior 工程师的价值在于「在给定 SLA 与精度预算下,组合并调参这些开关」。

原理与架构

2.1 投机采样:draft–verify 的并行化魔法

投机采样的核心洞察:验证 K 个 token 和验证 1 个 token,对大模型而言访存成本几乎相同(都只搬一遍权重),但前者把 K 步串行 decode 压成了 1 步并行 forward。

关键数学保证:投机采样通过拒绝采样(rejection sampling) 确保输出分布与「纯大模型采样」严格相同——它是无损加速,不改变模型行为。设 draft 模型在每个位置的接受率为 α,每轮草稿长度 K,则期望每次大模型 forward 产出的 token 数为:

E[tokens]=1αK+11αE[\text{tokens}] = \frac{1 - \alpha^{K+1}}{1 - \alpha}

  • 加速比直接由接受率 α 决定:α 越高(draft 与 target 分布越接近),加速越大。实践中 α 常在 0.6–0.8,对应 2–3× 的 wall-clock 加速。
  • draft 模型选型权衡:用更大的 draft 模型 → α 更高但 draft 本身更慢;用 EAGLE/Medusa 这类「自投机」(额外预测头复用 target 隐状态)→ 几乎零额外模型成本。这是工程上的核心调参点。
投机方案draft 来源优点代价
独立 draft 模型同系列小模型(如 7B verify + 1B draft)实现直接多一个模型占显存
Medusatarget 上挂多个并行预测头无独立模型需训练 head,α 偏低
EAGLE / EAGLE-2复用 target 特征层做自回归 draftα 高、显存省实现复杂
n-gram / 提示查找从 prompt 里查重复片段零模型成本仅在 RAG/长上下文重复场景有效

2.2 全链路 Prefix Caching:让相同前缀只算一次

多轮对话、固定系统 prompt、few-shot 模板的共同特征是:大量请求共享一段完全相同的前缀。Prefix Caching 把这段前缀的 KV Cache 计算并缓存,后续请求命中即跳过 prefill。

工程要点:

  • 块级哈希 + Copy-on-Write:vLLM 的 Automatic Prefix Caching 以 PagedAttention 的 block(如 16 token)为粒度对前缀内容做哈希,相同哈希链共享同一物理块;某请求要在共享块上续写时,触发 CoW 复制,避免污染他人缓存。
  • 显存复用 = 算力复用:命中前缀既省了 prefill 算力(首 token 延迟 TTFT 大降),又通过物理块共享省了 KV 显存(同一份系统 prompt 不再为每个请求各存一份)。
  • 全链路扩展:长上下文场景下 KV 体积超过 HBM 时,可做 KV offload(HBM→DRAM→NVMe 分层)与跨实例的 prefix 缓存共享(如 LMCache),把「会话级复用」升级为「集群级复用」。
  • 失效与淘汰:缓存按 LRU 淘汰;前缀一旦有一个 token 不同,后续块全部 miss——这要求把最稳定的内容(系统 prompt)放在最前面

2.3 量化:用精度换显存与带宽

decode 是 memory-bound 的,把权重位宽减半,访存量减半,decode 近乎线性加速,同时显存占用对应下降(7B FP16 ≈ 14GB → INT4 ≈ 4GB)。代价是数值精度损失,核心难点是如何在低位宽下保住精度

四类量化的本质区别:

方案量化对象核心思想位宽硬件依赖适用
INT8(W8A8)权重+激活对称/非对称线性量化,per-tensor/per-channel8bit通用服务端通吃、精度损失小
AWQ仅权重(W4A16)激活感知:靠激活幅度找出 ~1% 显著权重通道并缩放保护4bit推理快、校准轻显存吃紧的 decode 主导场景
GPTQ仅权重(W4A16)二阶误差补偿:用 Hessian 逐列量化并把误差转移到后续权重3/4bit校准较重极致压缩、精度可控
FP8权重+激活浮点低精度,保留指数位动态范围8bit需 Hopper/Ada近无损、训推统一

需要强调的是:AWQ 与 GPTQ 都是 W4A16(4bit 权重、16bit 激活),区别在「怎么挑要保护的权重」——AWQ 看激活分布(前向信息),GPTQ 看 Hessian(二阶曲率信息)。FP8 则是另一条路:不压位宽到 4bit,而是用硬件原生浮点格式,在 H100 上既快又几乎无损,是数据中心新主流。

2.4 动态 batching、排队模型与 SLA

加速机制再强,也要在服务质量约束下运转。推理服务的两个核心 SLA 指标:

  • TTFT(Time To First Token):受 prefill 与排队等待主导。
  • TPOT / ITL(Time Per Output Token / inter-token latency):受 decode 阶段 batch 大小主导。

排队论视角的工程结论:

  • 利用率与尾延迟是死对头:把 GPU 利用率推到 ~100%(M/M/1 排队下 ρ→1),平均等待时间 Wq=ρμ(1ρ)W_q = \frac{\rho}{\mu(1-\rho)}指数级爆炸。所以 SLA 服务必须预留安全裕量,目标利用率常设在 70–85%。
  • 准入控制(admission control) 是守护尾延迟的闸门:在请求进入队列前预估排队时延,超 SLA 即直接 429 限流或降级到小模型,宁可拒绝也不让所有请求一起劣化
  • batch 越大吞吐越高、但 TPOT 越长:动态 batching 需在「吞吐 vs 单请求时延」间权衡,可按优先级分队列——交互式请求小 batch 保 TPOT,离线批量请求大 batch 冲吞吐。
  • 抢占与公平性:KV 显存不足时调度器会抢占低优请求(swap 到 DRAM 或直接 recompute),需配合优先级与防饿死策略。

动手实践:AWQ vs GPTQ 量化对比

实验目标:对同一个小 HF 模型分别做 AWQGPTQ 4bit 量化,量化前后对比三项指标——显存占用、单 token 时延、perplexity(精度损失),亲手感受「精度换性能」的兑换比。无 GPU 时走 llama.cpp GGUF Q4 在 CPU 上对比。

3.1 环境准备(NVIDIA GPU 路径)

# 推荐 Python 3.11;用 uv 或 venv 隔离
python3 -m venv .venv && source .venv/bin/activate

# 量化与推理依赖(需 CUDA 环境)
pip install torch --index-url https://download.pytorch.org/whl/cu121
pip install transformers accelerate datasets
pip install autoawq gptqmodel optimum # AWQ + GPTQ(GPTQ 走 gptqmodel,勿装 auto-gptq)

# 选一个小模型便于快速跑通,如 Qwen2.5-0.5B / TinyLlama-1.1B
export MODEL_ID="Qwen/Qwen2.5-0.5B-Instruct"

3.2 代码:AWQ 量化 + 显存/时延测量

# awq_quant.py —— 对小模型做 AWQ INT4 量化并测显存/时延
import time, torch
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_id = "Qwen/Qwen2.5-0.5B-Instruct"
quant_path = "qwen0.5b-awq"

tok = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoAWQForCausalLM.from_pretrained(model_id, trust_remote_code=True)

# AWQ 量化配置:4bit、group=128、GEMM kernel
quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}
model.quantize(tok, quant_config=quant_config) # 用内置校准集做激活感知量化
model.save_quantized(quant_path)
tok.save_pretrained(quant_path)
print(f"[AWQ] 量化完成 → {quant_path}")
# bench.py —— 通用基准:显存峰值 + 平均 token 时延
import time, torch
from transformers import AutoModelForCausalLM, AutoTokenizer

def benchmark(path, label, n_tokens=128):
tok = AutoTokenizer.from_pretrained(path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
path, torch_dtype="auto", device_map="cuda", trust_remote_code=True)
torch.cuda.reset_peak_memory_stats()
prompt = "用三句话解释什么是投机采样。"
ids = tok(prompt, return_tensors="pt").to("cuda")

# 预热
_ = model.generate(**ids, max_new_tokens=8)
torch.cuda.synchronize()

t0 = time.perf_counter()
out = model.generate(**ids, max_new_tokens=n_tokens, do_sample=False)
torch.cuda.synchronize()
dt = time.perf_counter() - t0

gen = out.shape[-1] - ids["input_ids"].shape[-1]
mem = torch.cuda.max_memory_allocated() / 1e9
print(f"[{label}] 显存峰值={mem:.2f}GB 生成{gen}token "
f"TPOT={dt/gen*1000:.1f}ms/token 吞吐={gen/dt:.1f}tok/s")

if __name__ == "__main__":
benchmark("Qwen/Qwen2.5-0.5B-Instruct", "FP16 baseline")
benchmark("qwen0.5b-awq", "AWQ-INT4")
# benchmark("qwen0.5b-gptq", "GPTQ-INT4") # GPTQ 量化产物

3.3 代码:GPTQ 量化 + perplexity 评估

# gptq_quant.py —— 对同一模型做 GPTQ INT4 量化(backend=gptqmodel)
from transformers import AutoTokenizer, AutoModelForCausalLM
from optimum.gptq import GPTQQuantizer
import torch

model_id = "Qwen/Qwen2.5-0.5B-Instruct"
out_dir = "qwen0.5b-gptq"
tok = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True)

quantizer = GPTQQuantizer(bits=4, dataset="wikitext2", group_size=128, desc_act=False)
qmodel = quantizer.quantize_model(model, tok)
qmodel.save_pretrained(out_dir)
tok.save_pretrained(out_dir)
print(f"[GPTQ] 量化完成 → {out_dir}")
# ppl.py —— 在 wikitext 上测 perplexity,量化前后对比精度损失
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset

def perplexity(path, label):
tok = AutoTokenizer.from_pretrained(path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
path, torch_dtype="auto", device_map="cuda", trust_remote_code=True).eval()
data = load_dataset("Salesforce/wikitext", "wikitext-2-raw-v1", split="test")
text = "\n\n".join(data["text"])[:8000]
ids = tok(text, return_tensors="pt").input_ids.to("cuda")[:, :2048]
with torch.no_grad():
loss = model(ids, labels=ids).loss
print(f"[{label}] perplexity = {torch.exp(loss).item():.3f}")

if __name__ == "__main__":
perplexity("Qwen/Qwen2.5-0.5B-Instruct", "FP16")
perplexity("qwen0.5b-awq", "AWQ-INT4")
perplexity("qwen0.5b-gptq", "GPTQ-INT4")

运行后填入你的实测数据,得到类似下表(数值随模型/硬件变化,仅示意趋势):

配置显存峰值TPOT吞吐Perplexity
FP16 baseline1.00 GB23.7 ms/token42.2 tok/s10.14
AWQ-INT40.48 GB(↓52%)28.1 ms/token35.6 tok/s11.76(↑16%)
GPTQ-INT40.47 GB(↓53%)31.0 ms/token32.2 tok/s10.90(↑7%)

本机实测日志摘录(RTX 3060 6GB / torch 2.11.0+cu130 / Qwen2.5-0.5B-Instruct)

[FP16 baseline] 显存峰值=1.00GB 生成106token TPOT=23.7ms/token 吞吐=42.2tok/s
[AWQ-INT4] 显存峰值=0.48GB 生成128token TPOT=28.1ms/token 吞吐=35.6tok/s
[GPTQ-INT4] 显存峰值=0.47GB 生成128token TPOT=31.0ms/token 吞吐=32.2tok/s
[FP16] perplexity = 10.144
[AWQ-INT4] perplexity = 11.759
[GPTQ-INT4] perplexity = 10.903

环境提示:AWQ / GPTQ 建议用 Python 3.12 虚拟环境(uv venv --python /usr/bin/python3 .venv-sys),并安装 torchvisiongptqmodel(Transformers 5.x 的 GPTQ/AWQ 后端)。不要再装 auto-gptq(已弃用,Python 3.12 下常编译失败)。gptq_quant.py 约需 10 分钟(0.5B 模型 + wikitext2 校准)。一键复现:bash run_quant_lab.sh(ai-infra-labs 仓库)。

3.4 Mac / CPU 替代方案(无 GPU)

无 NVIDIA GPU 时,autoawq/gptqmodel 多数 kernel 无法运行,改用 llama.cpp 的 GGUF Q4 量化在 CPU/Apple Silicon 上完成同样的「量化前后对比」:

# 1) 编译 llama.cpp(Mac 自动用 Metal,纯 CPU 也可)
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
cmake -B build && cmake --build build -j

# 2) 下载 HF 模型并转 GGUF(FP16 基线)
pip install -r requirements.txt
python convert_hf_to_gguf.py /path/to/Qwen2.5-0.5B-Instruct --outfile qwen-f16.gguf

# 3) 量化成 Q4_K_M(4bit)
./build/bin/llama-quantize qwen-f16.gguf qwen-q4.gguf Q4_K_M

# 4) 对比文件大小(≈显存代理)与 perplexity
ls -lh qwen-f16.gguf qwen-q4.gguf
./build/bin/llama-perplexity -m qwen-f16.gguf -f wiki.test.raw # 基线 PPL
./build/bin/llama-perplexity -m qwen-q4.gguf -f wiki.test.raw # Q4 PPL

# 5) 对比单 token 时延(关注 eval time per token)
./build/bin/llama-bench -m qwen-f16.gguf -m qwen-q4.gguf

目的一致:无论 GPU 上的 AWQ/GPTQ 还是 CPU 上的 GGUF Q4,你都在亲手观测同一条铁律——4bit 量化把模型体积/显存砍掉约 60–70%、decode 因访存减半而提速,代价是 perplexity 小幅上升。这就是「精度换性能」的兑换比。

踩坑预警 (Gotchas)

  • 校准集决定 GPTQ/AWQ 质量:量化用的校准数据分布要贴近真实业务(中文模型别只用英文 wikitext 校准),否则精度损失被放大。
  • per-token 时延要预热 + synchronize:和 GEMM profiling 同理,CUDA 异步下发,不 torch.cuda.synchronize() 测出来的时延是假的;首次 generate 含 kernel 编译,必须丢弃。
  • AWQ/GPTQ 的「加速」依赖专用 kernel:量化只是省显存,真正提速靠 marlin/exllama 等反量化融合 kernel;版本不匹配可能不加速甚至更慢。
  • 小模型量化损失被放大:0.5B 这类小模型本身冗余少,4bit 的 PPL 涨幅会比 7B/70B 明显——别用小模型的损失幅度去推断大模型。
  • FP8 必须 Hopper/Ada:在 A100(Ampere)上跑 FP8 会回退或报错,FP8 实验需 H100/L40S。
  • perplexity 不等于下游质量:PPL 只是代理指标,最终要用业务任务(如 MMLU、人工评测)确认量化是否可接受。

深入思考

下面三题每题先给题干,再用 <details> 折叠一份图文并茂的参考答案。建议先合上答案自己想 3 分钟,再展开对照。

思考题 1:机制组合的优先级与叠加增益

你有一个「系统 prompt 很长 + 多轮对话 + decode 主导」的客服场景,GPU 是 A100(无 FP8)。结合 2.1–2.4 节,在投机采样、Prefix Caching、AWQ-INT4、动态 batching 四个开关里,你会先开哪个、它们之间是否有冲突或叠加增益?为什么 Prefix Caching 在这个场景的边际收益可能最高?

展开参考答案(含机制叠加增益图 + 对比表)

结论:先开 Prefix Caching,因为这个场景里「长系统 prompt + 多轮历史」让前缀重复率极高,命中即跳过整段 prefill,是边际收益最高、且与其余三者完全正交的「免费午餐」;接着叠 AWQ-INT4 把 decode 的访存减半,再用动态 batching 摊薄权重搬运,投机采样放最后并要警惕它与大 batch 抢算力。

为什么 Prefix Caching 边际收益最高:客服场景的每个请求都带着「同一段长系统 prompt + 同一段多轮历史」,这正是 2.2 节说的「大量请求共享完全相同前缀」。命中缓存后,这段前缀的 prefill 算力直接归零(TTFT 大降),KV 显存又因物理块共享而不再为每个请求各存一份——一举两得,且不改变任何数值、与其余机制零冲突。

四者的冲突与叠加(对比一遍):

开关省什么与其他机制关系本场景收益
Prefix Caching重复 prefill 算力 + KV 显存完全正交,无副作用最高(前缀重复率极高)
AWQ-INT4decode 访存带宽 + 显存与 Caching 叠加;A100 无 FP8 故选 W4A16高(decode 主导)
动态 batching单请求边际权重读取与量化叠加,但放大 batch 会拉长 TPOT中(受 SLA 约束)
投机采样decode 步数与大 batch 抢算力,收益可能被吃掉视 batch 而定,谨慎

关键冲突点:投机采样的本质是「用空闲算力换 decode 步数」,而大 batch 已经把 verify 的并行算力占满——此时 GPU 不再「闲」,投机采样多算的草稿反而成了浪费。所以在 decode 主导 + 大 batch 的场景,投机采样要么只对小 batch 的交互流量开,要么干脆不开。

思考题 2:SLA 与利用率为何天然冲突

业务要求 TTFT P99 < 500ms、TPOT < 50ms,但运营希望 GPU 利用率拉到 95% 降本。结合 2.4 节排队论 Wq=ρμ(1ρ)W_q=\frac{\rho}{\mu(1-\rho)},解释为什么这两个目标本质冲突;你会用准入控制 + 优先级分队列怎样设计,让交互流量保 SLA、离线流量吃满剩余算力?

展开参考答案(含利用率-尾延迟冲突图 + 算一遍)

结论:排队论里平均等待时间 WqW_q 随利用率 ρ 趋近 1 而指数级爆炸,所以「利用率 95%」与「尾延迟达标」本质冲突;解法不是二选一,而是用准入控制守住一个安全工作点(如 ρ≈0.8),再用优先级分队列让交互流量走低延迟通道、离线流量去「填谷」吃满那留出的 20% 裕量。

Wq=ρμ(1ρ)W_q=\frac{\rho}{\mu(1-\rho)} 算一遍(设单位服务时间 1/μ1/\mu 为基准 1):

  1. ρ = 0.70 → Wq=0.70/(10.70)=2.33W_q = 0.70/(1-0.70) = 2.33 个服务时间。
  2. ρ = 0.80 → Wq=0.80/0.20=4.0W_q = 0.80/0.20 = 4.0
  3. ρ = 0.90 → Wq=0.90/0.10=9.0W_q = 0.90/0.10 = 9.0
  4. ρ = 0.95 → Wq=0.95/0.05=19.0W_q = 0.95/0.05 = 19.0;ρ = 0.99 → Wq=99.0W_q = 99.0

从 0.8 到 0.95,利用率只多榨了 15 个百分点,平均等待却从 4 倍暴涨到 19 倍——而 P99 尾延迟比均值更敏感,会更早击穿 SLA。这就是「95% 利用率」与「TTFT P99 < 500ms」无法同时满足的数学根因。

设计方案(准入控制 + 优先级分队列):

  • 准入控制:在请求入队前预估排队时延,交互请求若预估会超 TTFT P99 阈值,直接 429 限流或降级到小模型——宁可拒绝个别请求,也不让所有交互流量一起劣化
  • 交互队列(高优):小 batch 保 TPOT,调度器优先服务,工作点压在 ρ≈0.8 留出安全裕量。
  • 离线队列(低优·填谷):大 batch 冲吞吐,专门吃掉交互流量留出的那 20% 算力;KV 显存吃紧时优先被抢占(swap 或 recompute),保证交互流量的 SLA 不被它拖垮。

如此一来,「整卡平均利用率」靠离线填谷做到很高,而「交互流量的尾延迟」靠裕量 + 抢占守住——两个目标在分层之后不再正面冲突。

思考题 3:投机采样上线翻车排查

上线投机采样后,吞吐不升反降。结合 2.1 节的接受率 α 与并行 verify 机制,从「接受率 α、draft 模型大小、草稿长度 K、batch 大小」四个维度给出你的分层排查清单——为什么「大 batch + 投机采样」常常互相抵消收益?

展开参考答案(含投机采样翻车排查决策图 + 算一遍)

结论:投机采样是「用空闲算力换 decode 步数」的赌注——只有当 α 够高、draft 够轻、K 适中、且 GPU 还有空闲算力时才赚;一旦 α 太低让草稿大量被拒、或 draft 太重拖慢、或 K 过长放大无效计算、或大 batch 已把算力占满,这笔赌注就由赚转赔,吞吐不升反降。

为什么大 batch 与投机采样互相抵消(算一遍直觉):投机采样赚的是「verify K+1 个位置和 verify 1 个位置访存成本几乎相同」——前提是 GPU 的算力在 decode 时大量空闲(「背景与现状」一节说 decode 算力利用率常低于 5%)。一旦把 batch 拉大,比如 batch=64,这 64 个请求的 decode 已经把 Tensor Core 喂得差不多满,GPU 不再空闲;此时再让大模型一次 verify K+1 个位置,等于把已经吃紧的算力再乘以 K 倍,verify 本身从「近乎免费」变成「实打实的额外算力开销」。若 α 又不够高,被拒的草稿还要重来——多算的全成了浪费,于是吞吐不升反降。

分层排查清单(对应 2.1 节调参点):

  1. 先看 α(接受率):监控 accepted_tokens / proposed_tokens。α < 0.5 说明 draft 与 target 分布偏离太大,换更贴近的 draft 模型或上 EAGLE/Medusa 自投机。
  2. 再看 draft 成本:draft 串行生成 K 个 token 的耗时若接近 verify 省下的时间,net 收益就没了——换更小 draft 或零额外模型成本的自投机头。
  3. 调 K(草稿长度):K 过长则被拒时丢弃的无效计算越多;按 E[tokens]=1αK+11αE[\text{tokens}]=\frac{1-\alpha^{K+1}}{1-\alpha} 找最优 K,α 低时 K 要更短。
  4. 最后看 batch:对已经吃满算力的大 batch 关闭投机,只对小 batch 的交互流量开——这与思考题 1 的结论一致。

延伸阅读

1. 核心 Paper

  • Fast Inference from Transformers via Speculative Decoding(Leviathan et al., 2023)— 投机采样的拒绝采样无损保证原始推导。
  • AWQ: Activation-aware Weight Quantization for On-Device LLM Compression and Acceleration(MLSys 2024)与 GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers(2022)— 两条 W4A16 路线的奠基论文,对照读最能理解差异。
  • EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty(2024)— 自投机的 SOTA,理解为何「复用 target 特征」能拉高 α。

2. 相关高 Star 仓库与源码必读路径

  • vllm-project/vllm — 看 vllm/v1/core/ 下 Prefix Caching 的块哈希实现,vllm/spec_decode/ 投机采样调度。
  • casper-hansen/AutoAWQModelCloud/GPTQModel — 对照看 AWQ / GPTQ 的 quantize 主流程如何挑选/保护权重(Transformers 5.x 官方 GPTQ 后端为 gptqmodel,勿再使用已归档的 AutoGPTQ)。
  • ggml-org/llama.cpp — 看 llama-quantizeggml-quants.c,理解 GGUF 各档(Q4_K_M 等)的分组量化实现。

3. 优质博客 / 视频

  • NVIDIA 技术博客「Mastering LLM Techniques: Inference Optimization」中投机采样与量化章节。
  • vLLM 官方文档的 Speculative Decoding / Automatic Prefix Caching / Quantization 三篇 how-to。
  • MIT 6.5940(TinyML & Efficient Deep Learning)量化与高效推理公开课。

下一篇L4.3 性能、成本与混合部署:把本篇的单点加速机制放到「成本-性能-混合云」的全局视角,讲清如何在多卡多实例间做性价比最优的混合部署。