把種子釘死:LLM 複現問題的最小修
複現是工程裡最便宜的品質訊號:同一份輸入、同一段程式碼、同一天跑兩遍,結果一致,你才能判斷這次改動是讓系統變好了,還是運氣好了。

把種子釘死:LLM 複現問題的最小修
複現是工程裡最便宜的品質訊號:同一份輸入、同一段程式碼、同一天跑兩遍,結果一致,你才能判斷這次改動是讓系統變好了,還是運氣好了。
LLM 系統裡,「跑兩遍結果不一樣」太常見了,常見到很多人誤以為這是模型的正常屬性。其實多數不一致是可修的。看不見的變數通常只有四個:model、seed/decoding、超時和網路、資料順序。把它們釘死,絕大多數「偶發」就消失了。
先釘死 model
同一個模型名稱(例如 `gpt-4o`、`qwen-plus`)背後可能指向不同版本。供應商升級權重後 API 行為會變,而你的評測腳本毫無察覺。上個月複現率 100% 的回歸用例,換個星期就開始偶發——90% 的情況不是你的程式碼壞了,是 `qwen-plus` 悄悄升到 `2026-06-17` 版了。
修法很簡單:呼叫時檢查回應元資料裡的 `model` 欄位,把它落庫到每次呼叫的日誌裡;更重要的,把精確版本號寫進評測設定檔,而不是靠「現在線上配的是什麼」。CI 裡可以直接斷言:`assert resp.model == EXPECTED_VERSION`,對不上就報 fail。這樣評測結果和模型版本永遠一一對應,三個月後翻報告才知道當時測的是哪一版。
再釘死 seed 和 decoding
temperature=0 並不等於確定性。取樣器、核心實作的浮點路徑都可能引入抖動,同一句話兩次跑出不同標點、不同標點之後的措辭都是「合法」的。絕大多數商用 API 不暴露 seed,這時正確做法是把 temperature 設 0(或最低檔)、top_p 設 1,並在文件中聲明「結果可能仍有 ±1 個 token 的漂移」。評測斷言不能寫在精確字串上——要比對到句子級(embedding 相似度 ≥0.92),要麼只檢查關鍵字欄位(JSON 裡的 status 欄位、工具呼叫名稱)。
自託管時(vLLM、TGI),seed 參數可以真正釘死輸出。這一步對 batch 評測和回歸測試是剛需:同一個 prompt 集合,兩次跑的 token 流必須可以 diff。vLLM 的 `seed` 參數對貪婪解碼(temperature=0)有效,對取樣解碼則取決於 kernel 對亂數流的支援,先在 dev box 上跑兩遍 diff 確認。
給超時和網路兜底
兩次跑的差異裡,一部分不是模型變了,而是請求失敗後被靜默降級:第一次超時、重試走了另一個 endpoint,兩個 endpoint 的模型版本可能差一個 minor;第一次 hang 了 40 秒,第二次正常 2 秒,於是「耗時」這個指標看起來模型退化了,其實只測到了一次網路抖動。
最小修:45 秒 hard timeout(線上是 30 秒),重試上限 1 次,且只重試 HTTP 5xx 和網路斷開。429 是限流,重試會堆積,應該直接記 fail 並警報。失敗就記 fail,不要悄悄重試——靜默重試是最大的複現殺手,因為它把環境問題偽裝成了模型行為。
最後釘資料順序
評測腳本最常見的一行事故:`for item in dict.items()` 或者遍歷 set。Python 3.7+ 的 dict 保序,set 永遠不保序,list 保序但來源如果是 `sorted(key=str)` 之外的任意順序,兩次跑就可能不同。更隱蔽的:如果你的評測資料來自 `requests` 回傳的 JSON,物件解析順序由 JSON 函式庫決定,大部分函式庫保序,但不是全部。
最小修:評測資料一次性 load 成 list,加顯式 `assert len(set(id(x) for x in items)) == len(items)` 防止重複,然後始終按同一個 index 遍歷。`--shuffle` 的 seed 也要釘死,不然每次跑分佈都不同,變異數大到你分不清是模型問題還是抽樣問題。
一個最小模板
import json, random
def run_eval(items, model_name, seed):
random.seed(seed)
out = []
for i, item in enumerate(items): # list, 順序固定
resp = call_llm(model_name, item, temperature=0, top_p=1)
out.append({
"idx": i,
"input": item,
"response_model": resp.model, # 精確版本, 不是 model_name
"tokens": resp.usage.completion_tokens,
"latency_ms": resp.latency_ms,
})
return out
把 `response_model` 和 `seed` 打進每份評測報告裡。三個月後重跑同一組 prompt,這兩行就是你判斷「有沒有偷偷升級」的唯一證據。
最後
模型在升級,供應商在迭代,資料在漂移。你能控制的只有這四行設定:生效的 model 版本、取樣參數、超時策略、資料順序。把它們全部釘死,複現就從許願變成流程,「偶發問題」就從玄學變成調查記錄。
留言區
歡迎分享你的想法!
載入留言中…