AI 创作声明:本文由作者提供原始素材、核心观点和最终判断,AI 参与结构整理、表达润色和发布格式化。文章发布前由作者审核确认。

这两天,我几乎把能折腾的地方都折腾了一遍。

Mac Studio 上装了 Qwen3.8-27B,4-bit、5-bit、6-bit、8-bit 的 MLX 量化版本全下了;Codex、Claude Code、Qwen Code 轮流接;DFlash2、MTP、oMLX 也一个个试。

我一开始想得很直接:

模型输出太慢,那就继续优化模型,让它每秒多吐几个 token。

结果折腾两天后,我才发现自己盯错了地方。

最能说明问题的一幕,是我只在 Coding Agent 里输入了一句:

1
Hi

模型却迟迟没有开始输出。

问题根本不在这句 Hi。在它前面,Agent 已经悄悄塞进了系统提示词、工具定义、项目指令、Skills、MCP 工具描述和会话历史。对模型来说,这不是一个 token 的请求,而可能是几千、几万 token 的完整上下文。

到这里,我才真正意识到:

对于本地 Coding Agent,决定实际体验的,不只是模型每秒能生成多少 token,更重要的是 Agent 在第一次请求时,究竟让模型处理了多少上下文。

我真正遇到的主要瓶颈,不是 Decode,而是 Prefill。


一、先交代我的硬件和模型环境

我的设备是 Mac Studio,配置如下:

  • Apple M4 Max
  • 16 核 CPU
  • 40 核 GPU
  • 128GB 统一内存
  • 546GB/s 内存带宽

Apple 官方规格显示,546GB/s 对应 16 核 CPU、40 核 GPU 的 M4 Max;128GB 统一内存也属于这一档可选配置。

不过,546GB/s 是硬件层面的峰值内存带宽,不等于运行 LLM 时一定能得到 546GB/s 的有效吞吐。实际性能还会受到很多因素影响:

  • MLX 内核实现;
  • 模型架构;
  • 量化方式;
  • KV Cache;
  • Prefill 批次大小;
  • GPU 调度与同步;
  • 推理框架额外开销。

模型方面,我通过 LM Studio 部署了 Qwen3.8-27B,并分别下载了 4-bit、5-bit、6-bit 和 8-bit 的 MLX 量化版本。LM Studio 的模型页目前确实列出了这四个版本。

按照 Qwen 官方模型卡Qwen3.8-27B 是 27B 稠密模型,原生支持图像和视频理解,原生上下文长度为 262,144 token,并包含多步 MTP 训练。LM Studio 给它的默认配置里,Thinking 开启,Reasoning Effort 默认为 xhigh

纸面上看,这套配置已经相当豪华。

但“模型能装进内存”和“Agent 用起来顺手”,完全是两回事。


二、第一个弯路:让 Codex 自动寻找最优配置

一开始,我让 Codex,也就是 GPT-5.6 SOL,自动测试不同量化版本和推理参数,希望它最后给我一套“最优配置”。

为了让结果看起来更科学,我还让它下载了一些公开测试集,对不同模型和参数组合做自动化评测。

整个过程跑了很久。

最后 Codex 推荐的配置,与 LM Studio 默认配置没有本质区别。

当时我多少有点郁闷:跑了这么久,就这?

现在回头看,这部分工作的边际收益确实很低。但问题并不是“公开 Benchmark 完全没用”,而是我测试的指标和真正关心的场景根本没有对齐。

公开 Benchmark 更适合衡量回答质量、知识能力、推理能力或者代码正确率,而我真正关心的是:

  • Coding Agent 第一次响应要等多久;
  • 10K、20K、50K 输入上下文要花多久完成 Prefill;
  • Agent 的工具定义占了多少 token;
  • 多轮工具调用后能不能复用 Prefix Cache;
  • 模型多久开始输出第一个可见 token;
  • 本地推理会不会因为长时间无流量而超时;
  • 视觉、工具调用和长上下文能不能同时正常工作。

这些才是我的真实工作负载。

所以,这次自动化评测真正的问题不是不够认真,而是我从一开始就优化错了目标。


三、输入一句 Hi,模型处理的根本不只是 Hi

完成模型配置后,我先后尝试了:

  • Codex CLI;
  • Claude Code;
  • Qwen Code。

它们都有一个共同点:功能非常完整。

但“完整”不是免费的。Agent 需要向模型描述大量能力,例如:

  • 系统行为约束;
  • 文件读取与编辑工具;
  • Shell 执行工具;
  • 搜索与 Git 操作;
  • Skills;
  • MCP server;
  • 项目级规则;
  • 会话历史;
  • 工具参数的 JSON Schema。

