~/blog/dgx-spark-gemma4-31b-rescue-mbp-32gb

DGX Spark · part 13

[Benchmark] 拯救 Gemma 4 31B:在 32GB MacBook Pro 上從 1.5 加速到 12.8 tok/s

2026-04-085 分鐘閱讀#gemma-4#31b#m1-max#ollamaEnglish

TL;DR

Gemma 4 31B 在 32GB MacBook Pro 上:Ollama 預設 1.5 tok/s(swap),降 context 到 2048 得 9 tok/s,oMLX 得 12.8 tok/s — 恢復 8.5 倍。真正的兇手不是模型大小,是 KV cache 分配。

白話版:為什麼一個大 AI 模型可以毀掉你的筆電

在筆電上跑 AI 模型時,模型需要兩樣東西放在記憶體裡:模型本身(它的「大腦」)和一個叫 KV cache 的暫存空間(它的「對話記憶」)。對話視窗越大,暫存空間就越大。

Gemma 4 31B — Google 最大的 dense 模型 — 佔 19 GB。MacBook Pro 有 32 GB RAM,應該還剩 13 GB。聽起來夠用。但 Ollama(最受歡迎的本機 AI 工具)預設對話視窗是 32,768 tokens。這個大小的暫存空間可以吃掉 10+ GB。加起來:29+ GB。再加上 macOS 本身的開銷,系統就開始把記憶體寫到 SSD — 這叫 swap,比 RAM 慢大約 100 倍。

結果:一台應該每秒生成 10+ 個 token 的筆電只跑 1.5,風扇全速轉,機身燙到不能摸。

我找到兩個解法。一個免費、10 秒搞定。另一個需要裝新工具但完全沒有妥協。


前言

最貴的 bug 是那種看起來像硬體限制的。整整兩天,所有人都以為 31B 就是裝不進 32GB 的 Mac。其實裝得進 — 只是預設設定不讓它進。

這是 Part 12:4 台機器 × 4 個模型 的延續。那篇 benchmark 顯示 MBP 跑 31B 只有 1.5 tok/s,而頻寬更低的 DGX Spark 反而有 9 tok/s。問題是:MBP 能救回來嗎?


犯罪現場:1.5 tok/s

起點。Ollama,預設設定,Gemma 4 31B:

ollama run gemma4:31b "Explain options trading" --verbose
eval rate: 1.53 tokens/s

ollama ps 揭露了兇器:

NAME          PROCESSOR          CONTEXT
gemma4:31b    14%/86% CPU/GPU    32768

14% CPU / 86% GPU。模型被 split 了 — 一部分在 CPU 上跑,走的是已經 swap 到 SSD 的記憶體。筆電表面溫度高到不舒服。風扇全速。

模型本身 19 GB(Q4_K_M GGUF)。MBP 有 32 GB。差 13 GB。那為什麼會 swap?


根本原因:KV Cache 吃掉了所有餘裕

預設 context window 是 32,768 tokens。對一個 31B 模型來說(48 layers、GQA heads、BF16 KV values),這個 context 大約要分配 10-12 GB 的 KV cache,加在 19 GB 的模型上面。

模型權重:        19 GB (Q4_K_M)
KV cache (32K ctx): ~10 GB (BF16)
macOS + apps:     ~5 GB
總計:            ~34 GB > 32 GB → swap

模型裝得下。模型加上預設的 KV cache 裝不下。


解法 1:降 Context Window(免費,10 秒)

最簡單的修法:叫 Ollama 分配更少的 KV cache。

curl http://localhost:11434/api/chat -d '{
  "model": "gemma4:31b",
  "messages": [{"role": "user", "content": "Explain options trading"}],
  "options": {"num_ctx": 2048},
  "stream": false
}'

或者在 Modelfile 裡設:

FROM gemma4:31b
PARAMETER num_ctx 2048

結果:

NAME          PROCESSOR    CONTEXT
gemma4:31b    100% GPU     2048

100% GPU。 沒有 CPU split,沒有 swap。數字:

