第 19 章 第六部分:专题研究

专题:Prompt Caching 策略逆向

14 个缓存失效向量、静态/动态边界分离、7 大架构策略——Claude Code 如何用缓存降低 80% 成本

Claude Code Prompt Caching 成本优化 KV缓存 专题研究

为什么这个专题值得单独写

Prompt Caching 不是一个”有了更好”的优化——对 Claude Code 这样的多轮交互 Agent 而言,它是生存必需品。没有缓存,一个 100 轮的 Opus 会话成本在 $50-100;有缓存(90% 命中率),成本降到 $10-19。

更关键的是,Claude Code 团队围绕缓存做了大量架构决策——很多看起来不合理的设计(比如 Plan Mode 不移除工具,而是加新工具),背后的原因都是缓存。

本章基于源码中的 splitSysPromptPrefix、addCacheBreakpoints、promptCacheBreakDetection.ts 以及 Thariq Shihipar(Claude Code 工程师)在 Claude Code Camp 上的分享做逆向分析。


Prompt Caching 的技术原理

每次在 Claude Code 中发送消息,整个对话历史都被重新发送到 API。第 10 条消息会发送系统提示 + 工具定义 + CLAUDE.md + 前 9 轮对话。

Prompt Caching 的本质是存储 Transformer 在处理前缀 token 时产生的 KV 缓存(Key-Value Cache)——注意力层中已计算的 Key 和 Value 向量。如果下一次请求的前缀相同,就直接复用这些向量,跳过重复计算。

前缀匹配的绝对性

如果前缀中任何一个字节发生变化,该位置之后的所有缓存全部失效。

来自 Claude Code Camp 的实验验证——把 “senior software engineer” 改为 “Senior Software Engineer”(仅两个字母大小写变化):

cache_read_input_tokens: 0  ← 完全缓存未命中

2,727 个 token 的 KV 计算全部作废。

服务端存储规模

缓存存的不是文本——是 GPU 显存中的 KV 矩阵。一个 100K token 的提示,KV 缓存约 500MB-1GB。这就是缓存写入有 25% 溢价的原因。

TTL 与热保持

⚠️ 2026-09-12 重要更正:本节原文写「默认约 5 分钟不活跃后过期」,并把 1 小时描述为需要显式开启的扩展选项。对订阅且在套餐额度内的用户(本章目标读者的主流形态)而言这是事实性错误——官方 prompt-caching 文档明确:主对话在订阅且额度内默认就是 1 小时,5 分钟反而是降级后的行为。以下按官方口径重写。

✅ 复核状态:截至 v2.1.286(2026-09-30),本节按请求桶 × 计费方式的二维 TTL 表述仍然成立,未被后续版本推翻。这一注记的作用是防腐烂——本节已被纠错过一次,下次扫描若没有复核注记,无法区分「仍然正确」与「没人看过」。

官方把每个请求归入两个固定桶:

  • 主对话(Main conversation):交互轮次、非交互 -p 运行、Agent SDK 轮次,以及与它们内联运行的 helper
  • 其他一切(Everything else):subagents、workflows、in-process teammates、forks、compaction、会话标题

Claude Code 逐请求决定 TTL。除非你自行指定,它只在「Claude 订阅且在套餐额度内」时请求 1 小时 TTL,且只给主对话(外加 Anthropic 服务端控制的一小部分 helper 请求)。两个桶在两种计费方式下的默认 TTL:

请求桶订阅且额度内动用 usage credits / API key / 云厂商
主对话1 小时5 分钟
其他一切5 分钟(服务端控制的 helper 请求除外,为 1 小时)5 分钟

另有两条必须记住的边界:

  • 超出套餐额度后自动降级:一旦超出套餐额度、Claude Code 动用 usage credits,主对话会被降回更便宜的 5 分钟 TTL
  • 子代理永远不在主对话桶内:即使在订阅下,子代理默认也只有 5 分钟,需显式为该桶设置才能延长(见后文「子代理不共享父缓存」)

其他保持不变的规律:

  • 每次缓存命中会重置计时器——活跃编码会话中,缓存持续保持热状态
  • 写入成本:Cache Write 5min = $6.25/M(1.25× 基础价),Cache Write 1h = $10/M(2× 基础价,2 次缓存读取就回本)
  • Auto-caching:只需在请求中添加 cache_control 字段,系统自动缓存最后一个可缓存块并随对话增长滑动断点,使用 4 个断点配额中的 1 个

⚠️ 下方实测表的口径:「5 分钟 28% → 1 小时 89%,成本降 64%」这组实测的 5 分钟基线,对应的是非订阅额度内的档位(API key / 云厂商 / 已动用 usage credits)。对订阅且额度内的读者而言,1 小时已经是默认行为,这组数据的意义在于说明「降级到 5 分钟会损失多少」,而非「开启 1 小时能获得多少」。

缓存 TTL 控制环境变量

v2.1.108(2026.04.14)引入两个关键环境变量(至今仍是优先级链中的第 1 级与第 5 级):

