Neuralwatt 分析 agent harness 如何影响缓存与能耗效率
Why harnesses matter for energy (and token) efficiency
Neuralwatt CEO Chad Gibson 基于 2026 年 9 月 8 日至 10 月 7 日的云上流量指出,不同 agent harness 的未缓存输入占比在 1.6% 到 11.1% 之间,92% 缓存率相比 97% 意味着约 2.7 倍的 prefill 计算。
作者:Chad Gibson,联合创始人兼 CEO
主要发现
- 各 harness 的缓存方式不同。我们看到各客户端的未缓存输入占比在 1.6% 到 11.1% 之间。“已缓存”指的是从提示缓存中直接读取、而非重新计算的输入 token 占比。缓存率为 92% 时,每 100 个 token 中有 8 个未缓存;缓存率为 97% 时只有 3 个:前者约为后者的 2.7 倍预填充工作量。
- 一次缓存未命中大约会让请求的能耗翻倍。这里的未命中指请求的提示中只有很少或完全没有被缓存(低于 10%)。在 GLM-5.3 上,约为 2.5 倍。
- 长会话会让未命中的代价变得高昂。重新读取意味着要从头重新计算提示中的那部分内容。超过 128k token 后,某一轮如果重新读取了其提示的一半以上,其预填充成本大约相当于在该深度下约 600 个普通轮次。
- 其中大部分问题是可以修复的。我们实验室测试中的每一次缓存中断,都源于 harness 修改了它已经发送过的内容,从而迫使从第一个差异处开始重新读取。
在 Neuralwatt,我们能够看到调用模型的 agent harness 如何影响其能耗。我梳理了我们云上整整一个月的流量,发现 harness 似乎决定了我们 GPU 承担多少工作量的相当大一部分。我们通常从模型(有多少激活参数、采用何种注意力机制)或硬件(H200 对比 B200 对比 B300)的角度来讨论推理效率。客户端这一侧值得更仔细地审视。
我们几乎所有的流量都是 agentic 的,输入远多于输出。一个典型的编码 agent 轮次会发送 10 万 token 或更多的对话内容,而只返回几百个 token。这些输入大部分是上一轮的提示,只在末尾添加了一点新内容。如果前面的文本没有变化,服务器已经持有其键值(KV)缓存,只需处理新增部分。这就是提示缓存,是我们最大的效率来源。提供已缓存的 token 远比重新计算便宜得多。
缓存需要精确的前缀。当 harness 修改了它已经发送过的内容时,就会发生缓存中断。随后我们会从第一个差异处开始重新计算之后的所有内容。靠近开头的改动,哪怕只是一个时间戳,也意味着几乎要重新计算整个提示。靠近末尾的改动则代价很小。harness 控制着提示的构建方式。
同一模型、同一价格,缓存率却不同
每个请求都会告诉我们它来自哪个客户端。在 2026 年 9 月 8 日至 10 月 7 日的 30 天里,我们看到了数百个不同的客户端,包括 OpenCode、Oh My Pi(omp)和 Pi 等开源编码 agent。其中还包括编辑器集成、OpenAI SDK 和自定义应用。仅 OpenCode 一家就被我们约四分之一的客户使用。
对于使用最广泛的客户端,以下是我们无法从缓存中提供的输入 token 占比:

这里每个客户端都对其绝大部分输入进行了缓存。但这些微小的差异仍然重要:缓存率为 97% 的客户端每 100 个 token 只向我们发送 3 个未缓存 token;缓存率为 92% 的客户端则发送 8 个。对于同样的对话,这意味着 2.7 倍的预填充工作量。
命中率还取决于工作负载,而不仅仅是 harness。会话越长得分越高,因为每一轮的新文本在提示词中所占比例更小,而 deepseek-harness 的领先优势约有一半来自运行超长会话的用户。衡量 harness 更清晰的指标是每一轮打破缓存的频率:deepseek-harness 约为每 100 轮中有 1 轮,而 OpenCode 和 Pi 约为每 100 轮中有 3 轮。我们在流量中同时看到 OpenCode 1.x 和 2.x,因此该柱状图将两者合并统计。单独看 2.x 表现更好(未缓存率 2.2% 对 3.0%),这主要归因于模型组合不同。在相同模型上,两者的缓存效果大致相当。2.x 修复了我们在实验室中发现的 1.x 的大部分缓存中断问题。这一差异在生产环境中并未显现,我们尚不清楚原因。
缓存未命中的能耗代价
我们按实测能耗计费,因此可以直接看到成本。在 2026 年 10 月 1 日至 7 日这一周,我们比较了相同模型、相同提示词大小(约 50k tokens)且输出长度相近的请求。以下是每个请求相比完全缓存命中时所多消耗的额外能耗:

