監督式微調 (supervised fine-tuning, SFT) 是指準備一批成對的輸入與目標輸出,讓預訓練模型繼續學習。前幾天不論使用 self-consistency、Best-of-N 或 self-refinement,增加的都是 inference-time compute,模型權重始終沒有改變;Day 18 把可追溯的 teacher data 準備好以後,今天才第一次讓 Qwen3-0.6B-Base 這個推理模型開始跑 forward、backward 與 optimizer update 等微調過程。
對語言模型而言,SFT 依然是 next-token prediction。Prompt 負責提出問題,response 則是希望模型模仿的 target;訓練時把 prompt tokens 從 loss 遮罩掉,只計算 response tokens 的預測誤差,再用梯度調整模型參數。概念上可以把它濃縮下面幾行:
labels = input_ids.clone()
labels[:prompt_length] = -100 # prompt 不計入 loss
logits = model(input_ids).logits
loss = cross_entropy(logits, labels)
loss.backward()
optimizer.step()
實際程式還要處理 padding、batch、mixed precision、梯度累積與 checkpoint,不過最佳化目標沒有因此改變:提高 teacher response 每個 token 的機率。Training loss 下降表示模型更會預測這批 targets,尚未回答它是否學到正確的推理方法,更無法保證它遇到沒看過的題目時會答得更好。
這次使用 Day 18 凍結的 320 筆 train examples。它們來自 8,192 個本機 GPU-generated candidates,經 final-answer verifier 篩選後再與 80 筆 calibration data 分開;calibration、development 與 sealed test 都沒有用來更新權重。每筆訓練 target 保存完整 trajectory,而非只留下最後答案。
你我大概可以想見,「模仿」這兩個字裡藏著某些問題。320 筆資料的 final answer 全部通過 verifier,中間步驟獨立驗證的數量仍是 0,而且其中 300 筆曾由 deterministic repair 補上標準的 \boxed{} 結尾。Repair 沒有改寫前面的 reasoning,SFT 卻會逐 token 模仿整段 target;final answer 的可信度、推理過程的可信度與輸出格式,從一開始就是三個不同層次。
正式設定沿用 pinned revision da87bfb 的 Qwen/Qwen3-0.6B-Base。這次直接更新全部參數,沒有使用 LoRA;模型參數以 float32 保存,forward 與 backward 在 RTX 3090 Ti 上使用 CUDA bfloat16 autocast。Max sequence length 固定為 512,prompt 與 response 上限分別是 256 與 255 tokens,micro-batch size 為 1,gradient accumulation 是 4,optimizer 使用 AdamW,learning rate 為 1e-5,weight decay 為 0,gradient norm cap 為 1.0。
我在此將訓練過程拆成三個部分,8 筆資料的 16-step overfit sanity 先確認 rolling loss 真的下降;32 筆資料的 16-step pilot 再確認 loss 有限、顯示卡記憶體沒有超過 85% 上限;兩道 gate 都通過後,才讓全部 320 筆資料進入 160-step primary run。
到了此段,Primary run 的 loss 才有了可以解讀的位置。它從 0.8555 降到 0.1809,最低一度來到 0.0909;整段訓練一共監督 104,474 個 response tokens,花了 177.7 秒,peak reserved VRAM ratio 是 0.561。這是一個明確的 optimization success:模型愈來愈能預測 Day 18 提供的 trajectories,訓練也沒有發散或超出記憶體上限。
其他工程面的證據同樣過關,最大參數絕對更新量為 0.000571,排除了 optimizer 根本沒有動到模型的情況。實驗當時保存的 full-parameter resumable checkpoint 是 7,164,344,214 bytes,包含 optimizer、RNG 與 data cursor 狀態;重新載入後,同一 probe 的 logits maximum absolute difference 為 0.0。
所以這次確實完成了 SFT,也得到一個真正更新過權重、當時能正確載回的模型版本。接著回到真正重要的問題:這個模型究竟學到了什麼?
第一輪 evaluation 還在回答之前先出了一點狀況。Primary run 已完成全部 160 steps,卻在 640 題 evaluation 遇到一段括號不平衡的 expression,未捕捉的 tokenize.TokenError 讓整批評估中止。修補只讓 evaluator 把這類輸出歸為 unsupported_expression,沒有改資料、seed、超參數、generation 或模型權重;失敗 run 與成功 replay 的 model SHA-256 完全相同。Verifier 也是實驗儀器,模型答得再古怪,它都應該留下可分析的失敗紀錄,而非帶著整場實驗一起倒下。
正式比較使用預先凍結的 640 道 candidate-unseen internal development problems。這些題目沒有參與 Day 18 的 teacher candidate 生成,Base 與 SFT 也使用相同 prompt、greedy decoding、512 output-token cap 與 final-answer verifier。MATH-500 繼續封存,sealed-test inference count 是 0。
讓我們來分析結果,看看如何解讀 loss curve 的表現:
Base trajectory SFT
correct 103/640 54/640
strict format 28/640 633/640
pass@1 16.09% 8.44%
模型顯然學到了某種穩定行為,只是最成功的部分並非數學能力。640 份輸出裡有 633 份符合 boxed-answer contract,Base 只有 28 份;相同 checkpoint 的正確題數卻從 103 降到 54。Loss 下降證明 target imitation 成功,633 份合規答案證明輸出 contract 幾乎學會,54/640 才回答目前的 candidate-unseen correctness。把前兩個成功直接接成第三個結論,正是後訓練實驗最容易犯的錯。
「會不會是格式規則太嚴苛,才讓 Base 看起來比較好?」這個疑問可由 error taxonomy 來回答。Base 的 103 個正解裡,只有 8 個同時符合 strict format,另外 95 個雖然格式不合規,final answer 仍被判為正確;SFT 的 54 個正解也全部被計入。換句話說,103 對 54 比的是答案正確性,28 對 633 才是格式合規,兩套計分沒有混在一起。
逐題配對後,可以觀察幾個變化:537 道 Base 原本答錯的題目中,SFT 救回 30 道;103 道原本答對的題目中,它破壞了 79 道;另外有 24 道維持正確、507 道維持錯誤。
Base wrong -> SFT wrong 507
Base wrong -> SFT correct 30
Base correct -> SFT wrong 79
Base correct -> SFT correct 24
淨差是 -49 題,也就是 pass@1 下降 7.66 個百分點。10,000 次 fixed-seed task bootstrap 得到的 95% interval 為 [-10.78, -4.53] percentage points,exact two-sided McNemar test 的 p-value 是 2.96e-6。這已經不是小樣本裡剛好少對幾題的模糊訊號;在這份凍結的 development contract 下,trajectory SFT 的正確率明確低於 Base。
輸出長度提供了另一條線索,Base 平均生成 511.5 tokens,中位數正好撞上 512-token cap;SFT 平均只剩 16.4 tokens,中位數是 10,總 output tokens 從 327,330 降到 10,488。模型快速學會用很短的文字交出 boxed answer,evaluation wall time 也跟著縮短,只是當正確題數幾乎腰斬時,這還稱不上效率提升,更不是 serving throughput benchmark。
為什麼 320 筆資料會造成這種結果?目前的實驗沒有拆解詳細因果機制,我個人的解讀是:格式訊號既一致又容易學,300 筆 targets 都曾補上相同型態的結尾;數學內容則跨越不同題型,trajectory 還只有 final answer 經過驗證。於是 loss 可以穩定下降,模型也迅速收斂到簡短、工整的回答習慣,卻沒有取得足以支撐 candidate-unseen reasoning 的監督訊號。
換句話說,本日的實驗內容說明 SFT 確實讓模型聽懂了我們反覆教它的部分,但我們似乎更該掌握訓練或微調前的 target 裡究竟該放什麼?如果 supervision 改成只給答案、保留完整軌跡,或把軌跡整理成步驟,模型最後學會的又會是哪一步驟?