环境变量效果适用场景
ENABLE_PROMPT_CACHING_1H=1请求 1 小时 TTL(两桶都生效)长会话重构、多轮 Agent 任务
FORCE_PROMPT_CACHING_5M=1强制 5 分钟(两桶都生效)调试缓存行为、对比两种 TTL、覆盖托管设置里的更长 TTL

6 级优先级(官方文档「Choose the TTL yourself」原文)。两个桶各有自己的设置项与环境变量,每个控制只接受 5m 或 1h,其他值一律被忽略:

  • 主对话:设置项 promptCacheTtl,或环境变量 CLAUDE_CODE_PROMPT_CACHE_TTL
  • 其他一切:设置项 subagentPromptCacheTtl,或环境变量 CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL

当多个控制同时生效时,Claude Code 取第一个匹配:

  1. FORCE_PROMPT_CACHING_5M=1——两桶都强制 5 分钟
  2. 该桶的环境变量
  3. 该桶的设置项
  4. 对子代理请求,其 frontmatter experimental 字段里的 cacheTtl(需 v2.1.248+;订阅动用 usage credits 时会忽略 1h)
  5. ENABLE_PROMPT_CACHING_1H=1——两桶都请求 1 小时
  6. 该桶的默认值(即上一小节的二维表)

⚠️ 版本门槛:promptCacheTtl / subagentPromptCacheTtl 两个设置项及其环境变量都需要 v2.1.242+(changelog v2.1.243,2026-08-25)。在低版本上配置不会报错,只会静默失效。截至 v2.1.286(2026-09-30)复核,官方 prompt-caching 文档仍写明该门槛,本条未变。

官方给 API key / 云厂商用户的明确建议:把 promptCacheTtl 设为 1h,以让主对话获得 1 小时缓存;该桶之外的请求在你也为它们指定之前,仍保持 5 分钟默认。

实测数据(3 小时重构会话):

TTL 设置缓存命中率成本
5 分钟(默认)28%基准
1 小时89%降低 64%

适用于 API、Bedrock、Vertex、Foundry 全后端。选择 1 小时 TTL 的关键判断:如果会话连续活跃超过 5 分钟(即 Agent 循环不会中断超过 5 分钟),1 小时 TTL 几乎无额外成本但命中率大幅提升。


SYSTEM_PROMPT_DYNAMIC_BOUNDARY:核心设计

源码中最关键的缓存相关常量:

// claude-code-main/src/constants/prompts.ts
const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =
  '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'

注释说得很清楚:

“Everything BEFORE this marker can use scope: ‘global’. Everything AFTER contains user/session-specific content and should not be cached.”

splitSysPromptPrefix 函数根据这个标记将系统提示拆分为三层缓存策略:

层级内容缓存范围
Attribution Header计费头不缓存
边界标记之前角色定义、安全规则、工具使用规范global(跨所有用户)
边界标记之后环境信息、用户配置、CLAUDE.md不缓存或 workspace

2026.02.05 变更:Anthropic 将缓存隔离从 organization 级改为 workspace 级。同一组织下不同 workspace 的缓存不再共享。这意味着多 workspace 组织的缓存效率会下降——此前共享的全局缓存现在按 workspace 隔离。Bedrock 和 Vertex AI 仍保持 organization 级隔离。

静态部分全用户一致,可全局缓存——这意味着 Anthropic 只需要为所有 Claude Code 用户维护一份共享的 KV 缓存。

传参形式本身是一个缓存变量(v2.1.275)。官方在 v2.1.275 改进了含 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 行的 --system-prompt 的缓存行为:该行上方的文本现在被全局缓存,官方原文接了一句 “as the SDK’s array form already is”。这句话给出了一个实现成熟度阶梯:SDK 的数组形式早已支持全局缓存,字符串形式直到 v2.1.275 才追平。

对 -p 与 SDK 用户而言,这条的含义是具体的:同一个系统提示,用数组传和用字符串传,在 v2.1.275 之前的缓存效果并不相同。所以本章讲的「静态/动态边界」不只是「内容怎么分」,还包括「用什么形式把这份内容递进去」——后者在 v2.1.275 之前是一个隐性变量。

多云缓存陷阱:在多云部署(Bedrock/Vertex AI/Foundry)中使用 Prompt Caching 时需注意平台差异——Anthropic API 层的缓存优化不一定在所有平台上正确计费。v2.1.211(2026-07-15)修复了一个持续数周的严重 bug:系统上下文块在 Bedrock/Vertex/Mantle/Foundry 上被错误地按”新鲜输入 token”计费。受影响用户的实际缓存节省可能接近于零——虽然缓存在技术上命中,但计费端未应用 cache_read 折扣。建议多云用户在升级到 v2.1.211+ 后验证 usage 响应中的 cache_read_input_tokens 与账单匹配。

偏差是双向的(v2.1.286)。上面这个案例的方向是「缓存在技术上命中,但账单没折扣」——用户多付。v2.1.286(2026-09-30)修的是反方向:Claude apps 网关的 spend meter 把 1 小时 prompt cache 写入按更便宜的 5 分钟费率计价,也就是账单少计;同一条目还修了流式 turn 中若运行 web search 这类服务端工具时只计第一次模型调用的输入 token——这属于计量口径偏差,与前面两条的费率口径偏差又不是同一回事。

