KV(Key-Value)缓存不是一个"优化技巧",而是贯穿大模型推理、推理服务、Agent 构建的一条结构性约束。下面按场景拆开讲。
一、KV 到底是什么:从注意力机制说起
要理解 KV 是什么,先看注意力怎么算。模型处理"北京的天气怎么样"这句时,当读到"怎么样",它要决定前面哪些词最重要,靠三个向量完成:
| 向量 | 含义 | 在这个例子里 |
|---|---|---|
| Query(查询) | 当前词发出的"搜索请求" | "怎么样"问:哪个词和我最相关? |
| Key(键) | 每个词的"标签",用于被搜索匹配 | "北京"偏"地名","天气"偏"气象" |
| Value(值) | 每个词的"内容",匹配成功后被提取 | 匹配到"天气"后,提取它的语义信息 |
计算分三步:先由当前词生成 Query;再与每个词的 Key 做点积得到注意力权重(匹配度打分);最后用这些权重对所有 Value 加权求和。上面那个例子里,"天气"匹配度最高(0.55),"北京"0.35,"的"几乎无关(0.05),权重加起来等于 1。
为什么 Key 和 Value 需要被缓存? 观察注意力热力图可以发现:每生成一个新词,它的 Query 都要与前面所有词的 Key 做匹配,再用所有词的 Value 加权求和。如果每次都从头算所有 K 和 V,计算量会随上下文长度不断增长。KV Cache 就是把已算过的 K 和 V 缓存起来,让新词直接复用。
二、场景一:单次推理内部------把 O(N²) 压成线性
这是 KV 缓存最原始的用武之地。用一个 4 token 的例子说明:
假设上下文是 [A, B, C, D],模型要生成第 5 个 token E。注意力的核心操作是:这一步的 Query 来自最后一个已知 token D,它与 A、B、C、D 四个 Key 做点积算匹配度,再按匹配度对四个 Value 加权求和,得到 D 位置的输出表示,模型用它预测出 E。
- 不用 KV Cache :每生成一个新 token 都要把整个前缀从头前向算一遍。生成 E 要算 4 组 K、V,生成第 6 个 token 要连 E 一起算 5 组......前缀长到 N 个 token 时要算 N 组,累计计算量与 N² 成正比。
- 用了 KV Cache:每个 token 的 K、V 只在它第一次进入上下文时算一次,之后一直留在缓存里。生成 E 这一步,A、B、C、D 的 4 组 K、V 已在缓存中,直接取用即可;等 E 被采样出来、送回模型之后,才计算 E 自身的 K、V 并追加进缓存。
但要注意一个常被误解的边界 :KV Cache 省去的是历史 token 的 K、V 投影重算 ,使每步解码不必重算整个前缀;但每个新 token 的注意力计算仍要遍历全部缓存的 K、V ,计算量随上下文长度线性增长------这正是长上下文解码越来越慢、KV Cache 的显存与带宽成为推理瓶颈的原因。
一句话:KV Cache 把"每步重算前缀"的二次增长,变成了"每步扫一遍缓存"的线性增长,但线性这一项并不会消失。
三、场景二:多轮对话与 Agent------从"每轮重跑"到"只算增量"
这是 KV Cache 在生产中价值最直观的地方。设想一个 Agent 进行到第 6 轮,上下文已累积 2000 个 token:
- 没有缓存 :每生成一个新 token,都要重新计算这 2000 个 token 的 K、V------相当于重跑整个前缀的前向计算。尽管前 5 轮内容完全没变,第 6 轮仍要像第 1 轮那样从头算整个前缀,而且此时前缀更长,代价比第 1 轮大得多。无缓存时 prefill 的注意力计算量随上下文长度平方级增长,随着对话深入,延迟和成本都会急剧攀升。这对需要几十轮工具调用的 Agent 任务来说是不可接受的。
- 有缓存:前文已算过的部分直接复用,只需计算新增 token 的部分。
前缀不变性是硬约束
复用的前提是要复用的上下文 token 前缀保持不变------若 token 序列从某个位置开始不同,首个不同 token 及其后的 KV 状态需要重新计算;此前位置的 KV 状态不受这次改动影响。
为什么改动点之后会失效? 大模型由数十到上百层 Transformer 堆叠,每一层独立生成自己的 K、V 缓存。这些层是串联 的:第 1 层输出喂给第 2 层作为输入,层层向下传递,像流水线上的工序。如果第 k 个 token 变化(比如系统提示词改了一个字),k 之前的状态不受影响,但从 k 开始的表示会逐层受影响------实际复用时,缓存只能保留到首个不同 token 之前。
代价取决于改动位置:变动点越靠前,需要重算和计费的 token 越多,延迟影响通常越大。
一个真实事故:一行时间戳让账单翻倍
某客服 Agent 每天处理 10 万次对话,一切正常。某天工程师为了让 Agent"知道"当前时间,在系统提示词里加了一行 Current time: {{now}}。第二天告警:所有对话的首 token 延迟从 0.5 秒涨到 3-5 秒,月度推理账单几乎翻了一倍。
代码完全没问题,模型也没换。原因是那一行时间戳使每次请求的 token 序列从时间戳所在位置开始不同,该位置及其后的 KV 状态无法复用;由于系统提示词位于上下文前部 ,模型往往仍需重算它之后的大部分输入 token 的键值对。这种"无形成本"在 Agent 系统里反复出现------开发者写下的一行看似无害的代码,可能让整条推理链路慢一个量级。
由此推出的三条工程纪律
基于这个机制,可以直接记住三条原则:
- 系统提示词和工具定义一旦确定就不要改。 任何改动,哪怕多一个空格,都可能改变 token 序列,使首个不同 token 及其后的缓存无法复用;改动越靠前,延迟和成本影响通常越大。
- 动态信息永远追加到末尾------时间戳、用户状态等变化的内容,作为新消息追加到对话末尾,而不是修改已有的系统提示词。
- 使用标准 API 格式,不要自行拼接消息。 结构化消息会被 Chat Template 翻译成模型训练时见过的固定 token 序列;自行用字符串拼成
"USER: ... ASSISTANT: ..."的根本问题是偏离了训练格式,会削弱模型的多步思考能力。至于缓存,它只认 token 字节序列,只要拼出的前缀字节级稳定,照样能命中;但若拼接方式不稳定(如每次向前缀注入动态内容),缓存也会随之失效。
这三条背后的直觉很简单:大模型处理上下文时,会把前面已处理过的内容缓存起来,下次只需处理新增的部分。
追加到末尾为什么就不破坏缓存?
这正是 KV Cache 前缀性质的直接推论:因果注意力决定了每个 token 在每一层的隐状态(以及由它算出的 K、V)只依赖该 token 自身和它之前的 token,与后面的 token 无关。因此在末尾追加新内容不会改变任何已缓存 token 的 K、V------新增内容只需在首次出现时计算一次(一次性缓存写入),此后就并入不断增长的"前缀",在后续所有轮次持续命中。
所以这不是"预编译",而是 "只增不改"的追加式注入。
四、场景三:跨请求复用------KV Cache 与 Prompt Cache 的分工
这里有一组极容易混淆的概念,原文专门做了辨析:
| KV Cache | Prompt Cache | |
|---|---|---|
| 性质 | 模型内部的机制 | 推理引擎/API 服务商的优化 |
| 作用范围 | 一次推理过程中 | 多次 API 请求之间 |
| 缓存对象 | 已计算 token 的键值对 | 相同前缀的计算结果 |
| 加速什么 | 单次请求内的 token 生成 | 减少跨请求的重复计算成本 |
两者的优化原理相似 (都利用前缀不变性),但作用层级不同。
Prompt Cache 怎么工作? API 服务商对请求的前缀进行匹配,如果多次请求的前缀相同,就直接复用之前算好的 KV Cache,而不需要重算这部分 token 的键值对。缓存读取的成本远低于首次计算,例如 Anthropic、DeepSeek、GPT-5 约为十分之一。
不过要留意:各家的启用方式和计费细节差异不小,有的能自动启用,有的需要手动指定,使用时需查最新文档。
这个机制在 RAG 里也有实际收益:上下文感知检索的代价是索引阶段额外 LLM 调用,但通过 prompt caching 完全可控------每百万文档 token 约 1 美元。
五、场景四:工具定义与 Skills------动态加载的缓存博弈
工具定义在上下文中占据很大篇幅(每个工具可能包含数百个 token 的描述和参数说明),所以它们天然处在静态前缀里。但这带来一个矛盾:
动态加载工具会破坏 KV Cache------若把全部工具定义放进静态前缀,每加载一个新工具就使整段缓存失效。
破解思路是"把新工具的完整 schema 追加到上下文末尾,静态前缀保持稳定",此后 schema 固定在轨迹的原位置,作为普通历史消息继续命中缓存;状态栏则只维护一份简短的工具名列表。
这里有个关键澄清: "追加到末尾"只发生在工具被发现的那一轮。此后这个 schema 块就固定在轨迹中的原位置------后续轮次的新消息追加在它之后,它本身成为普通的历史消息,而不是每轮都被重新搬运到最新的末尾。
两家 API 都给出了明确保证:
- OpenAI 要求后续请求保持
tool_search_output项的原位置,同一工具无需重复加载; - Anthropic 在会话历史的原位置内联展开
tool_referenceblock,官方文档明确表示后续每一轮都能保持缓存命中。
真正会导致重算的只有两种情况:Prompt Cache 的 TTL 过期(整段前缀一起重算,并非工具定义特有的代价),以及修改、移除或重排已加载的工具集(缓存从变动点起失效)。
这也意味着 "工具定义必须在上下文最前面"不再是铁律------前缀依然是静态的、只增不改的,只是工具定义获得了按需进入轨迹的能力;代价是模型必须在后训练中学会理解散落在上下文各处的工具定义。
Skills 的"对 KV Cache 友好"≠"零成本"
Skills 的设计是"少量目录常驻、完整正文按需加载"。但要厘清一个常见误解: "对 KV Cache 友好"并非"零成本" 。目录首次进入请求需要处理,完整 Skill 正文首次加载时也会产生新增计算;只有当前缀保持稳定时,后续请求才可以复用缓存。
Skills 的共同收益是:无需在启动时加载所有 Skill 正文,也无需在每次调用新 Skill 时回头改写已经建立的上下文。
六、场景五:上下文压缩------看似矛盾,实则互补
这里有个表面上的冲突:前面反复强调 KV Cache 要求前缀不变,但压缩不就是要改上下文中间的内容吗?
关键在于理解压缩发生的时机和位置 。压缩不是在单次 API 调用的过程中修改上下文,而是在两次 API 调用之间,由 Agent 框架对消息列表进行预处理:
- System Prompt 和 Tool Definitions 永远不动------这是最前面的"静态前缀",KV Cache 持续缓存。
- 压缩的对象是对话历史中的 tool results------当框架用压缩后的摘要替换原始工具输出时,替换位置之后的缓存会失效,但之前的缓存仍然有效。
- 这是一个有意识的权衡 :不压缩,上下文膨胀到超出窗口限制,任务直接失败;压缩后虽损失部分缓存,但上下文长度可控且信息密度更高。因此压缩频次需要权衡------频繁压缩会频繁破坏缓存,最好在上下文接近阈值时批量压缩,而不是每轮都压。
如果模型把 thinking 绑定在前缀上(见下文),压缩要付出的就不只是缓存。常见的"摘要旧轮次、原样保留最近几轮"在这里会出问题:保留下来的那几轮 thinking 产生时,前面是原始历史而不是摘要,前缀已经变了,它们会全部失效;原地截短旧的工具结果也是同理。Anthropic 推荐的做法有两种:要么把整段会话压成一条摘要消息,不再回传任何旧 thinking,让模型从摘要出发重新推理;要么交给服务端去压缩或清理上下文,服务端的这类改动不算修改前缀。
七、场景六:Agent 状态栏------为什么注入位置是"末尾的 user 消息"
Agent 有个普遍痛点:关于"已经打了几次电话"的知识没有被自动提炼出来,而是以原始通话记录的形式分散在 KV Cache 的向量表示中。模型每次做决策都必须花费额外的思考 token 去扫描上下文重新统计,效率极低且错误率很高。
状态栏的做法是把分散在上下文各处的隐式状态提炼为可直接使用的显式知识。原始轨迹中的信息是高度冗余的------大量 token 中只包含少量关键状态信息。
实现上一个重要细节 :Agent 状态栏在 API 层面实际上是作为一条 user 角色的消息插入到上下文末尾的------而不是修改开头的 system 消息。原因正是 KV Cache 约束:修改 system 消息会破坏整个前缀的缓存。
这里要澄清:这里的 user 角色只是 API 协议层面的技术选择,并不等同于"来自终端用户的输入"。Harness 是在借用 user 角色这个消息槽位,向模型注入由框架自动生成的系统状态信息------内容并非来自真实用户。
看官方的消息列表示例:
css
messages: [
{ role: "system", content: "You are a customer service assistant..." } ← Fixed (KV Cache cached)
{ role: "user", content: "Help me cancel my Xfinity plan" } ← Original user request
{ role: "assistant", content: null, tool_calls: [...] } ← Round 1
{ role: "tool", content: "Call log..." }
...
{ role: "user", content: "<agent_status>
- phone_call invoked 3 times (Xfinity: 3/3 max)
- Current time: 2025-09-14 10:30:45
- TODO: [1] Cancel plan (in_progress)
</agent_status>" } ← Status bar injected by Agent framework (as a user message)
]
这条消息在上下文最末尾,紧邻模型即将生成的新 token,因此能获得最高的注意力权重;同时因为它是追加而非修改,前面所有已缓存的内容都不受影响。
这同时解决了另一个问题:长上下文中注意力资源有限 。随着上下文增长,模型必须把注意力分配给更多信息片段,关键信息可能得不到足够权重;特别是在复杂 Agent 轨迹中,早期设定的任务目标和关键约束容易被后续大量工具调用结果淹没 ------模型会过度关注最近内容,对中部的信息产生"注意力衰减"。状态栏通过把关键元信息结构化地放在上下文末尾,实现了一种"强制性的注意力引导"。
状态更新的两种实现,各有缓存代价
"追加不破坏缓存"只在单次注入时成立。状态是会变的------下一轮 TODO 完成一项、工具计数加一,状态消息就过时了:
实现一:每轮替换。 每次调用前移除上一轮状态消息,在末尾追加最新状态。保证上下文中只有一份状态。代价是移除旧状态会使其位置之后的所有缓存失效------这与"动态时间戳"是同一个失效机制,区别只在于状态消息位于末尾,失效范围只覆盖上次注入后新增的消息(通常一轮),整个前缀仍可复用。
实现二:持久追加。 状态消息一旦注入就永久留在轨迹中,每轮只在末尾追加新的状态。Claude Code 的 <system-reminder> 采用的就是这种方式------历史状态消息保留在会话记录中,从不删改。这对缓存完全友好:所有消息只追加、不修改。代价是陈旧状态会在上下文中累积,既占用 token,也要求模型自己关注"最新一条"而忽略过时的旧状态。
取舍模型(设状态 S token、两次更新间新增后缀 R token、预计更新 N 次、缓存输入单价为普通输入的 α 倍):
- C_替换 ≈ (N−1)(1−α)R
- C_追加 ≈ αSN(N−1)/2
因此当 αSN/2 < (1−α)R 时倾向实现二,否则倾向实现一。该估算未计上下文占用和陈旧状态带来的歧义,实际选择还应结合服务商的缓存计费与实测命中率。
八、场景七:缓存作为架构约束------当性能建议变成正确性要求
这是最有意思的一层:在生产级 Agent 系统里,缓存不仅是性能优化手段,而是一个架构约束,决定了系统中许多看似无关的设计决策。
Claude Code 的实践揭示了这个模式:当 Prompt Cache 的经济效益足够显著时,缓存一致性会反过来主导系统的架构选择。
(1)提示词的结构由缓存边界决定。 系统提示词在物理上被一个缓存边界标记分为两部分:标记之前的内容可跨用户、跨会话全局缓存;标记之后包含用户和会话特定信息。这意味着提示词的排列顺序首先由缓存经济性决定,其次才是语义逻辑 。每个运行时条件(操作系统类型、当前模式、用户偏好等)若放在缓存边界之前,就会把缓存键的变体数量翻倍------若 N 个条件都是二值的,就产生 2^N 种组合。例如 3 个条件(macOS/Linux、普通/调试模式、中文/英文)会产生 2×2×2 = 8 种缓存键。
(2)子 Agent 必须与父 Agent 字节级对齐。 当主 Agent 派生子 Agent 或做旁路查询时,如果子 Agent 继承父 Agent 的上下文,其提示词、工具定义、模型配置、消息前缀和思考配置必须与父 Agent 逐字节匹配,才能命中 API 服务商的 Prompt Cache。当然,一些框架派生子 Agent 时使用不同上下文或提示词,就不要求字节级对齐。
(3)工具结果的替换字符串在首次出现时就被冻结。 当大型工具输出被替换为摘要预览时,替换后的字符串会被持久化保存;即使后续会话重启,系统也使用完全相同的替换字符串,以保证恢复后的消息序列与缓存中的字节流一致。
(4)多角色切换的代价。 transfer_to_agent 与 Skill 在缓存友好度上差别很大:
| transfer_to_agent | Skill | |
|---|---|---|
| 对前缀的影响 | 替换 system prompt 和工具集,每次切换都改变请求前缀;从变化点起前缀缓存通常无法复用 | 静态前缀保持不变;Skill 内容成为末尾轨迹,已有前缀可继续复用 |
所以判断标准是:角色差异主要来自知识、流程和写作风格时优先用 Skill;涉及权限、工具隔离、合规边界时用独立 Agent 并在 Harness 层用代码限制。
(5)插件与自我进化的代价。 运行中的插件会改变模型可见的工具集和提示片段,请求前缀一变,KV Cache 就从变化处开始失效。因此 dsh 插件的说明文档中需要专门描述对上下文和 KV Cache 的影响。
(6)连推理链也绑在前缀上。 Anthropic 从 Claude Fable 5.1 和 Claude Opus 5.5 开始引入的"保留思考"(preserved thinking)机制,初衷是防蒸馏:服务端用签名把每个 thinking block 绑定到产生它时的前缀上------也就是顶层的 system、tools,以及它之前的全部消息。之后只要这段前缀有任何改动,这个 thinking block 和它后面的所有 thinking 都会失效。
官方文档说得很直白:让 thinking 失效的改动,正是让缓存从头计算的那些改动。容易踩坑的写法都是熟面孔:每轮重建系统提示词(写入当前时间或模式开关)、每轮重新渲染首条用户消息里的环境信息、中途增删 tools、原地截短旧的工具结果、往旧消息里插入提醒又删掉。解决办法同样是把变化追加到末尾:新指令用一条追加在对话中的 system 消息,环境变化写进最新一轮,工具增删用专门的消息块声明,而不去改 tools 数组。
核心启示 :在设计 Agent 架构时,缓存经济性不是事后优化,而是前置约束 。越早将这个约束纳入架构设计,后续的工程代价越小。当推理链也绑定在前缀上时,"只追加、不改写"就从性能建议变成了正确性要求。
这也呼应了全书的"只增不改(Append-only)"设计模式:状态以追加方式演进,已写下的内容不再回头修改,换来的是可缓存、可重放、可审计;KV Cache 前缀稳定性是它的性能形态------改动越靠前,作废的缓存越多。
九、场景八:成本优化------省了多少,要实测
这是最"接地气"的一环。一个八轮客服退款任务的真实成本对照(gpt-4o-mini,分别开关"稳定前缀"和"压缩历史"):
| 方案 | 输入 token | 缓存 token | 总成本 | 比基线节省 |
|---|---|---|---|---|
| 无缓存、无压缩 | 20,700 | 0 | $0.003776 | --- |
| 仅稳定前缀 | 20,386 | 13,568 | $0.002707 | 28.3% |
| 仅压缩历史 | 16,177 | 0 | $0.003115 | 17.5% |
| 稳定前缀 + 压缩 | 16,035 | 6,144 | $0.002643 | 30.0% |
基线组中每轮输入从 1,113 token 涨到 3,668 token;工具返回跟着历史反复进入后续请求,八轮累计占了 9,544 个输入 token。两项优化同时开启后这个数字降到 5,248,总费用下降 30%。
关键结论 :仅稳定前缀已节省 28.3%,仅压缩历史又节省 17.5%,两者一起却只节省 30%,并不是二者相加 。原因是压缩历史的同时,也缩短了可以命中缓存的前缀。因此几项上下文优化同时使用时,必须在完整任务中一起实测,不能把各自的节省比例直接相加。
从输入侧看,最值得优先测试的三类办法是:复用 KV Cache(尽量保持前缀不变)、压缩上下文、分层选择模型;这些能力最好都能独立开关,这样既能看清每一项的作用,也能检查合用时会不会互相抵消。
十、场景九:常见错误模式(反面清单)
实验系统性测试了几种常见但有害的上下文管理模式:
| 错误模式 | 问题 | 正确做法 |
|---|---|---|
| 动态系统提示词 | 时间戳注入使每次请求前缀不同,缓存全废 | 时间信息作为 user 消息追加到末尾,或只在需要时通过工具调用获取 |
| 动态用户配置 | 每请求更新用户状态(API 次数、余额)嵌入上下文会破坏缓存 | 用专门的状态管理机制按需处理 |
| 工具定义动态排序 | 按使用频率调序,工具定义篇幅大,从首个变动的工具起全部失效 | 保持固定顺序(实验表明对模型选择工具的能力几乎没影响) |
| 滑动窗口对话历史 | 破坏前缀一致性致缓存失效;且丢失关键工具调用结果 | 见下文 |
滑动窗口的问题值得展开:窗口设为 10 条消息时,第 11 条进来最早那条就被丢弃。它有两个严重问题------第一 ,破坏上下文前缀一致性导致 KV Cache 失效;第二 ,可能丢失关键工具调用结果。举例:窗口大小 10 轮时,Agent 在第 2 轮调用文件读取工具拿到关键内容,到第 15 轮还需引用,但原始结果已滑出窗口,模型只能依赖被截断的对话推断,错误率显著上升。实验中,使用滑动窗口的 Agent 经常陷入循环,反复执行相同的工具调用,因为它"忘记了"之前已获得的结果。
十一、Frontier:KV Cache 未必是一次性的
前面所有讨论都建立在一条铁律上:前缀里改一个字节,后面的缓存就全废。这条铁律在今天确实成立,但原文指出它未必是必然的。
出发点是一个反直觉的观察:在 prefill 阶段,模型其实在"做笔记" 。当它读到上下文里的某个字段(比如"用户所在城市:北京")时,并不是把这个字段原封不动缓存下来,而是顺手把"这个字段意味着什么"的结论写进了后面每一层的 KV 状态里。测量发现,一个字段自己那几个 token 的 KV,对最终决策的贡献往往不到 1% ------真正影响输出的是它在下游留下的那些"读书笔记"。
这个发现打开了两种操作:
- 编辑(Editing) :既然结论已写进下游笔记,改掉一个字段后,只要模型有显式思考链(CoT),就能让改动顺着已缓存的思考传播下去,用大约 1% 的算力得到与"整段重算"一致的结果。反过来,如果没有 CoT,孤立地改字段会被忽略------因为结论早已固化在下游状态中,却没有一条思考路径去更新它。
- 组合(Composition) :把一段预先算好的"技能"缓存,通过旋转位置编码(RoPE)挪到新位置,直接拼接进另一段上下文,不必重算注意力------于是"用模块化缓存块拼出长上下文"从 O(L²) 的重算降到 O(L) 的拼接,结果与完整重算难以区分。
论文在 vLLM 上实现后,首 token 延迟(p90)最多降低数十倍至数百倍,前缀缓存命中率约 98.5% ,而输出与逐字重算在决策上完全一致(跨 12 个模型,logit 余弦相似度 0.90--0.999)。
一个恰当的比喻:你读厚文档时不会每改一个事实就从头重读,而是靠页边笔记------笔记里已写着"所以这意味着 X"。某个事实变了,只需修正那条笔记,由它支撑的结论就会随之更新;又因为笔记用可搬运的速记写成,你还能把上次为别的问题记的一页笔记,重新编号后(这就是 RoPE 重定位)粘到新问题里复用。
但要明确边界:这仍属研究阶段,前面那三条实践结论在当前生产系统中依然是应当遵守的默认原则。
十二、汇总:一张表看清各场景
| 场景 | KV 的作用 | 关键约束 / 代价 |
|---|---|---|
| 单次推理 | 复用历史 token 的 K、V,避免每步重算前缀,把 O(N²) 降为线性 | 每步注意力仍需遍历全部缓存,随长度线性增长;显存与带宽成为瓶颈 |
| 多轮对话 / Agent | 每轮只算新增 token,前缀直接复用 | 前缀任一字节改动,从改动点起缓存全废;改动越靠前代价越大 |
| 跨请求(Prompt Cache) | API 服务商匹配相同前缀,复用已算好的 KV | 跨请求层级,读取成本约首次的 1/10;各家启用与计费方式不一 |
| 工具定义 / Skills | 新工具 schema 追加到末尾,固定在原位置持续命中 | 动态加载工具会破坏缓存;必须"只增不改"追加式注入 |
| 上下文压缩 | 只动轨迹中的 tool results,静态前缀保持缓存 | 压缩点之后的缓存失效;需权衡频次,宜接近阈值时批量压缩 |
| Agent 状态栏 | 以末尾 user 消息注入,既拿最高注意力又不破坏前缀 | 更新状态时"每轮替换"或"持久追加"各有缓存代价 |
| 架构设计 | 缓存一致性主导提示词排列、子 Agent 对齐、字符串冻结 | 缓存经济性是前置约束,不是事后优化 |
| 成本优化 | 稳定前缀单独可省 28.3%,与压缩合用 30% | 不能把各项节省比例直接相加,必须端到端实测 |
一句话总结
KV 缓存表面上是"把算过的 K、V 存起来别重算",但它真正的身份是一条塑造整个系统设计的力量:
- 在推理层,它是把二次增长压成线性的核心手段,但线性项不会消失,于是显存与带宽成为天花板;
- 在 Agent 层,它把"前缀必须字节级稳定"变成了不可绕过的工程纪律,直接决定了提示词结构、工具加载方式、状态注入位置、压缩时机,甚至推理链的传递方式;
- 在商业层,它通过 Prompt Cache 直接把账单和延迟挂钩------一行时间戳就能让首 token 延迟涨到 3-5 秒、账单翻倍;
- 在架构层,当缓存效益足够大时,它从"性能建议"升格为"正确性要求"------"只追加、不改写"成为必须遵守的纪律。
而前沿研究正在松动"改一个字节全废"这条铁律:如果 KV 真的是"模型做的笔记",那么笔记就应该可编辑、可搬运。