第 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.302)。本章全篇的缓存模型都是会话内前缀复用——连续轮次、fork、compaction 摘要、workflow fan-out,所有这些复用都发生在同一个会话里。v2.1.302 给出了一个超出该范围的官方实例:

“Changed where the Claude in Chrome instructions sit in the system prompt so that new conversations can read them from the shared prompt cache”

也就是说,把 Claude in Chrome 的指令在系统提示里的位置挪动一下,是为了让新的对话也能从共享缓存里读到它。加上这一维之后,本章的缓存模型需要补一个此前没有的轴:不只是”前缀稳不稳定”,还有”这个稳定前缀能被谁读到”——会话内、跨会话、workspace,是三个不同口径。

⚠️ 作用域边界未知:官方原文只说”new conversations can read them from the shared prompt cache”,没有说明这个 shared cache 的作用域边界(是全用户 / workspace / 组织,还是仅同一台机器)。本章只记”新会话可读”这一事实,不推断边界。这同时也是下一节安全维度的直接前提:共享范围越大,一条被污染的前缀能影响到的请求就越多。

作用域的第一条公开线索:prompt-caching-scope header(v2.1.307)。v2.1.307 的修复条目让这个 beta header 名第一次出现在公开记载中:“Fixed the prompt-caching-scope beta header being sent behind custom ANTHROPIC_BASE_URL gateways and on Foundry, where it has no effect”——即该 header 此前被发给不支持它的自定义网关与 Foundry(修复后不再在这些平台上发送)。这确认了缓存作用域存在一个专门的控制通道,但 header 的取值与语义官方未记载——「边界未知」的结论不变,只是线索从 0 条变为 1 条。该条的网关兼容行为与子类二的网关条目(tool search)同族。