三个实例合起来,本节结论应该写成一句更准确的话:

缓存命中是一个技术事实,账单是另一个系统的输出,两者之间的偏差可以是双向的。 因此不能只验证「我有没有被多收」,也要意识到「账单显示的成本低于实际消耗」同样可能。

对应的验证动作也要跟着扩:不只是核对 cache_read_input_tokens 与账单,还要按 TTL 档位分别核对写入计费——5 分钟写入与 1 小时写入报在不同的字段(ephemeral_5m_input_tokens / ephemeral_1h_input_tokens,见诊断工具链第 3 项),两者被按同一个费率计价时,从总额上是看不出来的。


14 个缓存失效向量

promptCacheBreakDetection.ts 追踪 14 种可能导致缓存失效的条件。这个模块在每次 API 调用后检查 cache_read_input_tokens,如果发现意外下降,就分析是哪个向量导致的。

这些向量覆盖了系统提示变化、工具列表变化、MCP 工具变化、模型切换、CLAUDE.md 变化等方方面面。每个向量都通过 computeHash 计算配置哈希,与上一次状态对比。

关键设计:当检测到缓存中断时,系统会将差异信息写入调试文件,便于事后排查成本异常。

第 15 个向量:Thinking Block 缓存失效

当 non-tool-result 用户消息块添加时,所有先前的 thinking blocks 会被从上下文中移除,导致大面积缓存失效。这对 Agent 架构有重要影响:如果 Agent 循环中用户发送非工具结果的文本消息(如直接输入文字而非工具返回结果),会触发 thinking 块缓存全部失效。

这意味着 Agent 设计中应尽量避免”工具返回 → 用户文字输入 → 继续 Agent 循环”的模式——用户文字输入应通过 <system-reminder> 标签注入,而非作为独立用户消息块。

系统级失效:与用户行为无关的一类

前 15 个向量有一个共同前提:是用户或配置做了什么才触发失效。官方 changelog 里还记录了一批纯源码分析很难发现的类别——用户什么都没做,缓存照样失效。

需要先点明这张表的性质:它本质上是一份「官方已修 bug 的逆向清单」。本节每一行的来源都不是源码,而是 changelog 里被标为 Fixed 的条目——把别人修掉的 bug 反过来读成架构事实,这是本章方法论上比较独特的一处,也是这张表只能靠持续跟版维持的原因。

子类一:恢复态重渲染(resume / relaunch / compaction)

恢复一个会话时,系统需要把「当时的上下文」重新发一遍。只要重发的方式与首次不同,前缀就变了。

失效路径官方修复版本
恢复前台 spawn 的子代理——改变其工具列表与系统提示前缀;teammate 与恢复的子代理在后续轮次把 SubagentStart hook 上下文和预加载技能移出前缀v2.1.265
中途打断 Claude 思考后恢复会话——早先上下文的重发方式改变,伤害缓存复用v2.1.269
/clear 之后继续的会话(restart / --continue / --resume)在 SessionStart hook 打印了输出时,丢失首条消息的一部分 → 完全未命中v2.1.277
会话早先记录的附件在 resume / relaunch 后被重新渲染 → 丢弃 extended thinking 并 miss prompt cachev2.1.277
恢复的子代理与 teammate 重新渲染已加载的 MCP 工具定义 → 破坏 prompt cachingv2.1.277
恢复的 fork 子代理重建工具列表,而非重发首次使用的那份 → 破坏该 agent 的 prompt cachingv2.1.280
恢复的记忆文件的 age note 在 compaction / resume 之后于请求之间变化 → prompt cache missesv2.1.275
在待处理权限提示期间重启、之后恢复的会话发送了与之前不同的历史 → 从该点起破坏 prompt cachev2.1.281

子类二:外部连接抖动(MCP / 网关 / LSP)

失效路径官方修复版本
语言服务器中途断开/重连——整个 prompt cache 失效v2.1.235
LLM 网关 / 自定义 ANTHROPIC_BASE_URL 场景v2.1.237
MCP server 在会话中途断开,或 resume 后仍在连接(tool search 关闭时,例如走代理或网关)→ prompt cache 丢失v2.1.281
云端会话的 connector 工具列表不跨重启复用——MCP_DISCOVERY_CACHE=1 须设在云环境变量而非 settings 文件才生效;其他 MCP server 不再缓存、启动时连接v2.1.285

子类三:宿主应用侧干预(Desktop / VS Code / SDK)

失效路径官方修复版本
从宿主应用(Claude Desktop / VS Code / SDK)在 Claude 工作时切换模型 → 下一次 prompt 缓存未命中v2.1.280

这一条与策略 3「不要中途切换模型」是同一件事的两个方向:章内讲的是用户在会话内切模型,这里讲的是宿主应用在会话外替你切。两者都落在「缓存按模型隔离」上,但后者的触发点不在用户的控制范围内。

子类四:内建命令自伤(一等命令与开关)

这是全表最反直觉的一类:用户为了管理会话而执行官方命令,反而销毁了会话缓存。