这些内容最终都会成为模型输入的一部分。

所以,我在 Agent 里发送一句 Hi 时,实际请求更接近:

1
2
3
4
5
6
7
8
系统提示词
+ Agent 行为规范
+ 工具定义
+ Skills
+ MCP 描述
+ 项目级指令
+ 会话历史
+ 用户输入的 Hi

本地模型要先处理完这整段输入,也就是完成 Prompt Processing,才能进入逐 token 生成阶段。

一次 LLM 请求可以粗略拆成四个阶段:

阶段 主要工作 用户感受
模型加载 把模型权重载入统一内存 启动 server 时等待
Prefill 处理系统提示词、工具定义和历史上下文 输入后长时间没有输出
Decode 逐个或批量生成新 token 开始看到模型输出
工具执行 读取文件、执行命令、修改代码 Agent 开始实际工作

我一开始只盯着 Decode,也就是输出 token 的速度。

但实际使用时,真正让我感觉“慢”的,很多时候是 Prefill。

这两者不是同一个问题。


四、第二个弯路:只靠推理加速解决体验问题

1. DFlash2:Decode 变快了,第一口气还是没上来

我随后尝试了 DFlash2。

DFlash2 属于推测解码路线。它会先用较小的 draft model 生成候选 token,再由目标模型验证。与 DFlash 相比,DFlash2 又加入了局部卷积和候选选择器;核心目标仍然是用更少的目标模型前向计算完成更多 Decode。

这条路线在我的测试中确实能明显提高 token 生成速度。

但它解决不了我的核心问题:

DFlash2 主要优化 Decode,不会自动消除大型 Agent 在初始请求里塞入的上下文。

如果 Agent 一启动就让模型处理 10K token,那么在输出第一个 token 之前,模型依然要先完成这 10K token 的 Prompt Processing。

Decode 从每秒 20 token 提高到每秒 30 token,当然有价值;但如果第一段 Prefill 仍然要等很久,用户的第一感受还是“卡住了”。

另外,我使用的 DFlash2 路径还出现了视觉识别不可用或异常的问题。

这里必须把话说严谨:

不能直接得出“DFlash2 必然破坏视觉能力”的结论。更准确的说法是:我当时使用的 DFlash2 实现、模型文件和推理框架组合,存在视觉兼容性问题。

近期 llama.cppDFlash2 支持 PR 里也出现过多模态位置处理导致视觉请求失败的报告和修复讨论。这类问题更像是实现兼容性或模型加载路径的问题,不是算法层面的必然缺陷。

但对实际使用者来说,结论还是很简单:

只要视觉不能稳定工作,这条加速路径就不适合成为我的默认方案。

2. MTP:同样主要解决 Decode

我也尝试了 MTP,也就是 Multi-Token Prediction。

Qwen3.8-27B 本身包含经过训练的多步 MTP 模块。它的目标同样是减少传统自回归解码“一次只确认一个 token”的低效率,尝试在一次模型计算中预测或验证多个 token。

它可以改善 Decode,但收益不是固定值,会受到很多因素影响:

  • MTP head 的预测准确率;
  • token 接受率;
  • 量化位数;
  • 上下文长度;
  • 硬件型号;
  • 验证阶段的计算开销;
  • 推理框架实现。

这也是为什么我不再愿意拿某个公开数字直接套到自己的 Mac 上。不同模型、量化、上下文和框架组合,结果差异可能很大。

更重要的是,MTP 依然主要解决生成阶段的问题。它不会自动缩短一个臃肿 Agent 的系统 prompt。

所以,无论 DFlash2 还是 MTP,都没有真正解决我当时最主要的痛点。


五、oMLX:方向更接近需求,但我没有完成有效验证

随后我尝试了 oMLX。

oMLX 是面向 Apple Silicon 的 OpenAI-compatible 推理 server,支持 Continuous Batching 和分层 KV Cache。它可以把缓存放在内存和 SSD 中,尽量复用已经计算过的上下文。

从优化方向看,oMLX 比单纯提高 Decode 速度更接近我的真实需求。

因为 Coding Agent 的系统提示词和工具定义通常会反复出现。如果 Prefix Cache 能稳定命中,后续请求就不必每次从头处理全部前缀。

这正是本地 Agent 非常需要的能力。

