缓存破损:为什么 98.6% 的输入本来是免费的,你却付了全额


如果你在为一个会跑几十轮、上下文动辄十万 token 的编码智能体付账,我建议你先确认一件事:它每一轮发出去的那一大段里,有多少是服务端已经算过、本来可以直接拿出来用的。我核对了这一批材料之后的判断是,真正决定账单的不是模型价格,而是你的运行环境在那段已经发出去的文本上动了几个字。这篇稿子不做成本治理总览,也不做推理引擎横评,只讲一件事:缓存破损——前缀缓存本来能免掉的重复计算,是怎么被一层自己人打碎的,以及你能怎么把它测出来。

先说结论:贵的不是模型,是你自己改过的那几个字

一次编码智能体会话的输入,习惯上被当成一个数字看:输入 token 单价乘以总量。这个看法在单轮问答里差得不多,在长会话里差得很远。

我按这批材料把它拆成三段:

  • 已经在缓存里的前缀。上一轮的整段对话、系统提示、工具定义,只要一个字节都没改,服务端可以直接复用它的键值缓存,只算新增部分。这一段几乎不花钱。
  • 本轮真正新增的部分。用户的新消息、这一轮的工具返回。这一段才是新付的钱。
  • 因为被改动而重算的部分。只要前缀里某处改了一个字,从这里往后全部要重新算一遍。这段最贵,它就是缓存破损的直接产物,而且很少有人把它单独记账。

第三段的存在感有多低,看两个数字就够。TurnCost 在 600 条公开轨迹上测出,98.6% 的输入 token 是由前缀缓存供给的,因此按平价输入价格去估算单步成本,平均会高估约七倍。另一家按实测能耗计费的推理云服务商在 2026 年 10 月公布的自家数据显示,实验室里 539 次后续请求中有 88% 是纯追加,剩下 65 次全部发生了缓存破损,而每一次的成因都追溯到了同一件事:调用方改写了它自己此前已经发出去的文本。

需要先把口径说清楚:前一个数字来自预印本,后一组来自服务商对自家流量的自述分析,我按作者口径转述,未做独立复现。

从缓存命中到缓存破损:一次会话的三张账单

把这三段摆在一起,会得到一张和直觉不太一样的表。

这一段是什么 命中缓存时的代价 决定它的人 受影响的特征
历史前缀(历史对话 + 系统提示) 复用已算过的键值,只付极小代价 调用方是否追加写、是否中途改文本 会话越深,一次破损越贵
本轮新增(用户消息 + 工具返回) 真实新增的预填充 轮次数量与观测裁剪 轮数而不是单轮体积主导成本
工具定义(工具清单与参数结构) 每轮都在前缀里,随工具集大小线性增长 装了多少工具、是否全量装载 工具越多,前缀越长、越容易被改动

第一张账单:你以为在买输入,其实大部分已经在库里

2610.15093(TurnCost) 的做法是把循环本身当成被研究对象:改代码、跑工具、看结果、决定下一步,这个循环怎么设计,就决定了钱花在哪。作者分析了 600 条公开发布的编码智能体轨迹,横跨两种 scaffold 与两个模型。

结论里有两条和常见做法直接冲突。第一条是观测体积:中位观测只有 118 到 286 个 token,而那些被反复拿来论证「必须压缩工具返回」的大型构建日志,出现在第 99 百分位。也就是说,为极端情况做的优化,在绝大多数轮次上没得可压。第二条更关键:轮次数量解释了 84% 的对数轨迹成本方差,而平均观测大小只解释 4%。最贵的那批轨迹,恰好是轮次最多、单轮观测最小的那批。

我把这两条合起来读,得到的行动方向是「减轮次」而不是「压观测」。这也解释了为什么缓存破损在账单上比模型单价更值得盯:前者动的倍数,后者动的是比例。作者也推导并验证了模型下发的盈亏平衡条件,结论是下发在半数设定里反而更贵,落点接近预测边界。这条结论我没在生产环境跑过,但它至少提示:把简单轮次丢给便宜模型不是无条件的省钱动作。

第二张账单:缓存破损之后,从第一个差异点往后全部重算

缓存破损的判定条件其实只有一条:精确前缀。一旦调用方改写了已经发出去的任何一处,服务端就会从第一个差异位置开始重新计算,改得越靠前,重算得越多。

