評測科學主線 · 完稿待投
Peeking Without Penalty: Anytime-Valid Sequential Evaluation of Language Models
原文:Peeking Without Penalty(2026-07-26 重編版)
21 頁 · 292 KB · PDF
LaTeX 原稿由 tectonic 編譯,內文所有數字來自 93 個腳本生成的 macro。
開啟 →這是我在這台電腦上做完的第一篇完整論文。它的起點是一個所有人都在做、但沒人當成統計問題的動作:跑 benchmark 跑到一半,看一眼分數,覺得贏了就停下來寫進報告。這在序貫分析裡是教科書等級的錯誤 —— 我用模擬證明它把名目 5% 的偽陽性率推到 40.1%,然後給出一個可以隨便看、隨時停、錯誤率仍然守得住的替代協議。
先講清楚被攻擊的對象是什麼,再談方法。
所有 benchmark 比較都長這樣:挑一個預算能負擔的題數,跑,中途盯著累積差距,看起來分出勝負就停手。問題在於「中途盯著看」本身會消耗型一誤差——每多看一次,就多一次「剛好這一刻差距很大」而被誤判的機會。
固定樣本數的 t 檢定假設你只在終點看一次。實務上沒有人這樣做。所以整個領域用的誤差保證,跟實際的操作流程對不上。
方法上採用 safe anytime-valid inference:把題目隨機化後串流輸入,維護配對的 e-process 與信賴序列(confidence sequence),一旦「A 顯著優於 B」「兩者實務等價」「預算用盡」三種判決中任一被認證就停。無論中途看了幾次,型一誤差保證都不變。
另外兩個延伸:不放回抽樣(sampling without replacement) 版本能在只跑一部分題目的情況下,認證「跑完整份 benchmark 會得到什麼結論」;e-value 聚合(e-BH) 把保證從單一比較推廣到整個排行榜。
切換判定方式,看同一批模擬資料在兩種門檻下的結果差多少。
每個數字都由 macro 從 results JSON 注入,論文正文沒有手打數字。
跑的是 7 個開源模型(2B 到 120B 級)在 MMLU 與 GSM8K 子集上的逐題正確與否矩陣,全部本機 ollama 推論。31 組配對比較裡,SAVE 對每一組都給出認證判決,沒有任何一組是重播後以「預算用盡」收場。
拿得到的好處
整份 benchmark 本身就能分出勝負的 20 組,中位只需要 24% 的題目(最省的一組只要 3%)。省下的是等量的 token 成本與 GPU 時間。
誠實的另一面
剩下 11 組是整份 benchmark 也分不開的,SAVE 會誠實地判「實務等價」,並且花掉中位 83% 的題目才確認。省錢只發生在該省的地方。
以下是這台電腦上真實存在的目錄與腳本,不是流程圖示意。
整條線只用終端機。沒有 Jupyter、沒有 GUI、沒有雲端 notebook。資料抓取、模型推論、模擬、分析、製表、製圖、排版、稽核,每一段都是一支可以單獨重跑的 Python 腳本,串起來就是論文。
| 環節 | 工具 | 在做什麼 |
|---|---|---|
| 題目定案 | Claude Code CLI | 把構想壓成一句可否證的主張,寫進 claim card;轉不出來就退回 |
| 資料準備 | scripts/prep_data.py | 原始 MMLU / GSM8K → data/*.json 逐題檔 |
| 模型推論 | ollama(本機)+ collect_matrix.py | 7 模型逐題跑,可續跑、全程留 log |
| 統計核心 | save_eval/core.py(474 行) | e-process、信賴序列、e-BH,只用 Python 標準函式庫 |
| 驗證 | save_eval/test_validity.py(325 行) | 11 支模擬測試,先寫在方法之前 |
| 數字入稿 | make_tables.py / make_figures.py | results JSON → paper/generated/*.tex macro |
| 排版 | bin/tectonic(獨立 LaTeX 引擎) | 終端機直接編譯,不裝 TeX Live |
| 交付前稽核 | audit_claims.py / check_refs.py | 逐條主張比對數字;引用打 API 驗證 |
設計上最重要的一條:論文裡任何一個數字都不准手打。數字只能從 results/*.json 經由 make_tables.py 生成成 LaTeX macro(本篇共 93 個),正文只寫 \SimNaivePeek 這種指令。想改數字就得改資料重跑,「順手填一個記憶中的數」這條路被實體切斷。
進度的單位是檔案,不是時間。每一關都要交出一個可以被別人稽核的檔案,交不出來就停在那裡。
可否證主張 + 拒稿信
先以最兇審稿人身分寫一封拒死自己的信,每一砲都要有書面回應
新穎性查證
五個角度掃前人工作,附可點的證據,不是「我覺得沒人做過」
資料閘門
MMLU / GSM8K 逐題檔 + 抓取 log + schema 抽查
統計測謊器(先行)
11 支模擬測試寫在方法實作之前
分析與圖
results/*.json + 腳本生成的圖,禁止手繪
論文編譯
tectonic 零錯誤產出 21 頁 PDF
交付前掃描
34 條主張逐條比對數字,0 不符;引用 API 驗證
事後寫的測試會不自覺地遷就已經跑出來的結果。事前寫的才是真地雷。
save_eval/test_validity.py 裡的 11 支測試(t1–t11)是在統計核心寫完之前先寫好的,每一支都指定一個「如果我的方法是錯的,這個數字會爆掉」的界線:
測謊器當場抓到的自己人錯誤
我在聚合多個比較時取了 e-value 的最大值。直覺上「取最強證據」很合理。
模擬跑出來的錯誤率超標——取 max 會偷偷做多重比較卻不付代價。
改成正確的 e-value 聚合方式,重跑測試通過。這個錯誤如果沒有事前測試,會一路帶進論文,而且審稿人不一定看得出來。
整個專案自己抓到並記錄在案的錯誤共 3 次。這個數字被刻意當成品質訊號:抓到越多代表測謊器有牙,長期是 0 反而該懷疑測試沒在測東西。
寫出一個想法的 context 已經對它投入了感情,自評會系統性地漏在自己得意的地方。
所以審查一律用全新 context 或另一個模型跑。這篇的三道外部檢查:
audit_claims.py 把正文 34 條可查證的主張逐條抓出來,回去比對 results JSON。結果 0 不符。check_refs.py 對每一條引用打 arXiv / Crossref / DOI 三個通道驗證,並做標題模糊比對。抓到一條標題殘缺的引用。results/ 重推頭條數字,跟論文對得上才算過。寫在論文裡、不藏在附錄的那些。
資料規模
7 個開源模型、MMLU 與 GSM8K 的子集,全部本機推論。不是全 benchmark、也不含閉源 API 模型。
省不到的情況
整份 benchmark 本身分不開的比較,SAVE 幫不上忙——它會誠實花掉 83% 的題目告訴你「這兩個分不開」。這是設計,不是失敗。
投稿狀態:論文完稿,但還沒送出去。卡點是 arXiv 的行政規則 —— 第一次投 cs.LG 分類的作者,需要一位已在該領域發表過的人按一次背書(endorsement)。投稿包(tarball、metadata、摘要)都已備妥,等的是找到願意背書的人。
另外,2026-07-11 做過一次全面稽核,判定需要大幅修改(統計核心與論文宣稱不一致、頭條分母有選擇偏差、直接相關的前人工作漏引)。2026-07-26 重跑了統計核心、稽核腳本、結果與圖,並補寫相關工作章,論文從 17 頁擴為 21 頁。本頁附的是這個 7/26 重編版。
整篇論文可以從乾淨環境用七行指令重建。這是「每個數字可重算」的實際意思。
# 1. 建題目檔(MMLU / GSM8K → data/*.json) $ python3 scripts/prep_data.py # 2. 跑每一條統計保證的模擬證明(先於方法本體寫好) $ python3 save_eval/test_validity.py # 3. 模擬章節 + 真實模型逐題矩陣(需要 ollama,數小時,可續跑) $ python3 scripts/run_sims.py $ python3 scripts/collect_matrix.py results/roster.json # 4. 重播分析 → results/*.json → 論文 macro 與圖 $ python3 scripts/analyze_real.py $ python3 scripts/make_tables.py && python3 scripts/make_figures.py # 5. 編譯(獨立 LaTeX 引擎,不需要裝 TeX Live) $ cd paper && ../bin/tectonic main.tex
這篇是整套產線的錨。後面每一篇論文都在複製它的軌道:先寫測謊器、數字只能來自腳本、生成的人不能當評審、誠實卡關比灌水完成分數更高。
PAPER 01 · SAVE · anytime-eval