Ollama 默默把你的 Gemma4 KV cache 撐到 256K:DGX Spark 配置優化的真實坑
DGX Spark + Gemma4 31B + Ollama 預設配置會默默把 KV cache 拉到 256K context,21GB unified memory 蒸發,inference 卡 28 分鐘。記錄 root cause 與最佳配置:FA=0、KV cache f16、num_ctx 鎖 8K、用 /api/chat 不用 /v1/chat/completions。
我家裡那台 NVIDIA DGX Spark 跑 Gemma4 31B 跑了一個多月。某天個人 PKI 系統 mkbrain.maki.tw 突然不再有新文章——前段 pipeline 看起來都正常,只有 publish 那一層斷了。我以為是個簡單的 bug,結果挖到底是 DGX Spark + Ollama 的一個沒人講的預設陷阱。
這篇記錄整個 root cause 與最終的最佳配置。讀完你應該能避開我踩的所有坑。
TL;DR
如果你在 NVIDIA DGX Spark(GB10 chip) 上用 Ollama 跑 Gemma4 / 任何 large dense model,下面四件事是反直覺但有具體 evidence:
OLLAMA_FLASH_ATTENTION=1不該開 — GB10 是 SM121a,不是標準 Blackwell。Flash Attention / FlashInfer 的 SM120 wiring 在 CUTLASS issues 上被明確排除。OLLAMA_KV_CACHE_TYPE=q8_0對 Gemma4 不是 lossless — Gemma4 q8_0 KL divergence 0.108(明顯損失),加上 ollama issue #9683/#11949 有 decode -80% 的 bug 還沒確認修。- OpenAI-compat 端點
/v1/chat/completions不接受options.num_ctx— 你以為設了 4K context,其實 ollama 用 default 對 ≥48GiB device 自動拉到 256K。Gemma4 31B 在 256K context 的 KV cache 吃 ~21GB。 - 121GB unified memory 不是 OOM 而是 swap — 跟 discrete VRAM 不同,吃滿後 Linux kernel 會 swap,inference 不報錯只是默默卡 28 分鐘。
事件回放
症狀
我自己維運的個人知識基礎設施(mk-brain,把 LINE 對話裡的 URL 自動跑 6 層 pipeline 變成可搜尋的知識)。 4/25 起 mkbrain.maki.tw(Ghost Observatory)一篇新文章都沒有。
第一層檢查全部正常:
書籤 intake (4/27 12:13) ✅
enrichment (4/27 12:13) ✅
scoring (4/27 12:28) ✅
publish to Ghost (4/25 08:19) ❌ ← 卡這
第一層 root cause:SQL filter
掃 scoring log 看到:
[Ghost QG] BLOCKED: C02: core_insight too short or missing;
O02: fewer than 2 summary_lines for Key Takeaways
連續 20 筆 sig≥55 的高分書籤被 Quality Gate 擋下來。原因:core_insight 是空的。
挖到 enrich.py 的 LLM enrichment SQL:
WHERE fetch_status IN ('fetched', 'metadata_only')
但 RSS pipeline 把新書籤標 fetch_status='rss_prefetched'——這個值不在白名單裡。RSS 來源書籤永遠進不去 LLM enrichment,core_insight 永遠 NULL。
修法很直接,加一個 value 進白名單。但我跑 backfill 時,第一筆 LLM call 卡了 28 分鐘沒回應。
第二層 root cause:DGX 的隱藏 spill
跑 ssh dgx-ts ollama ps:
NAME SIZE PROCESSOR CONTEXT UNTIL
gemma4:31b 72 GB 100% GPU 262144 Forever
注意 CONTEXT=262144。我從來沒設這個值。Gemma4 31B Q4 weights 19GB,loaded 後居然吃 72GB。多出來的 ~53GB 就是 256K context 的 KV cache + activation。
DGX Spark 的 unified memory 121GB available。72GB 模型 + 其他服務(embed_server / open-webui / docker / OS)加起來 94GB used,剩 5GB free。再來個大 prompt prefill 進來,Linux 直接 swap,load average 飆到 78,inference 變成 swap I/O 等待——28 分鐘卡死的真相。
我以為自己有設 num_ctx=4096:
body = {
"model": "gemma4:31b",
"messages": [...],
"options": {"num_ctx": 4096}, # 沒生效
}
url = "http://dgx:11434/v1/chat/completions"
ollama 官方文件白紙黑字:/v1/chat/completions 是 OpenAI-compat 端點,不接受 options.num_ctx。要設 context 必須走 native /api/chat。我這條 endpoint 用了一個多月,options 一直被默默丟掉,ollama 用對 ≥48GiB device 的預設值——256K。
DGX Spark 的 5 個反直覺
我先跟自己跑的本地工具(Codex、Gemini、gemma4 自己)+ 兩個外部 Scout(Perplexity Max、SuperGrok)做了一輪 evidence-driven debate。Codex / Gemini / Grok 三方共識是「開 FA=1 + KV q8_0 救你」。但 Perplexity Max 用 GitHub issues + KL divergence benchmark 推翻了三方共識。
最終結論不是社群常識,是反常識:
1. Flash Attention 不該開
OLLAMA_FLASH_ATTENTION=0 ← 維持
- GB10 的 GPU compute capability 是 SM121a,不是 Data Center Blackwell(SM100/SM103)
- Flash Attention 3 只支援 sm_100a / sm_103a
- CUTLASS issues #2800/#2802/#2947 明確排除 SM121a
- FlashInfer SM120 attention kernels 存在但被 wiring issues 阻擋
- ollama 用 llama.cpp FA2 在 Blackwell「kernels 可跑但未針對架構最佳化」
- unified memory 上 FA 收益本來就比 discrete VRAM 小(不需 CPU↔GPU 搬 KV)
開了反而可能效能倒退。等 ollama 官方 GB10 FAQ 更新再考慮。
2. KV Cache q8_0 對 Gemma4 不是 lossless
# 不要設 OLLAMA_KV_CACHE_TYPE,維持 f16
localbench 2026-04 KL divergence benchmark:
| Model | KV q8_0 KL | 評估 |
|---|---|---|
| Gemma4 31B (Dense) | 0.108 | ⚠️ 明顯損失 |
| Gemma4 26B A4B (MoE) | 0.377 | ❌ 嚴重損失 |
| Qwen3.6 27B | < 0.04 | ✅ 幾乎無損 |
「q8_0 lossless」對 Qwen 成立、對 Gemma4 不成立。
更糟:ollama issues #9683 / #11949 紀錄 Gemma 系列 + q8_0 → decode 從 29.9 → 5.5 tok/s(-80%)的 bug,是否在 Gemma4 已修尚未確認。
3. unified memory 不是 OOM 是 swap
GB10 的「VRAM」實際上就是 128GB LPDDR5X 系統 RAM。CUDA 看到 121GB 的 "VRAM" 跟 OS 的 RAM 是同一塊。
- Discrete GPU(H100/A100/RTX 4090)VRAM 滿 → CUDA OOM → 程序 crash → 你立刻知道
- GB10 unified memory 滿 → Linux kernel swap → 從 NVMe 讀 evicted pages(慢 100x) → inference 不報錯只是慢得詭異
我那 28 分鐘 hang 不是 hang,是在等 NVMe I/O。ollama ps 顯示 14% CPU / 86% GPU 就是 swap 警訊。
4. OpenAI-compat 端點吃掉 options
# ❌ 錯:options 被丟掉
url = "http://dgx:11434/v1/chat/completions"
# ✅ 對:用 native /api/chat
url = "http://dgx:11434/api/chat"
body = {
"model": "gemma4:31b",
"messages": [...],
"stream": False,
"think": False,
"keep_alive": -1,
"options": {
"num_ctx": 8192,
"num_predict": 1024,
"temperature": 0.3,
},
}
如果你的 client 寫成 OpenAI SDK 風格(用 openai.ChatCompletion.create),無論你怎麼塞 options 都會被 ollama 端默默丟掉。寫了不會報 warning,這是最陰險的設計。
5. MAX_LOADED_MODELS=3 在 121GB unified 不現實
OLLAMA_MAX_LOADED_MODELS=2 ← 不要 3
Gemma4 31B 占 ~26GB(context 鎖 8K 後)+ Qwen35-reasoning 53GB ≈ 79GB。再來個第三個就擠爆。實務上 GB10 同時保 1 個大模型 + 1 個 embedding(bge-m3 ~1.3GB)才合理。
最終配置
DGX 端 systemd override
/etc/systemd/system/ollama.service.d/override.conf:
[Service]
Environment="OLLAMA_FLASH_ATTENTION=0"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_NUM_PARALLEL=1"
Environment="OLLAMA_MAX_LOADED_MODELS=2"
Environment="OLLAMA_KEEP_ALIVE=-1"
# 不設 OLLAMA_KV_CACHE_TYPE,維持 f16
sudo systemctl daemon-reload
sudo systemctl restart ollama
Client 端最低必要改動
不管你用什麼語言,做兩件事:
- endpoint 從
/v1/chat/completions改/api/chat - body 加
stream: false+keep_alive: -1+options.num_ctx: 8192+options.num_predict: 1024+think: false
量化效果
| 指標 | 修復前 | 修復後 |
|---|---|---|
| DGX free RAM | 5 GB | 62 GB |
| Gemma4:31b 占用 | 72 GB(context 256K) | 26 GB(context 8K) |
| 第一筆 LLM call | 28 分鐘卡死 | 30-50 秒 |
| Load average | 78 | 正常 |
| Swap | thrashing 持續增長 | 殘留 2.1GB 不再增長 |
光是改 client 一行 endpoint,DGX 釋出 57GB unified memory,順便讓 memhall(記憶層)的 bge-m3 embedding 服務也從 timeout 恢復成 7 秒回應。
適合誰看這篇
- 有 DGX Spark / GB10 機器,正在跑 ollama + dense model 的人
- 個人 PKI / 家庭 AI 中心 builder
- 會被「ollama 預設應該都很 sane」這個假設坑到的人
不適合:
- 跑 discrete GPU(你大部分問題不會發生)
- 跑 cloud-managed LLM(Anthropic / OpenAI)的人
教訓(按發生順序)
1. 「我以為配置都對」的傲慢
跑 mk-brain enrichment 用 /v1/chat/completions 跑了一個多月,自以為 options.num_ctx=4096 有生效。實際上 ollama doc 早就寫了「OpenAI-compat 不接受 options」。我從來沒回頭讀過自己呼叫的 endpoint 的 limitation 段。
→ 用任何第三方 endpoint,至少讀過一次 official doc 的 limitation。不要靠記憶。
2. Silent failure 比 Loud error 危險百倍
ollama 收到 options.num_ctx 後默默丟掉——不回 warning、不寫 log。下游收到看似正常的 response,pipeline 跑得「順利」。從 4/25 壞到 4/27,3 天沒發現。
→ Silent skip / silent default / silent fallback 是惡魔。寫自己的系統時,下游收不到合法值就要 raise,不要靜默 fallback。
3. 多 agent 的真實價值是「異議」不是「人多」
Codex + Gemini + 一個社群 X 抽樣三方都推「FA=1 + KV q8_0」這個社群常識答案。如果我聽三方共識,今晚會去重啟 ollama 開 FA、改 KV cache type,結果就是踩進 SM121a 不支援 + Gemma4 q8_0 KL 損失 + ollama #11949 bug 三個坑。
是另一個 agent 用 8 個 GitHub issue link + KL divergence benchmark 推翻了三方共識。
→「多 agent 投票」不等於「正確」。一個帶完整 evidence 的 dissent 比三個帶模糊共識的 confirm 更有價值。Mandatory Dissent 不是治理形式主義,是真的會救你。
4. 撞牆停手規則救了我兩次
第一次 backfill 28 分鐘 + 第二次 8 分鐘卡死——當下我很想再試 limit 5 / 換 model / 各種 hack。是「兩次失敗必須停」的個人規則逼我停下來改去檢查 DGX 狀況。停下來 + ssh 進 DGX = 找到 unified memory 滿、context 256K 的關鍵 evidence。
→ 撞牆規則不是束縛是保護。沒它今晚會繼續 hack pipeline code,永遠看不到 DGX 是真因。對個人 builder 來說,定義「撞牆訊號」並強制自己 stop 很重要。
5. unified memory ≠ discrete VRAM 的 mental model 切換
Discrete GPU(H100/A100/RTX 4090)VRAM 滿 → CUDA OOM → 程序 crash → 你立刻知道。GB10 unified memory 滿 → Linux kernel swap → 從 NVMe 讀 evicted pages(慢 100x) → inference 不報錯只是慢得詭異。
我那 28 分鐘 hang 不是 hang,是在等 NVMe I/O。ollama ps 顯示 14% CPU / 86% GPU 就是 swap 警訊。但我習慣 discrete GPU 的 OOM 心理模型,看不出這個訊號。
→ 換新硬體架構時,要主動學新的故障 signature,不要套舊心理模型。
6. Quality Gate 沒「擋太多」的 alert 是設計缺陷
我的 Ghost Quality Gate(C02 core_insight too short / O02 fewer than 2 summary_lines)是 binary block——空就擋、有就放。但沒有「給開發者一個信號」:擋掉的 20 筆書籤沒有 retry queue、沒有 dashboard、沒有日報提醒,只默默累積。
→ 任何 Quality Gate 設計都該有「block rate 異常」的 alert。當 24 小時內 publish=0 但 sig≥55 候選 ≥10 筆時,要主動通知。沒這個 alert 我會繼續壞 3 天 5 天。
7. 配置層 vs 程式層的 root cause 分辨
mkbrain pipeline 真因有兩層:
- 應用層:SQL filter 漏掉
rss_prefetched(pipeline 自己的 bug) - 基礎建設層:ollama 預設 context 256K + endpoint 吃掉 options(DGX/ollama 配置坑)
如果只看到應用層改 SQL,會以為修好了——但 backfill 還是會卡死。真正的 root cause 通常跨層,不會只在你最熟的那層。
→ debug 時刻意 ssh 到 infrastructure 看一眼,不要只在 application code 裡打轉。今晚要不是被 backfill 28 min 逼著去 DGX 看,這個 endpoint bug 會永遠藏著。
8. Evidence 應該寫進長期記憶不是只寫 commit
我把今晚事件寫成 episode 進個人記憶層(memory hall),標 tags + metadata。3 個月後我或其他 agent 再撞到類似症狀,搜「DGX num_ctx 256K」就能查回來。
如果只寫在 git commit message + Slack,3 個月後找不到——commit 會被埋進 100 個其他 commit 之間。
→ Commits 是 code 層的歷史,記憶層是 thinking 層的歷史。事故 / 決策應該兩邊都寫。
本文記錄 2026-04-27 個人 PKI(mk-brain)事故修復。完整七位一體 agent council 紀錄在我自己的 ~/Documents/agent-council/2026-04-27-mkbrain-publish-broken/ 中保存。
Maki / makichiang
blog.chibakuma.com / maki.tw