重點摘要
- Mini PC(Ryzen 7 4700U):gemma4:e4b 最快(1.45 tok/s),qwen3:14b 最完整(7/7 題全答);Bonsai 8B 因 AVX-512 需求完全無法使用
- 換一台機器差多少?:同款 gemma4:e4b,MacBook Air M3 跑出 9.75 tok/s,是 Mini PC 的 6.7 倍;Q2 輸出從截斷 300 tokens 變成完整 2218 tokens
- 開 Thinking 差多少?:速度幾乎不變(-6%),但 Q2 程式碼輸出 +65%,Q7 技術解釋 +124%,品質接近 GPT-3.5
你想在本地跑 AI 模型,但不知道哪款模型值得裝?硬體夠不夠?開 Thinking 模式到底有沒有差?這篇文章用實際跑出來的數據回答這三個問題——五款模型 × 兩台機器 × 三種設定,全部實測,沒有廣告。
測試環境分別是一台平價 Mini PC(Ryzen 7 4700U,16GB RAM,CPU-only)和 MacBook Air M3(24GB 統一記憶體)。七道測試題涵蓋:文字理解、Python 程式碼生成、SQL 查詢、TCP 技術解釋等真實使用情境。
測試環境規格
| 項目 | Mini PC | MacBook Air M3 |
|---|---|---|
| 處理器 | AMD Ryzen 7 4700U | Apple M3 |
| 記憶體 | 16GB DDR4(CPU-only) | 24GB 統一記憶體 |
| 顯示卡 | AMD Vega 7 iGPU,僅 128MB VRAM(不可用) | Apple GPU(共享統一記憶體) |
| 推論框架 | Ollama + llama.cpp(CPU 模式) | Ollama + llama.cpp(Metal) |
| 特殊限制 | 無 AVX-512,部分模型無法執行 | 無限制 |
重點摘要表(先看這三張表)
表 1:Mini PC 五款模型速度與完成度對比
| 模型 | 平均速度 | 完成度 | 主要問題 |
|---|---|---|---|
| Bonsai 8B | 0.001 tok/s | 完全不可用 | 需要 AVX-512,CPU 不支援 |
| qwen3:4b | 0.78 tok/s | 5/7(部分截斷) | Q2/Q5 在 300 token 限制下截斷或夾雜文字 |
| qwen3.5:9b | 0.57 tok/s | 5/7 | Q6/Q7 需延長 timeout 至 1800s |
| qwen3:14b | 0.58 tok/s | 7/7 全答 | 無截斷問題,但速度慢 |
| gemma4:e4b | 1.45 tok/s | 5/7(舊測試 600 上限) | Q2/Q4 在 600 token 舊限制下截斷 |
表 2:同款 gemma4:e4b,硬體差距
| 指標 | Mini PC(CPU) | MacBook Air M3 | 差距 |
|---|---|---|---|
| 平均速度 | 1.45 tok/s | 9.75 tok/s | 6.7× |
| Q2 輸出長度 | 600 tokens(截斷) | 2218 tokens(完整) | 記憶體夠,才不截斷 |
| Q4 輸出長度 | 600 tokens(截斷) | 1043 tokens(完整) | SQL 查詢完整輸出 |
| 最大 num_ctx | 受 16GB 限制 | 65536+ | 長文件處理能力天差地別 |
表 3:Mac 上同款模型,三種設定對比
| 設定 | 平均速度 | Q2 輸出 | Q7 輸出 | 適合場景 |
|---|---|---|---|---|
| Mini PC 舊測試(600 limit) | 1.45 tok/s | 截斷 | 309 tokens | 快速查詢 |
| Mac think:false,無限制 | 9.75 tok/s | 2218 tokens ✅ | 442 tokens | 日常程式碼 |
| Mac Thinking 開啟,無限制 | 9.16 tok/s(-6%) | 3670 tokens ✅ | 990 tokens | 複雜推理、技術解釋 |
第一層:Mini PC 上,哪個模型值得跑?
在只有 CPU 推論的環境下,選模型就是在「速度」與「品質」之間做取捨。以下是完整的三維評估。
Bonsai 8B:直接淘汰
Bonsai 8B 的速度是 0.001 tok/s——不是很慢,是根本無法執行。原因是它的量化版本依賴 AVX-512 指令集,而 Ryzen 7 4700U 不支援 AVX-512(只有 AVX2)。llama.cpp 在這種情況下會退回軟體模擬,速度接近零。如果你的機器是 Intel 第 11 代以後或 AMD Zen 4 以後,才有機會跑 Bonsai 8B。
qwen3:4b:最快但有截斷風險
qwen3:4b 在 Q1~Q7 七個量化等級測試中,平均跑出 0.78 tok/s,是 CPU 上可用模型裡的最高速。但在 num_predict=300 的限制下,Q2(程式碼生成)和 Q5(格式輸出)出現截斷或夾雜不相關文字的問題。如果你只需要短問短答,qwen3:4b Q6 量化(約 21 元台幣月費 API 等級)是最划算的選擇。
qwen3.5:9b vs qwen3:14b:9B 更快但 14B 更可靠
qwen3.5:9b 平均 0.57 tok/s,但 Q6/Q7 題遇到了 timeout 問題——需要將請求超時設定延長到 1800 秒才能完成。原因是 9B 模型在複雜任務上思考時間較長,但預設 timeout 不夠。
qwen3:14b 同樣 0.58 tok/s,卻跑出 7/7 完整答題率。它的 Q2 完整輸出 500 tokens、Q4 完整輸出 500 tokens,沒有截斷。代價是記憶體佔用更高,在 16GB 機器上跑 14B 模型時需要注意 KV Cache 可能 OOM(記憶體不足),建議設定 num_ctx 不超過 4096。
gemma4:e4b:速度最快的完整模型
gemma4:e4b 平均 1.45 tok/s,是所有可用模型中最快的,幾乎是 qwen3:14b 的 2.5 倍。在舊測試的 600 token 限制下,Q2 和 Q4 被截斷。但如果移除限制(num_predict=-1),這個問題就不存在——這正是下一層要說的。
Mini PC 選模型建議:
- 追求速度 → gemma4:e4b(1.45 tok/s,移除 token 限制)
- 追求品質 → qwen3:14b(7/7 完整,0.58 tok/s)
- 省記憶體 → qwen3:4b Q3/Q4(短任務夠用)
- 不建議 → Bonsai 8B(AVX-512 門檻)、qwen3.5:9b(需調 timeout)
第二層:同款模型,換台機器差多少?
用同款 gemma4:e4b,在 Mini PC 和 MacBook Air M3 上各跑一輪,看看換硬體能得到什麼。
速度差 6.7 倍,不只是快慢的問題
Mini PC 平均 1.45 tok/s,Mac 平均 9.75 tok/s。這個差距背後的原因是架構:Mini PC 用 x86 CPU 做矩陣運算,效率遠低於 Apple Silicon 的 Neural Engine + 統一記憶體架構。M3 的統一記憶體讓 CPU 和 GPU 共享同一塊 24GB,模型權重可以直接放在 GPU 能讀取的記憶體,不需要搬移。
記憶體夠,輸出才完整
這是硬體差距最直接的體現:Q2 要求生成一個能解析多種日期格式的 Python 函式,Mini PC 在 600 token 限制下就截斷了(回答還在中途),而 Mac 無限制跑出 2218 tokens 的完整函式。
Q4 要求生成帶有 CTE 和 Window Function 的複雜 SQL,Mini PC 截斷,Mac 輸出完整 1043 tokens 含說明。這不是模型能力的差異,是記憶體和 KV Cache 空間的差異。
24GB 統一記憶體的隱藏優勢:num_ctx 可拉到 65536+
num_ctx 決定模型能「看到」的上下文長度。Mini PC 的 16GB RAM 在跑 gemma4:e4b 時,實際可用的 KV Cache 空間有限,num_ctx 設太高就 OOM。Mac 的 24GB 統一記憶體可以輕鬆設定 num_ctx=65536,意味著可以貼入整個程式碼檔案、長文件、對話紀錄,模型不會「忘記」前面說了什麼。這個差距在實際工作流中比速度差距更重要。
第三層:同台機器,調設定差多少?
在 MacBook Air M3 上,用同款 gemma4:e4b 比較三種設定:舊測試的 600 token 限制、無限制(think:false)、開啟 Thinking 模式。
速度幾乎不變,但輸出長度和品質大幅提升
think:false 無限制:平均 9.75 tok/s。Thinking 開啟無限制:平均 9.16 tok/s。速度只差 6%,但輸出品質差距大:
- Q2(Python 程式碼):2218 tokens → 3670 tokens(+65%),Thinking 版本多了完整的邊界條件處理和說明文字
- Q4(SQL 查詢):1043 tokens → 1413 tokens(+35%),多了欄位說明和效能備注
- Q7(TCP 三次握手):442 tokens → 990 tokens(+124%),Thinking 版本多了為何需要三次的深層解釋
Thinking 模式讓模型在回答前先「想」一遍,輸出的答案更完整、更有條理。對於技術問題、程式碼生成、複雜推理,這個設定幾乎是免費的品質提升。
gemma4:e4b 全力展開:這就是本地 LLM 的真實上限
以下展示 Thinking 模式在 Mac 上的實際輸出,讓你判斷品質是否符合你的需求。
Q2 Thinking:完整的多格式日期解析函式
問題:「寫一個 Python 函式,能解析 YYYY-MM-DD、YYY/MM/DD(民國年)、YYYYMMDD(8位)、YYYMMDD(7位民國年)四種格式。」
Thinking 模式輸出 3670 tokens,以下是完整的函式程式碼:
from datetime import datetime
import re
def parse_mixed_date_string(date_str: str) -> datetime:
if not isinstance(date_str, str):
raise TypeError("輸入必須是字串 (string) 格式。")
date_str = date_str.strip()
# 模式 A: YYYY-MM-DD
if re.fullmatch(r'\d{4}-\d{2}-\d{2}', date_str):
try:
return datetime.strptime(date_str, "%Y-%m-%d")
except ValueError:
pass
# 模式 B: YYY/MM/DD(含民國年)
if re.search(r'[/]', date_str):
parts = date_str.split('/')
if len(parts) == 3:
year, month, day = parts[0], parts[1], parts[2]
try:
temp_date_str = f"{year.zfill(4)}-{month.zfill(2)}-{day.zfill(2)}"
return datetime.strptime(temp_date_str, "%Y-%m-%d")
except ValueError:
pass
# 模式 C: YYYYMMDD(8位)
if re.fullmatch(r'\d{8}', date_str):
try:
return datetime.strptime(date_str, "%Y%m%d")
except ValueError:
pass
# 模式 D: YYYMMDD(7位民國年)
if re.fullmatch(r'\d{7}', date_str):
try:
year = date_str[:-4]
month = date_str[-4:-2]
day = date_str[-2:]
temp_date_str = f"{year.zfill(4)}-{month}-{day}"
return datetime.strptime(temp_date_str, "%Y-%m-%d")
except ValueError:
pass
raise ValueError(f"無法識別或解析的日期格式:'{date_str}'")
非 Thinking 模式在同題只輸出 2218 tokens,函式邏輯正確但缺少邊界案例說明和型別標注說明。Thinking 版本多的那 1452 tokens 全是有用的:錯誤處理說明、邊界條件討論、使用範例。
Q4 Thinking:帶 CTE 和視窗函數的複雜 SQL
問題:「找出每個城市近 30 天消費金額前三名的顧客,輸出城市、姓名、消費總額。」
WITH RecentSpending AS (
SELECT o.customer_id, SUM(o.amount) AS total_spending
FROM orders o
WHERE o.created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY o.customer_id
),
RankedCustomers AS (
SELECT c.name, c.city, rs.total_spending,
RANK() OVER (PARTITION BY c.city ORDER BY rs.total_spending DESC) as city_rank
FROM RecentSpending rs
JOIN customers c ON rs.customer_id = c.id
)
SELECT city, name, total_spending
FROM RankedCustomers
WHERE city_rank <= 3
ORDER BY city, city_rank;
Thinking 版本(1413 tokens)在 SQL 後面額外附上了欄位說明對照表、RANK vs DENSE_RANK 的差異說明、以及在資料量大時建議加索引的備注。這種「自動補充說明」的行為,在程式碼審查或教學場景特別有用。
Q7 Thinking:TCP 三次握手的技術解釋
問題:「解釋 TCP 三次握手的過程,並說明為什麼需要三次而不是兩次。」
Thinking 模式的回答(節錄,990 tokens):
TCP 三次握手是在兩台設備開始傳輸實際資料之前,建立穩定可靠連線的過程:
- Client → Server (SYN):「我想連線,序列號從 X 開始」
- Server → Client (SYN-ACK):「我收到了,我也準備好,序列號從 Y 開始」
- Client → Server (ACK):「我收到你的確認,正式連線」
為何需要三次而不是兩次:三次握手確保雙方的「發送能力」和「接收能力」都得到驗證。第三次 ACK 讓客戶端確認成功收到伺服器的肯定回應,確保雙方對初始序列號達成共識。若只有兩次,Server 無法確認 Client 是否真的收到了 SYN-ACK,可能導致半開連線(half-open connection)堆積。
非 Thinking 模式(442 tokens)的回答只覆蓋了步驟本身,沒有解釋半開連線問題。Thinking 版本的 990 tokens 多出了協議設計的「為什麼」。
技術踩坑筆記
坑 1:AVX-512 問題不是模型 bug,是 CPU 選錯了
Bonsai 8B 需要 AVX-512,AMD Ryzen 4000 系列(Zen 2 架構)不支援。解法:換用 AVX2 相容的量化版本,或換到支援 AVX-512 的 CPU(Intel 11th Gen+、AMD Zen 4+)。在買 Mini PC 跑本地 LLM 之前,先確認 CPU 指令集支援情況。
坑 2:num_predict=300 在 CPU 機器上是陷阱
設 num_predict=300 看起來是「省時間」,但會讓程式碼生成等長輸出任務的測試結果完全失效。正確做法是設 num_predict=-1(無限制),然後觀察模型自然停止的位置。如果真的需要截斷,至少設到 1000 以上再做程式碼類測試。
坑 3:qwen3.5:9b 的 timeout 問題
qwen3.5:9b 在 Q6(長文生成)和 Q7(技術解釋)上,Ollama 預設的請求 timeout 不夠,導致連線中斷而不是模型輸出完成。解法:在呼叫 API 時設定 timeout 參數為 1800 秒,或在 Ollama 的環境變數中調整 OLLAMA_TIMEOUT。
坑 4:KV Cache OOM 發生在 num_ctx 設太高時
在 16GB 機器上跑 qwen3:14b,如果 num_ctx 設到 8192 以上,KV Cache 的記憶體需求會超過可用 RAM,導致 OOM 或系統卡死。建議 16GB RAM 跑 14B 模型時,num_ctx 不超過 4096;跑 4B 模型時,num_ctx 可以到 8192。
坑 5:think:false 是必要的,否則輸出會混入思考過程
qwen3 系列模型如果不設定 think:false,輸出會包含 <think> 標籤包裹的推理過程,混在正式答案裡,對程式解析造成困擾。在 Ollama API 呼叫時加上 "options": {"think": false},或使用 /set parameter think false。只有在你明確需要 Thinking 輸出時才開啟。
坑 6:num_predict 是物理截斷,不是智慧壓縮
很多人以為設 num_predict=600 會讓模型「給出精簡版本」,實際上不是。模型不知道 num_predict 這個參數的存在——它只是一個一個往下生成 token,到達上限時被外力硬切斷。結果是:程式碼寫到一半沒有結尾大括號、SQL 少了 WHERE 條件、解釋說到一半消失。
這次測試的 Q2(日期解析函數)和 Q4(SQL 排名查詢)在 num_predict=300 時全數截斷就是這個原因。移除限制(num_predict=-1)之後,模型自然停止,輸出完整。
坑 7:有些內容本質上無法壓縮到指定長度內
假設你問模型「給我一份完整 Kafka 設定檔,限制 50 個 token 內」——這兩個要求本身就互相矛盾。一份能正常運作的 Kafka 設定檔,光是必要欄位就需要遠超 50 token。模型沒有辦法把磚頭塞進比磚頭小的洞。
面對不可壓縮的內容,有三種做法:(1)直接移除 token 上限;(2)分步請求——先要最精簡模板,確認結構後再要完整版;(3)在 prompt 明確說「輸出必須完整可執行,不要省略任何欄位」,但前提是 token 上限本身要夠大。
補充:不同模型怎麼面對衝突指令?
這裡有個值得理解的差異:本地模型(Ollama + llama.cpp)的 token 限制來自 API 參數,是硬體截斷,模型本身完全不知道有這個限制存在。雲端模型(Claude、GPT-4 等)的「限制」則來自 prompt 文字指令,模型讀到這個指令後會嘗試推理你的真實意圖。
| 情境 | 本地模型(Ollama) | 雲端模型(Claude) |
|---|---|---|
| 限制來源 | num_predict API 參數 | prompt 文字指令 |
| 遇到「50 token 內給完整設定檔」 | 不知道有衝突,第 50 個 token 硬截 | 判斷兩個要求互相矛盾,主動說明,給完整版 |
| 答案冗長可縮短的情況 | 按字數截斷,不壓縮 | 推理目標,給出精簡版本 |
| 答案本質不可壓縮 | 截斷,輸出殘缺內容 | 告知無法在限制內完整輸出,給出建議 |
| GPT / Gemini 回答為什麼那麼短? | — | 不是 token 限制,是 System Prompt + RLHF 訓練偏好所致 |
這個差異的實際意義是:在本地 LLM 環境下,token limit 是你唯一能控制輸出長度的工具,設太小就會截斷。雲端模型則更像是在和一個理解你意圖的人對話——你不需要精準計算 token,只需要把你真正想要的說清楚。
進階應用:讓本地 LLM 記住對話上下文
本地 LLM 的 API 呼叫預設是無狀態的——每次送出的是獨立的單輪問答,模型不記得你上一題問了什麼。如果你想做多輪對話助理、程式碼審查工具、或任何需要「記住脈絡」的應用,就需要自己管理對話歷史。
為什麼不能無限加長 messages?
標準做法是把整段對話歷史塞進 messages 陣列一起送出。但對話越長,messages 陣列越大,最終超過 num_ctx 上限,前面的對話就會被硬截斷——模型在不知情的狀況下「失憶」,不會告訴你它看不到前面的內容。
解法不是把 num_ctx 設更大(那只是延後問題),而是主動管理 messages 陣列:用摘要壓縮舊對話,只保留近期原文加上一段精簡的歷史摘要。
三種管理策略比較
| 方式 | num_ctx 用量 | 記憶效果 | 適合場景 |
|---|---|---|---|
| 無管理(全部塞) | 持續增長,最終截斷 | 前期對話被硬切,模型不自知 | 短對話、單次任務 |
| Sliding Window(只保留近 N 輪) | 固定 | 早期資訊完全消失 | 客服機器人、無需長記憶的助理 |
| 摘要壓縮(推薦) | 固定,摘要極短 | 保留關鍵結論、數字、決策 | 開發助理、長程任務、知識型問答 |
摘要壓縮的運作方式
核心思路是:用同一個本地模型來摘要自己的舊對話。超過門檻後,把早期輪次壓縮成一段文字,之後每次送出時帶著「摘要 + 近期原文」,而不是全部歷史。
第 1-8 輪:原文保存在 messages[]
第 9 輪觸發壓縮:
old[1-5輪] → summarize() → "重點:xxx, yyy, zzz"
messages 只留 [6-8輪原文] + 新問題
第 9 輪實際送出的內容:
system: "對話背景摘要:重點 xxx, yyy, zzz"
user(6): ... ai(6): ...
user(7): ... ai(7): ...
user(8): ... ai(8): ...
user(9): 現在的問題
完整 Python 實作(本地 Ollama 版)
import json, urllib.request
MAC_URL = "http://192.168.1.xxx:11434/api/chat" # Mac 的 Ollama,Mini PC 不跑本地模型
MODEL = "gemma4:e4b"
def call_model(messages, think=False):
payload = {
"model": MODEL,
"messages": messages,
"stream": False,
"think": think,
"options": {"num_ctx": 4096, "num_predict": -1}
}
data = json.dumps(payload).encode()
req = urllib.request.Request(
MAC_URL, data=data,
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=300) as r:
return json.loads(r.read())["message"]["content"]
def summarize(messages):
"""把舊對話丟給模型,壓縮成條列式重點"""
history_text = "\n".join(
f"{'User' if m['role'] == 'user' else 'AI'}: {m['content']}"
for m in messages
)
prompt = f"""以下是一段對話記錄,請用條列式摘要最重要的資訊、結論、已確認的事實。
保留具體數字、決策、技術細節。100字以內。
對話:
{history_text}
摘要:"""
return call_model([{"role": "user", "content": prompt}])
class ChatSession:
def __init__(self, keep_recent=4, compress_threshold=8):
self.messages = []
self.summary = "" # 累積摘要
self.keep_recent = keep_recent # 保留最近幾輪原文
self.compress_threshold = compress_threshold # 超過幾輪就壓縮
def chat(self, user_input):
self.messages.append({"role": "user", "content": user_input})
# 超過門檻 → 壓縮舊對話
if len(self.messages) > self.compress_threshold:
old = self.messages[:-self.keep_recent]
new_summary = summarize(old)
# 把舊摘要 + 新摘要合併
self.summary = f"{self.summary}\n{new_summary}".strip()
self.messages = self.messages[-self.keep_recent:]
print(f"[已壓縮,摘要更新:{len(self.summary)} chars]")
# 組合本次送出的 messages
send_messages = []
if self.summary:
send_messages.append({
"role": "system",
"content": f"對話背景摘要(已發生的重點):\n{self.summary}"
})
send_messages.extend(self.messages)
response = call_model(send_messages)
self.messages.append({"role": "assistant", "content": response})
return response
# 使用方式
if __name__ == "__main__":
session = ChatSession(keep_recent=4, compress_threshold=8)
while True:
user = input("你:").strip()
if user.lower() == "exit":
break
reply = session.chat(user)
print(f"AI:{reply}")
if session.summary:
print(f"[背景摘要:{len(session.summary)} chars]")
效能調優:摘要用小模型,回答用大模型
摘要這個步驟本身也消耗一次推理呼叫。如果 Mac 上同時有快慢兩個模型,可以分工:快的模型做摘要,慢的(品質更好的)做正式回答:
MAIN_MODEL = "qwen3:14b" # 回答主要問題,品質優先
SUMMARY_MODEL = "qwen3:4b" # 做摘要,速度優先(簡單任務夠用)
def summarize(messages):
# 使用小模型做摘要
payload = {
"model": SUMMARY_MODEL,
...
}
這樣摘要時間從數十秒縮短到幾秒,而主要對話品質不受影響。MacBook Air M3 速度夠快(9+ tok/s),用同一個模型做摘要也無妨。
日常應用:把 Mac 當成你的私人 AI 主機
gemma4:e4b 在 MacBook Air M3 上跑出 9+ tok/s,這個速度對互動式日常使用完全夠用——不是只能跑 benchmark,而是可以當成隨時待命的個人助理。重點是:所有資料留在本機,不送雲端,不計費。
架構很簡單:Mini PC 負責發問和顯示,Mac 負責思考和回答。Mini PC 本身不跑模型,只是一個入口。
你(Mini PC)→ 問題 → Mac gemma4 → 回答 → 你
Mini PC:入口,不思考
Mac:腦子,負責推理
兩個最常用的場景
場景一:快速摘要
把一篇文章、一份 log、一段程式碼丟給 Mac,要它用幾句話說重點。不需要 Claude 等級的推理,gemma4 速度更快、更省錢(免費)。
#!/usr/bin/env python3
# summarize.py — 從 stdin 讀內容,打到 Mac gemma4 要摘要
# 用法:cat article.txt | python3 summarize.py
# cat error.log | python3 summarize.py --prompt "這份 log 的錯誤原因是什麼"
import json, sys, urllib.request, argparse
MAC_URL = "http://192.168.1.xxx:11434/api/chat"
MODEL = "gemma4:e4b"
parser = argparse.ArgumentParser()
parser.add_argument("--prompt", default="請用5句話以內摘要以下內容的重點:")
args = parser.parse_args()
content = sys.stdin.read().strip()
if not content:
print("ERROR: no input"); sys.exit(1)
payload = {
"model": MODEL,
"messages": [{"role": "user", "content": f"{args.prompt}\n\n{content}"}],
"stream": False, "think": False,
"options": {"num_ctx": 8192, "num_predict": -1},
}
req = urllib.request.Request(
MAC_URL, data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=300) as r:
print(json.loads(r.read())["message"]["content"])
# 使用範例
cat ~/Downloads/article.txt | python3 summarize.py
cat /var/log/app.log | python3 summarize.py --prompt "這份 log 有什麼異常?"
git diff HEAD~5 | python3 summarize.py --prompt "這幾個 commit 改了什麼?"
場景二:快速產程式驗證
想驗證一個想法、寫一段臨時腳本、或確認某個 API 用法——不需要開 IDE,直接從命令列問 Mac,幾秒鐘拿到可以跑的程式碼片段。
#!/usr/bin/env python3
# ask.py — 命令列直接問 Mac,拿回程式碼或答案
# 用法:python3 ask.py "寫一個 Python 函數,把 list 裡的重複元素移除但保留順序"
# python3 ask.py "用 curl 怎麼測試一個需要 Bearer token 的 API"
import json, sys, urllib.request
MAC_URL = "http://192.168.1.xxx:11434/api/chat"
MODEL = "gemma4:e4b"
question = " ".join(sys.argv[1:])
if not question:
print("Usage: python3 ask.py \"your question\""); sys.exit(1)
payload = {
"model": MODEL,
"messages": [{"role": "user", "content": question}],
"stream": False, "think": False,
"options": {"num_ctx": 4096, "num_predict": -1},
}
req = urllib.request.Request(
MAC_URL, data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=300) as r:
print(json.loads(r.read())["message"]["content"])
# 使用範例
python3 ask.py "寫一個 bash script,每天早上備份 ~/Documents 到外接硬碟"
python3 ask.py "Python requests 怎麼設定 retry 和 timeout"
python3 ask.py "這個 SQL 有什麼問題:SELECT * FROM orders WHERE date > NOW() - 30"
什麼時候還是要 Claude
| 任務 | Mac gemma4 | Claude API |
|---|---|---|
| 文章/log 摘要 | ✅ 夠用,免費 | — |
| 快速程式片段 | ✅ 夠用,快 | — |
| 翻譯、改寫 | ✅ 夠用 | — |
| 私人資料(不想送雲端) | ✅ 最佳選擇 | ❌ |
| 程式碼審查(跨檔案) | ❌ 沒有 context | ✅ |
| 複雜架構決策 | ❌ 推理不足 | ✅ |
| Agent Team 自動化開發 | ⚡ 出草稿 | ✅ 審查整合 |
原則是:不需要記憶 codebase、不需要複雜推理的任務,都可以先試 Mac。速度快、免費、資料不出門。遇到 Mac 答不好的,再升到 Claude。
進階應用二:Mini PC + Mac 混合架構,讓 Agent Team 更有效率
角色定義(固定,不隨任務改變)
Mini PC → 純指揮中心:跑 Claude Code、管理 Agent Team、處理串接邏輯
不跑任何本地模型,資源用在穩定性和協調上
Mac → 推理後端:跑 Ollama + gemma4:e4b
只負責生成,不做決策
Claude API → 審查 + 架構:程式碼審查、複雜邏輯、跨檔案推理
Mini PC 透過網路呼叫,不在本地
規則:Mac 不在線 → fallback 給 Claude API,不是 Mini PC 自己跑
當你用 Claude Code 的 Agent Team 跑自動化程式開發時,會面對一個現實問題:Claude API 的費用隨 token 用量線性增長,而很多任務其實不需要 Claude 的完整推理能力——DTO 生成、CRUD 樣板、SQL migration 這類結構性重複工作,本地的 gemma4:e4b 就能處理。
解法是把 Mac 當成 Agent Team 的「草稿後端」:Claude Agent 負責架構決策和程式碼審查,Mac gemma4 負責產生第一版草稿,再由 Claude 驗證整合。
架構分工
| 任務類型 | 交給誰 | 原因 |
|---|---|---|
| DTO / model class | Mac gemma4 | 結構固定,重複性高 |
| CRUD endpoints 樣板 | Mac gemma4 | Pattern 固定,不需要推理 |
| SQL migration | Mac gemma4 | 有範本可循 |
| Unit test 骨架 | Mac gemma4 | 快速產出結構,Claude 填邏輯 |
| 複雜業務邏輯 | Claude sonnet | 需要跨檔案理解,Mac 沒有 context |
| 安全相關程式碼 | Claude sonnet/opus | 不可靠的輸出風險太高 |
| 架構決策 / Code Review | Claude opus | 需要深度推理與判斷 |
前置設定:讓 Mac 的 Ollama 對區網開放
Ollama 預設只監聽本機。在 Mac 上把它開放給區網,Mini PC 才能連進來:
# Mac 上執行(停掉 Ollama app 後)
OLLAMA_HOST=0.0.0.0 ollama serve
# 從 Mini PC 驗證是否連得到(換成 Mac 的區網 IP)
curl http://192.168.1.xxx:11434/api/tags
不想暴露 port 的話,用 SSH Tunnel:Mini PC 上執行 ssh -L 11435:localhost:11434 [email protected] -N,之後打 localhost:11435 就等於打 Mac 的 Ollama。
工具腳本:mac_draft.py
Agent 透過 Bash tool 呼叫這支腳本,傳入任務描述,拿回草稿程式碼。腳本會自動檢查 Mac 是否在線,不在線就回傳 exit code 1,讓 Agent 自行處理 fallback。
#!/usr/bin/env python3
"""
mac_draft.py — Call Mac's local gemma4 for code draft generation.
Usage:
python3 mac_draft.py "write a SQLAlchemy User model with id, name, email"
python3 mac_draft.py --task "CRUD for User" --context "FastAPI, SQLAlchemy async"
Exit codes:
0 = success, draft printed to stdout
1 = Mac unreachable → fallback: implement with Claude directly
2 = model error
"""
import json, sys, urllib.request, urllib.error, argparse
MAC_HOST = "http://192.168.1.xxx:11434" # 改成 Mac 的實際 IP
MODEL = "gemma4:e4b"
TIMEOUT = 600
SYSTEM_PROMPT = """You are a code generation assistant. Output ONLY code —
no explanations, no markdown fences, no comments unless essential.
The output will be reviewed and integrated by another agent."""
def check_reachable():
try:
with urllib.request.urlopen(f"{MAC_HOST}/api/tags", timeout=5):
return True
except Exception:
return False
def generate(task, context=""):
prompt = f"Context: {context}\n\nTask: {task}" if context else task
payload = {
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt},
],
"stream": False, "think": False,
"options": {"num_ctx": 4096, "num_predict": -1},
}
data = json.dumps(payload).encode()
req = urllib.request.Request(
f"{MAC_HOST}/api/chat", data=data,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
return json.loads(r.read())["message"]["content"]
parser = argparse.ArgumentParser()
parser.add_argument("task", nargs="?")
parser.add_argument("--task", dest="task_flag")
parser.add_argument("--context", default="")
args = parser.parse_args()
task = args.task or args.task_flag
if not task:
print("ERROR: no task provided", file=sys.stderr); sys.exit(1)
if not check_reachable():
print(f"MAC_UNREACHABLE: {MAC_HOST}. Fallback: implement with Claude.",
file=sys.stderr); sys.exit(1)
try:
print(generate(task, args.context))
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr); sys.exit(2)
Agent 的實際使用流程
# Agent (sonnet) 在 Bash tool 中這樣呼叫:
# 1. 請 Mac 出草稿
draft=$(python3 ~/llm-benchmark/scripts/mac_draft.py \
--task "generate SQLAlchemy User model" \
--context "PostgreSQL, async, Pydantic v2")
# 2. 檢查是否成功
if [ $? -ne 0 ]; then
echo "Mac unavailable, implementing directly"
# Claude 自己寫
fi
# 3. 草稿給 Claude 審查後整合進 codebase
echo "$draft" # Claude 讀到這裡,決定是否採用、修改哪裡
告訴 Agents 這條規則:寫入 AGENTS.md
Claude Code 的 Agent Team 每個 subagent 啟動時沒有對話歷史。規則要寫進 AGENTS.md,agent 才會在每次任務開始時讀到它。在專案的 AGENTS.md 加上這個區塊:
## Mac Draft Resource (Local LLM Offload)
Mac (gemma4:e4b) is available as a fast code draft generator.
Tool: python3 ~/llm-benchmark/scripts/mac_draft.py
Use Mac draft BEFORE writing code yourself for:
- DTO / model class boilerplate ✅
- CRUD endpoints (standard pattern) ✅
- SQL migration scripts ✅
- Unit test scaffolding ✅
Do NOT use Mac draft for:
- Complex business logic ❌ (no codebase context)
- Security-sensitive code ❌ (unreliable)
- Cross-file refactoring ❌ (no context)
- Architecture decisions ❌ (use opus)
Workflow:
1. Call mac_draft.py with task description
2. exit code 1 (MAC_UNREACHABLE) → implement with Claude directly
3. Review draft: check patterns, imports, logic, security
4. Integrate into codebase
Mac generates the shape. Claude ensures it fits.
這樣每個 subagent 都會知道「遇到樣板類任務先叫 Mac 出草稿」,不需要每次重新交代規則。
結論與推薦
本次測試跨越三個維度,每層都有明確的答案:
Mini PC + Mac 混合架構的定位
Mini PC 的角色是指揮中心,不是推理引擎。它跑 Claude Code、管理 Agent Team、處理串接邏輯,資源用在穩定性和協調上。推理工作全部交給 Mac 的 gemma4:e4b。
- 日常問答 / 摘要:Mini PC 發問 → Mac gemma4 回答,免費、快速、資料不出門
- 草稿程式碼:Agent 呼叫 mac_draft.py → Mac 出草稿 → Claude 審查整合
- 複雜推理 / 架構決策:直接用 Claude API,不走 Mac
- Mac 不在線:fallback 給 Claude API,Mini PC 本身不需要跑任何模型
如果你的情境是 Mini PC 獨立運作(沒有 Mac),模型選擇建議:gemma4:e4b 速度最快(1.45 tok/s)、qwen3:14b 完成度最高(7/7 全答)、qwen3:4b 最省記憶體。但這個架構的速度上限就是 CPU 推論,不如 Mac 的 Apple Silicon。
MacBook Air M3 的最佳設定
- 日常程式碼:think:false,num_predict=-1,9.75 tok/s,輸出完整
- 複雜推理/技術解釋:Thinking 開啟,速度只慢 6%,品質提升明顯
- 長文件處理:設定 num_ctx=65536,充分利用 24GB 統一記憶體
本地 LLM 的真實上限在哪裡?
gemma4:e4b 在 MacBook Air M3 + Thinking 模式下,輸出品質接近 GPT-3.5 的水準——在程式碼生成、SQL 查詢、技術解釋這些結構清晰的任務上。它不是 GPT-4,創意寫作和跨領域推理還是有差距,但對開發者的日常工作場景已經夠用。
真正的瓶頸不是模型大小,而是硬體架構。同款模型在 Apple Silicon 上跑出的效果,在 x86 CPU 上根本發揮不出來。如果你認真考慮本地 LLM,MacBook Air M3 是目前性價比最高的入門選擇;Mini PC 路線則需要搭配 NVIDIA GPU(VRAM ≥ 8GB)才能真正發揮。