前幾天在桌機(Ryzen 5600X + RTX 3070 8GB)跑過一輪 Gemma 4 + Claude Code 的實戰,把 本地跑 Claude Code 實戰|Ollama + Gemma 4 + RTX 3070 使用心得 跟 Gemma 4 E2B vs E4B 完整實測|Thinking 模式的祕密 兩篇文章寫出來後,心裡留下一個問題:那一整套觀察能不能在筆電上重現?特別是 MacBook Pro 這種 Apple Silicon 架構——GPU 跟 CPU 共用統一記憶體、沒有獨立 VRAM、用 Metal 而不是 CUDA——很多人直覺會以為「筆電跑本地 LLM 會很慘」。這篇就把同一套實驗搬到 MacBook Pro M2 Pro 16GB 重跑一次,順便跟 RTX 3070 的數字放在一起看,幫筆電族的開發者找出本地 LLM 的實用上限。
為什麼這個對比值得做
兩個平台代表兩種完全不同的本地 LLM 思路:
- RTX 3070:NVIDIA 獨立顯卡 + CUDA 生態,8GB 專用 VRAM,優勢是計算速度與軟體成熟度,但 VRAM 是固定切出來的硬上限。
- M2 Pro 16GB:Apple Silicon SoC,統一記憶體讓 GPU 可以共用整台機器的 RAM,用 Metal backend 加速,優勢是記憶體彈性與功耗,劣勢是原始計算能力通常被認為比獨顯弱。
這兩種架構對「跑本地 LLM 要花多少錢」的決策影響很大。獨立顯卡的路線需要額外買硬體、處理散熱、受限 VRAM;Apple Silicon 的路線是許多開發者本來就有的筆電,不需要額外投資。但效能究竟差多少、哪些場景誰比較適合,很少有同一組測試方法、同一個模型、同一套 prompt 跑出來的直接對比。這篇就是要補上這個空缺。
測試環境
MacBook Pro M2 Pro 16GB
- MacBook Pro 14″ (2023)
- Apple M2 Pro:10 核 CPU(6P + 4E)、16 核 GPU、統一記憶體
- 16GB 統一記憶體(CPU / GPU 共享)
- 1TB SSD
- macOS 26.4
Ollama 用官方 macOS 版本安裝,直接在本機 http://localhost:11434 提供服務,不經過網路。Gemma 4 的 E2B 和 E4B 兩個變體都是 Q4_K_M 量化版本,直接從 Ollama library 拉下來,檔案大小分別是 6.67 GiB 和 8.95 GiB。如果對 Ollama 或 Gemma 4 家族不熟悉,可以先看 Ollama 入門教學 跟 Google Gemma 4 多模態開源模型介紹 。
對照組 RTX 3070
Windows 11 Pro + Ryzen 5 5600X + RTX 3070 8GB VRAM + 32GB DDR4-3200,原始數據在前兩篇文章,這裡只做對比。
測試方法
所有測試都透過 Ollama 的 /api/generate endpoint 直打,用 Python 腳本解析回應裡的 eval_count、eval_duration、prompt_eval_count、prompt_eval_duration 等欄位計算指標。每組測試前會先 warmup 確保模型已經載入,避免把冷啟動時間算進去。
import requests
# 對 Ollama 發送一次 generate 請求
resp = requests.post('http://localhost:11434/api/generate', json={
'model': 'gemma4:e4b',
'prompt': 'Explain what Docker is in 100 words.',
'stream': False,
'options': {'num_ctx': 8192}, # 預分配 8K context
})
data = resp.json()
# 生成速度:eval_count 個 token 花了 eval_duration 奈秒
gen_tps = data['eval_count'] / (data['eval_duration'] / 1e9)
# Prompt 處理速度:prompt 被模型吃完的速度
prompt_tps = data['prompt_eval_count'] / (data['prompt_eval_duration'] / 1e9)
print(f'生成 {gen_tps:.1f} t/s, prompt {prompt_tps:.1f} t/s')
TPS 基準:M2 Pro 意外地比 RTX 3070 快
先跑最基本的生成速度測試:送三種長度的 prompt,看 gemma4:e4b 每秒能吐出幾個 token。
| 測試 | 生成 tokens | M2 Pro 生成 (t/s) | RTX 3070 生成 (t/s) |
|---|---|---|---|
| 短句 | 15 | 40.1 | 34.0 |
| 中等 | 372 | 35.0 | 31.1 |
| 長文 | 2,557 | 32.1 | 29.7 |
這個結果跟一開始的直覺不一樣:M2 Pro 跑 Gemma 4 E4B 的生成速度全面贏過 RTX 3070,大約快 8–18%。雖然差距沒有很大,但至少打破「筆電跑不動本地 LLM」的刻板印象。幾個可能的原因:
- M2 Pro 的統一記憶體頻寬很高(官方 200 GB/s),對 Q4_K_M 量化這種「記憶體頻寬限制」型 workload 反而有利。
- Ollama 在 Apple Silicon 上使用 Metal backend,llama.cpp 在 Metal 的最佳化這兩年做得很積極。
- RTX 3070 的 CUDA 計算能力理論上更強,但 8GB VRAM 不夠塞整個模型加大 KV cache,瓶頸提早出現。
另一個讓人眼睛一亮的地方是冷啟動時間:M2 Pro 的模型載入只要 ~0.2 秒,RTX 3070 則要 4–5 秒。這是因為 Apple Silicon 的統一記憶體架構,模型檔從 SSD 讀進來就能被 GPU 直接使用,不需要額外把資料搬到 VRAM。對「偶爾才跑一次 LLM」的工作流(例如 shell alias、git hook、VS Code 快捷鍵)影響很大,等於每次呼叫都不用先付那幾秒的等待成本。
num_ctx 的陷阱:預分配 ≠ 真的塞了東西
這次測試最重要的一個觀念澄清。前一篇文章裡我們做過「不同 context length 對速度的影響」測試,結論是從 4K 到 64K,生成速度都差不多。很多網路教學也有類似結論,但這件事其實講了一半——
那個測試用的 prompt 永遠只有 5–6 個 token(”Explain what Docker is in 100 words.”),真正影響的只是 Ollama 為 KV cache 預分配多少記憶體空間,跟「模型每次 decode 時要掃多長的 attention」是兩件完全不同的事。只有真的把 prompt 塞長才會看到 context 對速度的真實影響。
所以這次多做了一組實驗:把 num_ctx 固定在 131072(Gemma 4 原生上限),然後送長度從 1K 到 122K tokens 的 filler prompt,看 prompt eval 跟生成速度的變化。
gemma4:e4b 長 prompt 實測
| 實際 prompt tokens | Prompt eval 耗時 | Prompt TPS | 生成 TPS | 總耗時 |
|---|---|---|---|---|
| 767 | 3.1s | 244 | 31.1 | 12.8s |
| 2,780 | 4.7s | 597 | 27.6 | 9.8s |
| 10,893 | 20.5s | 531 | 23.9 | 26.2s |
| 21,751 | 30.8s | 706 | 22.2 | 31.6s |
| 43,467 | 74.4s | 585 | 14.9 | 83.6s |
| 67,867 | 113.5s | 598 | 10.8 | 126.3s |
| 81,409 | 82.1s | 992 | 9.9 | 96.2s |
| 122,096 | 620.5s | 197 | 7.6 | 647.1s |
這組數據有三個值得注意的觀察:
- 生成速度隨 context 變大顯著衰減,從 31 t/s 掉到 7.6 t/s,約 4 倍差距。這才是 context length 對速度的真實影響——每 decode 一個新 token 都要對整個 KV cache 做一次 attention,context 越長單 token 計算量越大。
- Prompt eval 速度在中長度 prompt 時反而最快。短 prompt 只有 244 t/s,塞到 32K 時衝到 706 t/s。原因是長 prompt 可以 batch 起來一次算,batch size 夠大時 GPU 利用率反而更高。這也推翻了「prompt 越長越慢」的直覺印象——在中段區間是越長越快。
- 128K 上限前的懸崖。從 81K 塞到 122K tokens 時,prompt eval 從 82 秒暴增到 10 分 20 秒(慢 7.5 倍),prompt TPS 從 992 掉到 197。M2 Pro 16GB 帳面能撐 128K,但實際逼近上限時會撞到記憶體頻寬或壓縮瓶頸,慢到完全不能用。
gemma4:e2b 在長 context 全面反轉
同樣的測試再跑一次 e2b,結果是這次實驗最大的反轉:
| 實際 prompt tokens | e4b Prompt TPS | e2b Prompt TPS | e4b 生成 TPS | e2b 生成 TPS |
|---|---|---|---|---|
| 767 | 244 | 859 | 31.1 | 52.4 |
| 10,893 | 531 | 977 | 23.9 | 37.7 |
| 43,467 | 585 | 886 | 14.9 | 19.6 |
| 81,409 | 992 | 1,489 | 9.9 | 11.9 |
| 122,096 | 197 | 504 | 7.6 | 8.6 |
E2B 在長 context 場景全面贏過 E4B。Prompt eval 速度快 1.5–2.5 倍,生成速度也快 10–70%,逼近 128K 上限時還能勉強維持 500 t/s 的 prompt 處理速度。128K context 下 E2B 總耗時 4 分 18 秒,E4B 要 10 分 47 秒,E2B 快了 2.5 倍。
這個結論跟前一篇「E2B 被 Ollama 偷偷開 thinking 模式拖慢 10 倍」的結論乍看矛盾,但兩者其實不衝突:
- 短輸入明確任務:E2B 被 thinking 拖慢,應該用 E4B(或 E2B +
think=False)。 - 長 context 場景:E2B 的有效參數較少,每個 decode step 的 attention 運算便宜,反而更適合。但還是要記得加
think=False避免被預設的 thinking 干擾。
實用上限整理
把數據翻成「幾 K tokens 前開發者會覺得可用」的實用建議:
| 模型 | 舒適區(<30s) | 可接受(<2min) | 硬上限 |
|---|---|---|---|
| gemma4:e4b | ~30K tokens | ~64K tokens | ~100K tokens |
| gemma4:e2b | ~50K tokens | ~100K tokens | ~122K tokens |
帳面上能開到 131072 的 num_ctx,但那只是預分配的空間上限,不代表真的塞進去還能跑得快。前面幾篇文章(包含作者自己寫的)都把這兩個概念混在一起,這次補上這個修正。
Thinking 模式的 Ollama 行為跨平台重現
前一篇文章裡追到一個有趣的結論:Ollama 的 gemma4 renderer 會給 E2B 偷偷注入 <|think|> token,但 E4B 不會。這個行為跟官方文件「thinking 預設關閉」的說法矛盾,看起來是 Ollama 對 E2B 的特殊處理。當時的疑問是:這到底是 Windows / CUDA 特有的 bug,還是 Ollama 所有平台都這樣?
這次在 macOS + Metal 上把整組實驗重跑了一次——用同一個短句翻譯 prompt 對 E2B 測試 5 種組合:預設、think=False、think=True、raw=true 手動塞 no-think template、raw=true 手動塞 with-think template:
| 設定 | 耗時 | tokens | thinking 欄位 |
|---|---|---|---|
| E2B 預設 | 10.50s | 573 | 0(被過濾) |
E2B think=False | 0.58s | 18 | 0 |
E2B think=True | 9.98s | 536 | 1,623 字元 |
| E2B raw no-think template | 0.60s | 19 | 0 |
| E2B raw with-think template | 10.96s | 581 | 0 |
結論跟 RTX 3070 版本完全一致:
- E2B 預設耗 10.5 秒 / 573 tokens,加
think=False後變 0.58 秒 / 18 tokens,快 18 倍。 - 用
raw=true手動構造無 think token 的 chat template,結果跟think=False幾乎一樣(19 tokens)——證明是 Ollama 在 prompt 前面偷偷加了<|think|>。 - 用
raw=true手動塞 with-think template,E2B 直接把<|channel>thought Thinking Process:原始 thinking 內容漏在response欄位裡——因為 raw 模式跳過了 Ollama 的 gemma4 parser,平常會被過濾的段落全部現形。這是一個意外收穫的直接證據。 - E4B 不論用什麼設定都只產 19–23 個 token、0.85–1 秒,確認 E4B 訓練時根本不響應
<|think|>token。
重點在於:這個 Ollama 對 E2B 的特殊處理不是 Windows 或 CUDA 特有,macOS + Metal 完全重現。這強化了前一篇的結論——問題出在 Ollama 的 gemma4 renderer 本身,跟硬體或作業系統無關。完整的追查過程跟原理解說在 Gemma 4 E2B vs E4B 完整實測 ,有興趣的開發者可以參考。
跨平台對比總表
把所有關鍵指標放在一起對照:
| 指標 | M2 Pro 16GB | RTX 3070 8GB | 勝者 |
|---|---|---|---|
| E4B 生成速度(短句) | 40.1 t/s | 34.0 t/s | M2 Pro |
| E4B 生成速度(長文) | 32.1 t/s | 29.7 t/s | M2 Pro |
| E4B Prompt eval(熱快取短 prompt) | 279 t/s | 662 t/s | RTX 3070 |
| E4B Prompt eval(32K 長 prompt) | 706 t/s | n/a* | — |
| E4B 冷啟動 load | ~0.2s | ~4.4s | M2 Pro |
| E4B 短句 TTFT | 264ms | ~390ms | M2 Pro |
| num_ctx=64K 可行性 | 還有餘裕 | 接近 VRAM 上限 | M2 Pro |
| 長 prompt 128K 可行性 | 能跑但 10 分鐘 | VRAM 不夠 | M2 Pro |
| E2B thinking 被 Ollama 偷開 | 重現 | 重現 | 跨平台一致 |
* RTX 3070 沒跑長 prompt 大量塞滿的實驗,因為 8GB VRAM 根本放不下 32K+ prompt 的 KV cache。
該選哪個?
綜合所有數據,給幾個實用的決策建議:
MacBook Pro M2 Pro 16GB 適合
- 筆電族開發者想在出門、咖啡廳、高鐵上跑離線 LLM 輔助編碼。
- 需要比較長的 context(32K–100K)的 RAG、長文件摘要、Code review bot。RTX 3070 的 8GB VRAM 在這類工作根本塞不下。
- 需要短延遲多次呼叫的自動化工作流(git hook、shell script、桌面快捷鍵)——冷啟動只要 0.2 秒,每次呼叫都不用等。
- 在意電力與噪音:M2 Pro 跑 Gemma 4 E4B 時風扇幾乎沒轉起來,也可以電池供電。
RTX 3070 桌機適合
- 需要極高 prompt 處理吞吐量的場景——RTX 3070 熱快取下 prompt eval 能跑到 662 t/s,比 M2 Pro 快得多。批次處理大量短 prompt(分類、抽取)有優勢。
- 已經有遊戲主機想順便用,不想額外花錢買筆電升級。
- 想玩更大的模型時有升級空間(換 RTX 4090 24GB 就能跑 70B)。
模型選擇
M2 Pro 16GB 上兩個 Gemma 4 變體的使用情境其實有明顯分化:
- 短輸入 / 明確任務(commit 訊息、關鍵字抽取、情感分類):用
gemma4:e4b,或是gemma4:e2b+think=False。預設的 E2B 會被 thinking 拖慢 10 倍以上。 - 長 context 應用(RAG、長文件摘要、程式碼 review):用
gemma4:e2b+think=False,prompt eval 跟生成速度都明顯勝過 E4B。實用上限 ~100K tokens。 - 需要推理的複雜任務:用
gemma4:e2b+think=True,讓 E2B 啟動 thinking 模式,雖然慢但品質更好。
最佳實踐:Python 呼叫範例
把這次的結論整理成一個可以直接複製的 Python 封裝:
import requests
OLLAMA = 'http://localhost:11434/api/generate'
def quick_task(prompt: str) -> str:
"""短輸入明確任務:速度最大化。"""
r = requests.post(OLLAMA, json={
'model': 'gemma4:e4b',
'prompt': prompt,
'stream': False,
'options': {'temperature': 0.2, 'num_ctx': 8192},
})
return r.json()['response'].strip()
def long_context_task(prompt: str, num_ctx: int = 65536) -> str:
"""長 context 場景(RAG、長文件):用 e2b + think=False。"""
r = requests.post(OLLAMA, json={
'model': 'gemma4:e2b',
'prompt': prompt,
'stream': False,
'think': False, # 關鍵:避免 Ollama 偷開 thinking
'options': {
'temperature': 0.2,
'num_ctx': num_ctx, # 依實際 prompt 長度選
},
})
return r.json()['response'].strip()
def reasoning_task(prompt: str) -> str:
"""複雜推理任務:讓 e2b 啟動 thinking。"""
r = requests.post(OLLAMA, json={
'model': 'gemma4:e2b',
'prompt': prompt,
'stream': False,
'think': True,
'options': {'temperature': 1.0, 'num_ctx': 16384},
})
data = r.json()
# thinking 內容在 data['thinking'],最終答案在 data['response']
return data['response'].strip()
# 範例:用 quick_task 寫 commit 訊息
msg = quick_task('Write a conventional commit message for: added retry logic')
print(msg)
總結
這趟 M2 Pro 16GB 重跑 RTX 3070 實驗的重點整理:
- Apple Silicon 跑 Gemma 4 的生成速度比 RTX 3070 略勝,約快 8–18%,冷啟動快 20 倍以上。
- RTX 3070 的強項是熱快取短 prompt 處理(662 vs 279 t/s),長 prompt batch 情境 M2 Pro 反而更有效率。
num_ctx跟實際 prompt 長度是兩件事:前者只是預分配空間,後者才會影響 decode 速度。之前的教學都講了一半。- 長 prompt 會讓生成速度顯著衰減:E4B 從 31 t/s 掉到 7.6 t/s(128K),E2B 從 52 t/s 掉到 8.6 t/s。
- 長 context 場景 E2B 全面勝出 E4B,完全推翻了「短任務用 E4B」的直覺,前提是記得加
think=False。 - M2 Pro 16GB 的 E4B 實用上限約 64K tokens、E2B 約 100K,128K 帳面能開但跑一次要 4–10 分鐘。
- Ollama 對 E2B 偷偷注入
<|think|>的 bug 在 macOS + Metal 完全重現,確認是 renderer 層級的行為,跟硬體無關。
從結論看,「筆電族跑本地 LLM 夠不夠用」的答案很明確:MacBook Pro M2 Pro 16GB 不但夠用,在很多情境下比遊戲顯卡還舒服。原本以為 Apple Silicon 會是「勉強能跑」的級距,結果實測發現是「正常可用」甚至「長 context 優於獨顯」。對已經有一台 Mac 筆電的開發者來說,不用再買額外硬體就能把本地 LLM 接進日常工作流,這是一個比想像中更低的門檻。
如果還沒看過前兩篇,建議依序閱讀 本地跑 Claude Code 實戰|Ollama + Gemma 4 + RTX 3070 使用心得 跟 Gemma 4 E2B vs E4B 完整實測|Thinking 模式的祕密 ,三篇合起來是完整的「Gemma 4 本地部署從桌機到筆電」系列。下一步想探索的方向包含 gemma3:12b 在 16GB 統一記憶體下的極限、Ollama prompt cache 在長對話的實際效果、以及電池供電下的降頻影響,之後會陸續整理出來。