传参形式本身是一个缓存变量(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
DISABLE_PROMPT_CACHING_OPUS / DISABLE_PROMPT_CACHING_SONNET 对默认模型的 [1m] 后缀、其他区域、无日期三种拼写形式完全无效v2.1.317
/model 在「切回会话实际运行过的那个模型」时误报会丢失会话缓存v2.1.271

前两行的失效形态不同:v2.1.273 是真失效(thinking 被丢,缓存重写),v2.1.283 / v2.1.317 与 v2.1.271 是控制失效(开关不起作用)与误报(警告是假的)。v2.1.283 与 v2.1.317 两行合起来还暴露了一个新失效维度:缓存开关的匹配键是模型名字符串——同一个模型换一种拼写([1m] 后缀、区域标注、日期标注的有无),开关即失效。排障时应先确认 /model 显示的完整模型名拼写。三者合起来说明一件事:命令层的缓存语义没有被当成一等接口来维护,所以在排障时不能默认「官方命令对缓存是中性的」。

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

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

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

子类六:计费状态

新增一个成因类型全新的子类,目前只有一行,但值得单列:

失效路径官方修复版本
账户进入 usage overage 时,ScheduleWakeup 工具定义在会话与其 --resume 之间发生变化 → resume 后首个请求完全未命中v2.1.248

前五个子类的成因都在配置、连接、命令与时间;这一个在账单。 三段因果链是:账户进入 usage overage → 工具定义改变 → 缓存完全未命中。也就是说,“你花了多少钱”能改变”缓存能不能命中”——这是本章分类法里此前缺的一类成因,也是排查时最不容易想到的一类:它既不体现在配置 diff 里,也不体现在命令历史里。

本轮补充到既有子类的行

失效路径官方修复版本归入子类
会话中途新增的工具在经 ToolSearch 或压缩后再次加载 → 未命中v2.1.302子类一
自定义 ANTHROPIC_BASE_URL 下 tool search 默认开启(网关返回 400/422 时重试并关闭它)→ 改变工具集的呈现方式v2.1.304子类二
第一方 MCP server 的工具描述与 server instructions 不再被截到 4,096 字符(同族:v2.1.296 把”预先发送”的默认上限从 2,048 提到 4,096;v2.1.295 把经 tool search 加载的描述截断由 2,048 改为 16,384)→ 工具描述长度本身就是一个前缀变量v2.1.299子类二

最后一行值得单独看一眼:工具描述的截断长度是会变的,而它是前缀的一部分。这意味着即使你没有增删工具,官方调整一次截断上限,你的前缀就变了——这类失效既不是你造成的,也不体现在任何”工具列表变化”的归因里。


七个架构策略

基于源码分析和 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(可能丢失缓存状态),压缩保留了更多缓存

⚠️ 限定(v2.1.304):上面第四行需要一个补充。v2.1.304 的官方条目是 “Changed compaction to send Claude the skill list again afterwards; /reload-skills now sends only new or changed skills”——压缩之后会把 skill list 再发一遍。同版本还修了一条同向的:“Fixed resumed and cloud sessions sending Claude the whole skill list again each time plugins reloaded at startup”。

准确的说法应当是:压缩请求本身复用前缀,但压缩之后的第一个请求会重发 skill list——复用是分段的,不是无损的。v2.1.299 还有一条配套说明:/reload-skills 会说明它”重新发送完整 skill list 给 Claude,这是在压缩之后找回 skills 的方式”,即官方把”压缩后重新注入 skills”当成一条已知且有意的行为,而不是 bug。

工程含义:把压缩当作”缓存无损操作”来做成本模型时,需要为压缩后的第一个请求留出一次额外的前缀写入。

压缩的触发时机同样是缓存状态的函数(v2.1.314)。v2.1.314 把空闲压缩带入 VS Code 与 Claude Desktop 的 Code tab:一条 200K+ token 的会话闲置后被摘要,而摘要只在缓存已过期的下一条消息上启用;多云侧(Bedrock / Vertex / Foundry / 网关)的开启条件则是1 小时 prompt cache 开着。也就是说压缩在本章的成本模型里要记两笔缓存账——压缩请求复用前缀(省)与摘要的启用依赖缓存已死(时机);「空闲后回来第一跳的缓存命中」与「是否启用摘要」是同一个判断的两个输出。压缩从「窗口满时发生」扩为「闲置即摘要、摘要仅在缓存过期后启用」。

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 的缓存命中。这几乎是本章「为缓存设计功能,而不是为功能设计缓存」论点最具体的一个官方实例。

更强的一档:缓存 TTL 反向决定行为契约(v2.1.318)。fan-out 调整的是一次性调度,而 v2.1.318 的两条改写的是持续性行为:

  1. 3 分钟规则:后台子代理运行的 shell 命令,在所有 provider 上 3 分钟后强制转后台——官方 changelog 原文明确写出动机:“so the prompt cache doesn’t expire while it runs”。3 分钟可读作 5 分钟 TTL 桶内的安全余量:缓存 TTL 反过来决定了子代理的行为契约,这是本章论点至今最 explicit 的官方表述。
  2. 冷缓存等 5 分钟:Bedrock / Vertex / Foundry 或遥测关闭时,冷缓存下子代理进度摘要可以最多等 5 分钟子代理的 pending response——等待窗口与 5 分钟 TTL 桶的时长对齐,又一条「缓存状态驱动调度决策」的官方实例。

两条与 fan-out 的 5 秒并列为「为缓存设计功能」的官方实例,且这两条更强:不是在某个场景里让一步,而是把缓存生命周期写进了默认行为本身。

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 系)暂时没有此能力。


缓存复用也是一条污染通道

本章到此为止的全部内容——15 个失效向量、8 个架构策略、四层记忆栈、成本表——都把”提高缓存复用率”当作目标函数。这里补一个本章从未讨论的反向维度:复用是有方向的,它同时也是一条跨请求的信任通道。

R²ECAP(Reused Reasoning-Cache Poisoning),arXiv:2610.22873,2026-10-16。论文的起点是近年一个被广泛看好的方向:复用推理轨迹的 KV cache 不只省算力,还能像 RAG 一样提升任务表现。它证明这种复用引入了新的投毒漏洞。

攻击的形态值得逐条记:

  • 它是黑盒的,且只通过与系统的聊天界面交互完成植入——不需要访问模型权重、缓存存储或服务端配置。
  • 攻击者构造两条轨迹:一条前导轨迹把模型引向对抗目标,一条保留轨迹承载该目标。
  • 关键机制在最后一句:保留轨迹是以前导轨迹为条件生成的,因此即使前导轨迹及其缓存条目已被丢弃,它的 KV cache 仍保留残余的对抗性影响。

