在 Mac 上运行本地 LLM
本指南将带你在 macOS 上运行一个提供 OpenAI 兼容 API 的本地 LLM 服务。这样做可以获得完整隐私、零 API 成本,以及在 Apple Silicon 上相当不错的性能表现。
我们会介绍两种后端:
| 后端 | 安装方式 | 最适合 | 格式 |
|---|---|---|---|
| llama.cpp | brew install llama.cpp | 首 token 延迟最低,量化 KV 缓存更省内存 | 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 以上统一内存的机器上。
| 变体 | 磁盘占用 | 所需内存(128K 上下文) | 后端 |
|---|---|---|---|
| Qwen3.5-9B-Q4_K_M (GGUF) | 5.3 GB | 约 10 GB,含量化 KV 缓存 | llama.cpp |
| Qwen3.5-9B-mlx-lm-mxfp4 (MLX) | 约 5 GB | 约 12 GB | omlx |
经验法则: 总内存占用约等于模型体积 + KV 缓存。一个 9B 的 Q4 模型大约为 5 GB;在 128K 上下文下,Q4 量化 KV 缓存还会再增加约 4 到 5 GB。若使用默认的 f16 KV 缓存,则会膨胀到约 16 GB。llama.cpp 的量化 KV 缓存参数,是内存受限机器上最关键的优化手段。
如果要运行更大的模型,如 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
各参数含义如下:
| 参数 | 作用 |
|---|---|
-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。与上一项结合后,KV 缓存相比 f16 可降低约 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 缓存类型 | KV 缓存内存占用(128K 上下文,9B 模型) |
|---|---|
| f16(默认) | 约 16 GB |
| q8_0 | 约 8 GB |
| q4_0 | 约 4 GB |
如果你用的是 8GB Mac,建议启用 q4_0 KV 缓存,并把上下文降到 -c 32768(32K)。16GB 机器通常可以较轻松地跑 128K。32GB 及以上则可以尝试更大的模型,或更多并行槽位。
如果还是爆内存,优先先减小上下文大小(-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
获取模型名
如果你忘了当前服务暴露的模型名,可以查询模型列表接口:
curl -s http://localhost:8080/v1/models | jq '.data[].id'
方案 B:通过 omlx 使用 MLX
omlx 是一个 macOS 原生应用,用于管理和服务 MLX 模型。MLX 是苹果自家的机器学习框架,专门针对 Apple Silicon 的统一内存架构做了优化。
安装
从 omlx.ai 下载并安装。它自带图形界面用于模型管理,也带内置服务能力。
下载模型
打开 omlx,浏览并下载模型。搜索 Qwen3.5-9B-mlx-lm-mxfp4 即可。模型会保存在本地,通常位于 ~/.omlx/models/。
启动服务
omlx 默认在 http://127.0.0.1:8000 提供服务。你可以在应用界面里启动,也可以在有 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)。测试包含五类不同提示,每类跑三次,并按顺序测试后端,避免资源抢占。
结果
| 指标 | llama.cpp (Q4_K_M) | MLX (mxfp4) | 胜者 |
|---|---|---|---|
| TTFT(平均) | 67 ms | 289 ms | llama.cpp(快 4.3 倍) |
| TTFT(p50) | 66 ms | 286 ms | llama.cpp(快 4.3 倍) |
| 生成速度(平均) | 70 tok/s | 96 tok/s | MLX(快 37%) |
| 生成速度(p50) | 70 tok/s | 96 tok/s | MLX(快 37%) |
| 总耗时(512 tokens) | 7.3s | 5.5s | MLX(快 25%) |
如何理解这些结果
- llama.cpp 在提示处理阶段表现极强。它配合 Flash Attention 和量化 KV 缓存后,可以在约 66ms 内吐出首个 token。如果你做的是聊天机器人、自动补全这类强调“响应快”的交互应用,这个优势很有价值。
- MLX 在正式生成阶段更快,token 生成速度大约快 37%。如果你的场景更在乎总完成时间,比如批处理、长文生成、后台任务,那么 MLX 会更占优。
- 两者的表现都非常稳定,多轮测试的波动很小,可以把这些结果当成有参考意义的数据。
怎么选
| 场景 | 推荐 |
|---|---|
| 交互式聊天、低延迟工具调用 | llama.cpp |
| 长文生成、批量处理 | MLX(omlx) |
| 内存较紧(8 至 16 GB) | llama.cpp(量化 KV 缓存优势明显) |
| 同时服务多个模型 | omlx(内置多模型支持) |
| 最大兼容性(未来也想跑在 Linux) | llama.cpp |
连接到 Hermes
当本地服务已经跑起来以后,执行:
hermes model
然后选择 Custom endpoint,按提示输入基础 URL 和模型名,填写为你刚才配置好的后端值即可。
超时设置
Hermes 会自动识别本地端点(如 localhost 或局域网 IP),并自动放宽流式请求的超时限制。大多数情况下不需要额外配置。
如果你仍然遇到超时,比如在比较慢的硬件上跑超大上下文,可以手动覆盖流式读取超时:
# In your .env — raise from the 120s default to 30 minutes
HERMES_STREAM_READ_TIMEOUT=1800
| 超时项 | 默认值 | 本地自动调整 | 环境变量覆盖 |
|---|---|---|---|
| 流式读取(socket 级) | 120s | 自动提升到 1800s | HERMES_STREAM_READ_TIMEOUT |
| 静默流检测 | 180s | 完全禁用 | HERMES_STREAM_STALE_TIMEOUT |
| API 调用(非流式) | 1800s | 无需调整 | HERMES_API_TIMEOUT |
最容易出问题的一般是流式读取超时。它本质上是“等待下一段数据返回”的 socket 级超时。对于超大上下文,本地模型在预填充阶段可能几分钟都没有输出,因为它还在处理提示词。Hermes 的本地端点识别会自动照顾这一点。