Deep Dive · 推理工程

从 KV Cache 到 Speculative Decoding — 让大模型快起来的每一个技巧

掌握 LLM 推理的核心优化技术,从内存管理到并发策略,成为高效推理工程师

⚡ 基础概念

推理的两个阶段

Prefill 与 Decode — 理解性能瓶颈的起点

LLM 推理分为两个截然不同的阶段,它们的计算特征和优化策略完全不同。

Prefill 阶段(前缀填充): 处理用户输入的完整提示词,计算所有 token 的 attention。这个阶段是 计算密集型 的,GPU 利用率高。

Decode 阶段(生成阶段): 自回归地逐个生成输出 token。每次只生成一个新 token,但需要加载之前所有 token 的 KV Cache,这个阶段是 内存带宽密集型 的。

大多数推理时间耗在 Decode 阶段,而且越长的生成长度,Decode 的比例就越高。

Prefill vs Decode 计算流程
Prefill Stage Compute-Bound 输入 1024 tokens Self-Attention 所有 token 对 KV Cache 保存 计算复杂度: • Attention: O(seq_len²) per layer • seq_len = 1024 → ~10²⁶ ops • 时间: 单次前向传播完成 • 关键: 最大化 GPU 并行度 Decode Stage Memory-Bound Previous KV 1024 tokens New Token x_t Compute seq_len+1 Next Token 计算复杂度: • 每次: 一个 token 的 Attention • O(seq_len) × matrix mult • 时间: 重复 N 次(生成 N 个 token) • 关键: 最小化内存延迟
面试提示

关键区别: Prefill 可以充分利用 GPU 的并行计算能力(批处理),而 Decode 阶段受到内存带宽的严重限制,这是优化的重点。

⚙️ 核心原理

KV Cache 原理

为什么我们需要缓存 Key 和 Value

在 Transformer 中,自注意力机制计算 Attention(Q, K, V)。在生成阶段,我们逐个生成 token,但每次都需要与历史所有 token 进行 attention 计算。

不使用 KV Cache 时: 为了生成第 1024 个 token,我们需要重新计算前 1024 个 token 的 K 和 V,这是巨大的浪费。

使用 KV Cache: 我们只计算新 token 的 Q,然后与缓存的所有历史 K、V 进行 attention。这样避免了重复计算。

KV Cache Memory = 2 × num_layers × num_heads × d_head × seq_len × batch_size × 2 bytes 系数2:K 和 V;系数2字节:FP16 精度(2 bytes per value)

LLaMA 系列具体计算:

模型 Layers Heads d_head seq_len=2048 seq_len=4096 seq_len=8192
LLaMA 7B 32 32 128 8.4 MB 16.8 MB 33.6 MB
LLaMA 13B 40 40 128 13.1 MB 26.2 MB 52.4 MB
LLaMA 70B 80 64 128 52.4 MB 104.8 MB 209.6 MB

以 batch_size=1 为例。当 batch_size 增大时,内存成线性增长。对于 LLaMA 70B 和 8192 长序列,单个请求就需要 209.6 MB 的 KV Cache!

KV Cache 的增长过程
t=1 K₁, V₁ 128D Input t=2 K₁, V₁ K₂, V₂ 256D t=3 K₁..K₃ V₁..V₃ 384D t=n K₁..Kₙ V₁..Vₙ n × 128D 线性增长! 内存压力 每步: Qₙ (新)
核心公式

KV Cache 是推理优化的基础。它用线性的内存换取指数级的计算节省。总计算量从 O(n²) 降低到 O(n)。

🎯 优化策略

KV Cache 优化

减少内存占用的多种方法

KV Cache 是推理的内存瓶颈。以下是几个主要优化方向:

1️⃣
GQA / MQA(分组查询注意力)
标准 Multi-Head Attention 中,K 和 V 有与 Q 相同数量的头(num_heads)。GQA 让多个 query heads 共享一个 K/V head。MQA 是极端情况,所有 Q heads 共享单个 K/V head。
效果:KV Cache 缩小 8-16 倍(取决于分组比例)。LLaMA 2-70B 采用 GQA。
2️⃣
Sliding Window(滑动窗口)
在某些任务中,不需要完全保留所有历史 token 的 KV。只保留最近 W 个 token 的 KV(例如 W=2048)。
权衡:损失一些长依赖能力,但大幅降低内存。Llama 2、Mistral 等采用此策略。
3️⃣
KV Cache 压缩(Quantization)
将 KV Cache 从 FP16 (2 bytes) 量化为 INT8 (1 byte) 或甚至更低精度。
效果:内存减半,需要在线量化/反量化,加速主要取决于访问带宽。
4️⃣
Activation-Aware Quantization (AAQ)
不是均匀量化,而是根据激活值的分布进行非均匀量化,保护重要的 channels。
效果:相比均匀量化,质量更好,但计算成本更高。
KV Cache 优化技术对比
方案 内存占用 精度损失 应用 标准 FP16 100% 0% 基准 GQA 12.5% <0.5% 推荐 MQA 6.25% 1-2% 快速模型 滑动窗口 4-25% 2-5% 短上下文 KV INT8 50% 1-3% 折中方案 GQA + INT8 6.25% 1.5-3% 最优 相对于标准 FP16 KV Cache 的百分比
📊 量化技术