直接通过 HTTP 请求调用 oMLX 时,我能明显感觉到响应很快。但当我把它接到 Qwen Code CLI 后,却遇到了新的问题:

  • oMLX 后端已经生成并返回内容;
  • Qwen Code CLI 却没有正常显示或消费这些内容;
  • 最终请求无法正常完成。

这里只看现象,还不能武断地说“oMLX 后端做得不好”或者“Qwen Code 有 bug”。问题可能位于:

  • API endpoint 选择;
  • SSE stream 格式;
  • reasoning 字段;
  • tool call event 格式;
  • chunk 结束标记;
  • Qwen Code 的流式响应解析;
  • oMLX 对客户端协议的兼容实现。

所以,我当时能下的结论只有:

我遇到了 oMLX 与 Qwen Code CLI 之间的流式协议兼容问题,因此没有完成足够有效的验证。

继续排查当然可以,但时间成本已经超过潜在收益。我最后暂停了这条路线。

这也是两天里另一个很真实的教训:看到后端日志里“已经有输出”,不等于整个 Agent 链路已经跑通。服务端、协议适配和客户端解析,任何一层都可能把体验卡死。


六、真正的转机:我不再优化模型,开始精简 Agent

整个事情的转机,是我突然意识到:

我不一定非要继续使用 Codex、Claude Code 或 Qwen Code,也可以换一个更轻量的 Coding Agent。

于是我开始尝试 Pi。

Pi 默认只有四个核心工具:

1
2
3
4
read
bash
edit
write

Pi 的 SDK 文档也把这四个工具列为默认内置工具。它们已经覆盖 Coding Agent 最核心的工作:

  • 查看和搜索代码;
  • 执行命令;
  • 编辑文件;
  • 新建文件;
  • 运行测试。

相较于功能复杂的 Agent,Pi 的系统设计更克制。

在我的实际测试中,它的初始上下文只有数千 token,因此:

  • 启动更快;
  • Prefill 更短;
  • 第一个 token 出现得更早;
  • 工具调用过程更直接;
  • 对本地低吞吐模型更友好;
  • 不会为了大量暂时用不到的功能,提前支付上下文成本。

这让我得到一个非常明确的判断:

对于本地 LLM,Agent 的极简程度,本身就是一种性能优化。

云端模型的 Prefill 吞吐、并发能力和缓存条件通常更好,Agent 多塞几千 token,用户未必有明显感觉。

但本地模型的计算预算更有限。Agent 每增加一项工具,都可能同时增加:

  • 工具描述;
  • 参数 Schema;
  • 行为说明;
  • 调用示例;
  • 结果格式要求。

这些内容不只是占用上下文窗口,还会在首次请求时消耗真实计算时间。

所以,对本地模型来说,功能更多并不必然意味着体验更好。

有时候,工具更少,任务反而完成得更快。


七、Pi 的隐藏坑:五分钟 HTTP Idle Timeout

Pi 虽然更适合本地模型,但我随后又踩到了一个很隐蔽的坑。

Pi 默认配置是:

1
"httpIdleTimeoutMs": 300000

300,000 毫秒,也就是五分钟。

这个参数不是“整个任务最多只能执行五分钟”,而是:

如果 HTTP Header、Body 或流式响应连续五分钟没有任何数据,Pi 就会认为连接已经空闲或失效。

Pi 官方设置文档确认,httpIdleTimeoutMs 默认值是 300000,并且可以设置为 0 来禁用 Idle Timeout。与此同时,Pi 默认开启 Agent 级自动重试,retry.maxRetries 默认为 3

这就解释了我遇到的现象。

当我让 Qwen3.8-27B 以 xhigh 推理强度执行复杂任务时,模型可能会经历很长的 Prefill 或推理阶段。如果推理 server 在这段时间里没有向 HTTP stream 写入任何数据,五分钟后 Pi 就会判定请求超时,然后自动重试。

如果连续几次都发生同样的问题,最后就会出现类似:

1
Retry failed after three attempts

然后任务被终止。

但这不代表模型已经停止工作,也不一定代表 server 崩溃。模型可能仍在:

  • 处理超长输入;
  • 生成内部推理内容;
  • 等待第一个可输出 token;
  • 缓冲 reasoning 内容;
  • 执行尚未产生流式事件的计算。

真正的问题只是:Pi 在五分钟内没有收到任何网络数据。

解决方法很简单。在 Pi 的 settings.json 中加入:

1
2
3
{
"httpIdleTimeoutMs": 1200000
}

1200000 等于 20 分钟。也可以设置为 15 分钟:

1
2
3
{
"httpIdleTimeoutMs": 900000
}

