为什么 harness 对能源(及 token)效率至关重要

Hacker News Top 新闻

摘要

Neuralwatt 分析一个月云端流量发现,agent harness 如何构建 prompt 决定了大量 GPU 计算量:缓存命中率差异导致未缓存输入在 1.6% 到 11.1% 之间,一次缓存失效大致会让请求能耗翻倍,且这些问题多数可通过避免编辑已发送内容来修复。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/10/16 22:42

# 为什么运行框架对能效至关重要 来源:https://neuralwatt.com/news/why-harnesses-matter-for-energy-efficiency *作者:Chad Gibson,联合创始人兼 CEO* ## 核心发现 - **不同运行框架的缓存表现差异很大。**我们观察到各客户端有 1.6% 到 11.1% 的输入未能命中缓存。"已缓存"比例是指输入 token 中由提示词缓存直接提供、而非重新计算的部分。缓存命中率为 92% 时,每 100 个 token 中有 8 个未命中;而命中率为 97% 时仅有 3 个——预填充(prefill)工作量相差约 2.7 倍。 - **一次缓存未命中大约会使请求能耗翻倍。**这里"未命中"指请求的提示词缓存命中率极低或为零(低于 10%)。在 GLM-5.3 上,能耗约为命中时的 2.5 倍。 - **长会话中的缓存未命中代价高昂。**重读意味着从头重新计算那部分提示词。超过 128k token 的上下文中,一次重读超过一半提示词的对话,其预填充工作量相当于该深度下约 600 次正常对话的总和。 - **这些问题大多是可修复的。**在我们的实验室测试中,每一次缓存中断都是因为运行框架修改了它已经发送过的内容,导致从第一个差异处起需要重新读取。 在 Neuralwatt,我们能够观察到运行框架(agent harness)调用模型的方式如何影响其能耗。我翻阅了我们云平台一个月的流量数据,发现运行框架决定了 GPU 工作量中相当惊人的一部分。我们通常从模型角度(活跃参数量、注意力机制)或硬件角度(H200 vs B200 vs B300)来讨论推理效率,但客户端这一层值得更仔细的审视。 我们的流量几乎全部来自智能体(agentic)场景,输入远多于输出。一个典型的编码智能体对话轮次会发送 100k token 以上的对话内容,返回的却只有几百 token。大部分输入是上一轮的提示词加上少量新增内容。如果之前的文本没有变化,服务器端已持有其键值(KV)缓存,只需处理新增部分即可。这就是提示词缓存(prompt caching),也是我们最大的效率来源。从缓存中提供一个 token 远比重新计算它便宜。 缓存要求前缀完全一致。当运行框架修改了它已经发送过的内容时,就会发生缓存中断。我们需要从第一个差异处开始重新计算所有内容。靠近开头的改动——哪怕只是一个时间戳——意味着几乎整个提示词都要重新计算;靠近结尾的改动代价则很小。运行框架决定了提示词如何构建。 ## 同一模型、同一价格,缓存命中率却不同 每个请求都会告诉我们是哪个客户端发出的。在 2026 年 9 月 8 日至 10 月 7 日的 30 天里,我们看到了数百个不同的客户端,其中包括 OpenCode、Oh My Pi(omp)和 Pi 等开源编码智能体。此外还有编辑器集成、OpenAI SDK 以及自定义应用。仅 OpenCode 一个客户端,就服务了我们约四分之一的客户。 以下是使用最广泛的客户端的未缓存输入 token 占比: 柱状图:各客户端未缓存输入占比,从 deepseek-harness 的 1.6% 到 Node.js 应用的 11.1% 不等 图 1. 各客户端的未缓存输入占比。这里的每个客户端都缓存了大部分输入。这些微小差异仍然很重要:一个缓存命中率 97% 的客户端每 100 个 token 发送 3 个未命中 token;命中率 92% 的客户端则发送 8 个。对于同样的对话,预填充工作量相差 2.7 倍。 命中率也取决于工作负载本身,而不仅仅是运行框架。较长的会话得分更高,因为每轮新增文本在提示词中所占比例更小。deepseek-harness 领先的优势中,大约一半来自那些运行超长会话的用户。衡量运行框架本身更准确的指标是每轮中断缓存的频率:deepseek-harness 约为每 100 轮 1 次,而 OpenCode 和 Pi 约为每 100 轮 3 次。我们的流量中同时包含 OpenCode 1.x 和 2.x,因此柱状图将两者合并统计。2.x 单独来看表现更好(未缓存比例为 2.2% vs 3.0%),这主要是因为模型组合不同。在同一个模型上,它们的缓存表现基本相当。2.x 修复了我们在实验室中发现的大部分 1.x 缓存中断问题。但这一差异在生产环境中并未体现出来,我们目前尚不清楚原因。 ## 一次缓存未命中的能耗代价 我们按实测能耗计费,因此可以直接看到成本。在 2026 年 10 月 1 日至 7 日的一周内,我们对比了同一模型上、提示词规模相同(约 50k token)、输出长度相近的请求。以下是每个请求相对于完全命中缓存的请求所多消耗的能耗: 柱状图:缓存命中率低于 10% 的请求,在 GLM-5.3 上多消耗 140% 能耗,在 DeepSeek V4.1 Flash 上多消耗 91% 能耗 图 2. 缓存未命中的额外能耗。一个基本未命中缓存的请求,其能耗大约是同等大小命中缓存请求的两倍。在 GLM-5.3 上更是接近 2.5 倍。预填充还会与生成阶段竞争同一批 GPU 资源,因此浪费的预填充会拖慢同一服务器上其他所有用户的响应速度。 ## 长会话中缓存未命中的代价急剧上升 我们按轮次追踪了 2026 年 10 月 6 日至 13 日一周内我们流量最大的三个模型上的各个会话。我们只看那些在上一轮之后 5 分钟内落在同一台服务器上的对话轮次——这种情况下缓存应该是热的。大多数缓存中断都很小:提示词末尾几千个 token 被重新计算。 在超过 128k token 的上下文中,大约每 1000 轮中有 3 轮会从头重读超过一半的提示词。这寥寥几轮却占了该深度下所有重读 token 的 75%。每次平均重读约 230k token。该深度下的正常一轮通常只新增中位数约 370 个新 token,因此单次这样的重读就相当于约 600 轮普通对话的预填充工作量。 延迟也很明显。以下是正常对话轮次与需要重读大部分提示词的轮次的首 token 时间中位数对比: 柱状图:需要重读大部分提示词的轮次,首 token 时间从 5 秒升至 41 秒,而正常轮次为 0.8 秒至 2.8 秒 图 3. 长会话深处的首 token 时间。我更关注的是缓存中断发生在会话的多深位置,而不是它发生的频率。在会话早期改动一次提示词开头,代价很小;但在长会话的第 200 轮才做同样的事,你会得到一个明显的停顿和巨大的能耗成本。长智能体会话占据了我们大部分 token 量,所以这些开销会不断累积。 我们并不总是能从服务器端判断是运行框架编辑了提示词,还是缓存因内存压力被驱逐。以上数据包含了两者。我们在下面的实验室测试中展示的是能够复现的、由运行框架引起的早期提示词编辑;缓存驱逐可能是其余部分的原因。 ## 是什么破坏了缓存 我在两个地方寻找原因:实验室——在那里我们可以看到每一个 token——以及生产环境——在那里我们只能看到请求元数据。 ### 实验室 我们通过一个录制代理,将四个流行的开源智能体(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 上重复了相同的会话。每个智能体都完成了同一个针对小型 Python 项目的脚本化会话:先规划、然后构建、切换推理力度并切回、启动子智能体、空闲等待、被重启、连接和断开 MCP 服务器、添加技能,并运行足够长时间以触发压缩(compaction)。我们将每个请求与前一个请求进行对比,既以 JSON 形式比较,也以服务器渲染出的确切 token 进行比较。 在 539 个属于同一对话中后续轮次的请求中,88% 是纯追加操作:前一个提示词是新提示词的完全前缀。其余 65 个破坏了缓存。每一次中断都是因为运行框架修改了它已发送过的内容;没有一次是由我们的聊天模板引起的。空闲时间在四个智能体中均未造成中断。重启运行框架并恢复会话也没有,使用子智能体同样没有。 有两种原因会在用户未做任何操作的情况下破坏缓存: - **OpenCode 的计划模式提醒。**一旦你使用过计划模式,OpenCode 就会在之后每条新用户消息中附加一条提醒("您的操作模式已从计划切换为构建"),并在下一轮将该提醒从消息中移除。于是每一轮都要重新读取整个上一轮内容,包括其工具调用和结果。在我们的短会话测试中,这相当于每轮 330 到 2,340 个 token 的代价,并且随轮次规模增大而增长。OpenCode 2.x 将提醒放在其自己的消息中,因此不再触发重读。 - **omp 对重复读取文件的裁剪。**当一个文件被第二次读取时,omp 会将历史中之前的副本替换为 `\[Superseded by a newer read of this file\]`。在我们的测试中,这一个编辑出现在提示词约 62% 的位置,导致 15,520 个 token 中的 5,846 个需要重读。omp 还会裁剪"平淡无奇"的工具结果,并在空闲 90 分钟后从任意深度进行裁剪。设置 `compaction.supersedeReads: false` 和 `compaction.dropUseless: false` 可以保留缓存。 Pi 和 Kilo Code 没有出现此类中断。 其他原因取决于用户做了什么。代价大小取决于提示词中被修改的位置: - **切换推理力度(reasoning effort)。**两个模板都将力度值写在提示词的前几个 token 中:GLM-5.3 上是 `Reasoning Effort: Max`,DeepSeek V4.1 上是 1 到 100 的数字。在两个模型上第一个差异都出现在第 8 个 token,因此一次切换就要重读全部内容。实际上只有那一个值发生变化;它的长度以及它之后的所有内容都完全相同。 - **连接或断开 MCP 服务器。**OpenCode 1.x 和 Kilo Code 按字母顺序在工具列表中间插入新工具。OpenCode 2.x 不动工具列表,而是在末尾追加一条说明,因此连接和断开都是纯追加操作。Pi 添加工具时会同时编辑其系统提示。omp 在系统消息末尾追加一个段落。在 GLM-5.3 上,工具列表在系统提示之前渲染,因此改动落在非常靠前的位置:OpenCode 1.x 约在第 1,300 个 token 处。 - **添加技能(skill)。**四个智能体都在系统提示中列出技能。在 DeepSeek V4.1 上,第一个差异出现在第 461 至 2,948 个 token 之间,在 GLM-5.3 上出现得更晚,因为工具列表排在前面。 - **日期变化。**OpenCode 1.x 和 Kilo Code 将当天日期写入系统提示,约在第 2,000 个 token 处。OpenCode 2.x 保留会话原始日期,日期变化时仅追加一条说明。omp 将日期写在第一条用户消息中。Pi 不发送日期。对于覆盖日期的客户端,跨过本地午夜意味着需要几乎重读全部内容。 - **压缩(compaction)。**每个运行框架的处理方式不同。Pi 保留最后约 20k token 的原文,但重写了靠前的摘要。提示词几乎没缩短,而其中大部分仍要被重读。在同样 40k token 的窗口下,OpenCode 2.x 触发了三次压缩,总共重读了 67,850 个 token;1.x 触发了一次压缩,重读了 29,776 个 token。 切回本会话早前使用过的设置,比我预期的更便宜。服务器仍持有该设置下早期版本的提示词。在我们的测试中,切回推理力度能够命中 10.6k 到 27.5k 个缓存 token。每次新状态首次被使用时才会触发一次完整的重读,之后反复切换并不需要。 ### 生产环境 我们不记录也不保留客户提示词或响应的文本内容。本节全部数据来自请求元数据。上述 token 级别的分析来自我们自己的脚本化测试会话,而非客户流量。这些元数据记录了每个请求的推理力度设置以及发送的工具数量。我们用它来在 2026 年 10 月 6 日至 13 日的真实会话上验证上述原因中的两项,涉及 DeepSeek V4.1 Flash、GLM-5.3 和 GLM-5.3 Flash。我们选取的条件是:连续轮次在 5 分钟内落在同一台服务器上,且至少 8k token。为了避免少数重度账户影响结果,我们在每组中排除了重读 token 最多的三个客户。 柱状图:2.8% 的轮次在没有可见变化时中断缓存,推理力度变化时为 61%,工具数量变化时为 67% 图 4. 按变化类型分类的缓存中断。工具列表或推理力度的变化使缓存中断概率增加 20 倍以上,平均会消耗掉前一提示词约 40% 的 token。工具列表变化仅发生在约 0.5% 的轮次中,却占这些模型上所有重读 token 的 19%。该占比高度集中:三个账户就占了其中约三分之二。而单轮次的影响对其他所有用户都成立。 其余四分之三的重读 token 来自推理力度和工具数量均未变化的轮次。这个"我们看不到变化"的类别中包含了从服务器端无法察觉的原因:OpenCode 1.x 的提醒、omp 的裁剪、技能、日期、压缩以及缓存驱逐。接下来,我希望我们能记录每个提示词与前一个提示词首次出现差异的位置。这样无需阅读任何提示词文本,就能将这个类别拆分开来。 ## deepseek-harness 如何保持缓存完整 在我们的实验室捕获中,deepseek-harness 的每一个后续请求都是对前一个请求的纯追加,即使进程被重启、会话被恢复也是如此。阅读其源码后,我发现了几个值得借鉴的实践: 1. **请求从只追加的会话日志重建。**重试和恢复产生字节级完全一致的提示词。 2. **系统提示固定且极简。**它不包含日期、git 状态和操作系统信息,只写一次就不再改动。 3. **状态变更放在历史记录之后。**当审批策略或可用技能发生变化时,运行框架会追加一个简短的快照,声明其取代之前的版本。系统提示和较早的消息保持不变。 4. **工具列表在所有模式下保持排序一致。**模式切换通过文字说明实现,不增删工具。 5. **压缩尽量推迟进行。**摘要调用复用了现有的前缀,因此即使是压缩操作也大多命中缓存。将对话记录重新序列化为单条新消息的摘要器会冷启动。在我们见过的最大此类工作负载中,这类调用仅有 22% 命中缓存。 6. **每次变更都经过缓存影响评审。**仓库中的每个包都附有一小节 "KV cache effect"(KV 缓存影响)说明。 ## 你可以做什么 如果你构建或配置一个运行框架: - 保持每轮提示词的开头完全一致。将新消息和工具结果追加到对话末尾。对设置的变更也要在那里描述,不要改动之前的文本。 - 不要把日期、git 状态或任何会变化的内容放入系统提示。 - 保持工具列表稳定且有序。新工具追加到末尾。 - 压缩尽量推迟,并让摘要调用复用对话的确切前缀。 - 发送稳定的会话 ID。在 Neuralwatt 上,`X-NW-Conversation-ID` 可以让我们将会话保留在已持有其缓存的服务器上,即使经过压缩也不例外。 如果你使用一个运行框架: - 每个会话选择一个推理力度,而不是在会话中途切换。 - 对于 omp,考虑设置 `compaction.supersedeReads: false`。 - 在会话变长之前确定好推理力度、MCP 服务器和技能等设置。缓存中断在会话深处代价最大。 ## 在你自己的会话中查看 本文所有内容均来自我们为每个请求记录的数据,你也可以查看自己工作的同类数据。Neuralwatt 控制面板中的 Session Insights 以逐轮方式展示每个智能体会话:每轮提示词中有多少来自缓存、多少需要冷启动计算、首 token 时间以及实测能耗和成本。它从不显示提示词或补全文本。 Session Insights 页面,展示一个 35 轮的会话:缓存命中率 95.2%,总 token 数 5.4M,冷启动预填充 258.8K,并附有每轮缓存与冷启动提示词 token 的对比图及首 token 时间 图 5. Neuralwatt 控制面板中的 Session Insights。这是我本人在 2026 年 9 月 26 日的一次会话:一个编码智能体在 DeepSeek V4.1 Flash 上运行了 35 轮,上下文从 29k 增长到 257k token。其 95% 的提示词 token

相似文章

@ludamn513: https://x.com/ludamn513/status/2108809228978880988

X AI KOLs Timeline

文章以 Pi Durable 为切入点,拆解 Agent Harness 架构的演进:将持久化系统、agent loop 与执行环境解耦,实现无损状态恢复、codemode 工具调用以及 server/client 分离,并提出一套可 scale up 的 long-time Harness 架构设计,目标是将硬件资源消耗降低一个数量级。

@snwiki238337: 目前的 Harness 优化,有两个大方向。 一统全局脚手架,试图用一套Prompt 和控制逻辑。但随着业务场景增加,针对财务表格优化的指令往往会干扰代码生成任务,导致严重的规则冲突与上下文过度膨胀。 另一种是针对每个特定领域单独复制一份…

X AI KOLs Following

Google Cloud AI Research 与 UIUC 合作提出 OO-Harness,将面向对象设计(继承、封装、多态)引入 Agent Harness 的自动演化,通过类层级体系实现专用脚手架的代码复用与安全降级,在三大长程基准上主指标从 61.62% 提升至 77.41%,且在分布外任务上显著优于传统自演化方法。