在搭载本地 Qwen 3.8 27B 模型的 MacBook Pro 上对九种编码工具链进行基准测试

TL;DR

在单台 M4 MacBook Pro 上运行九种流行的编码代理工具链,并使用本地服务的 Qwen 3.8 27B 模型,揭示出三个明确的性能层级:(1) 轻量且稳定的工具链(pi、mini‑swe‑agent、chad)可实现约 8 tokens / s 的速度,且单轮延迟低于一秒;(2) 重量级但有纪律的工具链(dsh、cline、codex、goose)可达到 6–8 tokens / s,但首次 token 等待时间更长;(3) 启动耗时长的工具链(crush、opencode)在输出前需等待 3–4 分钟,速度下降至约 5–6 tokens / s。这些差异主要源于系统提示词大小、工具模式数量以及缓存复用效率。


实验设置

  • 硬件:Apple M4 MacBook Pro,24 GB 内存,macOS 26.6.2。
  • 模型:Qwen 3.8 27B,通过 unsloth/Qwen3.8-27B-GGUF 以 3 位量化,由 llama.cpp(构建版本 10470)提供服务。
  • 服务器:所有工具链共享一个 llama-server 实例;代理强制执行统一的采样策略(温度 = 1.0,top_k = 20,top_p = 0.95,min_p = 0.05)。
  • 缓存:32,768 token 的统一前缀缓存,分为四个槽位。
  • 基准测试:八个 Exercism Python 练习题,均以自动批准模式运行,使用相同的单句提示。指标由服务器自身计账系统收集(除两个 chad 行使用内部追踪外)。

指标定义

指标 含义
首个 token 前的等待时间 预填充系统提示词、工具模式和首个用户请求所需时间。
后续轮次的等待时间 第一轮之后的中位数和第 90 百分位暂停时间(不包括辅助请求)。
缓存复用率 后续轮次中从前缀缓存中提供 token 的百分比。
实际生成速度(tokens/second) 生成的总 token 数除以墙钟时间,包含预填充和工具开销。
通过数(门限) 在 1,200 秒超时内完成的 Exercism 任务数量。

为何本地推理比云端更难

  1. 庞大的系统提示词与工具模式 – 在笔记本电脑上读取速度约为 90 tokens/s,写入速度约为 10 tokens/s 的情况下,2,000 token 的提示词需耗时约 22 秒才开始生成;而 Opencode 使用的 18,000 token 提示词则需约 226 秒。云端 GPU 的预填充速度超过 10k tokens/s,将这些延迟压缩至不足一秒。
  2. 上下文窗口减小 – 在消耗完系统提示词后,剩余上下文通常小于 32 k token。Opencode 的 18k token 提示词仅留下约 44 % 的窗口用于实际工作,而轻量级的 pi 工具链则保留约 94 %。
  3. 辅助请求开销 – 频繁发出辅助请求(如代码摘要)的工具链会导致本地模型排队或重新预填充,使 GPU 实际“忙碌”时间超过墙钟时间的 100 %。

基准测试结果

工具链 版本 工具数 提示词(token) 首个 token 等待时间 后续轮次等待时间(中位数·p90) 缓存复用率 tokens / s 通过数(共 24 个)
mini‑swe‑agent 2.4.6 1 1,171 12.2 s 3.6 s·21 s 96 % 8.0 11(14 次超时)
pi 0.80.3 4 2,008 21.6 s 1.3 s·22 s 99 % 8.1 19(7 次超时)
cline 3.0.60–61 26 5,876 64.1 s 9.9 s·52 s 94 % 7.3 17
codex 0.151.0 10 7,804 87.8 s 9.6 s·28 s 94 % 6.9 19(5 次超时)
dsh 0.1.1‑rc.2 25 8,052 94.4 s 2.2 s·34 s 99 % 7.2 18
goose 1.50.0 18 9,617 110.3 s 1.0 s·22 s 100 % 8.0 22(3 次超时)
crush 0.92.0 26 16,263 199.8 s 1.8 s·40 s 100 % 5.8 18(8 次超时)
opencode 1.17.12 10 18,046 225.7 s 4.6 s·44 s 99 % 5.7 15(13 次超时)
chad(llama.cpp) 2.0.3 5 2,563 25.6 s 0.8 s·19 s 99 % 7.9 24
chad(MLX,串行) * 2.0.3 5 2,566 4.7 s 1.0 s·19 s 99 % 12.4 21(3 次超时)
chad(MLX,dflash2) * 2.0.3 5 2,562 4.6 s 0.9 s·36 s 99 % 17.4 22(3 次超时)

