Ollama + bge-m3 Embedding 產生 NaN 導致寫入失敗:完整診斷與修復
Ollama + bge-m3 embedding 對長中文文字產生 NaN,root cause 是 flash attention F16 overflow。一個環境變數修復:OLLAMA_FLASH_ATTENTION=0
如果你在用 Ollama 跑 bge-m3 做 embedding,遇到這個錯誤:
failed to encode response: json: unsupported value: NaN (status code: 500)
恭喜,你踩到一個已知但還沒被修好的 bug。這篇記錄我從「完全看不到錯誤」到「找到 root cause 並修復」的完整過程。
症狀
我的 mem0 MCP server 負責 AI Agent 的記憶寫入。幾個月來,add_memory 對長中文文字穩定失敗(短文字偶爾能過),但錯誤訊息只顯示一個莫名其妙的 re module 錯誤——完全沒有幫助。
因為有意義的記憶通常都是長文字(決策紀錄、技術筆記、願景描述),實際使用上大部分有價值的寫入都會失敗。所有 Agent 都回報過寫入失敗,但沒人知道為什麼。
第一層:被吞掉的錯誤訊息
第一個問題出在我的 server.py 裡的錯誤處理函數:
def _safe_error(exc):
msg = str(exc)
msg = re.sub(r"/[^\s:]+\.\w{1,4}", "<internal>", msg) # 用了 re
# ...
但檔案頂部沒有 import re。re 只在另一個函數裡有 local import,scope 不到這裡。
結果:所有 except handler 呼叫 _safe_error(e) 時,NameError: name 're' is not defined 覆蓋了真正的錯誤。你永遠看不到底層發生了什麼。
修復:加一行 import re 到檔案頂部。
第二層:真正的錯誤浮出水面
修完 import re 後,重啟 server,再測一次 add_memory——真正的錯誤出現了:
failed to encode response: json: unsupported value: NaN (status code: 500)
查 Ollama server 的 log:
POST /api/embeddings → 200 OK # 第一次 embedding(dedup 搜尋)
POST /collections/mem0_memories/points/query → 200 OK # Qdrant dedup
POST /api/embeddings → 500 # 第二次 embedding(寫入)
llm embedding error: failed to encode response: json: unsupported value: NaN
第一次 embedding 成功,第二次失敗。bge-m3 對特定輸入產生了 NaN embedding vector。
Root Cause:Flash Attention F16 Overflow
查了 Ollama 的 GitHub issues,找到:
ollama/ollama#13572:bge-m3 produces NaN output in some seemingly unrelated casesollama/ollama#14657:bge-m3 回傳 NaN,nomic-embed-text 正常- PR
#14739:handle NaN values in embedding responses
根因:llama.cpp 在 flash attention 開啟時,embedding 模式(無 KV cache)會做 F32 → F16 轉換。如果中間值超過 F16 最大值(≈65504),就會 overflow 成 Inf,softmax 之後變成 NaN。
長文字(尤其是中文 ~150 字以上)更容易觸發,因為 attention score 的數值範圍更大。
修復:一個環境變數
# 關閉 flash attention
export OLLAMA_FLASH_ATTENTION=0
如果你用 systemd 管理 Ollama:
# /etc/systemd/system/ollama.service
[Service]
Environment="OLLAMA_FLASH_ATTENTION=0"
sudo systemctl daemon-reload
sudo systemctl restart ollama
關閉 flash attention 後,embedding 會稍慢一點,但數值穩定。測試結果:NaN 完全消失,寫入成功。
額外防護:NaN 安全的 JSON 序列化
即使 Ollama 端修好了,server 端也應該有防護。我加了一個 helper:
def _safe_json(obj, **kwargs):
"""JSON dumps with NaN protection."""
return json.dumps(_sanitize_nan(obj), default=str, allow_nan=False, **kwargs)
def _sanitize_nan(obj):
if isinstance(obj, float) and (math.isnan(obj) or math.isinf(obj)):
return None
if isinstance(obj, dict):
return {k: _sanitize_nan(v) for k, v in obj.items()}
if isinstance(obj, list):
return [_sanitize_nan(v) for v in obj]
return obj
你該注意的 warning
如果你的 Ollama log 出現這行:
init: embeddings required but some input tokens were not marked as outputs -> overriding
這本身是無害的(很多 embedding model 都會出現),但它代表 Ollama 正在 override 模型的 tensor 配置。搭配 flash attention 開啟時,這個 override 路徑更容易觸發 NaN。
TL;DR
| 問題 | 解法 |
|---|---|
| Ollama + bge-m3 embedding 產生 NaN | OLLAMA_FLASH_ATTENTION=0 |
| NaN 導致 500 Internal Server Error | server 端加 _sanitize_nan() 防護 |
| 錯誤訊息被其他 bug 覆蓋看不到 | 檢查你的 error handler 有沒有自己壞掉 |
等 Ollama PR #14739 合併後,可以考慮重新開啟 flash attention 測試。在那之前,關掉它是最穩的 workaround。
如果你也在用 bge-m3 做中文 embedding 而且偶爾遇到神秘的 500 error——現在你知道為什麼了。
我是江中喬,一位具有 TPM 與產品管理背景的 AI 系統建構者,目前專注於 AI 認知增強系統與多 Agent 協作架構的設計與實踐。