# Mac 上跑本地大模型:该选哪个版本、免审模型用哪个(附下载地址与实测速度) > 同一个模型有 4bit/8bit/免审版,怎么选?一句话:稠密选小、MoE 选大。附下载地址与实测速度;2026-09 补测 oMLX 引擎,修正「换引擎不会变快」的旧结论。 # 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. 装环境** ```bash pip install -U mlx-vlm huggingface_hub hf_transfer ``` **2. 下载模型**(自动存到本地缓存) ```bash export HF_HUB_ENABLE_HF_TRANSFER=1 hf download froggeric/Qwen3.6-35B-A3B-Uncensored-Heretic-MLX-8bit ``` **3. 起服务**(对外提供 OpenAI 兼容接口,能看图) ```bash 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. 试一下** ```bash 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](https://lmstudio.ai/)(或用它的免图形界面版 `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 客户端收到 `...` 这样的文本。不是模型坏了,是服务端没解析: ```bash --enable-auto-tool-choice --tool-call-parser qwen3_coder_xml ``` ### 坑 2:`--speculative-config` 的 JSON 必须带 model 字段 ```bash # ❌ 拒绝启动 --speculative-config '{"method":"mtp","num_speculative_tokens":3}' # ✅ --speculative-config '{"method":"mtp","model":"","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` 统计不可靠,直接采样目录增长: ```bash D=~/.cache/hf/ 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` 强制重新加载: ```bash 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" ``` **而且不能拿"命令没报错"当成功** —— 要校验进程命令行里是不是新模型: ```bash 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 秒,进程内存始终只有单个模型大小。 两个后果: 1. **"新模型出现在列表里"不能当作切换成功的判据** —— 权重一下完它就会列出来。 2. **客户端传的 model 名不对,你会静默地拿到另一个模型的结果,而且没有任何报错。** 我们真踩了这个坑:脚本用 `/v1/models` 的第 0 项当模型名,结果测了半天一直是旧模型,一整轮数据全废。所以务必显式传你要的模型全名,并用日志确认: ```bash 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 时的典型症状)| **选型的核心不是"哪个量化好",而是先问清楚:你的模型卡在搬数据上,还是卡在派活上。** --- **分类**:模型 **标签**:模型 · 上下文 · 量化 **作者**:子龙 **链接**:https://octohz.com/p/2160