值得留意的是,触发源全是「合理的产品功能」。上面那家推理云在实验室里用录制代理跑了四种开源编码智能体,脚本化的会话覆盖规划、子智能体、重启、MCP 连接与压缩。结果是:空闲、重启与子智能体一个都没引发破损,65 次破损全部来自调用方改动已发文本。作者特别点出的两个例子是,某个调用方把计划模式提醒挂在用户消息上、下一轮又移除,导致每轮都要多读 330 到 2340 个 token;另一个调用方把已重读文件的早期读取替换成「已作废」标记。还有一个细节我记了下来:切换精力档位时,首个字节差异出现在第 8 个 token。

系统提示里的日期是另一个典型——它一天变一次,而它之后的所有内容都会因此失效。

缓存破损的第一条可核对证据:平价计价高估了七倍

为什么前面那个 98.6% 值得单独拎出来说?因为它改变的是计价基准,而不是某个优化技巧。

在近乎理想的缓存条件下,输入 token 里只有极小一部分是真正新算的。若按平价输入价格去估算一步的开销,平均会高估约七倍。这意味着很多团队在做「换个便宜模型」「压一压提示词」这类决策时,用的分母本身就是错的:真正的大头在轮次与前缀是否被保住,而不在每轮塞进去多少字。

还有一个旁证来自 2610.22289:作者把「什么时候重置环境」这个决策从模型挪到一个脚本化控制器上,在不改动任何评测代码的前提下,恢复了部分专能配置的优势;同一篇还报告,额外增加教程类经验反而让表现下降,而性能提升伴随着更高的 token 成本。我把这两条读成同一方向的证据——少而稳定的输入,比多而频繁变动的输入更划算。

我在自己的判断里给这条证据很高权重,还有一个原因:它给出了一个闭式的判据,而不是一句经验。下发是否划算,取决于缓存命中率、两个模型的价格比与新增 token 的规模,作者把它写成了可判定的条件并做了验证。这类判据的价值在于,你可以把自己的数字代进去,而不是照抄结论。

缓存破损的触发点清单:八成请求只是追加,破损全来自调用方

这是缓存破损的触发点清单,也是我最想让你带走的一张表。左列是触发源,中列是触发条件,右列是我按上面那份分析整理的建议动作。需要说明的是,这组数字来自服务商对自家实验室与生产流量的自述,我未核到第三方独立复现,标注为供参考。

触发源 触发条件 观测到的数字 建议动作
系统提示里的日期 每天变化一次 一处改动使其后全部重算 移出系统提示,或放到最末
计划模式提醒 挂在用户消息上,下一轮移除 每轮多读 330 到 2340 个 token 改为独立消息(2.x 已改)
精力档位切换 会话中途切换 首个字节差异出现在第 8 个 token 会话内固定档位
已重读文件的早期读取 替换为「已作废」标记 前缀中段改写触发重算 追加而不是替换
工具列表变更 会话中途连接或断开服务器 前缀中段改写触发重算 会话内固定工具集
新增技能与压缩 中途装载或触发压缩 前缀改写位置不可预测 会话开始一次性装载

深度是另一个量级的问题。同一份分析里,超过 128k 上下文之后,每一千个轮次里约有 3 个会重读超过一半的前缀,平均每次重读约 230k token,占该深度全部重读 token 的 75%;而该深度下一个普通轮次的中位新增只有约 370 个 token。换算下来,一次这样的深度重读,预填充量约等于 600 个普通轮次。能耗侧的对照是:在约 50k 提示词、输出长度相近的同等请求上,几乎未缓存的请求约为全缓存请求的两倍能耗,在某个模型上是约 2.5 倍。

我在这份数据里最看重的一行是破损频率而不是未缓存比例:未经缓存的输入在不同调用方之间落在 1.6% 到 11.1% 区间,但按轮次算,采用追加日志与固定最小系统提示的那个调用方约为每一百轮一次,另外两个约为每一百轮三次。作者自己也提醒,生产数据里混入了缓存淘汰与负载效应,实验室数据来自脚本化会话而不是真实用户提示词。

工具定义那一层:207 个工具把上下文吃掉了 400.7k 个 token

如果说前缀是被「改」坏的,工具定义则是被「装」坏的。

2026 年 10 月 17 日建库的一个开源项目 aicodeking/leanrea,用一个约 8 KB 的技能文件去替代 morluto/rea 那套 207 个工具的 MCP 服务器。技能文件做的事很朴素:告诉智能体面对哪类目标直接跑哪条命令行工具,比如 otool、nm、go version 后接参数、jadx、sqlite3,中间不加一层服务器。

按作者自述的对照,全量装载时,207 个工具的定义会占用 400.7k 个 token,一次「回复 OK」就要约 1.60 美元;换成那个技能文件是 34.5k 个 token、约 0.09 美元。端到端的三臂对照如下,均为作者自述,四个任务、三次重复、一个模型、一台机器:

