如何在macOS上设置本地编码代理
阅读原文· ikyle.me这篇文章不是泛泛的「本地跑大模型」,而是给 Mac 开发者一个实测过的、能打的生产环境编码代理方案,尤其 MTP 加速让速度不再鸡肋,可以直接抄作业。
来自ikyle.me的教程,指导在macOS上搭建本地编码代理,获Hacker News社区104个点赞。
最近我的互联网断了几次,搞得我没法用编码智能体,所以当我看到“Gemma 4 现在通过 MTP 多 Token 预测更新后运行速度快了 2 倍”这个更新时,我决定尝试让它跑起来。
我想要一个本地编码智能体方案,它需要:
- 足够快,能在我的 Mac 上实际使用
- 通过兼容 OpenAI 的 API 工作(这样我就可以在其他工具里用它)
- 最好还能在需要时处理截图/图片,这样我可以把智能体生成的截图喂给它。
而且我做到了!这个视频是实时的。它展示出智能体以完全可用的速度在响应。
经过一番测试,我最终采用的方案是:
- 在 macOS 上用 Metal 编译的 llama.cpp
- GGUF 格式的 Gemma 4 26B-A4B
- 用于投机解码的 Q8 MTP 草稿模型
- Gemma 4 多模态投影器
- Pi 作为终端编码智能体
这是在配备 64 GB 统一内存的 Apple M1 Max 上测试的,运行 macOS 15.7.7。
模型
主模型是:gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf。
HuggingFace 上的链接:models/unsloth-gemma-4-26B-A4B-it-GGUF/gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf
该文件大约 16 GB。加上 MTP 草稿头和多模态投影器后,模型文件夹大约 17 GB。
基准测试的提示词是:
Write a compact Python function that parses a unified diff and returns the changed file paths. Then explain two edge cases.
每次基准测试生成大约 128 个 token。
基线:llama.cpp + Metal
首先,我直接在 llama.cpp 中使用 Metal 加速运行主模型:
repos/llama.cpp/build/bin/llama-cli \
-m models/unsloth-gemma-4-26B-A4B-it-GGUF/gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf \
-ngl 999 \
-fa on \
-c 4096 \
-n 128
结果:
| 设置 | 提示词 tok/s | 生成 tok/s |
|---|---|---|
| Gemma 4 26B-A4B Q4, llama.cpp Metal | 298.0 | 58.2 |
58 token/秒不算快,但可用;不过对于编码智能体工作来说,你希望它尽可能快,尤其是当智能体需要多次调用工具时。
添加 MTP 草稿模型
Gemma 4 现在提供了 MTP 草稿模型:
MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf
这可以通过 llama.cpp 加载为一个投机草稿模型:
repos/llama.cpp/build/bin/llama-cli \
-m models/unsloth-gemma-4-26B-A4B-it-GGUF/gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf \
--model-draft models/unsloth-gemma-4-26B-A4B-it-GGUF/MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf \
--spec-type draft-mtp \
--spec-draft-n-max 3 \
-ngl 999 \
-fa on \
-c 4096 \
-n 128
首次使用 MTP 运行时,采用 4 个草稿 token,速度为 69.2 token/秒。不过,Unsloth 的《如何运行 MTP 模型》指南中包含了这条注释:
“我们发现 --spec-draft-n-max 2 是最好的起点,但不要假定 2 是最优值,因为性能取决于硬件。尝试 1 到 6 之间的任意值,使用对您的系统最快的那个。”
在遍历 --spec-draft-n-max 后,最佳结果是 3 个草稿 token 下达到 72.2 token/秒。
| 设置 | 提示词 tok/s | 生成速度(tok/s) | 加速比 |
|---|---|---|---|
| 仅主模型 | 298.0 | 58.2 | 1.00x |
| 主模型 + Q8 MTP 草稿 | 295.6 | 72.2 | 1.24x |
有用的一点是:提示词处理速度基本保持不变,而生成速度提升了约 24%。
调整 MTP
我测试了 `--spec-draft-n-max` 参数从 1 到 6 的值。
| `--spec-draft-n-max` | 提示词处理速度(tok/s) | 生成速度(tok/s) |
|---|---|---|
| 1 | 295.5 | 68.4 |
| 2 | 299.1 | 72.0 |
| 3 | 295.6 | 72.2 |
| 4 | 297.3 | 70.7 |
| 5 | 297.9 | 63.7 |
| 6 | 296.3 | 61.2 |
在我的 M1 Max 机器上,值为 3 时最快,值为 2 时也非常接近,两者都可以。高于这些值的配置会变慢。
MLX 对比
我还通过 mlx-lm 测试了 MLX 模型,以查明在 Mac 上运行模型哪种方式更快:llama.cpp 还是 mlx。
| 运行时 | 模型 | 生成速度(tok/s) |
|---|---|---|
| llama.cpp Metal + MTP | Unsloth GGUF Q4 + Q8 MTP | 72.2 |
| llama.cpp Metal | Unsloth GGUF Q4 | 58.2 |
| MLX-LM | Unsloth UD MLX 4-bit | 45.8 |
| MLX-LM | mlx-community 4-bit | 43.9 |
| MLX-LM | mlx-community OptiQ 4-bit | 38.1 |
我原本以为(针对 Mac 优化的)MLX 会最快。然而,在这个特定配置下,llama.cpp 比 MLX 更快,而启用 MTP 的 llama.cpp 显然是最佳选择。
我猜,llama.cpp 长期积累的所有优化和微调,使得它在 macOS 上尽管是跨平台,但优化得相当不错。
我还尝试通过 gemma-4-swift-mlx 运行 Gemma 4 MTP,但测试所用的 26B 4-bit MLX 检查点与加载器预期的权重键不匹配,而且我已经完成了之前的 MLX 测试,所以没有重新下载新模型并尝试调整以匹配,而是继续向前了。
添加图像支持
对于 Pi,我还希望能够附加截图。我最初为其设置的本地模型入口将模型声明为纯文本模型:
"input": ["text"]
这意味着 Pi 无法将图像工具输出正确发送给模型。
llama.cpp 服务器还需要 Gemma 4 多模态投影器才能正常使用多模态功能(只有 12B 版本原生支持多模态):
mmproj-BF16.gguf
当使用 `--mmproj` 加载时,llama.cpp 会声明多模态支持,Pi 就可以发送图像了。
我重新运行了加载投影器后的文本基准测试,以检查它是否改变了速度:
| 配置 | 投影器 | 提示词处理速度(tok/s) | 生成速度(tok/s) |
|---|---|---|---|
| llama.cpp Metal + MTP | 无 | 120.3 | 71.4 |
| llama.cpp Metal + MTP | mmproj-BF16.gguf | 297.4 | 72.2 |
最终带投影器的运行并未显示出文本生成速度下降。
现在是设置说明:
安装 llama.cpp
安装依赖项:
brew install cmake git tmux python@3.11
克隆并构建 llama.cpp:
mkdir -p ~/Developer/ML-Models/Gemma4/repos
cd ~/Developer/ML-Models/Gemma4
git clone https://github.com/ggml-org/llama.cpp repos/llama.cpp
cd repos/llama.cpp
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DGGML_METAL=ON \
-DGGML_ACCELERATE=ON
cmake --build build --config Release -j
我测试的构建版本包含:
GGML_METAL=ON
GGML_ACCELERATE=ON
GGML_BLAS=ON
GGML_BLAS_VENDOR=Apple
下载模型文件
创建一个 Python 环境:
cd ~/Developer/ML-Models/Gemma4
python3.11 -m venv .venv
source .venv/bin/activate
pip install -U huggingface_hub hf_xet
下载文件:
mkdir -p models/unsloth-gemma-4-26B-A4B-it-GGUF
huggingface-cli download unsloth/gemma-4-26B-A4B-it-GGUF \
gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf \
mmproj-BF16.gguf \
MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf \
--local-dir models/unsloth-gemma-4-26B-A4B-it-GGUF
最终会得到:
models/unsloth-gemma-4-26B-A4B-it-GGUF/
gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf
mmproj-BF16.gguf
MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf
启动本地服务器
这是最终的服务器启动命令:
repos/llama.cpp/build/bin/llama-server \
-m models/unsloth-gemma-4-26B-A4B-it-GGUF/gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf \
--model-draft models/unsloth-gemma-4-26B-A4B-it-GGUF/MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf \
--mmproj models/unsloth-gemma-4-26B-A4B-it-GGUF/mmproj-BF16.gguf \
--spec-type draft-mtp \
--spec-draft-n-max 3 \
-ngl 999 \
-fa on \
-c 65536 \
--parallel 1 \
--host 127.0.0.1 \
--port 8080
OpenAI 兼容的端点地址是:
http://127.0.0.1:8080/v1
我写了一个小的 start_server.sh 包装脚本,这样它可以在 tmux 中运行:
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SESSION_NAME="${SESSION_NAME:-gemma4-server}"
HOST="${HOST:-127.0.0.1}"
PORT="${PORT:-8080}"
CTX_SIZE="${CTX_SIZE:-65536}"
PARALLEL="${PARALLEL:-1}"
LLAMA_SERVER="$ROOT_DIR/repos/llama.cpp/build/bin/llama-server"
MODEL="$ROOT_DIR/models/unsloth-gemma-4-26B-A4B-it-GGUF/gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf"
DRAFT_MODEL="$ROOT_DIR/models/unsloth-gemma-4-26B-A4B-it-GGUF/MTP/gemma-4-26B-A4B-it-Q8_0-MTP.gguf"
MMPROJ="$ROOT_DIR/models/unsloth-gemma-4-26B-A4B-it-GGUF/mmproj-BF16.gguf"
LOG_FILE="$ROOT_DIR/logs/llama-server-mtp.log"
mkdir -p "$ROOT_DIR/logs"
tmux new-session -d -s "$SESSION_NAME" -c "$ROOT_DIR" \
"$LLAMA_SERVER \
-m '$MODEL' \
--model-draft '$DRAFT_MODEL' \
--mmproj '$MMPROJ' \
--spec-type draft-mtp \
--spec-draft-n-max 3 \
-ngl 999 \
-fa on \
-c '$CTX_SIZE' \
--parallel '$PARALLEL' \
--host '$HOST' \
--port '$PORT' \
2>&1 | tee -a '$LOG_FILE'"
启动它:
chmod +x start_server.sh
./start_server.sh
检查服务器是否在运行:
curl http://127.0.0.1:8080/v1/models
配置 Pi
Pi 从以下位置读取模型提供商:
~/.pi/agent/models.json
添加一个本地提供商:
{
"providers": {
"gemma4-local": {
"name": "Gemma 4 Local",
"baseUrl": "http://127.0.0.1:8080/v1",
"api": "openai-completions",
"apiKey": "local",
"authHeader": false,
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf",
"name": "Gemma 4 26B-A4B Q4 + MTP",
"reasoning": false,
"input": ["text", "image"],
"contextWindow": 65536,
"maxTokens": 8192,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
}
]
}
}
}
关键部分如下:
- baseUrl 指向 llama.cpp 的 OpenAI 兼容服务器。
- api 设置为 openai-completions。
- authHeader 为 false,因为这是本地服务器。
- input 同时包含 text 和 image,否则 Pi 会将其当作纯文本处理。
可选地将其设为默认提供商,在:
~/.pi/agent/settings.json
{
"defaultProvider": "gemma4-local",
"defaultModel": "gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf",
"defaultThinkingLevel": "minimal"
}
然后检查 Pi 能否看到它:
pi --offline --list-models gemma
预期输出:
provider model context max-out thinking images
gemma4-local gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf 65.5K 8.2K no yes
使用本地模型运行 Pi:
pi --provider gemma4-local --model gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf
或者使用非交互模式:
pi -p --provider gemma4-local --model gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf \
"Explain what this repository does"
用于截图:
pi -p @"/path/to/screenshot.png" "Describe this image and point out anything relevant to the UI"
最终配置
最终的本地编程智能体技术栈为:
| 层 | 选择 |
|---|---|
| 推理运行时 | llama.cpp |
| macOS 加速 | Metal + Accelerate |
| 主模型 | gemma-4-26B-A4B-it-UD-Q4_K_XL.gguf |
| 草稿模型 | gemma-4-26B-A4B-it-Q8_0-MTP.gguf |
| MTP 参数设置 | --spec-draft-n-max 3 |
| 多模态投影器 | mmproj-BF16.gguf |
| 服务器 | 运行在 127.0.0.1:8080 上的 llama-server |
| API | OpenAI 兼容的 /v1 |
| 编程智能体 | Pi |
| Pi 模型输入 | ["text", "image"] |
主要结论是 MTP 草稿模型值得使用。在这台机器上,它将 Gemma 4 的推理速度从 58.2 tokens/秒提升到了 72.2 tokens/秒,同时保持了足够简单的配置,可以作为本地 OpenAI 兼容服务器运行。
附言:有人建议使用 Qwen3.6 35B-A3B 而不是 Gemma 4 26B-A4B。根据我能找到的基准测试,Qwen 作为编程智能体的表现远优于 Gemma 4。不过 Qwen 也更慢:Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf + unsloth-Qwen3.6-35B-A3B-MTP-GGUF + mmproj-BF16.gguf 的速度是 55 tk/s,而不是 72 tk/s。当你坐在那里等待时,这个差距相当明显。
下载模型:
mkdir -p models/unsloth-Qwen3.6-35B-A3B-MTP-GGUF
huggingface-cli download unsloth/Qwen3.6-35B-A3B-MTP-GGUF \
Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf \
mmproj-BF16.gguf \
--local-dir models/unsloth-Qwen3.6-35B-A3B-MTP-GGUF
启动服务器:
LLAMA_SERVER=/Users/kylehowells/Developer/ML-Models/Gemma4/repos/llama.cpp/build/bin/llama-server
$LLAMA_SERVER \
-m models/unsloth-Qwen3.6-35B-A3B-MTP-GGUF/Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf \
--mmproj models/unsloth-Qwen3.6-35B-A3B-MTP-GGUF/mmproj-BF16.gguf \
--spec-type draft-mtp \
--spec-draft-n-max 3 \
-ngl 999 \
-fa on \
-c 65536 \
--parallel 1 \
--host 127.0.0.1 \
--port 8081
Pi 配置:
{
"providers": {
"qwen36-local": {
"name": "Qwen3.6 Local",
"baseUrl": "http://127.0.0.1:8081/v1",
"api": "openai-completions",
"apiKey": "local",
"authHeader": false,
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf",
"name": "Qwen3.6 35B-A3B Q4 + MTP",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 65536,
"maxTokens": 8192,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
}
]
}
}
}
参考资料:
- unsloth.ai/docs/models/qwen3.6
- unsloth.ai/docs/models/gemma-4
- unsloth.ai/docs/models/mtp
- github.com/ggml-org/llama.cpp
- github.com/earendil-works/pi
- 介绍 Gemma 4 12B:一个统一的、免编码器的多模态模型
- "MTP 使 Google Gemma 4 运行速度提升约 1.4–2.2 倍,且无精度损失"
- unsloth/gemma-4-26B-A4B-it-GGUF
- unsloth/Qwen3.6-35B-A3B-MTP-GGUF