人工智能|大模型——大语言模型提示词缓存详解(Claude Code案例)
AI 智能体每执行一步都会把完整对话历史重新发送给大语言模型。其中包含系统提示指令、工具定义以及三轮之前就已经处理过的项目上下文。每一轮交互中全部内容都会被重新读取、重新处理并且重新计费。对于长期运行的智能体工作流这类冗余计算往往是整套AI基础设施中开销最大的一项。一条 2 万 token 的系统提示词经过 50 轮交互就会产生 100 万 token 的冗余计算且全部按原价计费却不产生任何新增业务价值。该成本还会随着每一位用户、每一次会话持续累积放大。解决方案就是提示词缓存。但想要用好它你需要搞懂底层实际的运行机制。一、静态上下文 vs 动态上下文在对提示词做优化之前你需要分清哪些内容会发生变化、哪些内容保持不变。每一次智能体请求都包含两类性质完全不同的组成部分内容特征静态前缀system 指令、tool 定义、项目上下文、行为规范每轮都一样永远不变动态后缀用户消息、模型回复、tool 输出、终端观察每轮都在增长正是这种内容划分让提示词缓存得以实现。基础设施会保存静态前缀的数学计算状态后续凡是携带完全相同前缀的请求便可以直接跳过这部分计算从内存读取结果。一旦理解了这一点本文中所有架构层面的设计思路就都一目了然。二、KV Cache 到底在缓存什么想要理解缓存为何效果显著你需要搞清楚 Transformer 在处理提示词时究竟做了哪些工作。每一次大语言模型推理请求分为两个阶段Prefill 阶段处理你输入的整个 prompt。在所有 token 上跑一轮密集矩阵乘法构建模型的内部表示。这个阶段是compute-bound算力瓶颈非常贵。Decode 阶段一个 token 一个 token 地生成。每次把新 token 追加到序列里预测下一个。这个阶段是memory-bound显存瓶颈因为大部分工作是在读历史状态。在预填充阶段prefill phaseTransformer 会为每一个 token 计算三组向量查询向量Query、键向量Key与值向量Value。注意力机制依靠这三组向量判断各个 token 之间的关联关系。任意 token 对应的键K、值V向量仅依赖于它前面的 token一旦计算完成就不会再发生改变。没有缓存的情况下这些 Key 和 Value tensor 在每次请求结束后就被丢掉了下次请求要从头再算一遍。对于 2 万 token 的前缀那就是 2 万 token 的 attention 计算——完全没必要重复。KV Cache的做法是把这些 tensor 持久化在推理服务器上用 token 序列的加密哈希做索引。当新请求进来、前缀完全一致时哈希命中tensor 直接从内存里加载这段 prefill 计算整个跳过。这把每生成一个 token 的计算复杂度从O(n²)降到O(n)。对于一个重复使用 50 轮的 2 万 token 前缀省下的算力是非常可观的。三、prompt caching与KV cache的区别我认为其区别在于KV Cache 是 single session 的 cache而 prompt cache 是支持 cross global/project/session 的 cache也许这里用 sequence 而非 session 会更好。分析下图图a代表的是最原始的自回归计算过程如上文所述某些 token 需要重复多次计算。图b则是 KV cache多出了一个attention states的部分这部分可以存储已经计算过的结果再下一次自回归计算中复用这大大降低了计算量。。图c则是我们的主角 prompt cache它在 KV cache 的基础上多了一个 prompt cache。本质上就是增加了一个全局的管理器来实现跨序列的缓存。它可以将其他序列中缓存的结果提供给本次推理使用所以它提供的是一个 overall 的加速而非单次推理的加速。讲到这里有点类似 PagedAttention通过降低显存占用来间接地提升吞吐当然这是我粗糙的理解这里的缓存不一定在显存/内存上甚至可能是磁盘。至于性能我们一次性读取大量的 tokens 其实也没那么慢。相比计算的开销来说所以回过来 prompt cache 和 kv cache 的区别在于prompt cache 提供的是一个跨序列的 cache它的效果也是作用于全局多次而非单次推理。prompt cache 最大的问题就是如何维护与识别区别于 kv cache 本质上是“一定”会被复用prompt cache 可能会面临存储后无人问津的尴尬情况并且如何识别也是影响缓存命中的关键因素。四、经济账定价结构才是让这个架构决策真正有分量的地方。Cache Read0.1x 的基础输入价格——也就是九折优惠打完一折。Cache Write1.25x 的基础价格——存 KV tensor 要加收 25% 溢价。Extended 1-hour Caching2.0x 的价格。下面是 Claude 各模型的具体价格但这笔账只有在缓存命中率够高的时候才划算。五、与Claude Code 开展一次 30 分钟的编码会话Claude Code 的设计核心只有一个目标维持缓存热状态。下面从计费角度看看一场真实的 30 分钟编码会话实际运行情况。第 0 分钟Claude Code 加载系统提示词、工具定义以及项目的CLAUDE.md文件。该载荷超过 20000 tokens由于全部都是全新tokens这是整个会话开销最高的阶段但这笔成本仅需支付一次。第 1‑5 分钟你开始下达指令Claude Code 调用探索子智能体浏览代码库、打开文件、执行 grep 检索命令。所有这些内容会追加到动态后缀上下文。而 20000 tokens的静态前缀此时走缓存读取单价为每百万tokens 0.3 美元而非原价 3 美元每百万tokens。第 6‑15 分钟规划子智能体接收精简摘要而非原始输出结果直接传递原始输出会无谓膨胀动态后缀。子智能体生成实施方案你确认方案后Claude Code 开始修改代码。每一轮交互都会从缓存读取静态前缀缓存命中率攀升至 90% 以上每次访问都会重置生存时间TTL持续保持缓存热状态。第 16‑25 分钟你提出修改需求触发更多工具调用、终端输出动态后缀中累积的上下文持续增加。此时会话已经处理数十万tokens但每一轮交互那 20000 tokens的基础上下文都来自缓存读取。第 28 分钟你在终端执行/cost命令查看开销。如果不启用缓存按照 Sonnet 4.5 的计价标准200 万tokens需要花费 6 美元。在缓存效率 92% 的条件下其中 184 万toknes为缓存读取总开销仅 1.15 美元。单任务成本直接降低 81%。这就是一个热缓存应该长的样子为静态地基付一次钱然后免费读取任意多次。动态尾部是唯一会被持续计费的部分。使用体感我平时重度用 Claude Code每次敲/cost都会注意一下cache_read_input_tokens这一栏——动辄占到整个输入量的 90% 以上这个数字非常直观地告诉你缓存是不是在工作。如果哪天命中率突然掉下去基本就是提示你你可能刚刚做了什么破坏缓存的事——比如中途换模型、或者手抖改了某个 subagent 的定义。六、哈希缓存的脆弱性关于 prompt caching最反直觉的一点是“1 2 3” 可以命中缓存但 “2 1” 就是一次 miss。底层基础设施会对从开头开始的完整tokens序列做哈希计算。只要该序列里有任何内容发生变动哪怕仅仅是两个元素的顺序调换哈希值就会改变整个前缀部分都要重新计算按全价计费。这并非一个无关紧要的实现细节而是 Claude Code 所有工程设计决策都必须围绕的核心约束条件。以下是生产环境中真实发生过的缓存失效案例在系统提示词中插入时间戳导致每一次请求都生成独一无二的哈希值。JSON 序列化器在不同请求之间对工具模式的键采用不同排序方式造成前缀缓存失效。在会话中途更新 AgentTool智能体工具的参数直接清空整个 20000 tokens的缓存。由此衍生出三条使用规则会话过程中不要修改工具。工具定义属于被缓存的前缀部分新增或删除工具会让后续全部缓存失效。会话中途切勿切换模型。缓存是绑定特定模型的对话中途切换成本更低的模型需要从零重新构建全部缓存。禁止通过改动前缀来更新状态。Claude Code 不会去修改系统提示词而是在下一条用户消息后追加提醒标记以此保证前缀内容保持不变。补充一条在写自定义 Agent 的时候注意 Python dict 序列化成 JSON 时的 key 顺序。Python 3.7 的 dict 是有序的但如果你的 tool schema 有一部分来自合并多个 dict 或者来自数据库查询顺序可能每次都不一样。这种隐性的不确定性是最容易让你整夜找不到原因的 cache miss 来源。七、应用到自己的Agent将这套原则应用到你自己开发的智能体上无论你是直接使用 Claude Code还是从零自研智能体上述规则同样适用。请按照如下顺序组织你的提示词最上方放置系统指令与行为规则会话过程中不要改动。一次性加载全部工具定义不要新增、也不要删除工具。紧随其后放入检索得到的上下文与参考文档在整个会话周期内保持内容稳定不变。最底部存放对话历史和工具返回结果这部分就是你的动态后缀。如果你用的是 Anthropic API 的auto-caching缓存断点会随着对话增长自动推进。如果不用 auto-caching你得手动管理 token 边界——边界错了一个位置就意味着完全错过缓存。对于上下文压缩当你快接近上下文上限时要用“缓存安全的分叉”这种做法保留同样的 system prompt、tool、对话历史然后把压缩指令作为一条新消息追加在后面。缓存前缀得以复用真正要被计费的新 token 只有压缩指令本身。验证缓存是否在工作盯死 API 响应里这三个字段字段含义cache_creation_input_tokens写入缓存的 token 数cache_read_input_tokens从缓存读取的 token 数input_tokens没走缓存、按全价计费的 token 数缓存命中率cache_read_input_tokens / (cache_read_input_tokens cache_creation_input_tokens)把它当成你的可用率uptime指标来盯。命中率掉下去就是警报。八、prompt cache命中机制在如何使用的 part 中介绍的是最粗暴的前缀匹配即 OpenAI 使用的。同时在思考环节遇到了一个问题“如果我们只有中间某处 prompt text 不同怎么办呢”进一步推广“如果我们的 prompt 的存在一个范式只在某些固定位置不同怎么办呢例子可以参考 python 的 String Formatting比如下图两个 prompt 只有某个特定位置的 token 不同这时候简单的前缀匹配就会失效。因此有研究提到了Prompt Markup LanguagePML什么是 PML 呢提示标记语言PML旨在以结构化的方式表示提示特别是突出 prompt 中的不变 固定部分和可变 可更改部分。正如当前缀匹配遇到只有中间某处不同的 prompt 时它就失效了。而 PML 通过明确标记出这些可变部分巧妙地解决了这个问题。我举一个简单的例子大家一看便知item、quantity和no是可变槽位代表可以更改的部分。其余部分是固定文本。现在即使两个顾客点了不同的菜品、数量和忌口PML 都能识别出它们遵循相同的模板并准确地识别出哪些部分是可变的。所以相较于粗暴的前缀匹配PML 可以基于模版识别实现更准确的匹配 并且得到更高效的缓存。 我们还需要考虑一个问题只有当一个文本片段在大型语言模型LLM输入中出现在相同位置时其注意力状态才能被重用。原因是Transformer 将位置信息整合到了键k和值v注意力状态中也就是位置编码。对于单个 prompt 的 KV cache 而言这并不是问题因为在所有步骤中相同的 prompt 都位于相同的位置即输入的开头。那么如果我们使用非前缀匹配就会遇到这个问题即如何处理位置编码的影响共享的文本在不同 prompt 中可能出现在不同位置。为了实现跨 prompt 即不同位置的注意力状态重用cache system 必须解决两个问题首先尽管一个文本片段可能出现在不同 prompt 的不同位置系统仍须支持其重用。其次当系统接收到一个新的prompt时它必须能够高效地识别出其注意力状态可能已被缓存的文本片段从而实现重用。为解决这两个问题已有研究结合了两种思路。首先提出使用提示标记语言PML来明确提示的结构将可重用文本片段明确表示为 module即 prompt module。PML 很好的解决了上述的第二个问题识别哪些是重复文本片段还为解决第一个问题提供了可能因为每个 prompt module 都可以被分配唯一的 Position IDs。其次已有研究通过实验发现LLM 可以处理带有不连续 Position IDs 的注意力状态。只要 tokens 间的相对位置保持不变输出质量就不会受到影响。Our second idea is our empirical finding that LLMs can operate onattention stateswith discontinuous position IDs. As long as the relative position of tokens is preserved, output quality is not affected.这意味着我们可以提取不同的注意力状态片段并将它们拼接起来以构建新的语义。利用这一特性用户可以根据自身需求选择提示模块甚至在运行时替换某些含义。那另一个问题是由于位置的变化即使相同的文本在真正推理时也会是不同的 kv attention state。但是我们使用的是相同的 kv attention state是否会影响输出质量呢在论文 5.3 节中提到在所有数据集中使用 prompt cache 输出的准确性与 baseline 相当。但是仍然会有细微差异所以基于 PML 的 prompt cache 并不像 kv cache 一样是完美的替换。当然这里的解释还点模糊我暂时留下一个坑阅读完相关代码后我会在这里尝试进一步解释什么是“LLM 可以处理带有不连续 position ids 的注意力状态“。同时如何前文所说Claude 提供的cache_control特性某种程度上可以实现类 PML的效果。对比 OpenAI 和 ClaudeOpenAI 提供一个更直接的 caching 策略更用户友好但是在特定情况下容易失效。Claude 提供的是一个更专业的caching 策略初学者可能难以看懂但是学会后可以实现最佳的缓存性能。回答最后一个问题“使用 prompt caching 时是否会反过来影响输出的质量呢” KV cache 是不会影响推理的效果因为它是等价替换。同理使用基于前缀匹配的 prompt cache 也不会影响推理的效果。但是在上文分析中我们发现虽然实验结论证明基于 PML 的 prompt cache 能和 baseline 得到接近的输出但是它们之间还是存在不同并不是完美的等价所以在使用基于 PML 的 prompt cache 时应当考虑匹配机制对于输出质量的影响。九、TakeawaysPrompt Caching 不是一个你打开开关就能用的特性而是一种必须贯穿到架构层面的纪律。核心思想其实简单到一句话把静态内容放最上面动态内容从下面扩充。基础设施会对前缀做哈希、存储 KV tensor、然后在你每一次读取时给你九折。但纪律藏在所有细节里不要往 system prompt 里注入时间戳不要打乱 tool 定义的顺序不要在会话中途切换模型不要在缓存断点上游去修改任何东西持续监控和优化定期检查缓存相关的指标例如缓存命中率、延迟和缓存的 token 百分比根据实际使用情况优化 prompt 和缓存策略。这能确保缓存机制始终保持良好的效果。优化请求策略请使用较长的 prompt并在非高峰时段发起 API 请求因为在高峰时段缓存淘汰更为频繁。并且最近未使用的 prompt 会自动从缓存中移除。为了最大限度地减少淘汰请保持使用相同 prompt 前缀的连续请求流。通常情况下API 只对长度超过某个阈值的 prompt 进行缓存阈值通常是 1024且缓存的前缀通常在 5 到 10 分钟的不活动状态后失效。但是在非高峰时段缓存可能会持续长达一小时。[Claude Only]Claude 并不会像 OpenAI 一样自动的启用 prompt caching它需用手动的添加cache_control标识。因此通过巧妙设置缓存断点将不同的可缓存前缀部分加以区分。这样做既可以让缓存更有条理又能提高缓存命中率。Claude Code 在生产规模上演示了这套纪律的效果92% 的缓存命中率81% 的成本削减。如果你正在搭 Agent 却没有围绕 prompt caching 做设计你就是在把大部分利润留在桌上。七、参考资料(27 封私信 / 80 条消息) Prompt caching一篇就够了。 - 知乎Avi Chawla on X: Prompt caching in LLMs, clearly explained / XPrompt Caching 深度拆解Claude Code 是如何做到 92% 命中率的 — 鬼哥的空间[2311.04934] Prompt Cache: Modular Attention Reuse for Low-Latency Inference