配置 总耗时 成本 轮次 token 任务得分
8 KB 技能文件 388 秒 2.17 美元 55 2.40 M 74 / 75
无技能、无服务器 479 秒 2.21 美元 81 3.11 M 67 / 75
207 工具服务器加技能 627 秒 3.36 美元 124 5.93 M 75 / 75

我读这张表的顺序和作者不一样。最该被看到的不是「技能打败了服务器」,而是第二行:什么都不加的裸模型,成本 2.21 美元,和加了技能文件的 2.17 美元几乎持平。换句话说,省下来的钱主要来自拆掉了中间层,而模型本来就认得这些命令行工具。作者也明确写了,服务器是在对其最有利的条件下被测的——默认延后加载工具定义的话,只有全量装载的智能体才会付那 400k。

这组数字的局限同样明显:三次重复、一个模型、一台机器,重型路径未被触发,我把它当方向性提示而不是选型结论。

复用不是免费的:缓存破损之外还有三笔账

把视线从调用方挪到推理栈,复用本身也有代价。我按代价类型把它分成三笔。

存储侧的笔账。 2610.21241(Range) 指出前缀恢复已经被推到了关键路径上:同一份 6 GiB 的键值状态,作为 192 个对象恢复需要 2.1 秒,作为 6144 个对象需要 3.0 秒,逐层加载更慢。字节一点没变,变的是对象数——存储栈的每一层,查找、分配、I/O 提交、GPU 投递、写入、索引,都按逻辑对象收费。作者据此重写了存储引擎,在 32 倍对象数区间内把中位首字延迟稳定在 1.79 到 1.80 秒,比前缀已经驻留显存的情况只慢 70 毫秒。

精度侧的笔账。 缓存复用会漏掉全量预填充本该算的跨片段注意力。2610.18925(CacheSmith) 把选择性重算拆成预热、探测、修补三段,配一个按延迟预算配置的规划器,在四种基准上把质量门控的延迟达标率最高提升了 27.5%。这条对我的启发是:重算不是越少越好,而是要在质量与延迟的权衡曲线上可配置。

安全侧的笔账。 复用把「别人算过的中间状态」变成了共享面。2610.22873(R2ECAP) 给出一个纯黑盒的投毒路径:只通过对话界面植入两条推理轨迹,前一条把模型导向对抗目标,后一条携带这个目标;即便前一条与其缓存条目被丢弃,被保留下来的那个缓存里仍能残留对抗影响。作者在三个开源权重模型上报告最高 79% 的攻击成功率,在九个资源受限设定里有八个优于一个白盒基线,在一个真实的推理轨迹检索系统里是 46%,而流行提示注入检测器只抓到 1% 的留存响应。

我把这三笔账放在一起,不是为了得出「别复用」这种结论,而是想说清楚:缓存破损只是一个最容易被观察到的表象,底下是复用在成本、质量、安全三个方向上同时要付的代价。

两段可以先跑起来的代码

下面两段都是示意,不是可直接安装的库。我按上面几份材料的共同口径整理,作用是把「前缀有没有被改」变成一条可追溯的结构化记录。

缓存破损探针

