一個週末,一台 M4 Pro、48GB 記憶體,五個 4-bit 量化的本地模型,八項 benchmark。我想搞清楚:把重複性任務搬離雲端、省 token,離線可用到底要犧牲多少品質?這篇是完整的實測、選型,加上一路踩到的三個 speculative decoding 地雷。
起點:為什麼要把模型搬回本機
用雲端旗艦寫文章、做複雜推理,沒什麼好猶豫的。但真正吃掉帳單的,往往是那些重複、機械、又碰不得隱私的苦力活:結構化 JSON 萃取、命名實體辨識(NER)、分類、敏感資料去識別、文獻初篩。這些任務不需要旗艦的腦力,量大卻持續燒錢,而且個資類資料本來就不該離開本機。
所以方向很明確:一隻高階 + 兩隻小模型併發的離線本地棧,重複性任務全搬回來,只有真正需要腦力的定稿與推理才回雲端。
硬體是一台 M4 Pro、48GB 統一記憶體;推理引擎用 oMLX 掛在本機 :8090。受測的五顆模型(皆為 MLX 量化):
- Qwen3.6-27B(4-bit,~15GB):品質天花板候選
- Qwen3.6-35B-A3B(4-bit MoE,~20GB,每次約 3B active):速度候選
- Qwen3.5-9B(4-bit,5.82GB):萃取主力候選
- gemma-4-12b-coder(8-bit):coding 特化
- gemma-4-E4B(4-bit,~5GB):輕量工兵
帳面成績單:八項 benchmark 交叉對照
每項 benchmark 抽測約 30 題,未開推理(thinking),僅供相對排序。免責聲明放前面:30 題的樣本,分數顆粒大概是 1/30 ≈ 3.3%,±10% 內的差距都是雜訊,這張表拿來「分級」可以,拿來「排名」不行。
| Benchmark | gemma-12b-coder | gemma-E4B | Qwen3.5-9B | Qwen3.6-27B | Qwen3.6-35B-A3B |
|---|---|---|---|---|---|
| arc_challenge | 0.867 | 0.800 | 0.867 | 0.900 | 0.867 |
| cmmlu(中文) | 0.433 | 0.533 | 0.633 | 0.800 | 0.733 |
| hellaswag | 0.800 | 0.667 | 0.867 | 0.833 | 0.833 |
| humaneval | 0.900 | 0.733 | 0.833 | 0.933 | 0.900 |
| livecodebench | 0.167 | 0.333 | 0.133 | 0.467 | 0.500 |
| mathqa | 0.200 | 0.167 | 0.433 | 0.567 | 0.633 |
| mbpp | 0.633 | 0.700 | 0.567 | 0.867 | 0.767 |
| mmlu | 0.667 | 0.567 | 0.733 | 0.700 | 0.633 |
| 綜合平均 | 0.583 | 0.563 | 0.633 | 0.758 | 0.733 |
| 八項總耗時 | 1257s | 1725s | 2277s | 3266s | 824s |
看一眼就能抓到的東西:
- Qwen3.6-27B 是品質天花板:綜合 0.758 最高,中文(0.80)與 coding(mbpp 0.867 / humaneval 0.933)都居冠,但也最慢,光 livecodebench 單項就跑了 2138 秒。
- Qwen3.6-35B-A3B 是速度和品質的甜蜜點:MoE 架構,每次只啟用約 3B 參數,綜合 0.733 逼近 27B,卻是全場最快(824s,比 27B 快將近四倍)。難 coding(livecodebench 0.50)和數學(0.633)還反超 27B。
- 小模型各有位置:9B 格式最穩、中文與 mmlu 佳;gemma-E4B 屬輕量 fallback;gemma-12b-coder 的 humaneval 很強,但一碰難題(livecodebench 0.167)就掉。
那,等級約當雲端哪一代?
先講兩個前提:(一)benchmark 帳面分不等於實戰體感;(二)靜態選擇題最容易被中小模型追平,真實的長上下文與 agentic 差距通常比帳面大得多。 有這兩層打折,我的校準大概是這樣:
| 本地模型 | 帳面 benchmark 約當 | 實戰體感約當(含推理/agentic) |
|---|---|---|
| Qwen3.6-35B-A3B(主力) | GPT-4o/Claude 3.5 Sonnet(2024 版) | GPT-4o-mini ~ Claude 3.5 Haiku 之間,程式碼題可摸到 GPT-4o 下緣 |
| Qwen3.6-27B(備援) | 略勝 35B,帳面 ≈ GPT-4o | 同上,稍強但慢 |
| Qwen3.5-9B(萃取) | GPT-4o-mini ~ 早期 GPT-4-turbo 的知識面 | GPT-3.5-turbo 加強版/Llama-3-8B 之上,強在中文知識與穩定 JSON |
| gemma-4-E4B(工兵) | GPT-3.5-turbo/Claude 3 Haiku 級 | 同級,價值在吞吐不在聰明 |
說白了,本地棧的天花板帳面約當 2024 年的 GPT-4o;但任務一變成真正的長文與 agentic,體感更像 GPT-4o-mini,離現役旗艦還差一到兩個世代。 但這不是壞事,它剛好卡在「隱私鐵律下的高速苦力」這個位置,夠用。
為什麼日常主力是 35B-A3B,而不是分數最高的 27B
互動式討論回應速度就是一切。35B-A3B 綜合分只差 27B 0.025,卻快將近四倍;27B 唯一能救速度的招式是 MTP(Multi-Token Prediction),在這版 oMLX 已經確認不能用(下一節會講)。所以定案:35B-A3B 當日常主力,27B 降級為「不計時間、要中文或 coding 極致品質時」的 on-demand 備援。
兩顆小模型的實測併發(合計約 11GB)是真並行:循序 11.6 秒,併發 4.02 秒,加速比 2.89 倍。Qwen3.5-9B 設 is_pinned 開機即熱載,重複任務零冷啟;gemma-E4B 當第二併發槽保溫。重複性萃取的延遲被壓到幾乎感覺不到。
三個 speculative decoding 地雷(省你一個週末)
想再壓速度,自然會想到 speculative decoding:小模型先猜、大模型驗證。我把三條路都接了,結果三條全踩雷。記在這裡,省你自己摸索。這版是 oMLX 0.4.4。
地雷一:原生 MTP 是 no-op
Qwen3.6-27B-MLX-4bit 的 config 宣告了 mtp heads,但 4-bit 檔根本沒附 mtp.* 權重,MTPModule 被靜默跳過,加速為零(實測 +0.8%,純雜訊)。
地雷二:VLM-MTP 直接崩潰
改走 draft 路徑後,27B 被判為 vlm 引擎,撞上 mlx_vlm 的 bug:
AttributeError: 'tuple' object has no attribute 'hidden_states'
at mlx_vlm/speculative/mtp.py:170
純文字 LLM 被塞進 VLM 版 MTP 路徑,直接 500。
地雷三:dflash 載入器缺參
下載了專用 draft 模型後接 dflash,log 一句話就說穿了:
DFlashDraftModelArgs.__init__() missing 12 required positional arguments
→ Falling back to vlm engine.
oMLX 0.4.4 的 DFlashDraftModelArgs 載入器沒從 draft config 填必要欄位。E4B 接上去直接 500 崩潰;35B-A3B 則靜默 fallback,回傳 HTTP 200 但沒有任何加速。這種失敗最陰險:它看起來成功了。
教訓一:log 是唯一真相,別信 HTTP 200。 35B 那個「200」其實是 dflash 靜默退回,不看 log 會誤以為加速生效。
specprefill:同一個旋鈕,2.4 倍加速或 6 倍拖累
三條路裡唯一能用的是 specprefill。但它不是萬用加速,是「批次長文件專用旋鈕」。對 35B-A3B 做 ON/OFF A/B(長 prompt):
| 情境 | OFF | ON | 判讀 |
|---|---|---|---|
| cache-miss(每次不同的新長 prompt,16450 tok) | 27.57s | 11.68s | ✅ 快 2.36 倍 |
| cache-hit(同一長 prompt 反覆,8310 tok) | 0.81s | 4.78s | ❌ 反慢 約 6 倍 |
機制:specprefill 會繞過 oMLX 的 prefix/session KV 快取,每次全量重算 prefill。所以:
- 新長輸入、無共享前綴(一次性長全文分析、批次消化)→ 開,大勝。
- 互動式 agent(固定 system + tools prefix、session 累積上下文,多為 cache-hit)→ 關,開了會把每輪從 0.8 秒拖成 4.8 秒。
這直接推翻了我前一步「主模型接 specprefill」的決定。互動主模型(Hermes)定案改回 specprefill OFF,這顆旋鈕重新定位成「跑批次長文件時才臨時開」。
教訓二:加速旋鈕要看工作負載形狀,不是一鍵全開。 同一個 specprefill,在 cache-miss 是 2.4 倍加速,在 cache-hit 是 6 倍拖累。
最終棧與路由
| 模型 | specprefill | 角色 |
|---|---|---|
| Qwen3.6-35B-A3B | OFF | 互動主模型,吃 prefix 快取 |
| Qwen3.5-9B | on(短輸入 no-op) | 萃取主力,pinned 熱載 |
| gemma-4-E4B | off | 第二併發槽,高吞吐預處理 |
| Qwen3.6-27B | on(互動宜關) | on-demand 高品質備援 |
任務路由收斂成:敏感資料去識別/結構化萃取/文獻初篩 → 9B(併發溢流給 E4B);離線討論與 coding → 35B-A3B;要中文或 coding 極致品質且不計時間 → 27B。 需要真正腦力的定稿與複雜推理,仍然回雲端旗艦。
還順手清掉了沒有任何路由引用的孤兒模型(歷史 benchmark 殘留、壞掉的 draft),hub 從 60GB 瘦到 33GB,實回收 27GB。
帶得走的東西
- log 是唯一真相,別信 HTTP 200。 靜默 fallback 會偽裝成成功。
- 加速旋鈕看負載形狀,不是一鍵全開。 cache-miss 與 cache-hit 可以讓同一個旋鈕從救星變累贅。
- 敢推翻自己。 接了 specprefill、量完數據發現對互動主模型是淨負向,就該收回。證據大於面子。
常見問題
一台 M4 Mac 上的本地模型,約當雲端哪一代?
帳面最強的 35B-A3B 和 27B,靜態 benchmark 分數落在 2024 年 GPT-4o、Claude 3.5 Sonnet 附近。但實際跑長上下文和 agentic 工具鏈時,體感比較接近 GPT-4o-mini 或 Claude 3.5 Haiku,跟現役旗艦還差一到兩個世代。9B 大概在 GPT-4o-mini 等級,E4B 約當 GPT-3.5-turbo。
分數最高的是 27B,為什麼日常主力選 35B-A3B?
35B-A3B 是 MoE,每次只啟用約 3B 參數,綜合分 0.733 跟 27B 的 0.758 差距很小,但跑完八項 benchmark 只花 824 秒,27B 要 3266 秒,快將近四倍。難 coding 跟數學甚至反超 27B。互動討論回應速度就是一切,而 27B 唯一能救速度的 MTP 在這版 oMLX 是壞的,所以 27B 只留給不趕時間的場合。
specprefill 這種加速旋鈕該不該全開?
不該。specprefill 繞過 prefix 和 session KV 快取,每次從頭算 prefill。新的長輸入(批次長文件)可以快 2.36 倍,但固定 system prefix、反覆命中快取的互動式 agent 反而慢約六倍。開不開要看工作負載長什麼樣子。
這些 benchmark 分數可以拿來精確排名嗎?
不太行。每項只抽了約 30 題,±10% 的雜訊很大,拿來分級可以,拿來排名不行。靜態選擇題也是小模型最容易追分的地方,真實世界的推理和 agentic 差距通常比帳面數字大得多。