在你自己的決策上微調 Laya
在 typed-decisions 基準上,基礎 checkpoint 零樣本的得分接近隨機 —— 0.36 和 0.35,對 0.318 的 隨機基線而言 —— 而微調後的 checkpoint 在同一批 2,000 個決策上達到 0.766,高於 TypeSafe Jev 已釋出的 0.727,也高於 0.735 的教師自一致上限。微調承載了大部分價值,公開的 微調 notebook 在 Kaggle 免費的 2xT4 GPU 上跑完整個迴圈:構建資料集、用 RLCD 訓練、擬合校準溫度、評估,並把 結果推到 Hub。這一頁講解那個 notebook,並指出當資料換成你自己的之後,哪些部分仍然關鍵。
另一個完整示例 —— 在單張 16 GB GPU、不花錢調 API 的情況下訓練一個瀏覽器智慧體決策頭 —— 在 把 Laya 微調成瀏覽器智慧體決策頭。
notebook 按順序做了什麼
| # | 步驟 | 發生的事 |
|---|---|---|
| 1 | 環境 | 斷言兩張 T4 GPU 都可見且已分配 |
| 2 | 安裝 | laya、transformers、datasets 以及訓練依賴 |
| 3 | 預處理 | 1,200 個訓練用例(6,000 個型別化決策)變成帶軟目標的 tokenized 條目,寫成磁碟檔案供兩個 DDP rank 使用 |
| 4 | 訓練 | 在 torchrun --nproc_per_node=2 下跑 train_ddp.py,四個 epoch |
| 5 | 校準 | 每種型別一個溫度,在訓練前就留出的切片上擬合(在訓練指令碼內部,最後一個 epoch 之後) |
| 6 | 評估 | 微調後的 checkpoint 回答官方 test 劃分 —— 400 個用例、2,000 個決策 —— 並給出每用例延遲 |
| 7 | 指標 | 準確率、軟準確率、Brier、ECE、score MAE、檔位內一位、KL/TV 和延遲分位數;一張與 Jev 和教師上限正面比較的表 |
| 8 | 釋出 | (可選)用這次執行自己的數字生成一張模型卡,把資料夾上傳到 Hub |
| 9 | 報告 | benchmark_report.json,含指標表和各工作流的準確率 |
Kaggle 設定:Accelerator 選 GPU T4 x2,Internet 設 On。輸出落在
/kaggle/working/laya_finetuned_typed_decisions。
訓練配方
RLCD 在基準的金標分佈上訓練,而不是在硬標籤上:每個條目都帶上教師給每個選項分配的機率, 損失的兩半都讀這個目標 ——
- 一個策略梯度項,作用在取樣得到的帶噪 logit 投影上(GRPO 風格:每個條目四個樣本,探索 噪聲從 0.4 退火到 0.1),由適當的評分規則給獎勵(球面 0.75,排序機率 1.0);
- 一個全權重的軟交叉熵項,對著同一個分佈。
notebook 為 16 GB 顯示卡設定的旋鈕:
| epochs | 4 |
| 有效批大小 | 64 個序列(每微批 8 個,2 張 GPU,4 步累積) |
| 學習率 | encoder 2.5e-5,head 1e-4 —— AdamW,餘弦排程 |
| 記憶體 | fp16 autocast,encoder 和 head 上開梯度 checkpointing,梯度範數裁剪 1.0 |
| 序列預算 | max_len 1024,head_max_len 256,max_tokens_per_batch 4096 |
2xT4 上的執行時間,演示是分鐘級,真實資料是小時級:演示的 6,000 個決策大約 4–6 分鐘,而對約 30k 個問題跑四個 epoch 大約 4–5 小時。
要把它指向你的資料,替換那兩個 load_dataset 呼叫,並保持行 schema:每個用例帶 state、
questions 和 gold(教師對每個問題給出的機率),前處理器把它們變成條目。問題型別是
choice、score 和 noul;任何你能用它們對某個狀態表達出來的東西都可以。
校準是這次執行的一部分
這是照搬迴圈時最容易被丟掉的一步,而一旦有人用置信度做門控,它就是關鍵。
notebook 在把訓練資料分片到各個 rank 之前,先切出一份校準切片(最多 400 個條目,或 10%, 固定種子,每個 rank 上完全一樣)。在執行已經訓練過的條目上擬合溫度,測的是擬合本身而不是校準 —— 模型在那些條目上幾乎必然正確,最佳化器沒有什麼需要軟化,於是返回一個退化的 scale。
最後一個 epoch 之後,rank 0 用 LBFGS 在 log 溫度上擬合每種問題型別一個溫度(choice、
score、noul),鉗制在 [0.1, 10](切片不足十個條目時為 1.0,擬合拋異常時為 1.2)。
這些值作為 temperature 寫進 rl_agent_config.json,notebook 在同一次寫入裡移除任何繼承來的
temperature_by_options:那些舊的分桶值在推理時優先,會靜默蓋掉新的擬合。
溫度縮放不改變 argmax —— 也不改變準確率;變的是置信度。checkpoint 按交付狀態是過度自信的, 所以依賴任何閾值之前先擬合,並在留出資料上評估結果,再聲稱有改進。配置持久化的迴歸測試不需要 下載或訓練就能跑:
python tests/test_calibration_persistence.py
信任它之前先評估
評估是對官方測試劃分的一次完整遍歷:400 個用例、2,000 個決策,橫跨 Agent Trace
Observability、Customer Service、Invoice Processing 和 Security Incidents。它計算準確率、軟
準確率、Brier、ECE(通過 laya.common.ece_score)、score MAE、檔位內一位和延遲分位數,然後
構建一張正面比較表,其中的參考行是固定的:
| model | kind | accuracy | ECE |
|---|---|---|---|
| TypeSafe Jev 1.13.0 | general | 0.727 | 0.144 |
| ModernBERT-base (149M) | specialist | 0.646 | 0.179 |
| Teacher Self-Agreement | ceiling | 0.735 | — |
| Laya (published checkpoint) | fine-tuned | 0.766 | — |
你自己執行裡的 Laya 行也以同樣方式計算 —— notebook 用這次執行自己的數字重建那張表。兩個值得
照搬的習慣:把你關心的切片(一種語言、一個工作流)留在留出資料裡,並把校準和準確率一起報告,
因為訓練訊號是一個分佈,而不只是一個標籤。等你有了數字,倉庫的
Discussions 是分享它們的地方;基準和已知
限制在倉庫根目錄的 BENCHMARKS.md 裡。
推到 Hub
釋出那一格是迴圈的最後一公里,它刻意做得無聊:
- 在 Kaggle 裡放一個可寫的
HF_TOKEN(Add-ons → Secrets)。缺了它那一格會丟擲錯誤,並給出 確切的指示。 - 設定目標倉庫 —— 交付的那一格預設用專案自己名稱空間裡的一個名字,所以執行前先改掉它。
- 執行它。它寫出一張模型卡,卡上的數字來自這次執行的對比表,然後上傳
model.safetensors、encoder/、tokenizer/、rl_agent_config.json、那張卡和基準報告。
結果載入起來和任何其他 checkpoint 一樣 —— 沒有微調專用的 API:
import laya
agent = laya.load("your-org/your-checkpoint") # the repo you just pushed
result = agent.predict(state, questions)
一個滾動更新的 checkpoint_latest/ 在每個 epoch 後會被覆蓋,所以 Kaggle 超時或 OOM 的代價是
一個 epoch,而不是整次執行。
要當心什麼
- 迴圈的好壞取決於目標。 RLCD 在你的問題上模仿教師的分佈;訓練前(或訓練的同時)收集教師 置信度,並把它們的質量當作上限。
- 校準切片小是故意的。 最多 400 個條目或 10% —— 夠擬合三個按型別的標量,不夠用來驗證。 自己留出一份評估資料。
- 你的標籤必須能放進三種原語。 如果你的決策不是 choice、不是量表、也不是是非機率,先把 它塑造成其中一種。兩個尖銳的邊緣已經有記錄:選項多會削弱置信度的選擇 (#394),強制選擇的否定可能跟著問題走 而不是跟著狀態走(#377)。
- 要交付配置,不只是權重。 被移除的
temperature_by_options就是那種如果在一個照搬的配置 裡存活下來、就會靜默讓校準失效的東西。