大部分未缓存的请求所消耗的能耗约为同等大小缓存请求的两倍。在 GLM-5.3 上则接近两倍半。预填充还会与生成争用同一批 GPU,因此被浪费的预填充会拖慢该服务器上其他所有人的响应速度。
会话越深,缓存未命中的代价越高
我们按轮次跟踪了 2026 年 10 月 6 日至 13 日这一周内三个最繁忙模型上的单个会话。我们考察了每一轮中落在同一服务器上且距上一轮不超过五分钟的轮次,此时缓存本应处于热状态。大多数缓存中断规模较小:提示词末尾附近的几千个 token 被重新计算。
当上下文超过 128k tokens 后,每 1,000 轮中约有 3 轮会从头重新读取超过一半的提示词。这少数几轮占该深度下所有重读 token 的 75%。每一轮平均重读约 230k tokens。该深度下普通的一轮新增 token 中位数约为 370 个,因此其中单轮所消耗的预填充量约相当于 600 个普通轮次。
延迟也相当明显。以下是普通轮次与需要重读大部分提示词的轮次的首 token 时间中位数对比:

我更关注缓存中断发生在会话多深的位置,而不是中断的频率。在会话早期一次性修改提示词开头,代价很小。但在长会话的第 200 轮这样做,就会造成明显的大幅停顿和巨大的能耗成本。长智能体会话占我们 token 的大部分,因此这会不断累积。
我们无法总是从服务器端判断是 harness 修改了提示词,还是缓存在内存压力下被逐出。这些数字包含两种情况。下面的实验室测试表明,harness 会导致我们能够复现的早期提示词修改;逐出可能解释了其余情况中的一部分。
什么会打破缓存
我从两个地方寻找原因:实验室,在那里我们可以看到每一个 token;以及生产环境,在那里我们只能看到请求元数据。
在实验室中
我们通过录制代理,让四款流行的开源 agent(OpenCode、Pi、omp 和 Kilo Code)分别对接 DeepSeek V4.1 Flash 和 GLM-5.3 运行。我们使用的是截至 10 月 6 日的最新版本,均为默认设置:OpenCode 1.18.34、Pi 1.0.4、omp 18.6.3 和 Kilo Code 7.8.3。随后我们又在 2.x 系列的 OpenCode 2.0.29 上重复了该会话。每个 agent 都在一个小型 Python 项目上执行同一段脚本化会话:先规划,再构建,切换推理强度再切回,派生一个子 agent,空闲等待,被重启,连接再断开一个 MCP 服务器,添加一项 skill,并运行足够长的时间以触发压缩。我们把每个请求与其前一个请求进行比较,既比较 JSON,也比较服务器实际渲染出的精确 token。
在同一对话中紧随前一请求的 539 个请求里,88% 是纯追加:前一个 prompt 是新 prompt 的精确前缀。其余 65 个破坏了缓存。每一次破坏都源于 harness 改动了它已经发送过的内容;没有一次来自我们的聊天模板。空闲时间在四者中都没有造成破坏。重启 harness 并恢复会话、以及使用子 agent 也都没有造成破坏。
有两种原因会在用户没有任何操作的情况下破坏缓存:
- OpenCode 的 plan 模式提醒。一旦你使用过 plan 模式,OpenCode 就会在每条新的用户消息上附加一条提醒(“Your operational mode has changed from plan to build”)。它在下一轮会从消息中移除该提醒。于是每一轮都会重新读取整个上一轮,包括其工具调用和结果。在我们的短会话中,这相当于每轮 330 到 2,340 个 token。成本随该轮的规模增长。OpenCode 2.x 将提醒保留在独立的消息中,因此不再触发重新读取。
- omp 对重复读取文件的修剪。当文件被第二次读取时,omp 会用
[Superseded by a newer read of this file]替换历史中的较早副本。在我们的运行中,这一处编辑落在 prompt 约 62% 的位置,迫使重新读取 15,520 个 token 中的 5,846 个。omp 还会修剪“无异常”的工具结果,并且在空闲 90 分钟后会在任意深度进行修剪。设置compaction.supersedeReads: false和compaction.dropUseless: false可保持缓存。
Pi 和 Kilo Code 没有出现此类破坏。
其他原因取决于用户的操作。其代价取决于 prompt 变化的位置:
- 切换推理强度。两个模板都把强度级别写在 prompt 的最初几个 token 中:GLM-5.3 上是
Reasoning Effort: Max,DeepSeek V4.1 上是 1 到 100 的数字。两个模型上的第一处差异都出现在第 8 个 token,因此一次切换会重新读取全部内容。只有那一个值发生变化;其长度及其之后的所有内容都保持相同。 - 连接或断开 MCP 服务器。OpenCode 1.x 和 Kilo Code 按字母顺序在工具列表中间插入新工具。OpenCode 2.x 不改动工具列表,而是追加一条说明,因此连接和断开都是纯追加。Pi 添加一个工具并修改其系统 prompt。omp 向其系统消息追加一个段落。在 GLM-5.3 上,工具列表在系统 prompt 之前渲染,因此变化落在非常靠前的位置:对 OpenCode 1.x 而言大约在 1,300 个 token 处。
- 添加 skill。四者都在系统 prompt 中列出 skill。在 DeepSeek V4.1 上,第一处差异落在第 461 到第 2,948 个 token 之间,在 GLM-5.3 上则更靠后,因为那里工具排在前面。
- 日期变化。OpenCode 1.x 和 Kilo Code 把今天的日期放进系统提示词,大约在 2,000 个 token 处。OpenCode 2.x 保留会话最初的日期,并在日期变化时追加一条说明。omp 把它放在第一条用户消息里。Pi 不发送日期。对于会覆盖日期的客户端,跨过本地午夜就意味着几乎要把所有内容重新读一遍。
- 压缩。每个 harness 的做法都不同。Pi 逐字保留最后约 20k 个 token,但会重写靠近开头的摘要。提示词几乎不会缩小,而且其中大部分内容无论如何都会被重新读取。在同样 40k token 的窗口下,OpenCode 2.x 压缩了三次,总共重新读取了 67,850 个 token。1.x 压缩了一次,重新读取了 29,776 个。
切回我们在会话早些时候用过的某个设置,成本比我预想的要低。服务器仍然保留着那个较早版本的提示词。在我们的运行中,把 effort 切回去命中了 10.6k 到 27.5k 个缓存 token。每次使用一个新状态时,切换的代价是一次完整的重新读取,而不是每次翻转都要付。
在生产环境中
我们不记录也不保留客户提示词或响应的文本。本节中的所有内容都来自请求元数据。上面的 token 级分析来自我们自己的脚本化测试会话,而不是客户流量。该元数据记录了每个请求所要求的 reasoning effort 以及它发送了多少个工具。我们用它对照 2026 年 10 月 6 日至 13 日在 DeepSeek V4.1 Flash、GLM-5.3 和 GLM-5.3 Flash 上的真实会话,来验证其中两个原因。我们使用了同一服务器上五分钟内、至少 8k token 的连续轮次。为了避免少数几个重度账户主导结果,我们在每组中排除了重新读取 token 最多的三个客户。