结果(3 个开放权重 LLM、3 个数据集):最高 79% 攻击成功率;在 9 种资源受限设置中的 8 种优于白盒 SOTA HijackKV;在一个真实的推理轨迹检索系统中仍有 46% ASR;隐蔽性方面,仅 1% 的保留响应被主流 prompt injection 检测器发现。

对本章的落点:本章此前的隐含假设是”被缓存的是计算”。R²ECAP 说明被缓存的还有”影响”——而且这种影响能在产生它的内容被删除之后继续存活。把它与上文 v2.1.302 的跨会话共享缓存叠加,会得到本章此前完全没有的一条取舍:缓存命中率不是越高越好,它是信任边界的函数。 复用范围越大,一条被污染的轨迹能影响到的请求就越多。

⚠️ 置信度:中低,且不得外推到 Claude。该论文为 2026-10-16 预印本,未经同行评议;评测对象为开放权重模型,未覆盖 Claude,因此本章只把它当作机制级警示,不是”Claude Code 存在此漏洞”的证据。46% 那一档的”真实推理轨迹检索系统”的具体构成未核。


四层记忆栈:为 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.10/MTok(v2.1.296 起;v2.1.284 首发时 $0.20)5.0%(首发时 10.0%)changelog v2.1.284:$2/$10 per Mtok with \$0.20/Mtok cache reads;v2.1.296(2026-10-09)起改按 $0.10/MTok 计价
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 会高估缓存读折扣一倍,按它做的成本测算会得出比实际更乐观的节省额。做测算前先确认所用档位的分子与分母,不要沿用任何百分比常量。

⚠️ 随后又漂移了一次,而且这次变的是分子(2026-10-20 重算)。v2.1.296(2026-10-09)官方条目:/cost、status line、--max-budget-usd 与 SDK 的成本数字统一按 \$0.10 per million tokens 为 Sonnet 5.5 的缓存读计价(原为 \$0.20)。因此上表 Sonnet 5.5 一行的分子与占比同步修正为 $0.10 / 5.0%。

两点必须写清:① Opus 5.5($0.20/$4 = 5.0%)与 Fable 5.1($0.25/$10 = 2.5%)两行未变,不得一起改;② 修正后 Opus 5.5 与 Sonnet 5.5 的占比同为 5.0%,但分子不同($0.20/$4 vs $0.10/$2),所以上一段”两者共享同一个 $0.20、占比差一倍”这句已不成立,差异从”占比差一倍”变成”同占比、不同分子”。

这条最值得带走的不是数字而是纪律:上一轮纠错的原因是分母变了,13 天后同一组数字又被官方改了分子。「分子分母都要同时印出来」必须每次重扫时执行,不能只做一次。

一个典型的 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。v2.1.300 起可按 TTL 分档核对写入:api_request 事件新增 cache_creation_5m_tokens 与 cache_creation_1h_tokens,把缓存写入按生命周期拆开。这与上面第 3 项(5 分钟与 1 小时写入报在不同字段)以及「偏差是双向的」那一条直接呼应——两个 TTL 档位被按同一个费率计价时,从总额上看不出来,只能分档核对。
  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 的结论

再添一处读数不可信场景(v2.1.296):VS Code 的 prompt cache clock 会在面板延迟追上消息之后(例如屏幕关闭过、或远程窗口重连后)读数显示为 warm。这与上面两条是同一类问题但触发条件不同——前两条是显示延迟与数值偏少,这一条是状态本身被算错。三条合起来给出一个更稳的用法:这个时钟只适合定性判断”缓存是不是还活着”,任何定量结论都应以 /cost 与 usage 字段为准。

归因能力与被归因的原因是一对:上面第 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.319(2026-09-12 ~ 2026-11-06)

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

版本与本章相关的条目本章落点
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-10 增量(v2.1.287 ~ v2.1.304)

