文章

KV Cache 原理与 PagedAttention

KV Cache 原理与 PagedAttention

为什么 70B 推理权重只占 140GB(FP16),但并发一上来显存照样爆?答案是 KV Cache——它随请求数、序列长度动态增长,是推理显存真正的”隐形大户”。本文讲清它怎么来的、为什么传统分配会浪费、PagedAttention 怎么解决。


一、KV Cache 从哪来

Decoder-only 自回归生成:第 t 个 token 的注意力要”看到”前面所有 token(1..t-1)。但注意力的 K/V 投影只依赖各 token 自身,所以前面 token 算过的 K、V 可以缓存复用,不必每生成一个新 token 都重算全部历史。

生成第 t 个 token 时:
  需要:K_1..K_t, V_1..V_t   ← 历史 K/V
  其中 K_1..K_{t-1}, V_1..V_{t-1} 已在上一轮算过,缓存即可
  只需新算 K_t, V_t,拼到缓存末尾

显存占用公式

单层 KV Cache 大小 = 2 × (num_kv_heads) × seq_len × head_dim × dtype_bytes
总 KV Cache        = 上述 × num_layers

以 LLaMA-70B(80 层, GQA: 8 KV heads, head_dim=128, BF16=2B)为例:
  单层 = 2 × 8 × seq_len × 128 × 2 = 4096 × seq_len bytes
  总   = 4096 × seq_len × 80 = 327680 × seq_len bytes
  seq_len=4096 → ≈ 1.34 GB / 每请求
  seq_len=32K  → ≈ 10.7 GB / 每请求

关键洞察:KV Cache 与 序列长度成正比、与请求数成正比。100 个并发、平均 4K 上下文 → 仅 KV 就 ~134 GB,已经超过很多卡的权重占用。

GQA / MQA 为什么省 KV Cache

  • MHA(每注意力头独立 KV):KV heads = num_heads(最费)
  • GQA(LLaMA-2/3、Qwen):KV heads 降到 8/4 个,KV Cache 同比缩小 → 直接降低推理显存与 TPOT
  • MQA(PaLM):KV heads=1,最省但质量略降

二、传统连续分配的碎片问题

朴素实现给每个请求预留一块连续、且按最大长度预留的 KV 显存:

请求 A 预留 2K tokens 空间(实际只用 1.3K) → 浪费 0.7K
请求 B 预留 4K 空间(实际只用 0.5K)         → 浪费 3.5K

两个问题:

  1. 内部碎片:预留上限远高于实际使用
  2. 外部碎片:请求结束释放的连续块,难以拼给新请求(长度不匹配)
  3. 无法跨请求共享:多轮对话里相同的 system prompt,每个请求各算各的 KV

结果:GPU 显存明明还有空,却因碎片和预留策略报 OOM,并发数被人为压低


三、PagedAttention:把 KV Cache 像虚拟内存一样分页

vLLM 的核心创新(2023)。思想直接借用 OS 的分页 + 页表

传统:每个请求一块连续 KV 显存
PagedAttention:每个请求 = 一组固定大小的逻辑块(block),
                物理上分散存放,用「块表(block table)」映射

工作方式

1. KV Cache 切成固定大小的 block(如每块 16 个 token 的 K/V)
2. 每个请求的 block table 记录:逻辑块 i → 物理块 p
3. 生成时按需分配新 block,用完释放,物理显存像内存池一样复用
4. 注意力计算时,按 block table 把分散的物理块 gather 起来

收益

维度连续分配PagedAttention
内部碎片高(按 max 预留)仅最后一块不足部分(<1 block)
外部碎片近零(块可任意复用)
显存利用率20-40%90%+(接近 gpu-memory-utilization 上限)
并发数提升 2-4×
跨请求共享✅(copy-on-write 共享相同前缀块)

最后一个收益最关键:相同 prompt 前缀的多个请求,物理上共享同一批 KV 块,直到某个请求生成的分支不同才 copy-on-write 分裂。这正是 SGLang RadixAttention 把”共享”做到极致的底层原理(见 LLM 推理引擎对比与选型)。


四、KV Cache 量化(进一步压显存/带宽)

KV Cache 不仅占显存,decode 时每个 token 都要读全部历史 KV(访存密集),所以量化 KV 一举两得:

方案做法效果
FP8 KV CacheKV 以 FP8 存,计算前反量化显存/带宽减半,精度损失极小
INT8 KV Cacheper-tensor/per-channel 量化显存减半,常用且稳
INT4 KV Cache更激进,需校准显存 1/4,长上下文场景收益大
KV Cache 稀疏只保留重要 token 的 KV研究阶段,生产少用

vLLM 启用示例:

python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-7B-Instruct \
  --kv-cache-dtype fp8_e5m2 \
  --gpu-memory-utilization 0.95

五、Prefix Cache:跨请求复用长 prompt

除了分页,还可以显式缓存长 prompt 的 KV,避免重复 prefill:

场景:100 个请求都带同一份 8K 的 system prompt + 文档
朴素:每个请求都 prefill 这 8K → 100 倍浪费
Prefix Cache:第一次算完 8K 的 KV 存下来,其余 99 个直接复用
  • vLLM:--enable-prefix-caching(自动按 prompt hash 命中)
  • SGLang:RadixAttention(树状缓存,更细粒度)
  • 收益:长上下文/多轮 Agent/RAG 场景,TTFT 大幅下降、GPU 算力省下来给 decode

六、监控 KV Cache 使用

# vLLM /metrics 关键指标
vllm:gpu_cache_usage_sys    # KV Cache 显存占用比例(>0.9 要警惕 OOM)
vllm:num_preemptions        # 因 KV 不足被抢占的请求数(越高越说明显存紧张)

gpu_cache_usage_sys 长期 > 0.9 且 num_preemptions > 0,说明并发/上下文已超容量——要么降 --max-model-len、要么开 KV 量化、要么加卡(张量并行)。


关联知识

参考资源

学习时间

阶段时间备注
内容创建2026-07-20KV Cache 公式、GQA 影响、碎片问题、PagedAttention 分页思想、KV 量化、Prefix Cache

状态标记

📖 已掌握 — KV 显存公式、传统碎片根因、PagedAttention 分页+共享、KV 量化、Prefix Cache 📝 待补充 — FlashAttention 与 PagedAttention 的协同、KV 卸载到 CPU/NVMe