【infra之路】12-投机解码、量化与推理引擎对比
学习目标
掌握三种推理加速手段——投机解码(加速 Decode)、量化(减少计算量和显存)、以及主流推理引擎的架构差异和适用场景选择。这是推理系统阶段的最后一课,把前面所学的 Prefill/Decode、PagedAttention、Continuous Batching 全部串起来。
第一部分:投机解码(Speculative Decoding)
1.1 Decode 阶段的瓶颈
Decode 阶段每生成 1 个 token 都要做一次完整的前向传播,而每次前向传播的计算量其实很小(只有 1 个 token 的矩阵乘法),GPU 的计算单元远远没有被填满。瓶颈在于显存带宽——需要读取整个模型的权重和 KV Cache,但只做很少的计算。
LLaMA-7B Decode 一次前向传播: 需要读取: 模型权重 ~14 GB + KV Cache(随序列增长) 实际计算: 1 个 token 的矩阵乘法 ≈ 几十 GFLOPS A100 的计算能力: 312 TFLOPS 计算单元利用率: < 1% 这就是 memory-bound 的含义:GPU 大部分时间在等数据从显存搬到计算单元1.2 投机解码的核心思想
既然一次前向传播只生成 1 个 token 太慢,能不能一次生成多个 token 候选,然后批量验证?
朴素 Decode: 步骤1: 前向传播 → 生成 token_1 步骤2: 前向传播 → 生成 token_2 步骤3: 前向传播 → 生成 token_3 步骤4: 前向传播 → 生成 token_4 步骤5: 前向传播 → 生成 token_5 共 5 次前向传播,生成 5 个 token 投机解码: 步骤1: 用小模型(draft model)快速生成 5 个候选 token 步骤2: 用大模型(target model)一次性验证这 5 个 token 结果: 接受其中 3 个正确的,共 2 次前向传播,生成 3-4 个 token1.3 Draft Model(草稿模型)
投机解码需要一个小而快的 draft model,它和 target model 共享同一个词表(tokenizer),但参数量小得多:
Target model: LLaMA-70B(大、慢、准确) Draft model: LLaMA-7B(小、快、大致准确) 或者: Target model: LLaMA-7B Draft model: LLaMA-68M(极小、极快)Draft model 不需要完美,只要和 target model 的输出分布有一定重合就行。
1.4 验证机制
大模型验证小模型生成的 token 序列,不是简单地"对或错",而是用概率对比来保证输出分布和直接自回归生成完全一致:
小模型生成: ["the", "cat", "sat", "on"] 大模型验证: 位置1: 大模型 P("the") vs 小模型 P("the") 如果大模型概率 ≥ 小模型概率 → 一定接受 如果大模型概率 < 小模型概率 → 以 P_target/P_draft 的概率接受 位置2: 如果位置1被接受,继续验证 "cat" 位置3: 如果位置2被接受,继续验证 "sat" ... 遇到第一个被拒绝的位置 → 重新采样,停止 算法(伪代码): for t in range(K): # K 是投机长度 r = random_uniform(0, 1) if r < min(1, P_target(x_t) / P_draft(x_t)): accept x_t else: reject x_t resample from (P_target - P_draft) 的归一化差值分布 break数学保证:经过这个验证机制,最终输出的 token 序列和直接用大模型自回归生成的分布完全一致。不会牺牲任何精度。
1.5 加速比分析
假设: K = 5(每次投机 5 个 token) draft model 速度 = target model 的 10 倍 接受率 α = 0.7(70% 的 token 被接受) 朴素 Decode 生成 N 个 token: 时间 = N × T_target 投机解码生成 N 个 token: 每次投机: K 次 draft 前向 + 1 次 target 前向(批量验证) 每次接受的 token 数: K × α + 1(最后重新采样的也算 1 个) = 5 × 0.7 + 1 = 4.5 个 token 时间 = K × T_draft + T_target = 5 × 0.1T + 1T = 1.5T 每 token 平均时间: 1.5T / 4.5 = 0.33T 加速比: T / 0.33T ≈ 3 倍实际加速比通常在1.5-3 倍,取决于:
- Draft model 和 target model 的分布重合度(越高接受率越高)
- Draft model 相对速度(越小越快)
- 投机长度 K(太长接受率下降,太短收益小)
1.6 变体:Self-Speculative Decoding
不需要额外的 draft model,用 target model 自身的早期退出层或跳层方式做 draft:
Draft: 只用 target model 的前 8 层 + 一个轻量 LM Head 快速生成候选 Verify: 用完整的 32 层 target model 验证 好处: 不需要额外加载一个模型,节省显存第二部分:模型量化(Quantization)
2.1 为什么要量化?
LLM 推理的两个瓶颈都可以用量化缓解:
显存瓶颈: 模型权重太大,放不下 FP16: 7B × 2 bytes = 14 GB INT8: 7B × 1 byte = 7 GB INT4: 7B × 0.5 byte = 3.5 GB 计算瓶颈: 矩阵乘法太大,算得慢 FP16 × FP16 乘法 → FP16 累加 INT8 × INT8 乘法 → INT32 累加(GPU 有专用 INT8 计算单元,速度快 2 倍)2.2 量化的基本概念
量化就是把高精度的浮点数映射到低精度的整数:
FP16 → INT8(对称量化): 原始值范围: [-max, max] 映射到: [-127, 127] scale = max / 127 quantize(x) = round(x / scale) # FP16 → INT8 dequantize(q) = q × scale # INT8 → FP16(近似还原) 精度损失: 每个值的误差 ≤ scale/2 如果 max = 1.0,scale ≈ 0.0078,误差 ≤ 0.00392.3 权重量化 vs 激活量化
命名规则: W{weight精度}A{activation精度} W16A16: 权重 FP16,激活 FP16(无量化) W8A16: 权重 INT8,激活 FP16(只量化权重) W8A8: 权重 INT8,激活 INT8(两者都量化) W4A16: 权重 INT4,激活 FP16W8A16(只量化权重)——最常见的方案,因为权重是静态的,可以离线量化,精度损失很小。推理时权重从 INT8 反量化回 FP16 再做计算。减少显存占用但不加速计算。
W8A8(权重和激活都量化)——权重和激活都保持 INT8 格式,矩阵乘法直接用 INT8 Tensor Core 计算,速度翻倍。但激活是动态变化的,需要在线量化(运行时动态计算 scale),更难保持精度。
2.4 主流量化方法
SmoothQuant(W8A8)
核心问题:激活值中有少量outlier(异常大的值),直接量化会把大部分正常值的精度压得很低。
激活值分布: 正常值: [-0.1, 0.1](99% 的值) outlier: [5.0](1% 的值) 直接 INT8 量化: scale = 5.0 / 127 ≈ 0.039 正常值 0.05 → round(0.05/0.039) = 1 → 反量化 = 0.039(误差 22%) 精度损失巨大!SmoothQuant 的做法:把激活的 outlier "迁移"到权重上:
Y = X · W 引入平滑因子 s: Y = (X · diag(s)^{-1}) · (diag(s) · W) = X_smooth · W_smooth s_j = max(|X_j|)^α / max(|W_j|)^{1-α} # α 通常 0.5 效果: X_smooth 的 outlier 被缩小了 → 量化更精确 W_smooth 的 outlier 增加了 → 但权重是静态的,可以离线处理GPTQ(W4A16 / W8A16)
基于Optimal Brain Quantization框架的权重量化:
逐列量化权重矩阵: 1. 量化第 j 列 2. 计算量化误差 3. 用 Hessian 信息把误差补偿到后面的未量化列上 4. 继续量化第 j+1 列 效果: 每个量化步骤的误差被后续列"吸收",总体误差最小化GPTQ 是离线完成的,量化一次后保存 INT4/INT8 权重文件,推理时反量化回 FP16 计算。
AWQ(W4A16)
核心观察:不是所有权重都同等重要。显著性通道(salient channels)的权重对模型质量影响很大,不能粗暴量化。
权重矩阵 W 的每一列对应一个"通道" 某些通道(对应激活值大的方向)特别重要 AWQ 的做法: 1. 找到显著性通道(通过激活值的统计) 2. 对显著性通道乘以一个大的缩放因子 3. 量化(显著性通道的值更大,量化误差相对更小) 4. 推理时反量化后再除以缩放因子AWQ 比 GPTQ 更简单、更快,而且在 4-bit 量化下精度更好。
2.5 量化精度对比
| 方法 | 精度 | 速度提升 | 显存节省 | 适用场景 |
|---|---|---|---|---|
| FP16(基准) | 100% | 1× | 0% | 精度敏感场景 |
| W8A16 (GPTQ) | ~99.5% | 1× | 50% | 显存受限 |
| W4A16 (AWQ) | ~98% | 1× | 75% | 消费级 GPU |
| W8A8 (SmoothQuant) | ~99% | 1.5-2× | 50% | 高吞吐部署 |
| FP8 (H100 原生) | ~99.5% | 1.5-2× | 50% | H100 集群 |
2.6 KV Cache 量化
除了模型权重,KV Cache 也可以量化来节省显存:
FP16 KV Cache: 每 token 每层 16 KB INT8 KV Cache: 每 token 每层 8 KB(减少 50%) vLLM 配置: kv_cache_dtype="auto" # FP16 kv_cache_dtype="fp8" # FP8(H100 支持) kv_cache_dtype="int8" # INT8KV Cache 量化的精度损失通常很小,因为 K 和 V 的分布相对稳定。
第三部分:主流推理引擎对比
3.1 vLLM
架构特点: - PagedAttention 的提出者 - Continuous Batching - 支持 Tensor Parallel / Pipeline Parallel - 支持多种量化格式(GPTQ, AWQ, FP8) - Python 为主的代码架构 核心优势: - 显存利用率高(PagedAttention) - 社区活跃,生态丰富 - 易用性好,API 兼容 OpenAI 格式 - 支持模型种类多 不足: - Python 调度器有一定开销 - 前缀缓存能力不如 SGLangvLLM 的使用:
fromvllmimportLLM,SamplingParams llm=LLM(model="meta-llama/Llama-2-7b-hf",tensor_parallel_size=1,gpu_memory_utilization=0.9,max_model_len=4096,quantization="awq",# 可选enable_chunked_prefill=True,# 可选)params=SamplingParams(temperature=0.7,top_p=0.9,max_tokens=512)outputs=llm.generate(["请解释什么是 AI Infra"],params)3.2 SGLang
架构特点: - RadixAttention:高效前缀缓存 - 结构化生成(JSON schema、正则约束) - 数据并行推理(多实例负载均衡) - Zero-overhead scheduling 核心优势: - 共享前缀场景吞吐高 2-5 倍(RadixAttention) - 结构化生成速度快、准确 - 多轮对话场景表现好 - 支持 data parallelism(多 GPU 各跑一个实例) 不足: - 社区生态比 vLLM 稍小 - 学习曲线略高SGLang 的使用:
importsglangassgl# 启动服务# python -m sglang.launch_server --model meta-llama/Llama-2-7b-hf --port 30000@sgl.functiondefqa(s,question):s+=sgl.system("你是一个AI助手")s+="用户: "+question+"\n"s+="助手: "+sgl.gen("answer",max_tokens=512)state=qa.run(question="什么是PagedAttention?")print(state["answer"])3.3 TensorRT-LLM
架构特点: - NVIDIA 官方出品,深度 CUDA 优化 - 编译式推理:模型先编译成优化后的 engine - In-flight batching(类似 Continuous Batching) - 深度 kernel fusion(注意力、LayerNorm、量化 kernel) 核心优势: - 单 GPU 延迟最低(kernel 级优化) - 支持 NVIDIA 全系列 GPU(T4 → H100) - FP8/INT4 量化支持最完善 - 和 Triton Inference Server 深度集成 不足: - 需要编译 engine,部署流程复杂 - 模型更新需要重新编译 - 主要面向 NVIDIA GPU,不支持其他硬件 - 文档不如 vLLM 友好TensorRT-LLM 的使用:
# 1. 转换模型为 TensorRT-LLM 格式python convert_checkpoint.py--model_dir/path/to/llama-7b\--output_dir./llama-7b-trt--dtypefloat16# 2. 编译 enginetrtllm-build--checkpoint_dir./llama-7b-trt\--output_dir./llama-7b-engine\--max_batch_size32--max_input_len4096# 3. 推理python run.py--engine_dir./llama-7b-engine\--input_text"什么是AI Infra?"3.4 三者对比总结
| 维度 | vLLM | SGLang | TensorRT-LLM |
|---|---|---|---|
| 核心技术 | PagedAttention | RadixAttention | Kernel Fusion |
| 显存管理 | PagedAttention(Block) | Radix Tree + Paged | 预分配 + In-flight |
| 前缀缓存 | 基础支持 | 强(RadixAttention) | 基础支持 |
| 结构化生成 | 不支持 | 强(JSON/Regex) | 不支持 |
| 延迟 | 中等 | 中等 | 最低 |
| 吞吐量 | 高 | 高(共享前缀场景更高) | 高 |
| 易用性 | 高 | 中 | 低 |
| 社区活跃度 | 最高 | 高 | 中 |
| 适用 GPU | NVIDIA + AMD | NVIDIA | NVIDIA only |
| 部署复杂度 | 低 | 低 | 高(需编译) |
3.5 选型建议
场景 1: 通用部署,追求易用性 → vLLM 一行命令启动,OpenAI 兼容 API,社区文档丰富 场景 2: 共享前缀多(system prompt、few-shot) → SGLang RadixAttention 在这种场景下吞吐优势明显 场景 3: 结构化输出(JSON schema、function calling) → SGLang 内置结构化生成支持,速度快且准确 场景 4: 追求极致延迟,NVIDIA GPU → TensorRT-LLM Kernel 级优化,单请求延迟最低 场景 5: 多模型服务、需要负载均衡 → SGLang(data parallelism)或 vLLM + 外部 LB 场景 6: 企业级生产部署 → TensorRT-LLM + Triton Inference Server 完善的监控、版本管理、模型热更新第四部分:推理系统全景图
把整个推理系统的知识串起来:
用户请求到达 │ ▼ ┌──────────────────────────────────────────────────────────┐ │ 调度器(Scheduler) │ │ - 等待队列管理 │ │ - Continuous Batching(iteration-level 调度) │ │ - 优先级 + 抢占(Preemption) │ │ - Chunked Prefill(避免长 prompt 阻塞 Decode) │ └────────────────────────┬─────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────┐ │ 显存管理(Block Manager) │ │ - PagedAttention(虚拟内存 + Block Table) │ │ - RadixAttention(前缀缓存 + Radix Tree) │ │ - KV Cache 量化(FP16 → INT8/FP8) │ │ - 抢占时 Swap/Recompute │ └────────────────────────┬─────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────┐ │ 模型计算(Model Execution) │ │ - Prefill: 一次处理整个 prompt(compute-bound) │ │ - Decode: 逐 token 生成(memory-bound) │ │ - 量化: W8A8 / W4A16 / FP8(减少计算和显存) │ │ - 投机解码: draft model 生成 + target model 验证 │ │ - Kernel 优化: FlashAttention / FlashDecoding / Fusion │ └────────────────────────┬─────────────────────────────────┘ │ ▼ 生成结果返回面试高频问题
Q1: 投机解码会牺牲精度吗?
不会。验证机制在数学上保证了输出分布和直接自回归生成完全一致。只是通过"提前猜测+批量验证"的方式,把多次小前向传播合并成少数大前向传播。
Q2: 什么时候量化会导致明显的质量下降?
通常 8-bit 量化(W8A8/W8A16)质量损失很小(<1%)。4-bit 量化(W4A16)在小模型(<7B)上可能有感知差异,大模型(70B+)几乎无感。2-bit 及以下会明显退化。量化后建议跑 benchmark(如 MMLU、HumanEval)验证。
Q3: vLLM 和 SGLang 能同时用吗?
不能直接在同一个进程中使用,但可以部署多个实例做负载均衡。SGLang 本身支持 data parallelism——多个 SGLang 实例各跑一个模型副本,共享请求队列。
Q4: TensorRT-LLM 为什么延迟最低?
三层优化:1) Kernel fusion——把多个小 kernel(LayerNorm、Softmax、Residual Add)融合成一个大 kernel,减少 kernel launch 和显存读写;2) 编译优化——静态图编译,消除 Python 调度开销;3) 深度量化——FP8/INT4 直接用 Tensor Core 计算,硬件级加速。
总结
| 技术 | 解决的问题 | 核心机制 | 加速比 |
|---|---|---|---|
| 投机解码 | Decode 阶段 GPU 利用率低 | 小模型快速生成 + 大模型批量验证 | 1.5-3× |
| W8A8 量化 | 计算慢 | INT8 Tensor Core 加速矩阵乘法 | 1.5-2× |
| W4A16 量化 | 显存大 | 权重压缩到 4-bit,推理时反量化 | 显存节省 75% |
| KV Cache 量化 | KV Cache 显存占用大 | FP16 → INT8/FP8 | 显存节省 50% |
| PagedAttention | 显存碎片化 | 虚拟内存 + Block Table | 吞吐提升 2-4× |
| Continuous Batching | GPU 空闲等待 | 请求级动态调度 | 吞吐提升 3-10× |
| RadixAttention | 重复计算前缀 | 前缀缓存 + Radix Tree | 吞吐提升 2-5× |