如果确定只连接可信的本地 server,也可以设置为:

1
2
3
{
"httpIdleTimeoutMs": 0
}

不过我更倾向于保留一个较长但有限的超时,例如 15~20 分钟,而不是完全关闭。

否则,如果 server 真的死锁或者连接永久挂起,Pi 可能会一直等下去。


八、这两天真正有价值的经验

回头看,我一开始一直在研究:

  • 4-bit、5-bit、6-bit 还是 8-bit;
  • 每秒能生成多少 token;
  • DFlash2 能提高多少;
  • MTP 能提高多少;
  • 哪个推理 server 更快。

这些问题当然有价值,但不是我最应该优先解决的问题。

现在如果让我重新排一次优先级,我会这样做。

第一优先级:减少无效输入

先检查 Agent 到底发送了多少内容:

  • System Prompt 有多长;
  • Tool Schema 有多少;
  • 是否加载了暂时用不到的 Skills;
  • 是否连接了暂时用不到的 MCP;
  • 是否自动注入大量项目规则;
  • 是否保留了过多历史推理内容。

第二优先级:缩短 Prefill

重点观察:

  • Prompt Processing Speed;
  • Time to First Token(TTFT);
  • Prefix Cache 是否命中;
  • 重复系统 prompt 能否复用;
  • 上下文增长后,TTFT 如何变化。

第三优先级:选择合适的推理强度

不是所有任务都需要 xhigh

修改变量名、修复简单编译错误、增加日志、补一处测试、修改一项配置,使用 mediumlow 往往已经足够。

xhigh 更适合:

  • 大型架构设计;
  • 复杂 bug 定位;
  • 跨模块重构;
  • 长时间自主任务;
  • 需要大量权衡的技术决策。

第四优先级:最后再优化 Decode

完成前面三项后,才值得继续测试:

  • DFlash2;
  • MTP;
  • 不同量化位数;
  • 自定义 Metal kernel;
  • ANE/GPU Prefill;
  • Continuous Batching。

否则,很可能只是把一个次要阶段优化得很快,却没有改善真正的端到端延迟。


九、我现在的本地 Coding Agent 方案

经过这两天的折腾,我目前更倾向于这套组合:

1
2
3
4
5
6
7
8
9
10
11
Mac Studio M4 Max 128GB

Qwen3.8-27B MLX

LM Studio 或兼容的 OpenAI API server

Pi Coding Agent

按任务调整 reasoning effort

HTTP Idle Timeout 调整为 15~20 分钟

模型量化方面,我暂时不会再让通用 Benchmark 替我做决定。我会回到真实任务上比较:

  1. 同一个真实项目;

  2. 同一个任务;

  3. 相同 Agent;

  4. 相同 prompt;

  5. 分别记录 4-bit、5-bit、6-bit、8-bit 的:

    • Prefill 速度;
    • 首 token 延迟;
    • Decode 速度;
    • 内存占用;
    • 工具调用成功率;
    • 视觉识别质量;
    • 最终任务完成质量。

只有这种测试,才能回答哪个量化版本真正适合我。


最后:我优化错的不是参数,而是层级

这两天我最大的认知变化,是从:

如何让本地模型跑得更快?

转变成了:

如何让本地模型少做无意义的计算?

对于本地 LLM 来说,token 不只是上下文长度单位,也是实际的时间和算力成本。

云端 Agent 可以依靠更强的硬件和服务端缓存,把复杂系统 prompt、几十个工具和大量 MCP 定义一股脑塞给模型。但在本地环境里,我必须更克制。

所以,我目前的结论是:

本地 LLM 不一定需要功能最强的 Coding Agent,而是需要上下文开销更小、工具数量更克制、行为更可预测的 Agent。

DFlash2 和 MTP 可以提高 Decode 速度,oMLX 也可能通过 Prefix Cache 和 Prefill 优化改善体验。

但这些都是第二层优化。

第一层优化永远是:

不要让模型处理它根本不需要知道的内容。

我折腾两天后,真正换掉的不是模型,也不是量化版本,而是自己的优化顺序。

这篇文章的风格自检

  • 从 Mac Studio 上真实部署本地模型的卡点切入,没有从抽象概念开场;
  • 保留了 Benchmark、DFlash2、MTP、oMLX 和 Pi 的失败路径与判断修正;
  • 区分了个人实测、官方能力和仍未完成验证的兼容性问题;
  • 最后收束为可复用的优化顺序,而不是只给一句“换轻量 Agent”。