失效路径官方修复版本
/login、/upgrade、/extra-usage 丢弃会话中先前的 thinking → 下一次请求完整 prompt-cache 重写v2.1.273
DISABLE_PROMPT_CACHING_HAIKU 在 Haiku 是会话主模型时完全无效v2.1.283
/model 在「切回会话实际运行过的那个模型」时误报会丢失会话缓存v2.1.271

前两行的失效形态不同:v2.1.273 是真失效(thinking 被丢,缓存重写),v2.1.283 与 v2.1.271 是控制失效(开关不起作用)与误报(警告是假的)。三者合起来说明一件事:命令层的缓存语义没有被当成一等接口来维护,所以在排障时不能默认「官方命令对缓存是中性的」。

子类五:运行时周期性抖动

失效路径官方修复版本
OAuth token 刷新导致 tool definitions 重渲染——长会话中约每小时造成一次未命中,并丢失 extended-thinking 上下文v2.1.248(另有 v2.1.259 修复 telemetry 关闭会话的同类问题)
输出达到 token 上限被自动续写——下一轮 prompt cache 部分失效v2.1.269

这一类对告警策略有直接影响:本章第 3 条「命中率突然降为 0 就是告警」在这类路径上会产生周期性假阳性——周期性的未命中本身就是 token 刷新节律造成的,不是配置变更。补充判据:若未命中呈约一小时的周期性,优先怀疑 OAuth 刷新,而不是去查配置改动。


七个架构策略

基于源码分析和 Claude Code Camp 的分享,总结出 Claude Code 团队的七个缓存架构策略:

1. 静态内容前置

系统提示的布局严格遵循:

系统提示(静态,全局缓存)
  → 工具定义(启动时锁定)
    → CLAUDE.md(org 级缓存)
      → 对话历史(动态)

每一层都不变或低频变化,最大化前缀匹配长度。

2. 日期和状态放用户消息

日期、Git 状态等每轮都变化的信息,放在用户消息的 <system-reminder> 标签中,不放进系统提示。

❌ 系统提示中:"Today is April 12, 2026"  → 每天变化,破坏缓存
✅ 用户消息中:"<system-reminder>Today is April 12</system-reminder>"  → 系统提示不变

3. 不要中途切换模型

缓存按模型隔离。需要不同模型时使用子代理(subagent),而不是在同一会话中切换——这正是 Claude Code 多代理架构(第 8 章)的一个设计动机。

4. 不要中途增删工具

工具定义是缓存前缀的一部分。MCP 工具列表在启动时锁定(第 10 章),会话中途不增删。

这解释了一个看起来反直觉的设计:Plan Mode 不移除工具。进入规划模式时,Claude Code 不是移除文件编辑工具,而是添加 EnterPlanMode/ExitPlanMode 工具 + 消息指令。添加不影响已有前缀,但移除会破坏。

5. 延迟加载工具,但不改变工具池

不常用工具以轻量存根形式出现(仅名称 + 简短描述 + defer_loading 标记)。当 AI 需要使用时,通过 ToolSearch 加载完整 Schema。存根始终在,所以前缀不变。

平台侧的对应机制(v2.1.285)。策略 4 与策略 5 讲的都是用户侧纪律:不要在会话中途改变工具集。云端会话里现在有对应的平台侧开关——MCP_DISCOVERY_CACHE=1 可让 connector 的工具列表在会话重启后被复用,而不是重新拉取(其他 MCP server 不再缓存、改为启动时连接)。

这里有一个容易被忽略的细节:配置位置本身有语义。官方原文明确要求把这个变量设在云环境变量里,而不是 settings 文件——设在 settings 文件里不生效。也就是说,工具列表稳定性已经不再纯粹是用户纪律问题:平台给了开关,但开关的语义依赖你把它写在哪里。这与系统级失效表子类二里那条 v2.1.285 是同一条 changelog 的两个切面。

6. 压缩复用父缓存

当上下文窗口满时,Claude Code 执行压缩(Compaction)——用模型生成摘要替换早期消息。关键设计:

  • 压缩请求使用完全相同的前缀(系统提示 + 工具 + CLAUDE.md)
  • 仅对话部分被替换为摘要
  • 前缀的 KV 缓存被完全复用
  • 相比 /clear(可能丢失缓存状态),压缩保留了更多缓存

7. Fork 操作共享前缀

子代理(Fork)继承父会话的系统提示和工具定义。从 addCacheBreakpoints 的注释中:

“For fire-and-forget forks (skipCacheWrite) we shift the marker to the second-to-last message: that’s the last shared-prefix point, so the write is a no-op merge on mycro.”

skipCacheWrite 参数用于控制 Fork 不写入自己的缓存尾部——避免污染父会话的缓存空间。

对称的另一侧:普通子代理不共享父缓存。Fork 会共享,但子代理(subagent)不会。官方「Subagents and the cache」章节明确:子代理以自己的系统提示与工具集开启一个独立对话,其首个请求不读父缓存(前缀不同),只能在自己的轮次中预热自己的缓存。更关键的是——子代理落在主对话 TTL 桶之外,因此即使在订阅下也只有 5 分钟,除非显式为该桶设置(见「6 级优先级」小节)。

