事故是怎麼發生的

07-13 22:10,tool-calling adapter 300 次迭代訓練完成,隨即 fuse 成 gemma4_e4b_agent_fused(這個生產模型後來沒有問題)。22:42, RLCF evolution demo 啟動,它呼叫的是同一支訓練腳本 gemma4_e4b_mlx_train.py,但沒有帶 --adapter-path

訓練腳本的預設值剛好指向 gemma4_e4b_agent_lora/—— 跟生產 adapter 同一個目錄。10 次迭代跑完,val loss 是 1.059, 接近未訓練狀態的底線,但沒人知道這個數字意味著什麼, 因為腳本靜默寫入、沒有任何確認步驟。

為什麼沒有立刻發現

fuse 發生在 22:10,覆寫在 22:42。生產 fused 模型用的是 22:10 的乾淨 adapter, 所以 8080 server 繼續正常跑。當天跑的所有 server-side 測試都過。 問題被隱藏在 adapter 目錄裡,只有直接用 adapter 跑回歸測試才會看到。

「server 正常」和「adapter 目錄正常」是兩件事——事後想很明顯, 事前根本不會去想。

偵測:「舊 holdout 突然過了」是第一個線索

隔天巡檢時,用被覆寫後的 adapter 重跑 verify_contrastive_grounding,得到 present 3/5—— 歷史基準是 5/5。同時發現 refusal holdout 裡有一條 translate 題「突然通過了」, 這條在 v5d 訓練後一直是已知的 fail。

第一反應是「修好了」。但停一下再想:translate holdout 本來是被 tool-calling 訓練引入的「web_search 替代病」,從來沒有主動修過。 一個從來沒動過的 holdout 憑什麼突然通過?

答案是:val loss 1.059 的 adapter 幾乎等於 base model。 base Gemma-4 E4B 本來就會拒絕 translate 任務, 所以通過了 refusal holdout——這不是修好了,是 adapter 根本沒有效果。 「舊測試突然過了」不是喜訊,是警訊。

交叉驗證定案

拿生產 fused server(8080)跑同兩個案例:book_flight 和 translate_text 的 present 案(要求呼叫工具)。server 兩個都正確呼叫,通過。 同 system prompt 條件下 server 過、adapter 側 3/5——這就是「權重分岔」的直接證據。

最後從 logs/evolution_run.log 找到 「Saved final weights to gemma4_e4b_agent_lora/」這行,timestamp 22:42, 和 adapters.safetensors 的 mtime 一致。事故時間線定案。

還原:checkpoint 是唯一救援路

訓練時有設 --save-every,所以 300 次迭代的中間點都還在。 把覆寫後的壞 adapter 另存為 rlcf_demo10_clobbered_adapters.safetensors 留存記錄,然後 cp 0000300_adapters.safetensors adapters.safetensors

還原前驗 checksum 和 config 相容性(rank 16、35 層匹配)。 還原後三支回歸:restraint 8/8、refusal 4/5(translate holdout,同 v5d 基準)、 grounding present 4/5 / absent 4/5——回到 v5d 的已知 profile。

防再犯:一行 diff 解決

gemma4_e4b_rlcf_engine.py 的 train_cmd 改為顯式帶 --adapter-path gemma4_e4b_agent_lora_rlcf。 RLCF 的輸出從此寫到自己的隔離目錄,不會碰到主 adapter。

其實這是很簡單的修法。問題是原本沒有人想到需要這樣寫—— 因為兩個用途共用一個輸出路徑,在「只有一條腳本」的早期完全合理, 等到有第二個呼叫者出現,才變成定時炸彈。

關鍵教訓

自動化觸發器必須顯式指定輸出目錄:訓練腳本的預設值是為互動式使用設計的,不適合被自動化流程直接呼叫。兩種用途共用一個輸出路徑是定時炸彈。

--save-every checkpoint 是事故救援的唯一路:這次 300 次迭代能完整還原,全靠中間點都還在。如果沒有 checkpoint,只能重新訓練。永遠保留。

量測前先核對 mtime:跑回歸測試前確認 adapters.safetensors 的修改時間是否晚於已知的訓練完成時間。mtime 異常就停下來查。

「server 正常」不代表 adapter 目錄正常:fused 模型和 adapter 目錄是兩個獨立的生產物。fuse 發生在覆寫之前,生產不受影響;但這個「偶然的時間差保護」不能依賴。

「舊 holdout 突然過了」先懷疑量錯對象:一個從來沒有主動修過的 fail 突然通過,最可能的解釋是測試沒有測到真正的模型能力。先懷疑、再慶祝。

來源:個人開發日誌 2026-07-17 · Gemma-4 E4B Agent Kit · adapter 還原 v5d 基準 · 0000300 checkpoint 救援