指標預設 (ctx=32768)num_ctx=2048
tok/s1.59.0
PROCESSOR14%/86% CPU/GPU100% GPU
記憶體~30 GB (swap)~23 GB
筆電溫度微溫

一個參數快 6 倍。代價:對話限制在 2048 tokens(~1500 字)。問快速問題夠用。長對話或文件分析就是硬天花板。


解法 2:oMLX — 沒有妥協

oMLX 是為 Apple Silicon 打造的 MLX 推論伺服器。核心功能:兩層 KV cache — hot(RAM)和 cold(SSD)。RAM 滿了,KV cache blocks 寫入 SSD(safetensors 格式),不觸發 macOS swap。

差別在於:macOS swap 是不受控的 — OS 自己決定把什麼換出去,包括模型權重和系統 process。oMLX 的 SSD tier 是受控的 — 只有 KV cache 會被 offload,而且只在需要時。

安裝

oMLX 不在 PyPI 上。從 source 裝:

git clone https://github.com/jundot/omlx.git /tmp/omlx
cd /tmp/omlx
pip3 install --break-system-packages -e .

模型設定

oMLX 用 MLX 格式模型,不是 GGUF。下載到預設 model 目錄:

python3 -c "
from huggingface_hub import snapshot_download
snapshot_download('mlx-community/gemma-4-31b-it-4bit',
                  local_dir='$HOME/.omlx/models/gemma-4-31b-it-4bit')
"

MLX 4-bit 模型 17 GB — 比 Ollama 的 Q4_K_M(19 GB)小 2 GB。在邊界情況下這 2 GB 很重要。

執行

omlx serve --port 8800 --max-process-memory auto

透過 OpenAI 相容 API 查詢:

curl http://localhost:8800/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"gemma-4-31b-it-4bit",
       "messages":[{"role":"user","content":"Explain options trading"}],
       "max_tokens":500}'

結果

輪次tok/s
112.8
212.8
312.7

12.8 tok/s。 比 Ollama 預設快 8.5 倍。沒有 context window 限制。

測完後我檢查了 SSD cache 目錄:

du -sh ~/.omlx/cache/
0B    /Users/coolthor/.omlx/cache/

零 bytes。500 tokens 的 KV cache 小到完全放得進 RAM。SSD tier 不需要介入 — oMLX 只是比 Ollama 更好地管理了記憶體,讓總量控制在 32 GB 以內。


TurboQuant:開了但看不見

oMLX 支援 TurboQuant — Google 的 KV cache 壓縮技術,把 BF16 cache 壓到 2-4 bits。這功能在目前版本被作者隱藏了(UI 上關掉),但程式碼可用。

啟用方法,建立 ~/.omlx/model_settings.json

{
  "version": 1,
  "models": {
    "gemma-4-31b-it-4bit": {
      "turboquant_kv_enabled": true,
      "turboquant_kv_bits": 4
    }
  }
}

啟動 log 確認生效:

TurboQuant attention patch applied
TurboQuant KV cache enabled for VLM: 4.0 bits

開啟 TurboQuant 的 benchmark:

設定tok/s
oMLX(無 TQ)12.8
oMLX + TQ 4-bit12.4
oMLX + TQ 2-bit12.5

沒差。500 tokens 的 KV cache 在 BF16 下才幾百 MB。壓縮幾百 MB 省下的記憶體本來就不在壓力區。TurboQuant 的價值在長 context — 8K+ tokens 時 KV cache 長到幾 GB,那時壓縮才有感。短對話下,它是隱形的。


26B 對照:裝得下的時候 Ollama 更快

為了確認 oMLX 是不是全面更快,測了 32 GB 裝得下的 26B MoE:

Runtime26B MoE tok/s
Ollama (Q4_K_M, 17 GB)47
oMLX (MLX 4-bit, 15 GB)41

Ollama 更快。llama.cpp backend(GGUF Q4_K_M)在 Apple Silicon 上跑 MoE 模型似乎比 MLX 4-bit 更優化。