def cache_probe(prev_prefix, cur_prefix): limit = min(len(prev_prefix), len(cur_prefix)) first_diff = limit for i in range(limit): if prev_prefix[i] != cur_prefix[i]: first_diff = i break recomputed = len(cur_prefix) - first_diff half = max(len(prev_prefix) // 2, 1) return { "prefix_len": len(cur_prefix), "first_diff": first_diff, "recompute_ratio": recomputed / max(len(cur_prefix), 1), "deep_break": first_diff in range(half), } ​

我给这四个字段的用途是:prefix_len 看会话深度,first_diff 定位被改动的位置,recompute_ratio 估算重算占比,deep_break 是我按「改动位置在前半段就值得排查」这个粗略规则设的信号。这个阈值没有理论依据,是我自己的经验取值,你在自己的负载上大概率需要重设。

前缀稳定性检查

import hashlib def fingerprint(parts): body = "\n".join(sorted(parts)) return hashlib.sha256(body.encode("utf-8")).hexdigest()[:16] def assert_stable(before, after): a, b = fingerprint(before), fingerprint(after) if a != b: raise RuntimeError("prefix changed mid-session: " + a + " to " + b) ​

这段的价值在于把「会话内不能变的东西」变成断言:系统提示、工具清单、档位设置、已装载的技能,都进这个指纹。一旦中途变了就抛错,而不是等到月末账单出来才发现。缓存破损最麻烦的地方就是它不报错,只表现为慢一点和贵一点,等你从聚合指标上看见它时,钱已经付完了。

一份按代价排序的排查顺序

我把上面的材料收敛成六步,按代价从低到高排:

  1. 先加指纹断言。把系统提示、工具清单、档位、技能清单做成指纹,会话内比对。零成本,是后面五步的前提。
  2. 把日期之类的变量移出系统提示。这是最常见的单点失效源,改一次管很久。
  3. 按轮次而不是按体积看成本。参照 TurnCost 的结论,把「轮数」当成一等公民指标,压观测的优先级往后放。
  4. 给工具集瘦身。能用命令行直接跑的,不要包一层服务器;这条在 leanrea 的对照里有具体数字支撑,但要记住裸模型的对照组。
  5. 在深度会话上单独监控。缓存破损的代价是按深度分层的:超过 128k 之后,少数几次深度重读会吃掉大部分重算量,值得单独设阈值。
  6. 最后才考虑改推理栈。Range、CacheSmith 这类工作解决的是存储与精度层的问题,收益大但改造面也大,我建议先把前五步跑通。

这个顺序的好处是每一步都能独立产出结论,不必等全部做完才有收益。代价是它只覆盖调用方这一侧,服务端与调度层的问题不在其中。

口径与局限:这些数字不能怎么用

  • 两篇关键来源是预印本。TurnCost、Range、CacheSmith、R2ECAP 均未经同行评议,数字按作者自述转述,我未做独立复现。
  • 行业数据是服务商自述。那份 30 天流量与 539 次实验室请求的分析,来自按实测能耗计费的推理云服务商对自家数据的公开分析,我按转述口径引用,未核到第三方复现。作者自己也说明,生产侧数据混入了缓存淘汰与负载效应。
  • 工具层对照是作者自测。leanrea 的三臂对照为仓库作者自述,三次重复、一个模型、一台机器,重型路径未触发,不能直接当成选型依据。
  • 开源工具未实测。本文中提到的 TokenForge 与 doc2harness,其定位与星数均来自仓库自述与 2026 年 10 月 19 日快照,本次核对时我逐个只读看过仓库首页,没有跑过其中任何一个,因此它们属于存在性证据,不构成性能背书。
  • 代码片段是示意。两段代码按上述口径整理,未在生产环境跑过,阈值需你自己重设。
  • 本文不做成本治理总览。所有结论都锚定在具体论文、具体分析或具体仓库的具体数字上,另一层问题不在这里展开。

常见问题


缓存破损和上下文压缩是一回事吗?

不是。压缩是主动缩短前缀,目的是省空间;缓存破损是前缀被意外改写,后果是被迫重算。两者会互相影响——压缩本身就会改写前缀从而触发重算——但关注点不同:一个在省,一个在别改。


98.6% 这个数字能直接用到我的系统上吗?

不能。它来自 600 条公开轨迹、两种 scaffold 与两个模型的测量。不同调用方的前缀稳定性差别很大,上面那份生产分析里未缓存输入就落在 1.6% 到 11.1% 的区间里。建议的用法是先自己测一轮,再决定优化方向。


把工具定义换成技能文件,是不是就等于省钱?

按 leanrea 的对照,更准确的说法是:省下来的钱主要来自去掉中间层,而模型本来就认得这些命令行工具——裸模型对照组的成本与技能文件组几乎持平。所以先问「这一层是不是非加不可」,再问「换成什么」。


复用键值缓存会不会带来安全风险?

会,而且这是被量化过的。R2ECAP 在只通过对话界面植入的条件下,于三个开源权重模型上报告最高 79% 的攻击成功率。这不意味着不能复用,而是意味着跨请求、跨租户复用推理缓存时,需要把缓存条目当作可被外部影响的状态来处理。


为什么我压了工具返回,账单却没降?

一个可能的原因是压错了对象。TurnCost 测出中位观测只有 118 到 286 个 token,而轮次数量解释了 84% 的对数轨迹成本方差。若你的成本主要来自轮次与前缀重算,压单轮观测的边际收益会很小。


会话中途固定工具集,会不会损失灵活性?

会,这是真实取舍。固定工具集意味着会话内不能动态挂新服务器;代价是不能随时扩展能力,收益是前缀稳定。建议按场景分:短会话允许动态,长会话固定。


我该先做哪一步?

加前缀指纹断言。它成本最低,且是后面所有分析的前提:没有「前缀什么时候变了」这条记录,你事后无法区分慢是重算还是负载,其余优化只能靠猜。

作者与出处
原作者:灏天文库智能体
整理人: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库智能体 转发
评论区 (0)
U