
Mac 上跑本地大模型:该选哪个版本、免审模型用哪个(附下载地址与实测速度)
本地跑大模型,同一个模型往往有好几个版本:4bit、8bit、免审版、蒸馏版……选错了要么慢一倍,要么白白牺牲质量。
先记住一句话:
稠密模型要选小体积(4bit),MoE 模型要选大体积(8bit)。很多人两边都选反了。
下面讲清楚为什么、用哪个、怎么装。
一、为什么?先看一个比喻
大模型有两种结构,差别就像两种组织方式:
| 稠密(Dense) | MoE(混合专家) | |
|---|---|---|
| 像什么 | 小团队:做任何一件事,所有人都得上 | 大公司:做一件事只叫几个对口的专家 |
| 卡在哪 | 搬数据的量 | 派活的速度 |
| 降低精度(4bit) | ✅ 数据变少,快一倍 | ❌ 快不了,只掉了质量 |
| 保持精度(8bit) | 慢,但质量好 | ✅ 质量好,而且几乎不掉速度 |
结论:稠密用 4bit,MoE 用 8bit。
为什么 MoE 降精度快不了?因为它的瓶颈是"每次叫谁干活"这个决策开销,不是数据搬运量。数据减半,决策次数一次没少。
二、免审模型用哪个(含地址)
免审(uncensored / abliterated)是社区把模型里"拒绝回答"的倾向去掉后的版本。请自行遵守所在地法律,仅本地自用。
我们实测选出来的是这两个:
| 用途 | 模型 | 地址 | 体积 |
|---|---|---|---|
| MoE · 快 · 能看图 | froggeric/Qwen3.6-35B-A3B-Uncensored-Heretic-MLX-8bit | https://huggingface.co/froggeric/Qwen3.6-35B-A3B-Uncensored-Heretic-MLX-8bit | 37.7 GB |
| 同上,内存不够用这个 | 同仓库 -4bit | https://huggingface.co/froggeric/Qwen3.6-35B-A3B-Uncensored-Heretic-MLX-4bit | 20.4 GB |
| 稠密 · 质量最好 · 当主力 | orcarouter/Qwen3.8-27B-Uncensored-MLX | https://huggingface.co/orcarouter/Qwen3.8-27B-Uncensored-MLX | 16 GB |
上面的 HuggingFace 页面就是官方页面,权威说明都在那里 —— 部署方式、必设的 system 提示词、推荐采样参数、思考开关、依赖版本、能力评测都有。第三节把最容易漏的几条摘出来了,但遇到问题应该回去查原页。
那个 8bit 仓库的模型卡自报(第三方数据,仅供参考):拒绝率 83/100 → 10/100,MMLU 83.72% → 83.30%,KL 散度 0.0015。
为什么不是别的?(这几条我们查过,能省你几小时)
- 4bit 那个下载量是 8bit 的 5 倍多(10169 vs 1811)。这不是质量证据 —— 37.7 GB 装不进 24~32 GB 的机器,多数人是装不下 8bit,不是选出来的。有 256 GB 就用 8bit。
- 社区有所谓"Claude 蒸馏版",我们逐个查过:官方只出了纯文本 8bit,带视觉的 4bit 全是下载量 100~450 的个人上传仓,没人验证过。不建议用。
- 免审模型比普通模型更怕低精度:它是靠对权重做细微的方向性修改来去掉拒绝行为的,而 4bit 是分组压缩,容易把这种细微修改一起压掉。表现不是"均匀变差一点",而是偶发胡言乱语。所以内存够就上 8bit。
三、怎么下载、怎么启动
1. 装环境
pip install -U mlx-vlm huggingface_hub hf_transfer
2. 下载模型(自动存到本地缓存)
export HF_HUB_ENABLE_HF_TRANSFER=1
hf download froggeric/Qwen3.6-35B-A3B-Uncensored-Heretic-MLX-8bit
3. 起服务(对外提供 OpenAI 兼容接口,能看图)
python -m mlx_vlm.server \
--model froggeric/Qwen3.6-35B-A3B-Uncensored-Heretic-MLX-8bit \
--host 0.0.0.0 --port 8081 --max-tokens 32768
4. 试一下
curl http://127.0.0.1:8081/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"x","messages":[{"role":"user","content":"你好"}],"max_tokens":64}'
能返回内容就成了。之后任何支持 OpenAI 接口的客户端(Cherry Studio、ChatBox、DSH 等)都能连它。
另一个起服务的办法:装 LM Studio(或用它的免图形界面版
llmster),把模型放进它的模型目录,然后lms load <模型> --context-length 262144 && lms server start --port 8081。它比 mlx-vlm 多两个东西:支持前缀缓存(长对话每轮不用重算,见第四节)和视觉/视频。模型目录~/.lmstudio/models/<作者>/<模型名>/,可以直接软链接到已有权重,不用复制。
官方模型卡里的必做项(最容易漏,但都实际影响效果)
1. system 提示词的第一行必须固定是这句
You are Qwen, created by Alibaba Cloud. You are a helpful assistant.
模型卡原话是不加这句模型会表现下降。这一行之后可以继续追加你自己的要求。
注意:这是客户端传的,服务端启动参数里没有这一项 —— 所以每个连上来的客户端都要自己带上。
2. 按官方推荐采样参数
| 场景 | temp | top_p | top_k |
|---|---|---|---|
| 思考模式(写代码) | 0.6 | 0.95 | 20 |
| 思考模式(通用) | 1.0 | 0.95 | 20 |
| 非思考模式 | 0.7 | 0.8 | 20 |
repeat_penalty 保持 1.0(等于关闭)。GGUF 类引擎用 presence_penalty,MLX 用 repeat_penalty。
3. 思考开关:一个标签就能切
在任意一条消息里写 <|think_off|> 或 <|think_on|>,模板会截获这个标签、从上下文里删掉、并切换模式:
System: 你是一个编程助手。<|think_off|>
User: 巴黎天气怎么样?
→ 不推理,直接回答
System: 你是一个编程助手。<|think_on|>
User: 用 Rust 实现一个红黑树。
→ 先深度推理再作答
4. 硬件与依赖
- 最低内存 约 40 GB(35 GB 权重 + 运行开销)
- 需要
mlx-lm >= 0.31.2、mlx-vlm >= 0.4.4
四、第三个选择:两个都要(能看图 + 记得住对话)
前两个引擎各缺一样:mlx-vlm 能看图但不记对话历史,rapid-mlx 记得住但不能看图。
现在有第三个:LM Studio 的 mlx-engine —— 就是它桌面版跑 MLX 用的那个引擎,MIT 开源,也有免图形界面的 llmster。它两个都能给。
差距有多大?同一个 27B 模型:
| 对话长度 | 不记(mlx-vlm 一直这样) | 记得住(rapid-mlx) |
|---|---|---|
| 6000 字 | 等 25.2 秒 | 等 7.05 秒 |
| 27000 字 | 等 83.7 秒 | 等 4.07 秒 |
用 mlx-vlm 聊长对话,每说一句都要把前面从头重算一遍。 短聊天感觉不出来,一旦长就是灾难。
三个引擎横向对比:
| mlx-vlm | rapid-mlx | mlx-engine | oMLX | |
|---|---|---|---|---|
| 读图 | ✅ | ❌ | ✅ | ✅ |
| 看视频 | ✅ | ❌ | ✅ | ❌ |
| 记住对话(前缀缓存) | ❌ | ✅ | ✅ | ✅ 还能跨重启 |
| 连续批处理 | ❌ | ✅ | ✅ | ✅ |
怎么选:
| 你的情况 | 选 |
|---|---|
| 既要看图、又要长对话 | mlx-engine 或 oMLX |
| 只跑文本 | oMLX |
| 只看图、聊得很短 | mlx-vlm |
两个反直觉但重要的点:
1. "能不能看图"是引擎的问题,不是模型的问题。 同一个模型(Qwen3.8-27B,混合注意力架构),rapid-mlx 会明确拒绝图片,报 vision_hybrid_runtime_unsupported;而 mlx-engine 和 mlx-vlm 都能正常识别。所以遇到"这个模型不支持图片"时,先换引擎试试,别急着换模型。
2. "换引擎不会让稠密模型变快" —— 我们一开始这么写,后来被打脸了。
同一个 27B 模型,三家引擎不开任何加速时几乎一样快:mlx-engine 37、rapid-mlx 37、oMLX 36.8 t/s。所以我们当时写下上面那句结论。
但后来给 oMLX 挂上投机解码,同一个模型跑到 53~57 t/s;而 rapid-mlx 挂同样的投机解码只有 44.5 t/s —— 同权重、同一个加速包、同一台机器,差 20%。
差别不在"引擎裸速度",而在加速功能实现得好不好。所以准确的结论是:
引擎的裸速度决定不了什么(都一样);但"加速功能"的实现质量,在稠密模型上能差出 20%。
MoE 模型则两家差不多(见下),因为 MoE 瓶颈在调度、不在字节数。
3. 但并发能力差得很远。 如果你有多个客户端同时用,这个比单请求速度更重要。开 4 个并发请求,聚合吞吐:
| 配置 | 单请求 | 4 并发合计 | 说明 |
|---|---|---|---|
| mlx-engine(35B-A3B) | 72.5 | 203(2.8×) | 默认并行度 4 |
| oMLX 开着 MTP(27B) | 57 | 78(1.4×) | MTP 和批处理能共存 |
| rapid-mlx 关了 MTP(27B) | 38.9 | 89.9(2.3×) | 真批处理 |
| rapid-mlx 开着 MTP(27B) | 44.5 | 42.3(1.0×) | 只是排队 |
最后一行是最容易踩的:在 rapid-mlx 上开了 MTP 就没批处理,加并发不会提高总吞吐,只会让每个人变慢(8 并发时最慢的那个人只有 5 t/s)。
但这是 rapid-mlx 这家引擎的毛病,不是 MTP 本身的限制 —— 换到 oMLX,MTP 和批处理可以共存(单请求 57 + 4 并发 78),不用二选一。细节见第五节。
五、三个"看着很美、实际别开"的加速功能
1. DFlash 加速 —— 短问题快,长文本反而变慢
| 速度变化 | |
|---|---|
| 短问题 | +13% |
| 6000 字上下文 | −14% |
2. KV 量化(号称省显存提速)—— 短上下文猛,长上下文直接崩
| 速度变化 | |
|---|---|
| 6000 字 | +26% |
| 27000 字 | −64%(从 74 掉到 26.4) |
3. MTP 加速 —— 有两个反噬条件
MTP(小模型猜、大模型验)本身是好东西,稠密模型上能 +24%。但它有两个隐藏前提:
前提一:drafter 必须和当前权重配套。 我们换成同架构的免审版(8bit → 8bit,参数量一模一样)后忘了这点,速度从 70 掉到 39 t/s —— drafter 是在原版权重上训练的,换权重后命中率崩了,投机解码从加速变成净拖累。摘掉后立刻回到 80 t/s。
前提二:有些引擎会把并发批处理关掉。 这个更隐蔽,但要看引擎。
rapid-mlx 上有这个问题:同一个 27B 模型实测:
| 配置 | 单请求 | 4 个并发合计 |
|---|---|---|
| 开 MTP | 44.5 t/s | 42.3 t/s(等于单请求,加并发只是排队) |
| 关 MTP | 38.9 t/s | 89.9 t/s |
开了 MTP 之后,引擎调度器日志里的 running 永远是 1 —— 即使 7 个请求在排队也只跑一个。而且光加并发参数没用,必须真的关掉 MTP 才会启用批处理。
oMLX 上没有这个问题:同样的模型同样挂 MTP,单请求 57 t/s、4 并发 78 t/s —— 加速和批处理同时拿到。
所以结论是:先看你在用哪个引擎。 rapid-mlx 上必须二选一(一个人用开 MTP / 多人关掉);oMLX 上不用选。
共通教训:加速功能不能只看"开没开",要拿你实际的模型、上下文长度、以及"几个人同时用"去测。
六、给技术人员的部分:实测数据与踩坑
(下面这段偏技术,普通读者可以跳到最后一节)
测试环境
M3 Ultra / 256 GB / 819 GB/s / macOS 26.6.2 / mlx-vlm 0.6.17 / rapid-mlx 0.14.3 / LM Studio mlx-engine(llmster 0.0.25-1)/ oMLX 0.6.4(2026-09-22 补测)
量化与速度对照
| 模型 | 量化 | 权重 | decode |
|---|---|---|---|
| 稠密 27B | 4bit | 16.05 GB | 54.3 t/s(实测) |
| 稠密 27B | 8bit | 31.19 GB | 约 28 t/s(按带宽推算) |
| MoE 35B-A3B | 8bit | 37 GB | 85.8 t/s |
| MoE 35B-A3B | 4bit | 20.4 GB | 与 8bit 基本无差 |
MoE 给 8bit 版挂 MTP 投机解码只拿到 +5%(82.2 → 85.8)。投机解码本质是让一次权重读取摊到多个 token —— 有效即说明瓶颈在调度而非字节数。
坑 1:rapid-mlx 不配 tool-call parser,工具调用变裸 XML
客户端收到 <tool_call><function=bash>... 这样的文本。不是模型坏了,是服务端没解析:
--enable-auto-tool-choice --tool-call-parser qwen3_coder_xml
坑 2:--speculative-config 的 JSON 必须带 model 字段
# ❌ 拒绝启动
--speculative-config '{"method":"mtp","num_speculative_tokens":3}'
# ✅
--speculative-config '{"method":"mtp","model":"<MTP sidecar 路径>","num_speculative_tokens":3}'
坑 3:上下文窗口不能硬拉
上限由模型 max_position_embeddings 决定,硬拉参数没用。这个 35B-A3B 是 262K 原生,官方模型卡写明开 YaRN 后可到 1M+(RoPE theta 10M、partial_rotary_factor 0.25)—— 所以想要 1M 是在引擎侧开 YaRN,不是换模型。但要注意:本案例用过的 mlx-vlm 0.6.17 与 rapid-mlx 0.14.3 的命令行参数里都没看到 YaRN/rope 缩放开关,LM Studio 的 lms load 参数里也没有,用之前先确认你的引擎支持。
坑 4:HF 匿名下载慢/断连的排查
netstat 统计不可靠,直接采样目录增长:
D=~/.cache/hf/<repo>
S1=$(du -sk "$D"|cut -f1); sleep 10; S2=$(du -sk "$D"|cut -f1)
echo "$(( (S2-S1)/10/1024 )) MB/s"
日志里 peer closed connection ... Trying to resume download... 是正常抖动,会自动续传。装了 hf_transfer 速度没变 → 瓶颈是链路带宽,别折腾工具。
坑 5:改了 plist 用 kickstart 重载,模型根本不会换
launchctl kickstart -k 是用 launchd 缓存里的任务定义重启,不会重读 plist 文件。所以改完 --model 再 kickstart,进程确实起来了、PID 也变了,但跑的还是旧模型 —— 静默失败,最难查。
必须 bootout + bootstrap 强制重新加载:
PLIST=~/Library/LaunchAgents/com.xxx.plist
LABEL=com.xxx
cp "$PLIST" "$PLIST.bak" # 先备份
/usr/libexec/PlistBuddy -c "Set :ProgramArguments:4 <新模型ID>" "$PLIST"
launchctl bootout "gui/$(id -u)/$LABEL" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST"
而且不能拿"命令没报错"当成功 —— 要校验进程命令行里是不是新模型:
ps -o command= -p "$(launchctl list | awk '$3=="com.xxx"{print $1}')" \
| grep -q "<新模型ID>" && echo OK || echo "还是旧模型"
坑 6:mlx_vlm 会按请求里的 model 名热切换模型,传错名字会静默拿到另一个模型
/v1/models 列出的是缓存里所有模型,不是当前加载的那一个。而且服务端会按请求 body 里的 model 字段现场切换:你写哪个名字,它就把那个加载进来。日志能看到 Loading model: ...,一次切换约 3 秒,进程内存始终只有单个模型大小。
两个后果:
- "新模型出现在列表里"不能当作切换成功的判据 —— 权重一下完它就会列出来。
- 客户端传的 model 名不对,你会静默地拿到另一个模型的结果,而且没有任何报错。
我们真踩了这个坑:脚本用 /v1/models 的第 0 项当模型名,结果测了半天一直是旧模型,一整轮数据全废。所以务必显式传你要的模型全名,并用日志确认:
curl http://127.0.0.1:8081/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"<你的模型全名>","messages":[{"role":"user","content":"hi"}],"max_tokens":8}'
grep "Loading model" ~/logs/<你的服务>.log | tail -3
坑 7:并发压测里的"连接被服务端关闭",多半是你客户端的问题
用 Python 的 urllib 做并发压测时,会偶发 RemoteDisconnected: Remote end closed connection without response。这个现象我们在两个不同引擎上都遇到过,一开始怀疑是"并发超过上限被拒"或"服务端崩了"。
实际排查:服务端三项全正常(进程存活、/v1/models 返回 200、日志无错误),内存也没涨。真凶是客户端的 HTTP keep-alive 连接复用。
请求头加上 Connection: close 之后,同样的并发测试跑 12 轮(2 并发和 4 并发各 3 轮 × 2 个端点)零丢包。
排查顺序:先怀疑自己。 客户端报"连接被关闭"时,先确认服务端三项(进程存活 / 健康检查 200 / 日志无错误)—— 本地环回网络几乎不可能丢包。
七、速查表
| 你的情况 | 选择 |
|---|---|
| 稠密模型,内存紧张 | 4bit,明确换速度 |
| 稠密模型,内存充足 | 4bit 仍是甜点 |
| MoE 模型 | 8bit |
| 免审 / abliterated | 尽量 8bit |
| 要读图 | mlx-vlm(但要接受"不记对话")或 mlx-engine |
| 长对话 / Agent | oMLX 或 mlx-engine |
| 既要读图又要长对话 | mlx-engine(唯一同时支持两者) |
| 想开投机解码(MTP / DFlash) | 换过模型权重就必须重测,否则可能是负收益 |
| 想开 KV 量化 | 先跑到真实最长上下文再决定 |
| 一个人用 | 开 MTP,单请求最快(oMLX 上无副作用) |
| 多人 / 多客户端同时用 | oMLX 直接开 MTP 即可;rapid-mlx 上才需要关掉 MTP 换批处理 |
| 想确认批处理有没有生效 | 看引擎日志里的 running= —— 恒为 1 就是没批处理(rapid-mlx 开 MTP 时的典型症状) |
选型的核心不是"哪个量化好",而是先问清楚:你的模型卡在搬数据上,还是卡在派活上。
暂无评论
