编程 Agent 连接本机大模型的注意点
如果你曾经考虑过把编程 Harness 发出的 API 调用替换成本地模型,那么结果很可能令人失望。你可能快速跑了一下 llama-bench,以为自己会得到一个“每秒 X token”的结果。但这个基准测试无法告诉你 实际使用这些 Harness 时的体验。
不同 Harness 的开发体验差异非常大。你可能会坐在那里等几分钟才看到模型开始响应;或者刚开始运行得不错,中途却突然卡住。
“是谁把 Prefill 的时间全吃光了?”
这并不是你的错: 大多数编程 Harness 从设计之初就没有考虑本地模型。
这并不是要批评其他团队的优秀工作。我只是想可靠地测量: 当你把数据中心换成本地主机(localhost)之后,实际会发生什么。
需要说明的是,我一直在折腾 chad,这是一个专门针对 Apple Silicon 上 Qwen 3.8 27B 优化的 coding harness。
Laptop Physics:笔记本的物理限制
大多数 Harness 会从三个方面与本地模型“作对”。
1. 巨大的 System Prompt 和 Tool Schema
假设你的笔记本读取速度是 90 tokens/s,生成速度大约 10 tokens/s。
这分别对应编程 Harness 每一轮中的 Prefill 和 Generation。
按照这个速度:
每增加 1,000 个 Prompt Token,就意味着模型开始输出前,你要盯着光标等待约 11 秒。
而在模型真正开始干活之前,它必须先读取:
-
整个 System Prompt
-
所有加载的 Tool Schema
例如:
| Harness | Prompt Token |
|---|---|
| pi | 2,008 |
| OpenCode | 18,046 |
在数据中心 GPU 上,Prefill 可能达到 10k+ tokens/s,因此:
-
pi:约 0.2 秒
-
OpenCode:约 1.8 秒
几乎感觉不到区别。
但在你的笔记本上:
-
pi:约 22 秒
-
OpenCode:约 226 秒
也就是 将近 4 分钟。
这已经完全无法接受了。
2. 更小的有效 Context Window
LLM 处理完 System Prompt 后,真正能够用于工作的 Context Window 已经减少了。
你的上下文窗口其实比你想象得小。
具体剩余多少,取决于:
-
你的内存大小
-
模型权重占用
-
KV Cache
-
其他运行时开销
对于一台配置合理的笔记本,可以粗略认为还有 32,000 tokens 可用。
那么:
pi:
System Prompt 只占 2,008 tokens。
大约还有 94% 的 Context 可以真正用于工作。
OpenCode:
一开始就消耗了 18,046 tokens。
真正用于工作的 Context 只剩下约 44%。
3. 大量 Side Request
传统的:
本地客户端 → 远程服务器
架构下,Harness 可以随意向数据中心发起各种额外请求。
但本地模型不同:
你的笔记本既是 Client,又是 Server。
最好的情况是这些 Side Request 排队等待。
最糟糕的情况是:
它们不断触发重复 Prefill。
在测试的 24 个任务中:
-
OpenCode:33 次 Side Request
-
Crush:51 次
-
dsh:24 次
而且几乎都与 Agent Turn 重叠。
结果就是:
-
OpenCode:GPU 忙碌时间达到实际时间的 125%
-
Crush: 114%
也就是说:
一块 GPU 上同时跑了两个请求。
Harness 测试结果
作者使用 9 个 Coding Harness,测试了 8 个 Exercism 编程任务。
每个任务都使用:
-
相同的一句话 Prompt
-
Auto-approve 模式
-
同一台 M4 MacBook Pro
-
24GB 内存
-
macOS 26.6.2
-
Qwen 3.8 27B
-
3-bit 量化
-
llama.cpp build 10470
所有 Harness 共用同一个 llama-server。
采样参数统一为:
-
temperature = 1.0
-
top_k = 20
-
top_p = 0.95
-
min_p = 0.05
Context Cache 统一:
32,768 tokens / 4 slots
其中:
-
wait before 1st token:第一次生成 Token 前的等待时间,也就是 System Prompt + Tool Schema + First Request 的 Prefill 时间
-
wait / later turn:之后每一轮的等待时间
-
cache reuse:后续 Turn 有多少比例来自 Prefix Cache
-
experienced tokens/s:真正用户体验到的速度,包括 Prefill、Tool 等全部时间
-
pass:Exercism 任务是否通过,作者特别强调不要把它当成模型能力排名
| Harness | Tools | 首轮 Prompt | 首 Token 等待 | 后续 Turn | Cache | 实际 tok/s | 通过 |
|---|---|---|---|---|---|---|---|
| mini-swe-agent | 1 | 1,171 | 12.2s | 3.6s | 96% | 8.0 | 11/24 |
| pi | 4 | 2,008 | 21.6s | 1.3s | 99% | 8.1 | 19/24 |
| cline | 26 | 5,876 | 64.1s | 9.9s | 94% | 7.3 | 17/24 |
| codex | 10 | 7,804 | 87.8s | 9.6s | 94% | 6.9 | 19/24 |
| dsh | 25 | 8,052 | 94.4s | 2.2s | 99% | 7.2 | 18/24 |
| goose | 18 | 9,617 | 110.3s | 1.0s | 100% | 8.0 | 22/24 |
| crush | 26 | 16,263 | 199.8s | 1.8s | 100% | 5.8 | 18/24 |
| opencode | 10 | 18,046 | 225.7s | 4.6s | 99% | 5.7 | 15/24 |
| chad(llama.cpp) | 5 | 2,563 | 25.6s | 0.8s | 99% | 7.9 | 24/24 |
| chad(MLX serial) | 5 | 2,566 | 4.7s | 1.0s | 99% | 12.4 | 21/24 |
| chad(MLX dflash2) | 5 | 2,562 | 4.6s | 0.9s | 99% | 17.4 | 22/24 |
作者把这些 Harness 分成三类
① Lean and Stable:轻量、稳定
pi、mini-swe-agent、chad
特点:
-
精简 System Prompt
-
96–99% Cache Reuse
-
本地模型和云端模型都能比较好地工作
其中 mini-swe-agent 的问题是:
24 个任务只通过 11 个,并且 Timeout 最多。
② Heavy but Disciplined:比较重,但设计合理
dsh、cline、codex、goose
它们的问题主要来自:
-
较长的 System Prompt
-
大量 Tool Schema
但有一个很重要的优点:
Prefix 是稳定的。
所以一旦 Prefill 完成,后面还是比较能够接受的。
作者特别提到 Goose:
1.50.0 版本已经解决了一个问题。
早期版本每一轮都会把一个精确到分钟的 Timestamp 重新写进第一条 User Message,导致 Cache Reuse 从接近 100% 降到了 78%。
③ Heavy to Start:启动非常重
Crush、OpenCode
你会先等:
3~4 分钟
才能看到模型开始工作。
为什么作者认为 chad 特别适合本地模型?
作者认为这些 Harness 并不是设计得不好。
例如:
OpenCode 使用 18K Token 的 System Prompt
这对于:
数据中心 + Frontier Model + 10k+ tokens/s Prefill
完全合理。
同样:
Crush 使用 26 个 Tool Schema
当你拥有:
-
200K Context
-
几乎免费的高速 Prefill
这也完全没问题。
问题在于:
这些设计都是建立在“Prefill 几乎免费”的数据中心环境上的。
一旦换成:
本地模型 + 笔记本 + 低 Prefill 速度
原来的设计就会彻底暴露问题。
原文:https://nasutton.notion.site/Nine-coding-harnesses-vs-your-laptop-3d139990182b80d59fa3cf500f0450ba