读者容易从这里推出一个反直觉但重要的结论:「多代理」本身是缓存的敌人,不是朋友。真正享受前缀复用的是 fork、compaction 摘要调用、被恢复的子代理,以及 workflow fan-out。

workflow fan-out 的前缀对齐是一个值得单列的工程取舍:workflow 默认把除第一个之外的 agent 最多延迟 5 秒发出,好让它们能读到第一个 agent 已经缓存好的前缀。这是用延迟换命中率——在 fan-out 场景下牺牲 5 秒的首发延迟,换取后续所有 agent 的缓存命中。这几乎是本章「为缓存设计功能,而不是为功能设计缓存」论点最具体的一个官方实例。

8. Cache-Preserving Edit

当需要删除旧的 tool_result 时,Claude Code 不改本地 messages,而是通过 API 端协议(clear_tool_uses_20250919 和 clear_thinking_20251015)在服务端 KV cache 里就地删除。本地 messages 原样保留,cache_reference 和 cache_edits 在 API 层添加。

这种设计的核心思想:第一优先保护的不是完整上下文,而是 prompt cache 的命中率。本地消息流保持不变(前缀稳定),服务端通过 context management 协议增量修改缓存内容,避免前缀变化导致缓存失效。这依赖 Anthropic API 的原生 context management 协议,大多数自建推理栈(vLLM/mlx 系)暂时没有此能力。


四层记忆栈:为 Prompt Cache 而生的记忆系统

Claude Code 的记忆系统(第 9 章)围绕”尽可能不改动已被缓存的前缀”设计,形成四层栈:

层级内容缓存策略
① MEMORY.md 硬盖200 行 / 25KB,每次请求一模一样稳定缓存,最大化命中
② findRelevantMemoriesSonnet 独立调用的 side-query不污染主对话 prefix
③ 三级 Microcompactcache-preserving edit / time-based / API 差异化清理不改本地 messages
④ 当前对话 messages每轮更新动态部分

三级 Microcompact 详解:

  1. cache-preserving Microcompact(cachedMicrocompactPath):通过 API 端协议删除旧 tool_result,本地 messages 原样保留
  2. time-based Microcompact:60 分钟后”认输”主动止血,压缩长对话
  3. API Microcompact:按工具性质差异化清理——Bash 输出通常可丢弃,FileEdit 结果需保留

核心设计哲学:第一优先保护的不是完整上下文,而是 prompt cache 的命中率。


addCacheBreakpoints:断点放置策略

这个函数控制每次 API 请求中 cache_control 标记的放置:

// 关键约束:每个请求最多 1 个消息级 cache_control 标记
const markerIndex = skipCacheWrite
  ? messages.length - 2  // Fork:放在倒数第二条,即共享前缀的末端
  : messages.length - 1  // 正常:放在最后一条消息

注释中提到了 Anthropic 内部的 Mycro 系统(page_manager/index.rs: Index::insert),负责 KV 缓存的页管理。两个标记会导致中间位置的 KV 页被保护(无法被驱逐),浪费显存。一个标记则允许立即回收。

源码中还实现了 cache_editing 功能——通过 cache_reference 字段和 cache_edits 块,支持对缓存前缀中的工具结果进行增量修改,而不需要重新写入整个缓存。


成本计算

类型价格(每百万 token)相对成本
普通输入$5.00基准
缓存写入$6.25+25%
缓存读取$0.50-90%

⚠️ 适用模型档位已漂移(2026-10-01 重算):上表写的是旧 Sonnet / Opus 档位(基础输入价 $5/MTok)。2026-09 下旬官方连续更换了两代默认模型,基础价已经不是 $5,因此「缓存读取约为基础价 10%」这条经验法则现在只对其中一个档位成立。逐档重算如下——注意分子与分母必须同时印出来,只印百分比是这类错误的根源:

模型基础输入价(分母)缓存读价(分子)缓存读占比出处
Claude Opus 5.5(claude-opus-5-5,v2.1.280 起为默认 Opus,1M 上下文)$4/MTok$0.20/MTok5.0%changelog v2.1.280:$4/$20 per Mtok with \$0.20/Mtok cache reads
Claude Sonnet 5.5(claude-sonnet-5-5,v2.1.284 起为 Anthropic API 默认 Sonnet,1M 上下文)$2/MTok$0.20/MTok10.0%changelog v2.1.284:$2/$10 per Mtok with \$0.20/Mtok cache reads
Claude Fable 5.1(v2.1.257 起为默认 Fable 模型,1M 上下文)$10/MTok$0.25/MTok2.5%changelog:$10/$50 per Mtok with \$0.25/Mtok cache reads

这是一次典型的「分子没变、分母变了」:Opus 5.5 与 Sonnet 5.5 共享同一个 $0.20 的缓存读价,占比却一个是 5.0%、一个是 10.0%,相差一倍。后果是沿用旧的「约 10%」对 Opus 5.5 会高估缓存读折扣一倍,按它做的成本测算会得出比实际更乐观的节省额。做测算前先确认所用档位的分子与分母,不要沿用任何百分比常量。

一个典型的 Claude Code 会话(Opus,100 轮 + 压缩):

场景成本
无缓存$50-100
有缓存(90% 命中率)$10-19
降低~80%

