为什么 KV cache 值得单独写一篇
很多团队优化 LLM 推理时,第一反应是模型权重量化、tensor parallel、batch size 或更快的 GPU。但在真实服务中,另一个更隐蔽的瓶颈经常先出现:KV cache。
KV cache 保存的是 Transformer 自注意力层中历史 token 的 key/value 张量。没有它,模型每生成一个新 token,都要重新计算前面所有 token 的注意力表示。有了它,decode 阶段只需要读取历史 key/value,再处理当前 token。
问题在于,KV cache 不是免费的。上下文越长、并发越高、batch size 越大,它占用的显存越多。对长上下文 Agent、RAG、多轮对话和代码生成服务来说,KV cache 往往会变成真正的运行时内存账本。
一句话理解 KV cache
标准自回归生成可以简化成:
输入 prompt → 生成 token 1 → 生成 token 2 → 生成 token 3 → ...
每一步都需要关注前面所有 token。KV cache 的作用是把历史 token 的 key/value 存起来,后续 token 直接复用:
- Prefill:计算 prompt 的 K/V,并写入 cache
- Decode:只计算新 token 的 K/V,并读取历史 cache
因此,KV cache 同时影响两件事:
- 速度:减少重复计算。
- 显存:保存每一层、每个 token 的 key/value。
这就是它既是加速器,也是瓶颈的原因。
核心知识结构
可以把 KV cache 工程拆成 6 个模块。
1. Prefill 与 Decode
Prefill 处理输入 prompt,通常计算量大、并行度高。Decode 逐 token 生成,通常更受内存带宽和 cache 读取影响。
KV cache 在 prefill 阶段被写入,在 decode 阶段被持续读取和追加。
2. 显存增长模型
KV cache 大小通常随这些因素增长:
layers × sequence_length × batch_size × kv_heads × head_dim × precision
这意味着同一个模型,在短 prompt、低并发下看似很轻;一旦换成长上下文或高 batch,就可能因为 KV cache 爆显存。
3. PagedAttention
PagedAttention 把 KV cache 拆成固定大小 block,并允许这些 block 在显存中非连续存储。这样可以减少为不同长度请求预留连续大块显存造成的浪费。
4. Prefix Caching
如果多个请求共享相同前缀,比如相同 system prompt、相同 few-shot examples、相同文档开头,就可以复用已计算好的 KV-cache blocks,避免重复 prefill。
5. RadixAttention
RadixAttention 使用 radix tree 管理 token prefix 与 KV cache tensor 的映射,适合复杂 LLM 程序中多次调用、分支推理和共享上下文复用。
6. KV Cache Quantization
KV cache quantization 用更低精度保存 key/value,从而减少显存和带宽压力。它适合长上下文和高 batch,但必须验证准确率与任务稳定性。
KV cache 生命周期:从写入到复用
一个生产 LLM 服务中的 KV cache 生命周期大致如下:
- 用户请求进入调度器
- Prefill 计算 prompt 的 key/value
- KV blocks 写入 paged cache
- Decode 阶段逐 token 读取历史 blocks
- 如果新请求命中相同 prefix,直接复用已有 blocks
- 如果显存不足,按 eviction policy 回收低价值 blocks
- 如果启用 quantization,以低精度保存部分或全部 KV cache
- 监控 cache hit rate、TTFT、ITL、显存碎片和吞吐量
这里最容易被忽略的是第 5 到第 8 步。很多 Demo 只验证单请求能不能跑,但生产服务真正的收益来自缓存复用、内存布局和调度策略。
为什么只看模型权重大小会误导
假设一个模型权重量化后可以放进显存,服务仍然可能跑不起来。原因是模型权重是相对固定的,而 KV cache 是随请求动态增长的。
典型误判包括:
- 只用短 prompt 测试,没测长上下文。
- 只测 batch size = 1,没测并发。
- 只测 prefill,没测 decode 中后段。
- 只看 tokens/s,没看 TTFT 和 inter-token latency。
- 只看显存峰值,没看碎片和 cache eviction。
生产中更合理的测试方式是把 KV cache 单独作为资源统计。
metrics = {
"kv_cache_used_gb": current_kv_cache_memory(),
"kv_cache_hit_rate": prefix_cache_hits / prefix_cache_lookups,
"evicted_blocks": evicted_block_count,
"fragmentation_ratio": internal_fragmentation / allocated_cache,
"ttft_ms": time_to_first_token,
"itl_ms": inter_token_latency,
}
Prefix Caching:最接近免费午餐的优化
很多业务请求天然共享前缀。例如:
- 相同 system prompt
- 相同安全规则
- 相同工具说明
- 相同 RAG 文档头部
- 相同 few-shot examples
- 相同多轮对话历史
如果每个请求都重新 prefill,这些共享部分会被反复计算。Prefix Caching 的价值就是把已经处理过的前缀 KV-cache blocks 保存下来,新请求命中相同 prefix 时直接复用。
vLLM 的做法是对 KV-cache block 做 hash。hash 组成通常包括父 block hash、当前 block token,以及 LoRA、多模态输入、cache salt 等额外因素。这样既能复用相同前缀,又能避免不同租户或不同模型配置误共享。
工程上要注意 3 点:
- Prefix caching 不改变模型输出,但会改变系统调度和缓存命中表现。
- 命中率取决于 prompt 结构是否稳定。
- 多租户场景需要 cache isolation,避免跨用户上下文泄露。
PagedAttention:把显存当分页系统管理
传统 KV cache 管理容易出现两类浪费:
- 内部碎片:为了最大输出长度预留空间,但实际没用完。
- 外部碎片:不同请求长度差异太大,连续内存块难以高效分配。
PagedAttention 借鉴操作系统分页思想,把每个请求的 KV cache 切成固定 token block,再用 block table 记录逻辑 token 到物理 block 的映射。
这样做的好处是:
- 不要求每个请求占用连续显存
- 按需追加新的 cache blocks
- 更容易支持不同长度请求混合 batch
- 减少预分配造成的浪费
- 提升可承载 batch size
对服务端来说,PagedAttention 的意义不是单纯”更快”,而是让 KV cache 从静态大块分配变成动态资源管理。
RadixAttention:复杂 LLM 程序里的自动复用
普通 prefix caching 多用于共享 system prompt 或固定模板。但 Agent、Tree-of-Thought、自一致性、多轮对话、批量抽取等场景中,复用模式更复杂。
SGLang 的 RadixAttention 将 token sequence 到 KV cache tensor 的映射放入 radix tree 中。它可以在运行时查找共享 prefix、插入新路径,并根据 LRU 策略回收低价值叶子节点。
更关键的是,RadixAttention 不只是缓存结构,还会配合 cache-aware scheduling。也就是说,调度器会尽量把更容易共享缓存的请求组织在一起,从而提高实际命中率。
KV Cache Quantization:用精度换上下文和并发
当 KV cache 成为显存瓶颈时,可以考虑把 key/value 张量用更低精度保存。
它带来的收益包括:
- 更长上下文。
- 更大 batch size。
- 更高 cache hit rate。
- 更低 memory bandwidth 压力。
- 更低 TTFT 或 decode latency。
但它不是无风险优化。KV cache 直接参与 attention 计算,如果量化误差过大,可能影响长上下文检索、代码生成、数学推理和多步逻辑任务。
上线前至少应验证:
- 短文本对话质量
- 长上下文 needle retrieval
- 代码生成 benchmark
- RAG 问答准确率
- 工具调用 JSON 稳定性
- 采样输出分布变化
生产落地 Checklist
上线前建议检查这些点:
- 是否单独统计 KV cache 显存,而不是只看模型权重。
- 是否区分 prefill latency、TTFT、ITL 和整体吞吐量。
- 是否测试长上下文和高 batch size 的组合。
- 是否启用或评估 prefix caching。
- Prompt 模板是否稳定,能否提升 prefix hit rate。
- 是否有多租户 cache isolation。
- 是否使用 paged layout 降低碎片。
- 是否有明确 eviction policy。
- 是否记录 cache hit rate 和 evicted blocks。
- 是否评估 KV cache quantization 对准确率的影响。
- 是否为 Agent、RAG、代码生成分别建测试集。
- 是否把 scheduler 与 cache policy 联合优化。
结论
KV cache 是 LLM 推理系统里的运行时内存账本。
模型权重决定”模型能不能装下”,而 KV cache 决定”长上下文、高并发和持续生成能不能稳定跑”。生产级推理优化不能只看权重量化和 GPU 算力,还要系统设计:
- Prefix Caching 提高复用率
- PagedAttention 降低碎片
- RadixAttention 管理复杂共享前缀
- KV Cache Quantization 扩大上下文和 batch 空间
- Scheduler 决定缓存命中是否真实发生
- Metrics 证明优化是否有效
对工程团队来说,最实用的原则是:先把 KV cache 变成可观测资源,再谈优化。否则,很多所谓推理加速只是在短 prompt、低并发、无缓存压力的测试里成立。