LLM 缓存深入浅出:从 KV Cache 原理到命中率优化实战
很多人说"缓存命中了所以省钱",但如果你问他们命中了什么缓存、怎么命中的、为什么有时突然命中率暴跌——大概率答不上来。这篇文章帮你彻底搞懂。
引言:一个被严重低估的优化手段
当你看到 Claude API 返回 cached_tokens: 50000,或者 vLLM 日志打印 prefix cache hit 时,背后发生的事情远比"缓存命中"四个字复杂得多。
LLM 推理中,缓存是最容易被低估的成本优化手段。一个精心优化的 Agent 系统,通过 Prompt Caching 可以将 API 成本降低 90%,首 Token 延迟(TTFT)降低 85%。但现实中,很多团队的缓存命中率从最初的 72% 跌到 18% 却浑然不知。
本文将回答三个问题:
- LLM 有哪些缓存?它们分别做什么?
- 缓存命中的底层原理是什么?跨用户共享安全吗?
- 如何系统地提高缓存命中率?
第一部分:LLM 缓存全景图
先澄清一个常见误解:LLM 语境下的"缓存命中"通常指 Transformer 推理层的 KV Cache,不是 Redis,也不是 HTTP 缓存。但 LLM 生态中确实存在多个层级的缓存机制,容易混淆。
1.1 KV Cache —— 最核心的推理缓存
Transformer 架构中,每生成一个新 Token 都需要对前面所有 Token 做 Attention 计算。这意味着第 N 个 Token 的生成本质上需要"看"前 N-1 个 Token,计算它们的 Key(K)和 Value(V)向量。
如果不做任何优化,每次生成都要从头计算所有历史 Token 的 K/V——这对于一个 100K 上下文的请求来说,计算量是灾难性的。
KV Cache 的核心思想极其简单:把历史 Token 的 K/V 计算结果缓存起来,下一轮只计算新增 Token。
用一个例子说明:
第 1 轮请求:
┌─────────────────────────────────┐
│ System Prompt(10,000 tokens) │ ← 需要计算全部 10K token 的 K/V
│ + 用户问题 │
└─────────────────────────────────┘
→ 生成 KV Cache,存入 GPU 显存
第 2 轮请求:
┌─────────────────────────────────┐
│ System Prompt(10,000 tokens) │ ← 命中缓存!跳过计算
│ + 历史对话 │ ← 命中缓存!跳过计算
│ + 新用户问题 │ ← 仅计算这部分的 K/V
└─────────────────────────────────┘
→ 复用已有 KV Cache,仅计算增量
结果:TTFT 更快、GPU 占用更小、成本更低。
1.2 缓存的四个层级
| 层级 | 名称 | 缓存内容 | 存储位置 | 举例 |
|---|---|---|---|---|
| L1 | KV Cache | Attention 的 K/V 矩阵 | GPU 显存 / CPU 内存 | vLLM PagedAttention |
| L2 | Prefix Cache | 公共前缀的 KV Cache | GPU 显存 | SGLang RadixAttention |
| L3 | Semantic Cache | 语义相似查询的结果 | Redis / 向量数据库 | AI Gateway 缓存 |
| L4 | Exact Match Cache | 完全相同输入的输出 | Redis / 内存 | HTTP 风格缓存 |
本文重点讨论 L1(KV Cache) 和 L2(Prefix Cache),因为它们是推理引擎层面的核心优化,也是大多数人在讨论"LLM 缓存"时真正指代的东西。
1.3 缓存存在哪里?
GPU 显存是最常见的存储位置。KV Cache 的体积可以用这个公式估算:
KV Cache 大小 ≈ 2 × Token 数 × 层数 × Hidden Size × 精度字节数
对于一个 70B 参数的模型,100K Token 上下文的 KV Cache 可以轻松达到数十 GB。因此,现代推理框架(vLLM、TensorRT-LLM、SGLang、LMDeploy)都主要将 KV Cache 存放在 GPU VRAM 中。
当显存不够时,vLLM 的 PagedAttention 机制会将 KV Cache 分页管理,类似操作系统的虚拟内存,在显存和 CPU 内存之间按需换入换出。
第二部分:Prefill 与 Decode —— 理解为什么缓存如此重要
要理解缓存的价值,必须先理解 LLM 推理的两个阶段。
2.1 Prefill 阶段 —— 最贵的部分
当你发送一个请求时,模型首先需要"阅读"整个 Prompt,计算所有 Token 的 Attention,生成 KV Cache。这个阶段叫做 Prefill。
Prefill 的计算复杂度接近 O(n²)(n 为输入 Token 数)。对于 100K Token 的 Agent Prompt,Prefill 是 GPU 资源消耗最大的环节。
2.2 Decode 阶段 —— 逐 Token 输出
KV Cache 生成后,模型进入 Decode 阶段,逐个生成输出 Token。每个新 Token 只需要与已有的 KV Cache 做 Attention,计算量远低于 Prefill。
2.3 缓存如何改变游戏规则
如果没有缓存,每一轮对话都要重新 Prefill 整个上下文(包括 System Prompt、工具定义、历史对话)。对于一个 100K Token 的 Agent 请求:
- 无缓存:每轮都做 100K Token 的 Prefill → 成本极高
- 有缓存:仅 Prefill 新增的几 K Token → 成本降低 90%+,TTFT 降低 85%+
这就是为什么缓存被称为 LLM 推理的"免费加速器"。
第三部分:Prefix Cache —— 跨用户共享的秘密武器
3.1 什么是 Prefix Cache?
KV Cache 的一个强大变体是 Prefix Cache(也叫 Prompt Cache)。它不仅在同一会话内复用缓存,还能在不同用户之间共享公共前缀的 KV Cache。
原理很简单:如果两个请求的前 N 个 Token 完全相同,那么前 N 个 Token 的 KV Cache 就可以共享。
用户 A 的请求:
┌──────────────────────────────┐
│ System Prompt(固定) │ ← 公共前缀
│ Tool Schema(固定) │ ← 公共前缀
│ 用户问题:如何部署 Redis? │ ← 差异部分
└──────────────────────────────┘
用户 B 的请求:
┌──────────────────────────────┐
│ System Prompt(固定) │ ← 命中 A 的缓存
│ Tool Schema(固定) │ ← 命中 A 的缓存
│ 用户问题:怎么部署 K8s? │ ← 需要新计算
└──────────────────────────────┘
用户 B 可以直接复用用户 A 已计算好的公共前缀 KV Cache,跳过绝大部分 Prefill 计算。
3.2 安全吗?会串数据吗?
这是最常被问到的问题。答案是:不会。
KV Cache 存储的是 Attention 计算的中间矩阵(K 和 V 向量),不是用户的聊天文本。共享的条件是 Token 序列完全一致——哪怕多一个标点符号,缓存都会失效。而且,只有公共前缀会被共享,每个用户自己独有的输入部分始终独立计算。
3.3 主流框架的支持情况
| 框架 | 缓存机制 | 特点 |
|---|---|---|
| SGLang | RadixAttention(Radix Tree) | 专门为跨请求前缀共享设计,吞吐量极高 |
| vLLM | Automatic Prefix Caching | 基于 Block 级别的 KV 复用,自动匹配公共前缀 |
| TensorRT-LLM | Paged KV Cache + Shared Prefix | NVIDIA 官方方案,支持前缀复用 |
| LMDeploy | Cache-aware 前缀复用 | 支持多轮对话和跨请求复用 |
| OpenAI API | Prompt Caching | 自动启用,API 无状态天然就是跨请求共享 |
| Anthropic Claude | Prompt Caching | 支持显式缓存控制,cache hit 成本降低 90% |
3.4 一个被忽略的事实
Copilot、Cursor、Claude Code、ChatGPT、Devin 这类大规模 AI 产品的实际 GPU 成本,远低于按 Token 单价简单计算的结果。核心原因就是海量用户共享了巨大的公共前缀 Cache。如果不共享,这些产品的 GPU 账单会爆炸。
第四部分:如何判断缓存命中了?
不同平台的监控方式各不相同:
OpenAI API
OpenAI 在 2024 年底默认启用了 Prompt Caching。在 API 响应的 usage 字段中,可以看到命中的缓存 Token 数:
{
"usage": {
"prompt_tokens": 12000,
"cached_tokens": 10000
}
}
cached_tokens 就是命中的缓存 Token 数量。
Anthropic Claude
Anthropic 的返回更加详细,区分了"创建缓存"和"读取缓存":
{
"usage": {
"input_tokens": 12000,
"cache_creation_input_tokens": 10000,
"cache_read_input_tokens": 9000
}
}
cache_creation_input_tokens:首次创建缓存(写入)cache_read_input_tokens:从缓存读取(命中)
自建推理引擎
vLLM 日志: "prefix cache hit" 或 "KV cache usage: 85%"
SGLang 控制台:"Radix cache hit rate: 82%"
第五部分:缓存的匹配规则 —— 为什么"差不多"不行?
这是一个极其关键的认知:缓存匹配基于 Token 的完全一致性,不是语义相似性。
"你是助手" → Token 序列: [1234, 5678, 9012]
"你是助手。" → Token 序列: [1234, 5678, 9012, 3456] ← 多了一个句号
"你是助手" → Token 序列: [1234, 5678, 9013] ← 全角空格,Token 变了
三种写法的含义几乎相同,但 Token 序列不同,缓存全部失效。
两种匹配粒度
Prefix Cache(前缀缓存):只要求前缀 Token 一致。例如 System Prompt + Tool Schema + 历史对话的前 N 轮相同即可命中。这是最实用的模式。
Exact Match Cache(完全匹配缓存):整个 Prompt 完全一致,直接复用最终输出结果。这更接近 HTTP 缓存的概念,适合问答机器人等确定性场景。
第六部分:为什么 Agent / Tool Calling 特别依赖缓存?
Agent 系统是 Prompt Cache 的最大受益者,原因如下:
- 超长 System Prompt:一个典型的 Coding Agent 可能有 5K-30K Token 的 System Prompt
- 超长 Tool Schema:MCP 工具定义、API Schema 加起来可能 10K-50K Token
- 多轮对话上下文:每次请求都携带完整的历史对话
- 大量并发请求:不同用户同时使用同一个 Agent
这意味着一个 Agent 请求可能有 100K+ Token 的输入,其中 80%+ 是固定的公共前缀。如果不缓存,每轮 Prefill 的成本是天文数字。
这正是为什么很多 Agent 框架会要求固定工具排序、固定 JSON Schema、避免动态 Prompt——目的就是最大化缓存命中率。
第七部分:如何系统地提高缓存命中率?
这是本文最实用的部分。缓存命中率的优化可以分为四个层面。
7.1 Prompt 层面:稳定前缀
固定 System Prompt 的内容和顺序。 不要根据请求动态调整 System Prompt 的结构。如果必须变化,把可变部分放在 Prompt 的末尾,让公共前缀尽量长。
✅ 正确做法:可变部分放末尾
┌─────────────────────────────┐
│ System Prompt(固定) │ ← 所有用户共享
│ Tool Schema(固定) │ ← 所有用户共享
│ ─────────────────────────── │
│ 用户个性化上下文(可变) │ ← 放在最后
│ 用户问题(可变) │
└─────────────────────────────┘
❌ 错误做法:可变部分穿插在中间
┌─────────────────────────────┐
│ System Prompt(固定) │
│ 用户个性化上下文(可变) │ ← 打断了公共前缀!
│ Tool Schema(固定) │ ← 无法命中缓存
│ 用户问题(可变) │
└─────────────────────────────┘
7.2 Tool Schema 层面:固定排序和格式
固定工具定义的顺序和 JSON key 的排序。 哪怕是 {"a": 1, "b": 2} 和 {"b": 2, "a": 1} 这样仅顺序不同的 JSON,Token 化之后也可能完全不同。
✅ 固定顺序:
tools:
- read_file
- write_file
- grep
- edit_file
❌ 动态顺序(每次请求顺序不同):
tools:
- grep
- edit_file
- read_file
- write_file
7.3 RAG 层面:控制检索结果的稳定性
这是最容易踩坑的地方。 很多人以为 RAG 只是"检索文档然后拼接到 Prompt 里",但动态拼接严重破坏缓存:
- 每次检索返回的文档顺序不同 → 前缀不同 → 缓存失效
- 文档 Chunk 的边界不同 → Token 化结果不同 → 缓存失效
- 动态生成的上下文模板 → 每次格式微变 → 缓存失效
优化策略:
- 对检索结果按固定规则排序(如按文档 ID、相关性分数的确定映射)
- 固定 Chunk 大小和分词策略
- 将 RAG 内容放在 Prompt 的固定位置(如 System Prompt 之后、用户问题之前)
- 缓存检索结果本身——如果用户问了相似的问题,直接复用检索结果,避免重新 Embedding 和检索
7.4 架构层面:利用平台特性
选择支持跨请求缓存共享的平台或推理引擎:
- 使用 Anthropic Claude 时,显式标记缓存断点(
cache_control),将大块静态内容标记为可缓存 - 使用 OpenAI API 时,Prompt Caching 默认启用,但要注意缓存有效期为 5 分钟(Ephemeral)
- 自建推理服务时,优先选择 SGLang 或 vLLM(启用
--enable-prefix-caching),利用 Radix Tree 或 Block 级别的前缀共享
建立缓存命中率监控:
关键指标:
- Cache Hit Rate = cached_tokens / total_prompt_tokens
- 目标值:> 70%(Agent 系统)
- 预警值:< 50%(说明 Prompt 设计有问题)
根据生产环境的观察,缓存命中率的变化通常遵循这样的模式:刚启用时 70%+,随着功能迭代逐渐跌到 18%。这是因为每次功能更新都可能悄悄改变 Prompt 结构,而团队没有持续监控命中率。
第八部分:缓存的成本模型
“缓存命中了所以不计费”——这是另一个常见误解。
不同平台的定价策略不同:
| 平台 | Cache 写入价格 | Cache 读取价格 | 节省比例 |
|---|---|---|---|
| Anthropic | 基础价格 × 1.25(贵 25%) | 基础价格 × 0.1(便宜 90%) | 90% |
| OpenAI | 与普通输入同价 | 约普通输入的 50% | 50% |
| 自建(vLLM/SGLang) | GPU 显存 + 计算成本 | 仅 GPU 显存成本 | 80%+(取决于显存利用率) |
关键结论:
- 缓存不是免费的,但命中率越高,综合成本越低
- Anthropic 的缓存经济模型最激进——写入贵 25%,但读取便宜 90%,适合高并发、大前缀的场景
- 自建推理引擎的缓存"成本"体现为显存占用——KV Cache 会占用大量 VRAM,需要在显存预算和命中率之间做平衡
第九部分:输出为什么不缓存?
你可能注意到了,以上讨论的都是输入侧的缓存。为什么输出不缓存?
因为 LLM 的输出是非确定性的:
- Temperature > 0 时,每次输出的 Token 概率分布不同
- Top-p、Top-k 采样引入随机性
- 同一个 Prompt 多次调用,输出可能完全不同
所以 KV Cache 缓存的是 Attention 的中间计算结果(K/V 矩阵),而不是最终的输出文本。
不过,有一种特殊情况叫 Semantic Cache:
用户问:"北京今天天气?"
→ Embedding → 查找语义相似的已缓存问答
→ 命中:"北京天气 25℃ 晴"(直接返回,不走模型)
这种 Semantic Cache 通常部署在 AI Gateway 层,用 Redis + 向量数据库实现。它与 KV Cache 是完全不同的东西,但可以作为补充优化手段。
第十部分:总结与行动清单
核心结论
LLM 的缓存 = 把历史 Token 的 Attention 计算结果(KV)存到 GPU/内存中复用
"大模块缓存命中率高" = 大量相同前缀 Token 能复用已有 KV Cache
效果 = 降低推理成本 + 提高 TTFT + 提高 GPU 吞吐量
立即行动清单
- 检查当前缓存命中率:查看 Anthropic 的
cache_read_input_tokens或 vLLM 的 prefix cache 日志 - 固定 Prompt 结构:System Prompt、Tool Schema 的内容和顺序保持稳定
- 固定 JSON key 排序:工具定义、配置文件等使用确定性的序列化顺序
- RAG 结果排序稳定化:检索结果按固定规则排序,避免每次拼接顺序不同
- 可变内容放末尾:用户个性化内容、动态上下文放在 Prompt 最后
- 建立监控告警:缓存命中率低于 50% 时自动告警
- 评估平台选择:高并发大前缀场景优先考虑 Anthropic(90% 缓存节省)或自建 SGLang/vLLM
延伸阅读
- vLLM Automatic Prefix Caching 文档
- SGLang RadixAttention 论文
- Anthropic Prompt Caching 指南
- The Five Eras of KVCache - Modular
- Prompt Cache Hit Rate: The Production Metric Your Cost Dashboard Is Missing
- KVFlow: Efficient Prefix Caching for Multi-Agent Workflows
- LMCache: 最快的 KV Cache 层
理解缓存,是理解现代 LLM 推理经济学的第一步。希望这篇文章帮你建立了完整的认知框架。