16:35:53 offloaded 42/43 layers to GPU(模型開始載入)
16:38:53 llm server not responding
16:38:54 llm server loading model
16:39:02 llm server not responding
16:39:03 llm server loading model
...(反覆了將近 6 分鐘)
16:42:28 llama runner started in 410.58 seconds(終於啟動)
17:04:49 -- Boot --(機器直接重開機)
用同款 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 空間的差異。
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 的差異說明、以及在資料量大時建議加索引的備注。這種「自動補充說明」的行為,在程式碼審查或教學場景特別有用。
原則是:不需要記憶 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。
#!/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。
真正的瓶頸不是模型大小,而是硬體架構。同款模型在 Apple Silicon 上跑出的效果,在 x86 CPU 上根本發揮不出來。如果你認真考慮本地 LLM,MacBook Air M3 是目前性價比最高的入門選擇;Mini PC 路線則需要搭配 NVIDIA GPU(VRAM ≥ 8GB)才能真正發揮。
// Producer-Consumer pattern with BlockingCollection
var queue = new BlockingCollection<WorkItem>(boundedCapacity: 100);
// Producer thread
Task.Run(() =>
{
foreach (var item in GetWorkItems())
{
// Blocks if queue is full (back-pressure!)
queue.Add(item);
Console.WriteLine($"Produced: {item.Id}");
}
queue.CompleteAdding(); // Signal no more items
});
// Consumer thread
Task.Run(() =>
{
// Blocks automatically when queue is empty
// Exits when CompleteAdding() is called and queue is drained
foreach (var item in queue.GetConsumingEnumerable())
{
ProcessItem(item);
Console.WriteLine($"Consumed: {item.Id}");
}
});
這篇文章是我從 DBA 到全端架構師這幾年,在 SQL Server 效能優化上踩過的坑的總整理。不是教科書式的理論,而是每一條都是我實際測試、實際踩雷後的血淚經驗。如果你正在處理 SQL Server 效能優化的問題——DELETE 後空間沒釋放、查詢莫名其妙變慢、鎖定機制搞不清楚——這篇應該能幫你少走不少冤枉路。
TL;DR 重點摘要
DELETE 不會釋放磁碟空間,只是標記刪除。要真正回收空間,必須用 TRUNCATE 或 ALTER INDEX REBUILD。
NOT IN 是效能炸彈,改用 NOT EXISTS 可以讓查詢快數十倍,尤其在子查詢結果集大的時候。
在 Azure SQL Database 上,大量 DELETE 後看到儲存空間快滿了——這是假警報。空間根本沒被釋放。DELETE ... WITH (TABLOCK) 效果有限,必須搭配 TRUNCATE 或 ALTER INDEX ALL ON [TableName] REBUILD 才能真正回收。
-- Problem: Without TABLOCKX, T2 can read T1's uncommitted changes
-- Session 1
BEGIN TRAN T1;
UPDATE Orders SET Amount = 999 WHERE OrderID = 1;
-- (not committed yet)
-- Session 2 (runs concurrently, sees Amount = 999 → Dirty Read!)
SELECT Amount FROM Orders WHERE OrderID = 1;
-- Solution: Use TABLOCKX for exclusive access
BEGIN TRAN T1;
SELECT * FROM Orders WITH (TABLOCKX) WHERE OrderID = 1;
-- Now T2 is BLOCKED until T1 commits or rolls back
UPDATE Orders SET Amount = 999 WHERE OrderID = 1;
COMMIT TRAN T1;
否定查詢(NOT EXISTS vs NOT IN):NOT EXISTS 遠遠快於 NOT IN,差距可達數十倍。
-- NOT IN: Slow — performs O(n*m) comparison, NULL handling issues
SELECT * FROM Products
WHERE ProductID NOT IN (
SELECT ProductID FROM OrderDetails
);
-- NOT EXISTS: Fast — uses semi-join, stops at first match
SELECT * FROM Products p
WHERE NOT EXISTS (
SELECT 1 FROM OrderDetails od
WHERE od.ProductID = p.ProductID
);
-- Additional trap: if OrderDetails.ProductID contains ANY NULL value,
-- NOT IN returns ZERO rows! NOT EXISTS handles NULL correctly.
NOT IN 之所以慢,是因為它必須對子查詢的每一筆結果做比對,而且還要處理 NULL 的三值邏輯。NOT EXISTS 則是用半連接(Semi-Join)策略,找到第一筆匹配就停止。
排序與 TOP 的隱藏陷阱
加上 TOP 之後,SQL Server 的排序演算法會完全改變。沒有 TOP 時用完整排序(Full Sort),有 TOP 時用 Top-N Sort,記憶體需求和執行路徑完全不同。
SQL 執行順序(必背)
很多查詢優化的問題,根源是不理解 SQL 的實際執行順序:
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → TOP/OFFSET
注意 SELECT 在 WHERE 之後,所以你不能在 WHERE 中使用 SELECT 裡定義的別名。而 ORDER BY 在 SELECT 之後,所以可以用別名排序。理解這個順序,很多「為什麼這樣寫不行」的問題都迎刃而解。
另外,索引不只消除全表掃描,還能跳過排序階段。如果 ORDER BY 的欄位剛好有索引,SQL Server 可以直接按索引順序讀取,省掉排序的 CPU 和記憶體開銷。
4. 全文檢索 — 比 LIKE ‘%keyword%’ 快一百倍
如果你的應用有「搜尋文章內容」的需求,還在用 LIKE '%keyword%',那你的查詢基本上每次都是全表掃描。全文檢索(Full-Text Search)透過反向索引(Inverted Index)來加速文字搜尋,效能差距是數量級的。
建立全文檢索的前提與步驟
前提:目標表必須有主鍵(Primary Key)。因為反向索引需要唯一識別碼來對應每筆資料。
-- Step 1: Enable full-text search on the database (if not already)
-- (SQL Server installs Full-Text Search as a feature)
-- Step 2: Create a full-text catalog
CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
-- Step 3: Create a full-text index on the table
-- The table MUST have a primary key
CREATE FULLTEXT INDEX ON Articles (
Title LANGUAGE 1028, -- 1028 = Traditional Chinese
Content LANGUAGE 1028
)
KEY INDEX PK_Articles -- Must reference the PK
ON ftCatalog
WITH CHANGE_TRACKING AUTO; -- Auto-update when data changes
-- Step 4: Query using CONTAINS or FREETEXT
SELECT * FROM Articles
WHERE CONTAINS(Content, N'效能優化');
-- Compare with LIKE (full table scan every time)
SELECT * FROM Articles
WHERE Content LIKE N'%效能優化%';
SQL Server Profiler 在生產環境不一定能用(效能開銷太大,或者根本沒權限)。這時候 DMV(Dynamic Management Views)就是你的救星。
追蹤特定時間範圍的查詢
-- Find top queries by CPU time within a time range
SELECT TOP 20
qs.last_execution_time,
qs.execution_count,
qs.total_worker_time / 1000 AS total_cpu_ms,
qs.total_elapsed_time / 1000 AS total_elapsed_ms,
qs.total_logical_reads,
SUBSTRING(st.text,
(qs.statement_start_offset / 2) + 1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset) / 2) + 1
) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
WHERE qs.last_execution_time >= '2026-04-04 09:00:00'
AND qs.last_execution_time <= '2026-04-04 18:00:00'
ORDER BY qs.total_worker_time DESC;
查看當前執行中的程序
-- Quick check: who's running what right now?
EXEC sp_who2;
-- Or with more detail via DMV
SELECT
r.session_id,
r.status,
r.command,
r.wait_type,
r.wait_time,
t.text AS query_text,
r.cpu_time,
r.reads,
r.writes
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id > 50; -- Exclude system sessions
全庫儲存空間盤點
-- Iterate all user tables and check space usage
CREATE TABLE #SpaceUsed (
TableName NVARCHAR(128),
Rows NVARCHAR(20),
Reserved NVARCHAR(20),
Data NVARCHAR(20),
IndexSize NVARCHAR(20),
Unused NVARCHAR(20)
);
DECLARE @tbl NVARCHAR(128);
DECLARE tbl_cursor CURSOR FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE';
OPEN tbl_cursor;
FETCH NEXT FROM tbl_cursor INTO @tbl;
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO #SpaceUsed
EXEC sp_spaceused @tbl;
FETCH NEXT FROM tbl_cursor INTO @tbl;
END
CLOSE tbl_cursor;
DEALLOCATE tbl_cursor;
SELECT * FROM #SpaceUsed ORDER BY CAST(REPLACE(Reserved, ' KB', '') AS BIGINT) DESC;
DROP TABLE #SpaceUsed;
-- Table variable: optimizer always estimates 1 row
DECLARE @small TABLE (ID INT, Name NVARCHAR(50));
INSERT INTO @small SELECT TOP 10 ID, Name FROM Products;
-- Local temp table: has statistics, better for large datasets
CREATE TABLE #bigtemp (ID INT, Name NVARCHAR(50));
INSERT INTO #bigtemp SELECT ID, Name FROM Products;
CREATE INDEX IX_bigtemp_ID ON #bigtemp(ID); -- Can add indexes
-- Global temp table: visible to all sessions
CREATE TABLE ##shared (ID INT, Name NVARCHAR(50));
-- Other sessions can SELECT from ##shared
# Python Crawler — Everything That Can Go Wrong
## ROC Date Format
- [source: analyst] "1150309" 被解析成 AD 1150 年,要用 7 位 YYMMDD ROC 格式
- [source: analyst] TPEX 欄位名 TransactionAmount 不是 TradingMoney
## Holiday / Empty Response
- [source: analyst] TWSE API 假日返回空值,必須 guard `if not data: return []`
然後在 CLAUDE.md 裡強制 agent 在開工前讀 brain:
## Domain Brain — MANDATORY before ANY implementation work
Before writing any plan, code, or review, you MUST:
1. Find the `## Domain Brain:` line in the project's CLAUDE.md
2. Read each listed brain file
3. If you skip this step and a bug was documented in brain, that is YOUR failure