更改工具列表或 effort 级别会使缓存中断的可能性增加 20 倍以上,并且平均花费约为前一个提示词的 40%。工具列表变更发生在大约 0.5% 的轮次中,却占这些模型上所有重新读取 token 的 19%。这一份额是集中的:三个账户占了其中约三分之二。逐轮效应在其余所有人身上都成立。
其余四分之三的重新读取 token 出现在 effort 和工具数量都没有变化的轮次上。这个“我们看不到任何东西”的桶里装着我们从服务器端看不到的原因:OpenCode 1.x 中的提醒、omp 的剪枝、skills、日期、压缩以及缓存驱逐。接下来,我希望我们记录每个提示词首次与上一个提示词不同的位置。这样我们就能在不读取任何提示词文本的情况下把这个桶拆开。
deepseek-harness 如何保持缓存完整
在我们的实验室抓取中,deepseek-harness 的每一个后续请求都是对前一个请求的纯追加,即使进程重启并恢复会话后也是如此。阅读其源码,我发现了一些值得一看的做法:
- 请求从仅追加的会话日志重建。重试和恢复会产生逐字节相同的提示词。
- 系统提示词固定且极简。它省略了日期、git 状态和操作系统。它只写一次,之后不再改动。
- 状态变更放在历史之后。当审批策略或可用 skills 发生变化时,harness 会追加一个简短的快照,说明它取代了先前的快照。它不改动系统提示词和更早的消息。
- 工具列表保持排序,并且在每种模式下都完全相同。用文字说明来强制执行模式,而不增删工具。
- 压缩发生得较晚。摘要调用复用了已有的前缀,因此即便是压缩也大多是缓存命中。而将对话记录重新序列化为单条新消息的摘要器则从冷启动开始。在我们见过的最大此类工作负载中,这些调用的缓存命中率仅为 22%。
- 每项变更都会接受缓存影响审查。仓库中的每个包都带有一个简短的“KV 缓存影响”章节。
你可以做什么
如果你在构建或配置一个 harness:
- 每一轮都保持提示词开头完全一致。将新消息和工具返回结果添加到对话末尾。对设置的更改也写在那里,保持前面的文本不变。
- 不要把日期、git 状态或任何会变化的内容放进系统提示词。
- 保持工具列表稳定且有序。将新工具添加到末尾。
- 较晚进行压缩,并让摘要器调用复用对话的精确前缀。
- 发送稳定的会话 ID。在 Neuralwatt 上,
X-NW-Conversation-ID让我们能把会话保留在已经持有其缓存的服务器上,即使跨越压缩也是如此。
如果你使用某个 harness:
- 按会话选择推理强度,而不是在会话中途切换。
- 对于 omp,可以考虑
compaction.supersedeReads: false。 - 在会话变长之前,先确定好推理强度、MCP 服务器和技能等设置。因为那正是缓存中断伤害最大的时候。
在你自己的会话中查看
本文中的所有内容都来自我们为每个请求记录的数据,你也可以看到自己工作中的相同数据。Neuralwatt 仪表盘中的 Session Insights 会逐轮展示每个 agent 会话:每个提示词有多少来自缓存、有多少需要冷计算、首 token 时间,以及实测能耗和成本。它从不显示提示词或补全文本。

