前幾天在桌機(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_counteval_durationprompt_eval_countprompt_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。

測試生成 tokensM2 Pro 生成 (t/s)RTX 3070 生成 (t/s)
短句1540.134.0
中等37235.031.1
長文2,55732.129.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 tokensPrompt eval 耗時Prompt TPS生成 TPS總耗時
7673.1s24431.112.8s
2,7804.7s59727.69.8s
10,89320.5s53123.926.2s
21,75130.8s70622.231.6s
43,46774.4s58514.983.6s
67,867113.5s59810.8126.3s
81,40982.1s9929.996.2s
122,096620.5s1977.6647.1s

這組數據有三個值得注意的觀察:

  1. 生成速度隨 context 變大顯著衰減,從 31 t/s 掉到 7.6 t/s,約 4 倍差距。這才是 context length 對速度的真實影響——每 decode 一個新 token 都要對整個 KV cache 做一次 attention,context 越長單 token 計算量越大。
  2. Prompt eval 速度在中長度 prompt 時反而最快。短 prompt 只有 244 t/s,塞到 32K 時衝到 706 t/s。原因是長 prompt 可以 batch 起來一次算,batch size 夠大時 GPU 利用率反而更高。這也推翻了「prompt 越長越慢」的直覺印象——在中段區間是越長越快。
  3. 128K 上限前的懸崖。從 81K 塞到 122K tokens 時,prompt eval 從 82 秒暴增到 10 分 20 秒(慢 7.5 倍),prompt TPS 從 992 掉到 197。M2 Pro 16GB 帳面能撐 128K,但實際逼近上限時會撞到記憶體頻寬或壓縮瓶頸,慢到完全不能用。

gemma4:e2b 在長 context 全面反轉

同樣的測試再跑一次 e2b,結果是這次實驗最大的反轉:

實際 prompt tokense4b Prompt TPSe2b Prompt TPSe4b 生成 TPSe2b 生成 TPS
76724485931.152.4
10,89353197723.937.7
43,46758588614.919.6
81,4099921,4899.911.9
122,0961975047.68.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=Falsethink=Trueraw=true 手動塞 no-think template、raw=true 手動塞 with-think template:

設定耗時tokensthinking 欄位
E2B 預設10.50s5730(被過濾)
E2B think=False0.58s180
E2B think=True9.98s5361,623 字元
E2B raw no-think template0.60s190
E2B raw with-think template10.96s5810

結論跟 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 16GBRTX 3070 8GB勝者
E4B 生成速度(短句)40.1 t/s34.0 t/sM2 Pro
E4B 生成速度(長文)32.1 t/s29.7 t/sM2 Pro
E4B Prompt eval(熱快取短 prompt)279 t/s662 t/sRTX 3070
E4B Prompt eval(32K 長 prompt)706 t/sn/a*
E4B 冷啟動 load~0.2s~4.4sM2 Pro
E4B 短句 TTFT264ms~390msM2 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 在長對話的實際效果、以及電池供電下的降頻影響,之後會陸續整理出來。

發佈留言