開工前的意外:基礎模型早就從 E2B 換成 E4B 了
動手寫資料之前照慣例先核對 repo 狀態。結果發現自上次 commit(2026-07-11)以來已有 11 個新 commit,對應 2026-07-13 一段沒有記憶記錄的工作——基礎模型從 E2B(2.5GB) 真的換成了 E4B(3.9GB)。不是改名,是實際更大的 checkpoint;訓練腳本改名成 `gemma4_e4b_mlx_train.py`,所有 verify 腳本的預設也已改指向 E4B。
同時發現一條新支線:`gemma4_e4b_rlcf_engine.py`(沙盒編譯器回饋 RL 進化引擎)+ `generate_rsi_dataset.py`(100 筆遞歸自我提示合成資料)+ SQLite episodic memory, 走 `{thinking, tool_calls}` 格式,跟本次鎖定的 aider whole-file 16 題評測是 不同賽道、還沒整合。跟使用者核對方向:把計畫改蓋在 E4B 上,而非沿用舊 plan 的 E2B。
Stage 0 E4B 基準:整體弱了,但目標題的落差縮小了
用 E4B tool-calling 側 fused 模型(角色等同舊專案的 v3-fused)當基準,跑 `evals/run_eval.py --repeats 3`。結果:all-pass 5/16、pass@3 6/16、pass@1avg 5.67。 比舊 E2B v3-fused 基準(7/16、9/16、8.0)整體較弱。
但從目標五題(03/12/13/14/16)的角度看,E4B 已經免費修好了其中的 12 和 13 (過去在 E2B 上分別是 0/3),代價是在其他題退步(`04_fix_exception` 從 PASS 掉到 0/3)。 真正需要 coding_v2 訓練才能修的落差縮小為 03/14/16 三題。
不只是量——四類任務形狀的落差
逐題讀 03/12/13/14/16 各自的 `task.json`、`workspace/` 和 `checks/` 之後, 發現舊 12 筆資料除了量少,還缺了三種任務形狀:
`16_parse_duration` 需要多元件任意順序組合、去重、未知符記規則,舊資料教的是 單一規則二選一。`14_count_level` 要修既有 bug,舊資料教的是從 stub 從零實作。 `12_write_tests_stack` 要 mutation-testing 等級的完整覆蓋,舊資料教的是 選對例外型別。`13_trace_qa` 是 3 層呼叫鏈,舊資料最深只到 2 層。
所以問題不是「12 筆換成 50 筆同類型資料就好」,而是要補四種具體不同的推理方向。
50 筆資料設計:四類新任務形狀
新增四類(共 38 筆)+ 原樣複製舊 12 筆 = 50 筆,用新腳本 `coding_contrastive_v2_to_kit.py`(不 import 舊 REJECTED 檔):
`BUGFIX_TASKS`(12 筆/6 對):6 個場景——標籤比對、溫度門檻、使用者名稱唯一性、 分頁計算、運費計算、config 行解析,每對共用同一段「用錯策略的既有程式碼」, 配兩種不同規格要求不同修法,直接對齊 `14_count_level` 的任務形狀。
`MULTI_COMPONENT_TASKS`(10 筆/5 對):格點移動、資源配額、日期偏移、權限字串、 購物車字串,每對鎖定一個精確度軸(重複堆疊 vs 拒絕、大小寫、空白嚴格度、必填 vs 預設、 未知符記 raise vs 忽略),對齊 `16_parse_duration` 的多元件組合。
`COMPREHENSIVE_TEST_TASKS`(10 筆,不成對):10 個小型 stateful class (BoundedQueue / RateLimiter / LRUCache / PriorityQueue 等),prompt 逐項列出 要測的行為面向 + 最少測試數,回應示範真的測到每個面向,對齊 `12_write_tests_stack`。
`DEEP_CHAIN_TASKS`(6 筆/3 組×2 變體):3 層檔案呼叫鏈(review 提交/訂位鎖定/ 檔案上傳掃毒),單問 / 複合問成對,對齊 `13_trace_qa` 的呼叫深度。
寫資料時踩到一個坑:heredoc 用 quoted `<<'PYEOF'` 時完全不解釋跳脫, 多寫了一層反斜線;含正則反斜線的內容放進非 raw 的 `'''...'''` 觸發 Python SyntaxWarning(invalid escape sequence)。都在當下用 `-W error::SyntaxWarning` 抓到並修正(改用 `r'''...'''`)。全量 apply_chat_template 驗證:50 筆零失敗、 序列長度 825–1395 tokens,全部低於 2048 的訓練過濾門檻。
訓練、快測、全套守門:all-pass 5/16→13/16
訓練 90 iters(比 v1 的 60 iters 多,因資料變多到 50 筆,讓平均曝光落在 ~13 epoch 而非過擬合的 ~40 epoch)、`--save-every 20`。val loss 2.84→0.997,peak mem 9.08GB, 無當機。訓練腳本的 `hf_repo_to_path` 只吃 HF cache 裡的 repo id——踩了一次坑, 直接吃本地 prepare 過的目錄路徑不行,改用預設值才對。
fuse 90-iter checkpoint → `gemma4_e4b_agent_fused_coding_v2/`,Stage 2 單 repeat 快測五個目標題:14 轉 PASS,進入 Stage 3。Stage 3 全套 16 題 × 3 repeats: all-pass 13/16、pass@3 13/16、pass@1avg 13.0(vs E4B 基準 5/16)。 9 題意外通用(01/04/05/06/09/10/11/15),03/16 仍 0/3,12 從 3/3 掉到 0/3。
03 和 16 的 0/3 不是訓練問題,是 max_tokens 截斷
沒有直接設計新資料,先讀了 Stage 3 run 裡 03/16 的原始 `aider_stdout` 逐字稿:
`16_parse_duration`:模型寫到 94%(16/17 行)正確實作時被截斷。邏輯完全正確 (regex 抓 `(\d+)([hms])`、`unit_seen` set 去重,逐字對應訓練資料的手法), 純粹是 `max_tokens=4096` 不夠用。
`03_add_function`:模型把整個輸出預算花在反覆糾結一個想像出來的歧義("KB" 該切成 K+B 還是整個當 unit),三次 repeat 都在給出任何程式碼之前就撞到 token 上限—— 不是不會,是收斂太慢且沒有輸出餘裕。
把 `evals/model_settings.yml` 裡只有 `gemma4_e4b_agent_fused_coding_v2` 這條的 `max_tokens` 從 4096 調到 8192(其他模型設定不動),沒有重新訓練。03/16 各自單測 × 3 repeats 全部轉 PASS;全套 16 題重跑確認無新退步: all-pass 15/16、pass@3 15/16、pass@1avg 15.0。 唯一剩餘:12_write_tests_stack 的機率性手誤(詳見另篇)。
重大範疇警告:這顆 fused 模型不能替換主 tool-calling 側
`gemma4_e4b_agent_fused_coding_v2` 是純 coding 獨立訓練——base 是原生 E4B、 完全沒摻 v5d 等價的 tool-calling 資料。只驗 aider whole-file 編輯任務, 沒跑 `verify_restraint`/`verify_refusal_generalization`/`verify_contrastive_grounding` /`verify_server_toolcalling`。
直接用它替換主 fused 會犧牲 tool-calling 能力。正確做法:兩個模型分開部署 + 路由,或驗證合併是否安全再考慮 fuse 成單一部署。
關鍵教訓
任務形狀比任務數量先:12→50 筆有效,不是因為量純粹多了,而是補上了四種之前完全沒教的推理方向。量充足但形狀錯,仍然零遷移。
全套守門看到 0/3 先讀逐字稿再下結論:03/16 的 0/3 直覺像「模型還沒學會」,實際是輸出被截斷或收斂太慢,一行 yml 設定就修好,不需要重訓。
max_tokens 要跟訓練資料的回應長度一起設計:新資料的 worked-example 比舊版長,deploy 設定沒跟上,eval 就誤判成訓練失敗。
E4B 比 E2B 便宜修好了目標題裡的 12/13:換更強的 base model 有時比繼續推 SFT 更有 ROI。新基準要先跑過,再決定 SFT 的目標範圍。
獨立 adapter 的範疇警告要明確記錄:分開訓練能解域衝突,但用的人要知道「這顆不是完整的生產模型」,不然上線就踩坑。