Claude 如何实现 92% 缓存命中率:一个案例研究
每当一个 AI 智能体执行一步操作时,它都要交一笔“税”。
它会把所有内容从头重新读一遍。
系统指令。工具定义。三轮之前就已经加载过的项目上下文。全部。每一轮都是如此。
这就是上下文税(context tax) 。而对于长时间运行的智能体式工作流来说,它往往是整个 AI 基础设施中最昂贵的一项成本。
来看一笔账:如果一个系统提示有 20,000 个 token,并且运行 50 轮,那就意味着要对 100 万个 token 的冗余计算按原价计费,而这些计算没有产生任何新增价值。
解决办法是提示缓存(prompt caching) 。但要想把它用好,你必须真正理解底层究竟发生了什么。
从“哪些会变,哪些不会变”开始
在你优化任何东西之前,必须先清楚地思考一个智能体提示(也就是上下文)的结构。
你的智能体发送的每一个请求,都包含两个本质上不同的部分:
静态前缀(static prefix) —— 包括系统指令、工具定义、项目上下文、行为规范。这部分内容在一个会话的每一轮中都是完全相同的。
动态尾部(dynamic tail) —— 包括用户消息、工具输出、终端观察结果。这部分内容对每个请求来说都是独特的,并且会随着对话推进不断增长。
这一区分至关重要。静态前缀才是你不断重复计算、却毫无必要的昂贵部分;而动态尾部才是唯一真正需要重新计算的那部分内容。
提示缓存的工作原理,就是把静态前缀对应的数学状态存储起来,这样后续请求就可以完全跳过对它的重新计算。你只需要为这个前缀的处理付费一次。此后每一轮,系统都直接从内存中读取它。
为什么这会有效:Transformer 实际上在做什么
如果你想真正理解缓存为什么这么有效,就必须理解:模型在读取提示时,内部究竟发生了什么。
每一次 LLM 推理请求,都分为两个阶段:
阶段 1:Prefill
这是模型处理完整输入提示的阶段。它是计算受限(compute-bound)的,也就是说,模型会对上下文中的每个 token 执行密集的矩阵乘法运算。模型会读取所有内容,并为它们构建内部表示。这是慢而昂贵的阶段。
阶段 2:Decode
这是模型逐个生成输出 token的阶段。它是**内存受限(memory-bound)**的,而不是计算受限,因为模型在这个阶段花费的大部分时间,是读取之前已经计算好的状态,而不是继续做重计算。
在 prefill 阶段,Transformer 会为每个 token 构建三个向量:Query、Key 和 Value。注意力机制(attention mechanism)会利用这些向量,来判断序列中每个 token 与其他所有 token 之间的关系。
这里有一个关键洞察:Key 和 Value 向量只依赖于它们前面的 token。一旦某个前缀的这些向量被算出来,它们就再也不需要改变。
下面这张图,直观地解释了我们刚才讨论的内容:
如果没有缓存,那么这些 Key-Value 张量(KV tensors) 会在一次请求结束后立刻被丢弃。下一次请求又要从零开始,把那 20,000 个 token 全部重新算一遍。
KV 缓存(KV caching) 的解决方案,就是把这些张量存下来。基础设施会把它们保存在推理服务器上,并用输入文本的密码学哈希值(cryptographic hash) 进行索引。当一个新请求带着同样的前缀到来时,哈希匹配成功,这些张量会被立即取回,模型就能跳过前面的全部重计算。
这会把每个生成 token 的计算复杂度,从 O(n²) 降到 O(n) 。对于一个在 50 轮中不断重复出现的 20,000-token 前缀来说,这种降幅是巨大的。
经济账
真正让这个架构决策变得如此重要的,是它背后的定价结构。
下面是 Anthropic 针对各个模型家族的缓存定价方式:
有三个数字你必须牢记:
- 缓存读取(cache reads) 的价格,是基础输入价格的 10% ,也就是对每一个从缓存中读取的 token 提供 90% 折扣。
- 缓存写入(cache writes) 的价格,比基础输入价格高 25% ,也就是为了存储这些 KV 张量,你只需要付出一个小幅溢价。
- 扩展到 1 小时的缓存,价格是基础价格的 2 倍。
但这个账能不能算得过来,取决于你的**缓存命中率(cache hit rate)**能否保持足够高。这就引出了一个最好的现实世界案例:看看真正做得好的系统,实际会是什么样子。
Claude Code:一个 30 分钟会话的完整拆解
Claude Code 的构建目标,完全围绕着一个核心:始终保持缓存是热的(keep the cache hot) 。
要具体理解这是什么意思,我们不妨走一遍一个典型的 30 分钟编码会话,并精确追踪:到底哪些东西被计费了,哪些没有。
第 0 分钟:会话开始
Claude Code 会加载它的系统提示和工具定义。它还会读取项目根目录中的 CLAUDE.md 文件,这个文件描述了代码库结构和开发约定。这一整段负载,通常会超过 20,000 个 token。
这是整个会话中最昂贵的时刻。每一个 token 都是新的。但这个成本,你只需要付一次。
第 1 到第 5 分钟:第一批命令
你输入第一条指令,例如:“look at the auth module and suggest improvements.”
Claude Code 会派出一个 Explore Subagent(探索子智能体) 。它在代码库中导航、打开文件、执行 grep 命令,并逐步构建出相关代码的整体图景。这些内容都会被追加到动态尾部中。
那 20,000-token 的静态基础部分呢?它已经在缓存里了。现在读取它的价格,是 3.00/MTok。你真正付费的,只是新产生的工具输出和你的消息。
第 6 到第 15 分钟:深入工作
Plan Subagent(规划子智能体) 会接收来自 Explore Subagent 的发现结果。它不会原封不动地转发所有原始结果(那会导致动态尾部无谓膨胀),而是传递一个精简摘要。这样可以让后缀保持可控,并维持高缓存效率。
规划器随后生成一份结构化的实现计划。你审阅、批准,然后 Claude Code 开始动手修改。这个循环中的每一轮,都会从缓存中读取那 20,000-token 的前缀。每一次缓存命中,都会重置缓存的 TTL,从而让缓存继续保持“热”状态,供之后的轮次使用。
第 16 到第 25 分钟:迭代调整
你提出进一步调整要求。Claude Code 修改它的做法。更多的工具调用,更多的终端输出。动态尾部在不断增长,但它增长的部分只包含这个会话中新出现的独特内容。
此时,这个会话累计处理的 token 总量已经达到几十万级别。但那 20,000-token 的基础前缀,在每一轮中都始终是从缓存里读取出来的。
第 28 分钟:运行 /cost
如果没有缓存,这样一个会话很容易就会跨过 200 万 token。按照 Sonnet 4.5 的价格,这差不多就是 6 美元。
而当缓存以高效率运行时:
- 绝大多数 token 都是以 $0.30/MTok 的价格从缓存中读取
- 只有新的动态尾部 token 才会被重新计算
在实际中,对于单个任务,你通常可以期待80% 以上的成本下降。再把这个数字乘上每一个用户、每一天的使用量,影响就会非常可观。
为了总结,下面是随着会话推进,系统提示布局大致会呈现的样子:
会毁掉一切的那条规则
关于提示缓存,有一件最违反直觉的事情是:
1 + 2 = 3。 但 2 + 1 是一次缓存未命中(cache miss)。
底层基础设施会对提示做哈希。这个哈希是密码学里的标识符。只要顺序稍有改变——即便只是两个元素互换了位置——哈希就会变化。缓存就失效了。整个前缀又要按原价重新计算一遍。
由此可以引出三条规则:
不要在会话中途增加或删除工具。
缓存前缀里包含了工具定义。只要工具集合发生变化,后面一切缓存都作废。
绝不要在会话中途切换模型。
缓存是模型专属的。如果你在对话中途切换到一个更便宜的模型,就必须重建整个缓存。
绝不要通过修改前缀来改变状态。
Claude Code 的做法是:在下一条用户消息里加一个标签来提醒系统。前缀本身永远不改。
这对你意味着什么
上面所有内容解释的是 Claude Code 如何处理缓存。如果你正在构建自己的智能体,同样的规则也完全适用。
你的提示应该这样组织:
最顶部是系统指令和规则。中途不要改。
然后是你预先加载好的全部工具。中途不要增加,也不要删除。
再下面是检索到的上下文和文档。在整个会话期间保持静态。
最底部则是对话历史和工具输出。
当自动缓存(auto-caching)开启后,随着对话推进,缓存断点会自动向前移动。
Claude Code 会自己管理它的缓存。Anthropic 现在也已经把自动缓存加入 API 中,所以你也能为自己的智能体做到同样的事。
在没有自动缓存的时候,你必须自己记住 token 边界在哪里。只要边界划错,就等于根本没用上缓存。
当你为了上下文窗口上限而做压缩(compaction)时,应使用cache-safe forking:保持同样的系统提示、工具和对话历史不变,然后把压缩操作作为一条新的消息追加进去。
压缩调用看起来几乎和上一次完全一样。缓存前缀会再次被重用。唯一按“新增内容”计费的部分,只是那条压缩指令本身。
如果你想判断 API 是否真的按预期工作,就要在每次响应中盯住这三个字段:
cache_creation_input_tokens:写入缓存的 token 数cache_read_input_tokens:从缓存中读取的 token 数input_tokens:按正常方式处理的 token 数
你的缓存效率分数,本质上就是“读取 token 数”相对于“创建 token 数”的比例。你应该像监控服务可用性一样,持续盯着它。
关键结论
提示缓存并不是一个“打开就行”的功能。它是一种你必须围绕其去构建的架构纪律(architectural discipline) 。
Claude Code 是目前最好的例子,它展示了:当这件事在大规模上真正做到位时,会是什么样子。
92% 的缓存命中率。81% 的成本下降。
如果你正在构建智能体,这就是蓝图。你无法忽略“上下文税”;它是客观存在的。唯一重要的问题是:你是在继续为它买单,还是在把它消掉。
参考资料:
https://www.dailydoseofds.com/p/kv-caching-in-llms-explained-visually/
https://x.com/trq212/status/2024574133011673516?s=20