量化:GPTQ / AWQ / GGUF

将 32-bit FP32 权重压缩到 4-8 bit

量化概念: 用更少的 bit 表示权重。一个权重从 FP32 (4 bytes) 压缩到 INT4 (0.5 bytes),模型大小减小 8 倍。

两种方式:

  • PTQ (Post-Training Quantization):量化已训练好的模型,快速但精度损失可能更大
  • QAT (Quantization-Aware Training):在训练时就考虑量化,精度更好但成本高

GPTQ:Optimal Brain Quantization (OBQ) 的改进

原理: OBQ 方法使用 Hessian 矩阵来确定哪些权重对模型输出最重要,优先保护这些权重。GPTQ 是其优化版本,通过分层量化提高效率。

流程:

1
计算 Fisher Information
估计每个权重对输出的影响程度(Hessian diagonal)
2
贪心量化
逐个量化权重,每次选择量化损失最小的权重
3
调整剩余权重
为补偿已量化权重的损失,调整剩余权重

AWQ:激活权重量化

核心洞察: 不是所有权重对输出的影响都相等。某些权重与大的激活值相乘(重要路径),应该保护;而与小激活值相乘的权重可以更激进地量化。

算法: 寻找最优的缩放系数 α,使得通过缩放权重和激活值,能在 INT4 精度下保持精度。

W_quant = Quant( scale_w(α) × W ) / scale_w(α) 通过学习 α,保护重要的权重-激活路径

GGUF:通用格式

特点: GGUF (GUFF Format) 是一个与推理框架无关的格式,支持多种量化方式:INT4、INT5、INT8、FP16 等。特别针对 CPU 推理优化(llama.cpp)。

优势: 可以在消费级设备上运行大模型,虽然速度不如 GPU,但开启了更多应用场景。

量化过程对比
GPTQ (Hessian-Based) Pre-trained FP32 Hessian Compute Greedy Quantize INT4 量化权重 AWQ (Activation-Aware) Pre-trained FP32 Activation Analysis Scale Learning INT4 保护重要路径 GPTQ: 准确但慢(需计算 Hessian) | AWQ: 快且准确(推荐) GGUF (通用格式) Any Model 各种精度 Serialize to GGUF CPU Run 支持的量化方式: • Q2_K, Q3_K, Q4_K, Q5_K, Q6_K • Q8_0 (高质量) • IQ2_S, IQ2_M, IQ3_S, IQ3_M, IQ4_NL 通过 llama.cpp 支持 CPU/GPU 可在笔记本运行 7B/13B 模型 开启开源 LLM 的民主化应用
GPTQ
INT4
准确但计算成本高,适合离线量化
AWQ
INT4
快速高效,成为工业标准,推荐
⚡ 推理加速

Speculative Decoding

用小模型加速大模型的推理

问题: LLM 推理是内存密集型的。Decode 阶段的主要时间消耗是加载大模型的权重和计算 attention。每生成一个 token 需要一次完整的模型前向传播。

思路: 用一个小、快的草稿模型(draft model)生成多个候选 token,然后用大模型一次性验证这些候选。如果大模型验证通过,这些 token 都被接受;否则部分被拒绝。

关键性质: 输出分布完全与大模型相同(通过拒绝采样保证)。

Speedup = (Prefill_time + Decode_time_draft × K + Verify_time) / (Prefill_time + Decode_time_large × K) K 为生成的 token 数,理想情况下草稿模型快 10-100 倍
Speculative Decoding 工作流程
Step 1: 草稿模型快速生成 Draft Model LLaMA 1B 生成 K 个候选 x₁, x₂, ..., xₖ x₁ x₂ xₖ Step 2: 大模型并行验证 Large Model LLaMA 70B 一次前向传播 计算 K+1 位置 (seq_len + K) 验证逻辑 p_large(xᵢ) vs p_draft(xᵢ) 拒绝采样 Step 3: 接受/拒绝决策 ✓ 接受 所有 K 个 token 都匹配大模型 ✗ 部分拒绝 前 i 个接受 第 i+1 个重新生成 加速效果 期望加速: 2-3x 取决于 K 和接受率
核心算法:拒绝采样