版本与本章相关的条目本章落点
v2.1.293Haiku 5.5 成为 Anthropic API 默认 Haiku(1M,$0.10/$0.50 per Mtok;>100K 提示为 $0.50/$2.50)本章成本表未收录(该表只覆盖 Opus 5.5 / Sonnet 5.5 / Fable 5.1 三档的缓存读价占比口径);Haiku 5.5 的分段计价见第 18 章教训一
v2.1.295经 tool search 加载的 MCP 工具描述截断由 2,048 改为 16,384 字符子类二:工具描述长度是前缀变量
v2.1.296Sonnet 5.5 缓存读价由 $0.20 改为 $0.10(/cost、status line、--max-budget-usd、SDK 同步);MCP 工具描述上限 2,048 → 4,096;VS Code 缓存时钟在面板延迟追消息后读数 warm成本表二次重算(分子变了);子类二;诊断工具链第 7 项
v2.1.299第一方 MCP server 的工具描述不再截到 4,096 字符子类二:截断长度变化会改变前缀
v2.1.300OTel api_request 新增 cache_creation_5m_tokens / cache_creation_1h_tokens诊断工具链第 5 项:可按 TTL 分档核对写入
v2.1.302Chrome 指令挪位以便新会话从 shared prompt cache 读取;会话中途新增工具经 ToolSearch 或压缩后再次加载 → 未命中新增「作用域」维度(共享范围超出单会话);子类一
v2.1.304压缩之后重发 skill list(/reload-skills 只发新增/变更);自定义 ANTHROPIC_BASE_URL 下 tool search 默认开启策略 6 的限定;子类二

⚠️ 本轮保留的既有复核结论:截至 v2.1.286 的二维 TTL 表述、v2.1.242+ 的门槛表述本轮未推翻,继续成立;本节的增量是把锚点前移,不是替换。

2026-11 复核增量(v2.1.305 ~ v2.1.319,其中 305~316 发布于 2026-10 下旬)

版本与本章相关的条目本章落点
v2.1.307prompt-caching-scope beta header 在自定义网关与 Foundry 上不生效(修复为不再在这些平台上发送)作用域小节第一条公开线索;子类二网关族
v2.1.314空闲压缩摘要只在缓存已过期的下一条消息启用;多云侧开启条件是 1 小时缓存开着策略 6:压缩触发与缓存状态耦合
v2.1.317DISABLE_PROMPT_CACHING_OPUS / DISABLE_PROMPT_CACHING_SONNET 对 [1m]、其他区域、无日期拼写无效子类四:开关匹配键是模型名字符串
v2.1.318后台子代理 shell 命令 3 分钟转后台(官方动机:防止 prompt cache 过期);冷缓存下进度摘要最多等 5 分钟策略 7 之后新小节:缓存 TTL 反向决定行为契约

⚠️ 本轮保留的既有复核结论:v2.1.242+ 门槛与二维 TTL 表述在本轮全量 changelog(437 个版本块,抓取时最新 v2.1.319)复核中仍未被推翻。

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。两者指向同一件事——命中率不是目标函数本身。它同时也是对本章开头「前缀匹配的绝对性」那一节的一个补充视角:绝对性描述的是单条请求的失效规则,而生产环境里的成本大头由会话级足迹分布决定,两者不在同一个尺度上。

但这是一对需要并置呈现的对立证据,不是定论(arXiv:2610.22130,DropKV,2026-10-15)。DropKV 在基准上得到了几乎相反的结论。它的做法是把一个 NP-hard 问题绕开:现有淘汰方法面临”最小化输出扰动”的联合优化是组合不可解的困境,要么启发式在数学上不优,要么实现难以 kernel 化;DropKV 用 Decoupled Residual-Output Perturbation 把联合淘汰决策解耦为逐 token 独立打分,并在论文实证验证的 cone condition(在三个长上下文 LLM 上验证)下给出可证明的常数因子近似保证。实现侧用融合 Triton kernel(不物化完整注意力矩阵),打分 kernel 加速 19.8×,端到端 prefill 最高 9.9% 加速;在 RULER 与 LongBench 上于同等缓存预算下持续优于 SOTA 基线。

两者不矛盾,因为评测载体不同:

When Fancy Eviction FailsDropKV
评测载体两家公司的生产 traceRULER / LongBench 长上下文基准
结论复杂策略相对 LRU 收益甚微有近似保证的淘汰持续优于 SOTA
结构性原因前缀复用由活跃会话的规律节奏主导,recency 可预测需要在长程中定位稀疏证据,recency 不够用

由此把本小节的结论改写得更精确:“命中率不是目标函数本身”仍然成立,但还要补一句——也不存在脱离负载形态的最优淘汰策略。 给读者的可操作判断是:先问自己的负载是”会话节奏型”还是”长程检索型”,再决定要不要为淘汰策略付出工程复杂度。 把这两篇当成互相推翻,是本小节最容易发生的误读。

