實驗進度與觀察。最新條目在最上面。
新增 pr-text.yml(job pr-text-hygiene):squash merge 的 commit 取自 PR 標題與內文,原本的 commit-hygiene 看不到。標題與內文只經 env 傳入。兩個 job 共用 .github/scripts/check-disallowed-text.sh 與同一份 pattern 檔。
pilot 重用 AC2 改為 validation-only:archive 整檔照算 SHA-256,但只讀 metadata 與 validation 陣列;結果 JSON 只取 run_name、config、training、logits、environment,不含 metrics。
權重刪除前先確認結果 JSON、manifest、archive 三處一致;曲線索引對非重用點也驗 archive。
曲線索引逐點比對「訓練時記下的抽樣指紋」與「現在重抽的 curve_sample(k, seed)」,numpy 升版造成樣本改變時會報錯;AC2 重用點補上現算的指紋並標註來源。測試裡另釘了 5 組抽樣指紋。
等價判斷新增:torch 與 transformers 版本必須與目前安裝的相同(忽略 +cpu 這類 build 標籤);schedule 層補獨立測試。
TF-IDF 的 100 倍:修正 docstring,PLAN §4.1 註明 RQ3 對 TF-IDF 只報溫度校準後的訊號與 margin,多數類排除在 RQ3 外。
scipy 列入 dev group。
審查實測(seed 42 val,k=100):TF-IDF 未校準 MSP 的 AURC 在純餘弦為 0.0854、100 倍為 0.0533;entropy 為 0.1418 與 0.0539。倍數確實改變未校準訊號,所以 RQ3 不用它們。
關於 test 洩漏:Drew 在 PR #6(AC2 結果)看過 BERT lr 5e-5 的 test 數字。lr pilot 的選擇規則是機械式的(只比 validation 的正確筆數,再比 validation OOS 正確筆數,再取較小 lr),看過 test 數字不會影響它選出什麼,所以不構成洩漏。
.github/workflows/pr-text.yml、.github/scripts/check-disallowed-text.sh、tests/test_text_hygiene.py
API 花費:US$0
2026-09-23(晚):步驟 3 準備,抽樣、步數協定、pilot、執行器與 ModernBERT 相容性試跑
超參數協定寫進 docs/PLAN.md §4.1:步數 = max(S_min, 5 epoch 步數),S_min 與各 encoder 的 learning rate 都只用 validation pilot 選,選定值手動填進 configs/curve.yaml。
新模組:sampling.py(k-shot 抽樣,OOS 用寫死對照表)、steps.py(步數規劃,訓練後核對實際步數)、runs.py(續跑規則與「等價 run」判斷)、protocol.py、pilots.py、curves.py、baselines.py;metrics.wilson_interval。
logits archive 升到 format 2:加三個 split parquet 的 SHA-256;讀 format 1(AC2)時只在 dataset revision 等於鎖定值才從 SPLIT_FILES 補上並標註來源,不符就報錯。
AC2 重用:BERT 若選中 lr 5e-5,曲線的 k=100 三個點與 lr pilot 的 5e-5 點都重用 AC2,前提是逐欄設定、訓練列數、步數與 seed 都對得上,且 archive 的 SHA-256 三處一致;任何一項不符就重訓。對照已提交的三個 AC2 結果 JSON 測過,S_min 取 100、200、400 或不設都判定等價。
基準:多數類與 TF-IDF centroid 在每個 (k, seed) 的同一份抽樣上計算,存成與 encoder 相同的 archive 與結果 JSON。
ModernBERT 相容性試跑(scripts/compat_trial.py、scripts/export_onnx.py),結果在 results/compat/。只跑 50 步與一次 validation,不是正式 pilot 或曲線。
ModernBERT 在 M4 MPS 上可以訓練。 兩個模型同設定:batch 32、max_length 64、50 步、lr 5e-5(試跑用佔位值)、seed 42。
ModernBERT-base
bert-base-uncased
參數量
149.7M
109.6M
每步時間(中位數,去掉前 5 步)
0.234 s
0.139 s
第一步
1.13 s
0.51 s
峰值記憶體(Metal driver,採樣)
4.80 GiB
3.18 GiB
峰值記憶體(存活 tensor,採樣)
2.30 GiB
1.66 GiB
loss(前 5 步 → 後 5 步)
5.22 → 4.22
5.08 → 5.00
NaN
無
無
attention 走 sdpa(MPS 上沒有 Flash Attention 與 unpadding),沒有報錯或退回警告。
每步時間 ModernBERT 約為 BERT 的 1.69 倍。
全量 5 epoch(2,385 步)預估: 直接乘是 559 s,但同法估 BERT 得 332 s,而 AC2 實測是 822 到 886 s(每步約 0.36 s),短試跑低估約 2.6 倍(原因未查:可能是長時間執行的降頻、每個 epoch 存檔與每步記憶體採樣)。用 AC2 實測乘上 1.69 倍,ModernBERT 全量每個 seed 約 24 分鐘 ,以這個數字排時程。
粗估整條曲線(S_min 以 400 計,每個 seed 約 5,400 步):ModernBERT 18 點約 2.7 小時,OOS 消融 3 次約 1.2 小時;BERT 若重用 AC2,剩 15 點約 1 小時。
ONNX 匯出成功,兩種 exporter 都可以。 torch.onnx.export(dynamo 與 TorchScript)加 ONNX Runtime 1.30 CPU,10 筆 validation(padding 到 14)最大絕對 logit 誤差 2.1e-5,argmax 10/10 一致;另換一批 3 筆、長度 25 的輸入也只差 1.1e-5,動態 batch 與長度可用。模型檔約 600 MB。TorchScript exporter 有 TracerWarning(masking 相關),實測沒出錯,但 RQ7 以 dynamo 為主。int8 量化還沒試,留給步驟 6。RQ7 可以維持用 ModernBERT。
TF-IDF centroid 若直接把餘弦(0 到 1)當 logit,validation 的溫度擬合在 k ≥ 5 撞到搜尋下界 T = 0.05;改用 CLIP 慣例的 100 倍餘弦,T 落在 2.9 到 7.2 之間。這是 seed 42、43 在 scratch 目錄的檢查,不是正式基準結果。
磁碟剩約 11 GB。試跑權重與兩個 ONNX 檔(共 1.7 GB)已刪除。
短試跑的每步時間與長時間訓練差很多(見上),時程估計以 AC2 實測為準。
pilot 重用 AC2 時,判斷等價要讀 AC2 結果 JSON 的 config 與 training 欄位;那個檔案裡也有 test 數字,但程式只取這兩欄,validation 數字從 archive 只讀 validation 陣列。
新增:src/tinyrouter/{sampling,steps,runs,protocol,pilots,curves,baselines}.py、configs/{modernbert-base,curve}.yaml、scripts/{compat_trial,export_onnx}.py、results/compat/*.json
相依:optional group onnx(onnx 1.23.0、onnxruntime 1.30.0、onnxscript 0.7.2),CI 不安裝
API 花費:US$0
2026-09-23 (夜):兩條學習曲線與 OOS 消融完成
協定凍結後(lr 5e-5、S_min 400、5 epochs)依序單獨執行,每條以程式印出的完成行為證據(不看離開碼):
make curve MODEL=bert:completed 18/18 encoder points (bert),18:26 到 19:24。k=1 到 50 的 15 點新訓練;k=100 的 3 點重用 AC2(判定為等價 run,archive 完整)。
make curve MODEL=modernbert:completed 18/18 encoder points (modernbert),19:25 到 21:47,18 點全部新訓練。
make oos-ablation:completed 3/3 ablation points,21:48 到 22:47(ModernBERT、k=100、OOS 訓練 0 筆 × 3 seeds)。
make verify-logits:75 個 archive 全部與 manifest SHA-256 一致(AC2 3 + 基準 36 + BERT 新 15 + ModernBERT 18 + 消融 3)。
曲線權重全部刪除,只保留 logits。
(本條只記錄執行完成;數字分析在步驟 4 由分析程式從 logits 產生,不在此手抄)
第一次開跑(17:22)的 shell 迴圈在 zsh 下沒拆 make $t,兩條曲線沒跑卻 exit 0。PR #11 加上完成性檢查與 Makefile 無目標防護(make "curve MODEL=bert" 現在 exit 2),並有測試對應這次事故。
results/curves/{bert,modernbert,oos-ablation}.json、results/runs/*.json、results/logits-manifest.json
API 花費:US$0
2026-09-23 (晚 2):S_min pilot(validation only),協定凍結
make pilot-steps:k=5(每個 intent 5 筆、OOS 13 筆)、seed 42、lr 5e-5,兩個 encoder 各試 S_min ∈ {100, 200, 400},只看 validation。
規則:兩個模型 val in-scope 答對數加總最高者,平手取較小 S_min。選出 400,Drew 確認,寫入 configs/curve.yaml。
超參數協定至此全部凍結 :lr 5e-5(兩個模型)、S_min 400、5 epochs、batch 32、max_length 64。正式曲線開跑後不再依中途結果修改任何一項。
S_min(實際步數)
BERT val in-scope / OOS
ModernBERT val in-scope / OOS
100(120)
9.73% / 10%
56.20% / 16%
200(200)
36.73% / 45%
63.43% / 21%
400(400)
75.87% / 47%
67.20% / 27%
又落在範圍上限,而且小 k 尚未訓練飽和 :BERT 從 200 到 400 步仍大幅上升(37% → 76%)。所以 k=1、5、10 的曲線點量的是「400 步預算下」的表現,不是模型極限;README 必須寫明。k ≥ 25 時 5 個 epoch 已超過 400 步,S_min 不生效。
學習速度 vs 最終表現 :步數很少時 ModernBERT 學得快很多(120 步 56% vs 10%),到 400 步 BERT 反超。「誰比較有效率」取決於訓練預算。單一 seed,不下結論,等正式曲線。
S_min=100 在 k=5 實際是 120 步,因為 5 個 epoch = 120 步 > 100,符合 max(S_min, epoch 步數) 的定義。
results/pilots/steps.json、configs/curve.yaml
API 花費:US$0
2026-09-23 (晚):learning rate pilot(validation only)
make pilot-lr:k=100、seed 42,兩個 encoder 各掃 {1e-5, 2e-5, 5e-5},只看 validation。BERT 5e-5 重用 AC2 seed 42(判定為等價 run),其餘 5 次新訓練。
Drew 確認兩者都用 5e-5,寫入 configs/curve.yaml。
模型
1e-5
2e-5
5e-5
BERT(val in-scope / OOS recall)
90.17% / 45%
95.73% / 66%
96.73% / 68%
ModernBERT
94.77% / 67%
95.93% / 72%
97.13% / 75%
兩個模型的最佳值都在掃描範圍上限。 只能說「在 {1e-5, 2e-5, 5e-5} 中 5e-5 最好」,真正的最佳 LR 可能更高。Drew 決定照凍結的協定接受 5e-5,不在看到結果後擴充範圍;兩者停在同一個邊界,比較仍對稱。README 需寫明此限制。
LR 太小會學不完:BERT 1e-5 在第 5 個 epoch 的 loss 仍有 0.40(5e-5 為 0.03),val in-scope 低 6.5pp。
單一 seed、val 只有 100 筆 OOS,ModernBERT 與 BERT 的 OOS recall 差 7 筆,不下結論。
每次 run 時間(M4):BERT 約 15 分鐘,ModernBERT 約 21 到 23 分鐘。
results/pilots/lr.json、configs/curve.yaml
API 花費:US$0
2026-09-23:AC2 通過(BERT 流程正確性檢查)
make ac2:bert-base-uncased 在完整 OOS+ 訓練集(15,000 in-scope + 250 OOS)訓練 151 類,seeds 42 / 43 / 44,commit b3a59e2,工作目錄乾淨(git_dirty: false)。
超參數是骨架的起始值,未調參:lr 5e-5、5 epochs、batch 32、max_length 64、warmup 10%、weight decay 0.01。
開跑前 PR #5 經兩輪審查:續跑比對 config、三處 sha 一致才算完成、判定驗 seed 身分、過期的 ac2.json 開跑即刪。
seed
val in-scope
val OOS recall
test in-scope
test OOS recall
訓練時間
42
96.73%
68.0%
96.51%
58.3%
886 s
43
96.73%
69.0%
96.40%
55.8%
847 s
44
96.73%
73.0%
96.22%
56.7%
822 s
AC2 門檻 95.7%(test in-scope,三個 seed 都要過):PASS 。test in-scope 平均 96.38%;Larson et al. 2019(BERT、OOS+、oos-train)為 96.7%,OOS recall 59.2%,本次 56.9%。這是工程驗收,不宣稱重現原論文。
三個 seed 的 val in-scope 都是 2,902 / 3,000。已從 logits 檔直接重算確認不是同一份結果:三個檔 SHA-256 不同,val 上約 2.5% 的預測彼此不同,test 數字也不同;判定為巧合。
OOS 是弱點,符合預期 :in-scope 96%,但 test 上約 43% 的 OOS 被分進某個 intent。這正是 RQ2 到 RQ4 要處理的問題。
效率(M4、MPS):參數 109.6M;每個 seed 約 14 分鐘(2,385 步);峰值記憶體 driver 約 4.6 GB、存活 tensor 約 1.8 GB(MPS 沒有原生峰值 API,採樣值,量法見結果檔)。
磁碟可用空間從 15 GB 降到 11 GB。主要佔用是 uv 全域快取(33 GB,全機共用,非本專案)與 HF 模型快取(2.3 GB);建議 Drew 執行 uv cache prune,本專案不代為刪除。
背景執行時包了一層 echo exit,所以背景任務的離開碼永遠是 0,不能拿來判斷成功與否;要看 log 裡 make 的輸出與 results/ac2.json。
results/ac2.json、results/runs/bert-base-uncased-full-seed{42,43,44}.json、results/logits-manifest.json
logits 檔(不進 git):results/logits/bert-base-uncased-full-seed{42,43,44}.npz,各約 4.8 MB
API 花費:US$0