如果 p_draft(x_i) > 0,则以概率 min(1, p_large(x_i)/p_draft(x_i)) 接受候选 token。这保证了输出分布与大模型完全相同,是一种无偏的加速。

💾 内存管理

PagedAttention 与 vLLM

解决 KV Cache 内存碎片化问题

问题: 在批处理请求时,不同请求的序列长度不同。如果为每个请求预分配固定大小的连续 KV Cache 内存,会导致严重的内存浪费。例如,为最长序列 (4096 tokens) 预分配内存,但某个请求只用 512 tokens,其余内存闲置。

解决方案: PagedAttention — 借鉴操作系统的虚拟内存管理。KV Cache 被分成固定大小的"页"(通常 16 个 token),请求可以非连续地占用这些页。

传统连续内存 vs PagedAttention
传统方式:连续内存分配 Req 1 512 未使用(浪费) Req 2 1024 Req 3 768 ↑ 严重浪费 内存使用率: (512+1024+768)/4096 = 62% 平均情况下浪费 20-40% 的内存 PagedAttention:分页管理 页大小:16 tokens Req1 Req1 Req2 Req2 Req2 Req2 Req3 Req3 Req3 页表 Req1: [0, 1] Req2: [2, 3, 4, 5] Req3: [1, 6, 2] 非连续映射 内存使用率: (2+4+3)/9 = 100% 支持更大的批次规模、减少浪费
vLLM 的优势

吞吐量提升: 通过 PagedAttention,vLLM 可以在相同 GPU 内存下支持 20-40 倍的吞吐量增加。这是因为内存利用率从 ~60% 提升到接近 100%。

🔄 并发策略

Continuous Batching

动态调度请求,最大化 GPU 利用率

传统静态批处理问题: 将 N 个请求组成一个批次,所有请求一起处理,直到最长的请求完成。短请求必须等待长请求,造成 GPU 空闲。

Continuous Batching: 请求逐个完成后,立即将新请求加入批次,无需等待整个批次完成。这样长短请求交错进行,GPU 利用率接近 100%。

静态批处理 vs 连续批处理
静态批处理 (Static Batching) Request 1 (1024 tokens) Request 2 (512) 等待 (浪费) Request 3 (256) 等待 t=0 t=1024 steps GPU 利用率: (1024+512+256)/3072 = 59% 大量空闲时间,GPU 浪费 连续批处理 (Continuous Batching) Req 1 (finish) Req 2 Req 3 Req 4 (new) t=0 t=1024 steps 吞吐提升 2-5x
静态批处理
59%
GPU 利用率低,简单易实现
连续批处理
90%+
GPU 利用率高,需要复杂调度
🛠️ 工具对比

推理框架对比

vLLM、TensorRT-LLM、SGLang、llama.cpp 等主流框架

LLM 推理框架众多,各有特色。选择合适的框架对性能和开发效率都有重大影响。

框架 后端 关键特性 量化支持 吞吐量 适用场景
vLLM PyTorch PagedAttention、连续批处理、异步推理 AWQ、GPTQ、INT8 ⭐⭐⭐⭐⭐ 通用、学术
TensorRT-LLM NVIDIA TensorRT 自动优化、多 GPU 支持、自定义 kernel INT8、FP8、INT4 ⭐⭐⭐⭐⭐ 高性能、生产环境
SGLang vLLM 基础 结构化输出、前缀缓存、编译优化 AWQ、GPTQ ⭐⭐⭐⭐ 结构化任务
llama.cpp C++(CPU) 轻量级、GGUF 格式、跨平台 GGUF (多种) ⭐⭐⭐ 消费级设备
Ollama llama.cpp 包装 易用、模型库、API 简单 GGUF ⭐⭐⭐ 本地快速部署
TGI PyTorch + Flash Attention 流式输出、多模型、生产就绪 AWQ、GPTQ、INT8 ⭐⭐⭐⭐ Hugging Face 生态
框架选择建议
  • 学术研究: vLLM(快速迭代、文档完善)
  • 生产高性能: TensorRT-LLM(最快但需 NVIDIA GPU)
  • 结构化输出: SGLang(受限解码、JSON 模式)
  • 消费设备: ollama(易用,7B/13B 模型可流畅运行)
  • 生产通用: vLLM 或 TGI(平衡性能和易用性)
📝 总结

总结与面试题