这是我自己在 2026 年 9 月 26 日的一个会话:一个编码 agent 在 DeepSeek V4.1 Flash 上运行了 35 轮,上下文从 29k token 增长到 257k token。其提示词 token 的 95% 来自缓存,而小的红色顶盖是每一轮新增的文本。缓存中断会显示为高耸的红色柱,因此如果你在会话中途切换推理强度或连接 MCP 服务器,就能看到它带来了多少成本。你可以用它来在你自己的工作负载上比较不同的 harness 或设置,并检查某项更改是否确实保持了缓存热度。
我们这边正在做什么
我们将每个会话路由回持有其缓存的服务器。在我们最繁忙的模型上,这已经能让每个会话 88–96% 的 token 留在其归属服务器上。当会话必须迁移时,我们的共享 KV 缓存池让新服务器可以加载前缀,而不是重新计算。
我们最近测量了更智能的前缀感知路由器在此基础上还能额外挽回多少:命中率提升十分之一到十分之二个百分点。剩余浪费的大部分来自提示词的构建方式。
接下来,我们计划向客户最常使用的开源 harness 上游发送修复建议和推荐默认值。我们还希望研究一些方法,让服务器在提示词开头附近发生小改动(例如推理强度行或日期)后仍能复用已缓存的工作。这将有助于我们无法自行更改的客户端。
为什么这值得测量
缓存中断不会以错误的形式出现。你仍然能得到答案,只是更晚,并且为此消耗更多能量。测量每个请求的能耗让我们看到这种浪费累积起来有多大。事实证明,其中大部分都可以通过一项设置或对 harness 构建提示词方式的小改动来修复。
AI 的运转依赖能源。我们能看到的能源越多,就能减少越多浪费,无论是单个开发者的会话,还是行业的下一个数据中心。
方法说明。我们通过每个请求的 User-Agent 头中的名称来识别客户端。SDK 和运行时标签(Node.js 应用、OpenAI SDK 应用)涵盖了许多不同的应用。我们只展示被众多客户使用的客户端,绝不识别单个客户。服务器以固定大小的块缓存提示词,因此即使是纯追加,也可能在上一轮末尾留下少量 token 需要重新读取。我们将这些计为未缓存输入,在图 1 中为编码代理增加了不到 0.1 个百分点。缓存率是指在 2026 年 9 月 8 日至 10 月 7 日期间,所有模型中从缓存提供的提示词 token 所占比例。能源按分摊到每个请求的 GPU 能耗计量;缓存与未缓存的对比保持提示词大小和输出长度固定,覆盖 2026 年 10 月 1 日至 7 日。深度会话和原因分析覆盖 2026 年 10 月 6 日至 13 日,基于 DeepSeek V4.1 Flash、GLM-5.3 和 GLM-5.3 Flash,跟踪每个会话在同一服务器上五分钟内的连续轮次。
Chad Gibson 是 Neuralwatt 的联合创始人兼 CEO。他在微软工作了 20 多年,之后投资早期初创公司,并于 2024 年联合创立了 Neuralwatt。
来源:Hacker News · neuralwatt.com