只要对话超过两轮,缓存读取的节省就远超写入成本。


对 Agent 开发者的启示

  1. Prompt Caching 应该是 Day 1 的架构约束。不是”性能优化”,是”能不能活下去”
  2. 一切设计服务于前缀稳定性。功能增减不应改变系统提示和工具列表
  3. 监控缓存命中率。记录每次调用的 cache_read_input_tokens,突然降为 0 就是告警
  4. 为缓存设计功能,而不是为功能设计缓存。Plan Mode 的设计就是最好的例子——先问”怎么不破坏缓存”,再问”怎么实现功能”
  1. 考虑三层缓存堆叠。单层 Prompt Caching 有根本限制——仅对完全相同的文本前缀有效。在 Agent 产品中,可以考虑三层缓存堆叠:(1) Provider prefix caching(Anthropic/OpenAI 层):对高稳定的系统提示和工具定义最有效;(2) Semantic response caching(如 vCache, arXiv:2502.03771):基于语义相似度而非精确文本匹配,覆盖语义相似但文本不同的查询;(3) Exact-match response cache(Redis 等):对完全相同的模板化查询直接返回存储响应。Manus 团队声称 KV-cache 命中率是”生产级 AI Agent 最重要的单一指标”,三层堆叠可以最大化覆盖率。

缓存诊断工具链

上面第 3 条说「记录 cache_read_input_tokens,突然降为 0 就是告警」。官方现在已经提供了远比原始字段更强的东西——带归因的诊断:

  1. /cost 的 Prompt cache (main) 行(需 v2.1.251+):给出会话级命中率、未命中次数、重新缓存的 token 数、warm/cold 状态。statusline 脚本可从 prompt_cache 对象读到同样数值
  2. 可能原因自动点名(需 v2.1.260+):该行会直接给出上次未命中的 likely cause,例如 likely cause: tool definitions changed。v2.1.280 起归因范围扩了一档——会点名 thinking mode 与 thinking display 的变化(原文:“Improved /cost cache-miss causes to name thinking mode and thinking display changes”)
  1. 验证 TTL 是否真的生效:claude -p "hello" --output-format json,读 usage.cache_creation——1 小时写入报在 ephemeral_1h_input_tokens,5 分钟写入报在 ephemeral_5m_input_tokens
  2. /claude-api cost-optimize(v2.1.247):按缓存、token 卫生、批量、effort、模型选择逐项度量地剖析并优化项目 API 开支
  3. 组织级可观测性:OpenTelemetry exporter 可按用户与会话上报缓存读写 token
  4. 恢复态不再必然丢缓存(v2.1.281):auto mode 在新进程中恢复会话后,权限分类器可以复用先前的 prompt cache 而不是重写它——这是官方在「恢复即失效」这个方向上给出的第一个正向改进,与系统级失效表子类一那一整组条目方向相反
  1. 读客户端时钟前先确认版本:VS Code 侧有一个常显的 prompt cache clock,它在 2026-09 修过两处显示问题——v2.1.275 修复「重开会话后时钟显示的分钟数过少(当 Claude 正在工作时发过消息)」,v2.1.271 修复「hover 文本延迟才出现、自动压缩图标同时弹出浏览器自带 tooltip」。在修复前的版本上,这个读数是不可信的,不要拿它去证伪 /cost 的结论

归因能力与被归因的原因是一对:上面第 2 项在 v2.1.280 获得「点名 thinking 变化」的能力,而系统级失效表子类四里的 v2.1.273 修复的正是「/login / /upgrade / /extra-usage 丢弃会话先前 thinking → 下一次请求完整重写」。一条是归因能力,一条是被归因的那个原因——两者成对出现,说明 thinking 在缓存语义里是一个与工具定义同等重要的一等变量,而不只是「模型的内部状态」。

定位闭环:本章的「14 + 1 个失效向量」是一套分类法,官方的 likely-cause 是自动归因。正确用法是先用 /cost 拿到归因结论,再回本章清单找对应的结构原因——前者告诉你「哪一类中招了」,后者告诉你「为什么会中招、怎么从设计上避免」。两者不是替代关系。


开发者实战:五大缓存失效陷阱

#陷阱后果对策
①中途修改 CLAUDE.md整个前缀失效前置对策(v2.1.206+):提交前运行 /doctor,利用其 CLAUDE.md 修剪建议——删除 Claude 可从代码库自行推导的内容,减少 CLAUDE.md 体积。小文件 → 更少的”需要修改 CLAUDE.md”场景 → 更少的缓存失效事件。补救对策:修改后重新开始会话,或接受一次全价请求。
②动态时间戳/随机内容缓存命中率归零日期等变化内容放 <system-reminder> 而非系统提示
③中途切换模型缓存按模型隔离使用子代理而非同会话切换
④/compact 隐藏成本不同 system prompt 导致全价计费一次了解 /compact 有两种路径:cache-safe forking(复用前缀)和独立总结请求(破坏缓存)
⑤/resume 缓存失效10-20x 成本暴增恢复会话后先检查 cache_read_input_tokens,若为 0 考虑重启

