从 KV Cache 到 Speculative Decoding — 让大模型快起来的每一个技巧
掌握 LLM 推理的核心优化技术,从内存管理到并发策略,成为高效推理工程师
推理的两个阶段
Prefill 与 Decode — 理解性能瓶颈的起点
LLM 推理分为两个截然不同的阶段,它们的计算特征和优化策略完全不同。
Prefill 阶段(前缀填充): 处理用户输入的完整提示词,计算所有 token 的 attention。这个阶段是 计算密集型 的,GPU 利用率高。
Decode 阶段(生成阶段): 自回归地逐个生成输出 token。每次只生成一个新 token,但需要加载之前所有 token 的 KV Cache,这个阶段是 内存带宽密集型 的。
大多数推理时间耗在 Decode 阶段,而且越长的生成长度,Decode 的比例就越高。
关键区别: 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。这样避免了重复计算。
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 是推理优化的基础。它用线性的内存换取指数级的计算节省。总计算量从 O(n²) 降低到 O(n)。
KV Cache 优化
减少内存占用的多种方法
KV Cache 是推理的内存瓶颈。以下是几个主要优化方向:
效果:KV Cache 缩小 8-16 倍(取决于分组比例)。LLaMA 2-70B 采用 GQA。
权衡:损失一些长依赖能力,但大幅降低内存。Llama 2、Mistral 等采用此策略。
效果:内存减半,需要在线量化/反量化,加速主要取决于访问带宽。
效果:相比均匀量化,质量更好,但计算成本更高。
量化: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 是其优化版本,通过分层量化提高效率。
流程:
AWQ:激活权重量化
核心洞察: 不是所有权重对输出的影响都相等。某些权重与大的激活值相乘(重要路径),应该保护;而与小激活值相乘的权重可以更激进地量化。
算法: 寻找最优的缩放系数 α,使得通过缩放权重和激活值,能在 INT4 精度下保持精度。
GGUF:通用格式
特点: GGUF (GUFF Format) 是一个与推理框架无关的格式,支持多种量化方式:INT4、INT5、INT8、FP16 等。特别针对 CPU 推理优化(llama.cpp)。
优势: 可以在消费级设备上运行大模型,虽然速度不如 GPU,但开启了更多应用场景。
Speculative Decoding
用小模型加速大模型的推理
问题: LLM 推理是内存密集型的。Decode 阶段的主要时间消耗是加载大模型的权重和计算 attention。每生成一个 token 需要一次完整的模型前向传播。
思路: 用一个小、快的草稿模型(draft model)生成多个候选 token,然后用大模型一次性验证这些候选。如果大模型验证通过,这些 token 都被接受;否则部分被拒绝。
关键性质: 输出分布完全与大模型相同(通过拒绝采样保证)。
如果 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),请求可以非连续地占用这些页。
吞吐量提升: 通过 PagedAttention,vLLM 可以在相同 GPU 内存下支持 20-40 倍的吞吐量增加。这是因为内存利用率从 ~60% 提升到接近 100%。
Continuous Batching
动态调度请求,最大化 GPU 利用率
传统静态批处理问题: 将 N 个请求组成一个批次,所有请求一起处理,直到最长的请求完成。短请求必须等待长请求,造成 GPU 空闲。
Continuous Batching: 请求逐个完成后,立即将新请求加入批次,无需等待整个批次完成。这样长短请求交错进行,GPU 利用率接近 100%。
推理框架对比
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 和 Decode 阶段的区别,为什么优化策略不同?
Prefill 是计算密集型,可以充分利用 GPU 的并行计算能力。Decode 是内存密集型,受内存带宽限制。因此 Prefill 需要最大化计算吞吐,Decode 需要最小化内存访问。
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。批量处理时线性增长。
GQA、MQA、Sliding Window 如何降低 KV Cache?各有什么 trade-off?
GQA:多个 Q 头共享一个 K/V 头,降低 8-16 倍。MQA 更极端,单个 K/V 头。Sliding Window:只保留最近 W 个 token,损失长距离依赖。都需要在设计时考虑,训练后难以改变。
GPTQ 和 AWQ 的区别?为什么 AWQ 现在更常用?
GPTQ 基于 Hessian 计算,准确但需要大量计算。AWQ 基于激活值分布,快速高效,精度相近。AWQ 成为工业标准因为它速度快(小时级 vs 天级)且精度不输 GPTQ。
Speculative Decoding 如何工作?为什么输出分布与大模型相同?
草稿模型生成 K 个候选,大模型一次验证。使用拒绝采样:以 min(1, p_large/p_draft) 的概率接受。这保证边际分布与大模型相同,是无偏加速。期望加速 2-3x。
PagedAttention 解决了什么问题?内存利用率提升多少?
传统连续分配导致严重碎片化(60% 利用率)。PagedAttention 使用虚拟内存映射,KV Cache 按页分配,可以非连续。利用率提升到接近 100%,支持更大批量。
Continuous Batching 为什么比静态批处理快那么多?
静态批处理中,短请求必须等待长请求,GPU 大量空闲。连续批处理中,请求完成后立即加入新请求,GPU 始终有工作,利用率从 60% 提升到 90%+,吞吐提升 2-5x。
如何估计一个推理系统的吞吐量?影响因素有哪些?
吞吐 = tokens_per_second × batch_size。关键因素:(1) 内存带宽(主要瓶颈),(2) 模型大小,(3) 序列长度,(4) 量化方式,(5) 批处理策略,(6) 硬件(A100 > V100)。
为什么 INT4 量化通常不损失性能,但 INT2 会?
权重分布不均匀,少数通道很重要。INT4 仍能保留足够信息,量化后通过激活缩放补偿。INT2 太激进,丢失关键信息,难以恢复。AWQ 通过学习缩放系数,可推进极限到 INT3。
选择推理框架的关键考虑因素?vLLM vs TensorRT-LLM?
vLLM:开源、易用、快速迭代、学术友好。TensorRT-LLM:最快、生产稳定、需 NVIDIA。选择 vLLM 用于研究和快速原型,生产高性能选 TensorRT。中间选项:TGI(平衡)或 SGLang(结构化)。
如果限制内存为 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 上。
优化推理延迟 (latency) 和吞吐量 (throughput) 的策略有什么矛盾?
低延迟需要小批量/及时响应。高吞吐需要大批量。单个请求的延迟 = 预热时间 + 序列长度/tokens_per_sec。优化方向:(1) 对延迟敏感:小批量 + 快速调度,(2) 吞吐优先:大批量 + 连续批处理。可用分离队列兼得。