oMLX 的優勢嚴格限於記憶體邊界 — 模型差一點就塞不下的情況。其他情況下 Ollama 更快更簡單。


為什麼 2 GB 很重要

真正的故事在模型大小:

Runtime格式31B 大小塞得進 32 GB?
OllamaGGUF Q4_K_M19 GB勉強(需要小 ctx)
oMLXMLX 4-bit17 GB是(KV cache 還有空間)

oMLX 的模型小 2 GB。這 2 GB 就是「放得下 KV cache」和「放不下、swap、1.5 tok/s」的分界。這不是 oMLX 的優化 — 是量化格式的差異。MLX 4-bit 在這個模型上比 GGUF Q4_K_M 更緊湊。


這次學到什麼

最花時間的事

假設問題出在硬體。「MBP 跑不了 31B」這個說法在兩天的 benchmark 裡一直沒被質疑,直到有人看了 ollama ps 的 PROCESSOR 欄。14%/86% CPU/GPU split 就在那裡 — 一行診斷可以省好幾個小時。

可遷移的診斷經驗

  • 永遠檢查 ollama ps 的 PROCESSOR 欄。 100% GPU = 健康。有 CPU% = 記憶體壓力。這是 Ollama 最重要的單一診斷指標。
  • num_ctx 是隱藏的記憶體乘數。 預設 32768 可以分配 10+ GB 的 KV cache。模型快滿記憶體時,降 context 是最便宜的解法。
  • oMLX 的 SSD cache 不一定會用到 SSD。 它是安全網 — KV cache 小的話全部在 RAM 跑。它的價值是防止災難性失敗(macOS swap),不需要手動調參。
  • 模型格式大小有差異。 GGUF Q4_K_M 和 MLX 4-bit 都叫「4-bit」但大小差 10-15%。在記憶體邊界上,這很重要。

放之四海皆準的模式

預設設定是為常見情境優化的,不是為你的情境。當效能災難性地差時,先查預設值在分配什麼,再怪硬體。


決策樹:32GB Mac 上跑 31B

想在 32GB Mac 上跑 Gemma 4 31B?
│
├─ 快速解(10 秒):
│   Ollama + num_ctx=2048 → 9 tok/s
│   ⚠️ Context 限制在 ~1500 字
│
├─ 更好的解(5 分鐘設定):
│   oMLX + MLX 4-bit 模型 → 12.8 tok/s
│   ✅ 不限 context,SSD cache 安全網
│
└─ 如果 31B 不是必要的:
    Gemma 4 26B MoE + Ollama → 47 tok/s
    ✅ 輕鬆塞得下,更快,品質不差

同系列文章:Part 12:4 台機器 × 4 個模型 · Part 11:E2B vs E4B

常見問題

為什麼 Gemma 4 31B 在 MacBook Pro 上這麼慢?
Ollama 預設的 context window(32768 tokens)會分配大量 KV cache。19 GB 模型加上 KV cache 超過 32 GB RAM,macOS 開始 swap 到 SSD。速度從可能的 9-13 tok/s 掉到 1.5 tok/s。
怎麼在 32GB Mac 上加速 Gemma 4 31B?
兩個選擇:(1) 在 Ollama 設 num_ctx=2048 減少 KV cache — 拿到 9 tok/s 且 100% GPU。(2) 改用 oMLX,它會自動管理 KV cache 的 SSD 層 — 拿到 12.8 tok/s 且不限 context。
oMLX 是什麼?怎麼幫助?
oMLX 是為 Apple Silicon 打造的 MLX 推論伺服器,有 SSD-backed KV cache 兩層架構。模型權重放 RAM,KV cache 溢出時寫入 SSD 而不是觸發 macOS swap。從 source 安裝:pip install -e . 從 GitHub repo。
TurboQuant 能加速 Gemma 4 31B 嗎?
短 context 下不行。TurboQuant 壓縮 KV cache(BF16→4-bit),但 500 tokens 的 KV cache 只有幾百 MB — 太小沒差。TurboQuant 在長 context(8K+ tokens)才有效,那時 KV cache 會長到幾 GB。