极端案例:ProjectDiscovery 的缓存重构。安全公司 ProjectDiscovery 通过将动态工作内存从系统提示中完全移除并移到用户消息末尾,缓存命中率从 7% 飙升至 84%,总 LLM 成本降低 59%,累计 98 亿 token 从缓存中提供。这个案例验证了一个关键原则:缓存优化不是渐进改进,而是架构决策——ProjectDiscovery 的 7%→84% 不是调参的结果,而是重新设计了提示的布局。这补充了陷阱②的对策——不只是”日期放 <system-reminder>”,而是”任何可能变化的内容都不应该出现在前缀中”。

中转站 Cache 支持:8 个主流中转站中,仅少数完全支持 Anthropic Cache 协议(包括 cache_control、cache_edits 等),使用前务必验证。

版本验证:v2.1.270 ~ v2.1.286(2026-09-12 ~ 2026-09-30)

本章的版本锚点此前散落在 TTL 门槛、多云陷阱、系统级失效表、成本表与诊断工具链多处。这一段按版本区间做一次集中复核,覆盖 17 个版本(一手源:code.claude.com/docs/en/changelog.md)。

版本与本章相关的条目本章落点
v2.1.271/model 切回原模型时误报丢缓存;VS Code 缓存时钟 hover 延迟系统级失效表子类四;诊断工具链第 7 项
v2.1.273/login /upgrade /extra-usage 丢弃先前 thinking → 完整重写系统级失效表子类四(与诊断工具链第 2 项成对)
v2.1.275--system-prompt 含边界行时其上方文本全局缓存;记忆文件 age note 跨请求变化;VS Code 时钟分钟数过少SYSTEM_PROMPT_DYNAMIC_BOUNDARY 专节;子类一;诊断工具链第 7 项
v2.1.277SessionStart hook 输出致 /clear 后完全 miss;附件重渲染;子代理/teammate 重渲染 MCP 工具定义系统级失效表子类一(3 行)
v2.1.280宿主应用切模型;恢复的 fork 子代理重建工具列表;Opus 5.5 成为默认 Opus($4/$20,缓存读 $0.20);/cost 归因点名 thinking子类三、子类一;成本表重算;诊断工具链第 2 项
v2.1.281MCP server 中途断开;权限提示期重启后历史不同;auto mode 恢复后可复用缓存;长会话文件缓存一致性子类二、子类一;诊断工具链第 6 项
v2.1.283DISABLE_PROMPT_CACHING_HAIKU 在主模型为 Haiku 时无效系统级失效表子类四
v2.1.284Sonnet 5.5 成为 Anthropic API 默认 Sonnet($2/$10,缓存读 $0.20)成本表重算
v2.1.285MCP_DISCOVERY_CACHE=1 须设在云环境变量才生效策略 4/5 平台侧机制;子类二
v2.1.286网关 spend meter 把 1 小时写入按 5 分钟费率计价;流式 turn 只计首次模型调用输入 token多云缓存陷阱(偏差双向)

本轮的核心增量集中在系统级失效表:从 6 行扩到 18 行,新增 12 行(其中 11 条是官方修复的失效路径,另 1 条是 v2.1.285 的工具列表缓存行为变更),并新增了「内建命令自伤」这一子类。

2026-08~09 KV 缓存前沿:张量级压缩与范式转换

上述章节专注于 “Prompt 级别” 的缓存策略(哪些文本放前缀、哪些放动态)。2026 年 8 月起出现了多个在 “KV 张量级别” 做压缩的新方案,两者互补而非替代。

S4R:采样+子空间+稀疏重建(5x 压缩)

Han et al.(arXiv:2608.00528,2026-08-01)提出 S4R(Selective Sampling, Subspaces, Sparse Reconstruction):

  • 从非 sink token 中采样(7:3 recent-to-uniform),对采样子集做截断 SVD 得到低秩基
  • 所有非 sink token 存储为 r 维系数(而非完整 D’ 维 KV)
  • 解码时:query 投影到 key 子空间 → 潜空间相关性评分 → 仅重建局部窗口 + top-k 全局位置
  • 5x KV 压缩,接近全缓存 LongBench 准确度,无需外部校准数据

TurboQuant:极坐标量化(6x 压缩,零精度损失)

Google 研究团队将 KV 向量从笛卡尔坐标转为极坐标(PolarQuant),消除逐块归一化元数据开销:

  • 2.5-3 bits/value(从 16 bits),6x 压缩,零精度损失
  • Key 上添加基于 Johnson-Lindenstrauss 变换的 1-bit 校正层(QJL)
  • H100 attention kernel 加速最高 8x,即插即用
  • 已有 PyTorch (Triton)、MLX、llama.cpp 社区实现

BeaconKV:近期 query 不足以预测未来注意力(2026-09-04)

BeaconKV: Key-Value Cache Compression Guided by Beacon Queries for Efficient Large Reasoning Model Inference(arXiv:2609.04971,2026-09-04)做了一件对本章特别重要的事:它证伪了上面 S4R / TurboQuant 共有的一个隐含前提——用「最近的 query」估计未来 token 的重要性。作者证明该假设在长程推理中不成立:某些解码步会生成 Thought Revisiting Tokens(TRT),重新关注远处的上下文(例如早期制定的解题计划)。这些 TRT 对应的 query 在嵌入空间里聚成少数几个相似簇,于是可以维护一组 beacon queries 来预判哪些 KV 会被重访,且方法是 training-free 的。

