在 Mac 上运行本地 LLM
本指南会带你在 macOS 上运行一个提供 OpenAI 兼容 API 的本地 LLM 服务。这样你可以获得完整隐私、零 API 成本,以及在 Apple Silicon 上相当不错的性能。
这里会介绍两个后端:
| Backend | Install | Best at | Format |
|---|---|---|---|
| llama.cpp | brew install llama.cpp | 首 token 延迟最低,量化 KV cache 带来更低内存占用 | GGUF |
| omlx | omlx.ai | token 生成速度最快,原生 Metal 优化 | MLX (safetensors) |
两者都会暴露兼容 OpenAI 的 /v1/chat/completions 接口。Hermes 可以直接接入任意一种,只需要把地址指向 http://localhost:8080 或 http://localhost:8000。
本指南面向 Apple Silicon Mac(M1 及更新机型)。Intel Mac 也能运行 llama.cpp,但无法获得 GPU 加速,性能会明显慢很多。
选择模型
如果你想快速上手,我们推荐 Qwen3.5-9B。这是一个推理能力很强的模型,在量化后可以比较轻松地放进 8GB 以上统一内存的机器里。
| Variant | Size on disk | RAM needed (128K context) | Backend |
|---|---|---|---|
| Qwen3.5-9B-Q4_K_M (GGUF) | 5.3 GB | 使用量化 KV cache 时约 10 GB | llama.cpp |
| Qwen3.5-9B-mlx-lm-mxfp4 (MLX) | ~5 GB | ~12 GB | omlx |
内存经验法则: 模型体积 + KV cache。9B 的 Q4 模型大约是 5 GB。上下文设为 128K 时,若使用 Q4 量化,KV cache 大约再增加 4 到 5 GB;如果用默认的 f16 KV cache,则会膨胀到大约 16 GB。对于内存吃紧的系统来说,llama.cpp 的量化 KV cache 参数是关键技巧。
如果你想用更大的模型(27B、35B),通常需要 32 GB 以上统一内存。对于 8 到 16 GB 的机器,9B 往往是最合适的甜点位。
方案 A:llama.cpp
llama.cpp 是最通用的本地 LLM 运行时之一。在 macOS 上它会默认使用 Metal 做 GPU 加速。
安装
brew install llama.cpp
安装完成后,你就能全局使用 llama-server 命令。
下载模型
你需要一个 GGUF 格式的模型。最简单的来源是通过 huggingface-cli 从 Hugging Face 下载:
brew install huggingface-cli
然后执行:
huggingface-cli download unsloth/Qwen3.5-9B-GGUF Qwen3.5-9B-Q4_K_M.gguf --local-dir ~/models
有些 Hugging Face 模型需要认证。如果你遇到 401 或 404,请先执行 huggingface-cli login。
启动服务
llama-server -m ~/models/Qwen3.5-9B-Q4_K_M.gguf \
-ngl 99 \
-c 131072 \
-np 1 \
-fa on \
--cache-type-k q4_0 \
--cache-type-v q4_0 \
--host 0.0.0.0
各个参数的含义如下:
| Flag | Purpose |
|---|---|
-ngl 99 | 把所有层都尽量卸载到 GPU(Metal)。设置成较大的数,确保不会留在 CPU 上 |
-c 131072 | 上下文窗口大小(128K tokens)。如果内存紧张,可以调小 |
-np 1 | 并行槽位数。单用户场景建议保持 1,更多槽位会分摊你的内存预算 |
-fa on | 开启 Flash Attention,可减少内存使用并加快长上下文推理 |
--cache-type-k q4_0 | 把 key cache 量化到 4-bit,这是最大的省内存手段之一 |
--cache-type-v q4_0 | 把 value cache 量化到 4-bit。和上面一起使用时,相比 f16 可把 KV cache 内存削减约 75% |
--host 0.0.0.0 | 监听所有网卡。如果你不需要网络访问,可以改成 127.0.0.1 |
当你看到下面输出时,说明服务已经启动:
main: server is listening on http://0.0.0.0:8080
srv update_slots: all slots are idle
面向受限机器的内存优化
对内存有限的系统来说,--cache-type-k q4_0 --cache-type-v q4_0 是最重要的优化参数。在 128K 上下文下,影响如下:
| KV cache type | KV cache memory (128K ctx, 9B model) |
|---|---|
| f16 (default) | ~16 GB |
| q8_0 | ~8 GB |
| q4_0 | ~4 GB |
如果你是 8 GB Mac,建议使用 q4_0 KV cache,并把上下文缩减到 -c 32768(32K)。16 GB 机器通常可以较舒适地跑 128K;32 GB 及以上则可以考虑更大模型或多个并行槽位。
如果还是爆内存,优先先减小上下文窗口(-c),再考虑更激进的模型量化,例如把 Q4_K_M 改成 Q3_K_M。
测试它
curl -s http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3.5-9B-Q4_K_M.gguf",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 50
}' | jq .choices[0].message.content
获取模型名
如果你忘了模型名,可以查询 models 接口:
curl -s http://localhost:8080/v1/models | jq '.data[].id'
方案 B:通过 omlx 使用 MLX
omlx 是一个 macOS 原生应用,用来管理和提供 MLX 模型服务。MLX 是 Apple 自家的机器学习框架,专门针对 Apple Silicon 的统一内存架构做了优化。
安装
从 omlx.ai 下载并安装即可。它提供图形界面做模型管理,并内置了服务能力。
下载模型
使用 omlx 应用浏览并下载模型。搜索 Qwen3.5-9B-mlx-lm-mxfp4 并下载。模型会存储在本地(通常位于 ~/.omlx/models/)。
启动服务
omlx 默认会在 http://127.0.0.1:8000 提供模型服务。你可以直接在应用 UI 中开启服务,如果 CLI 可用,也可以用 CLI。
测试它
curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3.5-9B-mlx-lm-mxfp4",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 50
}' | jq .choices[0].message.content
列出可用模型
omlx 可以同时提供多个模型:
curl -s http://127.0.0.1:8000/v1/models | jq '.data[].id'
基准测试:llama.cpp vs MLX
两个后端都在同一台机器上测试(Apple M5 Max,128 GB 统一内存),使用同一个模型(Qwen3.5-9B),量化等级也尽量对齐(GGUF 使用 Q4_K_M,MLX 使用 mxfp4)。一共用了 5 个不同提示,每个提示跑 3 次,两个后端串行测试以避免资源争抢。
结果
| Metric | llama.cpp (Q4_K_M) | MLX (mxfp4) | Winner |
|---|---|---|---|
| TTFT (avg) | 67 ms | 289 ms | llama.cpp(快 4.3 倍) |
| TTFT (p50) | 66 ms | 286 ms | llama.cpp(快 4.3 倍) |
| Generation (avg) | 70 tok/s | 96 tok/s | MLX(快 37%) |
| Generation (p50) | 70 tok/s | 96 tok/s | MLX(快 37%) |
| Total time (512 tokens) | 7.3s | 5.5s | MLX(快 25%) |
这意味着什么
-
llama.cpp 在 prompt 处理阶段表现更强。它依靠 flash attention + 量化 KV cache,可以在约 66ms 内给出首个 token。如果你在做聊天机器人、自动补全这类对“体感响应”非常敏感的交互应用,这是一个很有价值的优势。
-
MLX 一旦开始生成,速度会快约 37%。对于批处理、长文本生成,或者任何更看重总完成时间而不是起始延迟的任务,MLX 往往更早结束。
-
两者的表现都非常稳定,不同轮次之间的波动几乎可以忽略。这些数据是可以参考的。
该选哪一个?
| Use case | Recommendation |
|---|---|
| 交互式聊天、低延迟工具调用 | llama.cpp |
| 长文本生成、批量处理 | MLX (omlx) |
| 内存紧张(8-16 GB) | llama.cpp(量化 KV cache 几乎无可替代) |
| 需要同时提供多个模型 | omlx(内建多模型支持) |
| 追求最大兼容性(包括 Linux) | llama.cpp |
连接到 Hermes
当你的本地服务跑起来后:
hermes model
选择 Custom endpoint,然后按提示输入 base URL 和 model name。直接使用你上面设置好的后端对应值即可。
超时
Hermes 会自动识别本地 endpoint(localhost、局域网 IP),并自动放宽流式请求超时。大多数场景下不需要额外配置。
如果你仍然遇到超时错误(例如在慢硬件上跑超大上下文),可以手动覆盖流式读取超时:
# In your .env — raise from the 120s default to 30 minutes
HERMES_STREAM_READ_TIMEOUT=1800
| Timeout | Default | Local auto-adjustment | Env var override |
|---|---|---|---|
| Stream read (socket-level) | 120s | 自动提升到 1800s | HERMES_STREAM_READ_TIMEOUT |
| Stale stream detection | 180s | 完全禁用 | HERMES_STREAM_STALE_TIMEOUT |
| API call (non-streaming) | 1800s | 无需调整 | HERMES_API_TIMEOUT |
最常引发问题的是 stream read timeout,它控制的是“等待下一块数据到达 socket”的截止时间。在大上下文预填充阶段,本地模型可能要花好几分钟处理 prompt,却还没有任何输出。自动检测机制会透明地帮你处理这一点。