数值解读

  • 轻量级工具链(pi、mini‑swe‑agent、chad)将系统提示词控制在 ~2k token 以内,实现 >99 % 的缓存复用率,稳定在 ~8 tokens/s。chad 的 MLX 版本通过在进程内持有缓存,使吞吐量翻倍(最高达 17.4 tokens/s)。
  • 重量级但有纪律的工具链 虽然首次 token 等待时间较长(最高约 ~110 s),但保持了高缓存复用率,因此稳态吞吐量仍可观(约 7 tokens/s)。
  • 启动耗时长的工具链(crush、opencode)在输出前需等待超过 3 分钟,速度降至 ≤6 tokens/s,使其在笔记本上不切实际。
  • 通过率 与延迟密切相关:只有 chad(两种变体)解决了全部 24 个任务;其次是 goose(22/24)。mini‑swe‑agent 的通过数较低,尽管 token 速度尚可,但频繁超时是主因。

本地模型工具链的设计启示

  1. 精简系统提示词 – 每多一个 token 就在笔记本上增加约 0.01 秒的预填充延迟。目标应控制在 <2k token 以内。
  2. 限制工具模式数量 – 每增加一个工具都会增加提示词大小,并减少可用上下文窗口。
  3. 持久化前缀缓存 – 在轮次间复用缓存预填充(≥95 % 复用率)可消除重复工作,显著提升实际吞吐量。
  4. 将代理循环与模型共置 – chad 的进程内 MLX 实现表明,拥有缓存可消除网络往返,带来 2×–3× 的速度提升。
  5. 提供草稿模型 – DFlash2 草稿模型使 chad 的 token 速率从 12.4 → 17.4 tokens/s,证明轻量级草稿模型在早期 token 生成中的价值。

社区反馈精选

"如果你需要在资源受限环境中使用的编码代理,可以试试 hax —— 一个仅 0.7 MB 的原生二进制文件,采用极简提示词和工具集。" – OleksandrC

"这类基准测试变化很快;一个可复现的仓库能让任何人基于自己的硬件比较每轮 token 数、首次 token 延迟和通过率。" – humbleferret

"jcode 在内存使用上表现最佳,使用一个极小的 Rust 二进制文件,但未被纳入本研究。" – lrvick

"Reasonix 可能是一个有趣的对比对象,因为他们高度关注前缀缓存复用。" – swiftcoder

这些评论强化了轻量提示词、开源可复现性以及存在未被原始矩阵涵盖的其他超轻量代理的重要性。


如何复现该基准测试

整个实验已在 chad 仓库中脚本化:

# 克隆仓库
git clone https://github.com/nathansutton/chad.git
cd chad

# 安装依赖(推荐使用 uv)
uv run python benchmarks/matrix/run.py setup   # 安装模型,构建 llama.cpp
uv run python benchmarks/matrix/run.py smoke   # 健康检查
uv run python benchmarks/matrix/run.py llama   # 通过 llama.cpp 服务器运行所有工具链
uv run python benchmarks/matrix/run.py mlx     # 使用 MLX 后端运行 chad
uv run python benchmarks/matrix/run.py table   # 生成上述 Markdown 表格

所有原始数据(grid.jsonturns.jsonl)均已提交至基准代码库,确保从单行数据到聚合结果的完整可追溯性。


总结

在将云端 LLM 替换为笔记本上的本地模型时,决定性因素是 提示词大小引发的预填充延迟。那些为近乎免费的云端预填充设计的工具链(大提示词、多工具模式)在本地变得无法使用。通过采用有纪律的极简提示词、持久化前缀缓存,以及尽可能采用进程内模型循环,可在消费级硬件上实现接近云端的吞吐性能。

Sources

相关