分词是缓存之外的第二个瓶颈(2026-10-14)

本章”前缀匹配的绝对性”讲的是字节变了缓存就废,读者自然会推出”所以我只要让前缀不变就省钱”。这一条指出成本账里还有一笔本章从未记的分项。

Delta Tokenizer: Reference-Identical Tokenization of Session Deltas and Segment-Parallelism for Long Prompts on Multicore CPUs(arXiv:2610.19801,2026-10-14)。问题的表述很干净:驱动 LLM 的 agent 把同一段对话一遍遍重发,每轮只在末尾加一点文本;推理引擎复用共享前缀的 KV cache,只有新 token 过模型——但分词没有这样的捷径。服务引擎的 CPU 前端把每个请求都从第一个字符重新转成 token ID,拉长 TTFT 并在每轮消耗 CPU。

Delta Tokenizer 只编码请求与同会话上一次请求之间的 delta:保留先前的 token 序列,只对追加文本与”追加可能影响到的短尾”重新编码,并在分词器自身的切分规则不可能移动的位置拼接,从而保证结果与参考编码完全一致;增量路径把一轮会话的分词开销相对 Hugging Face 参考实现缩短约 40×(另在 16 核上,四百万字符请求的并行分块编码比串行快 8.2×)。

与本章的接合点很硬:本章”前缀匹配的绝对性”说的是字节一致,Delta Tokenizer 说的是增量也必须字节一致——它必须证明增量编码的结果与全量编码逐 token 相同,否则缓存就废了。这从另一个方向印证了核心约束:前缀稳定性不只约束你写什么,也约束服务引擎怎么把它变成 token。 它同时给”为什么长会话 TTFT 仍然高”补了一个不在 KV 侧的解释。

⚠️ 置信度:abs 页一手核验(reference-identical、40×、8.2× 均逐字确认),但为预印本,40× 的测试条件未回正文。

2026-11 三根新轴:通信、执行效率、K/V 不对称

前沿节此前覆盖的方案(S4R / TurboQuant / BeaconKV / CME / MILO / DeepSeek-V4.1-Flash / DropKV / Delta Tokenizer)默认三个前提:单卡视角、压缩管线自身开销可忽略、K 与 V 是一个整体。2026-11 上旬的三篇预印本各打破一条:

  1. 通信轴(BasisKV,arXiv:2611.02583,2026-11-02)。张量并行下低秩压缩的收益会被 TP 通信吃掉——BasisKV 把 Value 压缩与输出投影绑定,同一份低秩 Value 表示同时缩减 Value cache 宽度与 TP 通信量,并把压缩后的 Value 复用于 Key 路由(预测部分 Key 信息,Key 特定残差存补不回的部分);8 卡 TP 端到端最高 1.42× 加速。此前全部方案默认单卡视角,分布式通信是第一根新轴。代码开源。
  2. 执行效率轴(FastKVComp,arXiv:2611.04382,2026-11-03,NPC 2026 已录用)。论点与本节「命中率不是目标函数本身」的纪律直接同构——压缩比也不是目标函数:面向分离式 serving 的 KV 压缩,其收益还取决于压缩管线自身跑多快;高带宽互连下低效压缩会吃掉省下的通信时间。fused TileLang kernel 最高 5.9×、无损后端吞吐从 <90 提到 >500 GB/s、作业完成时间最高降 2.04×。落点:评估任何压缩方案时,「压缩之后」的管线开销要进成本模型。
  3. 不对称轴(LowRAM,arXiv:2611.07025,2026-11-04)。K 与 V 结构不同——每个 attention head 内 Value 很大程度上可由对应 Key 预测。LowRAM 用 test-time 的 per-head 预测器 + 最差重构 token 残差存储,做到 4× 近无损 Value 压缩;与跨层低秩 Key 压缩组合,再叠加量化与淘汰,可达 26.9× 压缩、基线 94.5% 表现,峰值解码吞吐最高 5×。此前所有方案把 KV 当一个整体处理,「K/V 结构不对称」是第三根新轴。

⚠️ 置信度:中(预印本体例)。三篇的标题 / 编号 / 关键数字均经 arXiv abs 页摘要一手逐字核对(FastKVComp 的 NPC 2026 录用在 abs Comments 字段坐实),正文表格未逐字回源。

互补路线:投机解码

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 文档。