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。

Ollama 默默把你的 Gemma4 KV cache 撐到 256K:DGX Spark 配置優化的真實坑

我家裡那台 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

  1. OLLAMA_FLASH_ATTENTION=1 不該開 — GB10 是 SM121a,不是標準 Blackwell。Flash Attention / FlashInfer 的 SM120 wiring 在 CUTLASS issues 上被明確排除。
  2. OLLAMA_KV_CACHE_TYPE=q8_0 對 Gemma4 不是 lossless — Gemma4 q8_0 KL divergence 0.108(明顯損失),加上 ollama issue #9683/#11949 有 decode -80% 的 bug 還沒確認修。
  3. OpenAI-compat 端點 /v1/chat/completions 不接受 options.num_ctx — 你以為設了 4K context,其實 ollama 用 default 對 ≥48GiB device 自動拉到 256K。Gemma4 31B 在 256K context 的 KV cache 吃 ~21GB。
  4. 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 端最低必要改動

不管你用什麼語言,做兩件事:

  1. endpoint/v1/chat/completions/api/chat
  2. 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