对本章的意义:前面两种压缩方案都在「近期性」假设下做采样与量化,而推理模型恰恰是会回头看的负载。这也解释了为什么「压缩率 × 准确率」这个指标在 LongBench 这类基准上容易好看,在长程推理上会掉。

长上下文压成答案对齐的记忆嵌入(2026-09-22)

Compressing Long Context into Answer-Aligned Memory Embeddings for LLM Inference(arXiv:2609.25537,2026-09-22)把长上下文压成对齐任意冻结 decoder 嵌入空间的 Context Memory Embeddings,配合双层 KV cache(question-guided CME 选择 + 局部窗口),用冻结 LLM 的 answer-targeted distillation 训练压缩器。与 BeaconKV 的区别是路线:前者 training-free、靠 query 结构预判;后者要训练一个压缩器,但换来的对齐是面向「答案」而非面向「重建误差」。

MILO:按信息熵动态分配 rank 预算(2026-09-24)

MILO: Efficient Many-shot In-Context Learning with Block-wise Low-rank Compression(arXiv:2609.29913,2026-09-24)针对 many-shot ICL 下 KV cache 成为瓶颈的场景,做 block-wise 低秩压缩(每块含多个示例),并按信息熵动态分配 rank 预算;在 Qwen2.5 上取得 up to 50% 的 KV cache 削减。与 S4R 的差别在于 rank 不是全局固定而是逐块分配的——这与本章「不同内容有不同的缓存价值」的思路是同构的。

PrefixPlace:可证明的 Prefix KV 放置优化

Wang & Buyya(arXiv:2608.01655,2026-08-03)提出的 epoch 级 planner 在异构环境(T4/L4/A100)下平均达最优 99.84%,RAG 重放材料化成本节省比 vLLM-APC 高 40.3%。核心洞察:单纯最大化命中率是次优的——需要权衡计算成本、传输成本、GPU 内存和缓存命中率的复合目标。

DeepSeek-V4.1-Flash:架构与量化路线(2026-09-17)

DeepSeek-V4.1-Flash: Pushing the Limits of KV Cache Compression(arXiv:2609.19969,2026-09-17)走的是另一条路:模型架构 + 量化。552B MoE、100 万上下文,采用 Causal Encoder-Decoder(decode 激活 16B、prefill 仅 8B),配合 CSA2 跨层 KV 复用 + FP4 KV 缓存,把全局 KV 足迹压到 890 bytes/token 并常驻 HBM。这条路线与上面三种推理时压缩互补:前者改的是「KV 该存多少、存几份」,后者改的是「已存的 KV 怎么丢」。

淘汰算法:生产 trace 上的反证(2026-09-24)

When Fancy Eviction Fails: Rethinking Cache Replacement For LLM Prefix Reuse(arXiv:2609.28870,2026-09-24)值得单列,因为它是本节唯一用两家公司生产 trace、在 HBM 受限与大内存池两种设置下评估 14 种淘汰算法的工作。结论直接冲着本章的核心假设来:尽管这些算法与 Belady 最优的差距很大,但为传统缓存设计的复杂策略相对 LRU 收益甚微。原因是结构性的——前缀复用由活跃会话的规律节奏主导,使 recency 异常可预测;真正的问题是重尾的会话足迹,以及随序列长度增长而差异极大的 miss 成本。论文同时给出了 compute-savings ratio 与两个 offline oracle。

这一条是对上面 PrefixPlace 那条洞察的独立加强,且证据等级更高:PrefixPlace 说的是「单纯最大化命中率是次优的」,来自仿真;这篇说的是「精心设计的淘汰策略几乎打不过 LRU」,来自生产 trace。两者指向同一件事——命中率不是目标函数本身。它同时也是对本章开头「前缀匹配的绝对性」那一节的一个补充视角:绝对性描述的是单条请求的失效规则,而生产环境里的成本大头由会话级足迹分布决定,两者不在同一个尺度上。

互补路线:投机解码

KV 缓存压缩解决”内存占用”,投机解码解决”生成速度”——两者互补:

  • DSpark(DeepSeek 线上部署):60-85% 加速,兼容 Qwen3/Gemma4
  • JetSpec(阶跃星辰+UCSD):Qwen3-8B 上 9.64x 加速(因果并行树起草)
  • Saguaro(Stanford/Princeton/Together AI, ICLR 2026):嵌套投机,1.58x 超越标准投机解码

潜在颠覆:扩散语言模型

Mercury 2(Inception Labs)在 Blackwell GPU 上达 1,009 tok/s,AIME 2025 得分 91.1。如果扩散 LLM 成熟到前沿质量,整个自回归 KV 缓存优化栈将变得无关——扩散生成不需要顺序解码的 KV 状态维护。这是章节所有缓存策略的”终极风险”——值得注意但短期内不会发生。

延伸阅读:Thariq Shihipar 的 How Prompt Caching Actually Works in Claude Code(2026.02.25),以及 Anthropic Prompt Caching 文档。