核心知识点回顾与常见面试问题

核心知识体系

🎯
推理的两个阶段
Prefill(计算密集)vs Decode(内存密集)。大部分时间在 Decode,优化重点在内存
💾
KV Cache 是瓶颈
线性增长,内存成本高。GQA/MQA/量化/滑动窗口是主要优化方向
量化和小模型加速
AWQ 最实用,Speculative Decoding 可获得 2-3x 加速
📦
PagedAttention 和连续批处理
vLLM 的核心,内存利用率从 60% 提升到 100%,吞吐提升 2-5x

常见面试题

Q1

解释 Prefill 和 Decode 阶段的区别,为什么优化策略不同?

Prefill 是计算密集型,可以充分利用 GPU 的并行计算能力。Decode 是内存密集型,受内存带宽限制。因此 Prefill 需要最大化计算吞吐,Decode 需要最小化内存访问。

Q2

KV Cache 的内存公式是什么?对 LLaMA 70B 和 8K 序列,单个请求需要多少 GB?

2 × layers × heads × d_head × seq_len × batch × 2 bytes。LLaMA 70B:2 × 80 × 64 × 128 × 8192 × 1 × 2 = 209.6 MB ≈ 0.21 GB。批量处理时线性增长。

Q3

GQA、MQA、Sliding Window 如何降低 KV Cache?各有什么 trade-off?

GQA:多个 Q 头共享一个 K/V 头,降低 8-16 倍。MQA 更极端,单个 K/V 头。Sliding Window:只保留最近 W 个 token,损失长距离依赖。都需要在设计时考虑,训练后难以改变。

Q4

GPTQ 和 AWQ 的区别?为什么 AWQ 现在更常用?

GPTQ 基于 Hessian 计算,准确但需要大量计算。AWQ 基于激活值分布,快速高效,精度相近。AWQ 成为工业标准因为它速度快(小时级 vs 天级)且精度不输 GPTQ。

Q5

Speculative Decoding 如何工作?为什么输出分布与大模型相同?

草稿模型生成 K 个候选,大模型一次验证。使用拒绝采样:以 min(1, p_large/p_draft) 的概率接受。这保证边际分布与大模型相同,是无偏加速。期望加速 2-3x。

Q6

PagedAttention 解决了什么问题?内存利用率提升多少?

传统连续分配导致严重碎片化(60% 利用率)。PagedAttention 使用虚拟内存映射,KV Cache 按页分配,可以非连续。利用率提升到接近 100%,支持更大批量。

Q7

Continuous Batching 为什么比静态批处理快那么多?

静态批处理中,短请求必须等待长请求,GPU 大量空闲。连续批处理中,请求完成后立即加入新请求,GPU 始终有工作,利用率从 60% 提升到 90%+,吞吐提升 2-5x。

Q8

如何估计一个推理系统的吞吐量?影响因素有哪些?

吞吐 = tokens_per_second × batch_size。关键因素:(1) 内存带宽(主要瓶颈),(2) 模型大小,(3) 序列长度,(4) 量化方式,(5) 批处理策略,(6) 硬件(A100 > V100)。

Q9

为什么 INT4 量化通常不损失性能,但 INT2 会?

权重分布不均匀,少数通道很重要。INT4 仍能保留足够信息,量化后通过激活缩放补偿。INT2 太激进,丢失关键信息,难以恢复。AWQ 通过学习缩放系数,可推进极限到 INT3。

Q10

选择推理框架的关键考虑因素?vLLM vs TensorRT-LLM?

vLLM:开源、易用、快速迭代、学术友好。TensorRT-LLM:最快、生产稳定、需 NVIDIA。选择 vLLM 用于研究和快速原型,生产高性能选 TensorRT。中间选项:TGI(平衡)或 SGLang(结构化)。

Q11

如果限制内存为 8GB,能部署 LLaMA 13B 吗?需要什么优化?

13B FP32 ≈ 52 GB,不可行。需要:(1) INT4 量化 ≈ 3.3 GB 权重,(2) GQA 减少 KV Cache,(3) 滑动窗口(max_seq=2048),(4) batch_size=1。总计约 5-6 GB,可行。或使用 GGUF + llama.cpp 在 CPU 上。

Q12

优化推理延迟 (latency) 和吞吐量 (throughput) 的策略有什么矛盾?

低延迟需要小批量/及时响应。高吞吐需要大批量。单个请求的延迟 = 预热时间 + 序列长度/tokens_per_sec。优化方向:(1) 对延迟敏感:小批量 + 快速调度,(2) 吞吐优先:大批量 + 连续批处理。可用分离队列兼得。