Day 09 先把 verifier 的工作簡化成一件事:給它一個模型生成的回答,判斷最後答案是否正確。不過,當同一道題開始產生四個、八個或多個候選後,原本單一的「答對率」就不夠用了。候選池裡是否曾經出現正解、固定拿前四筆能不能成功,以及最後由多數決選出的答案是否正確,表面上都在談多抽樣,實際上是三個不同的問題。
Sebastian Raschka 的第四章程式開始處理 inference-time scaling:用 temperature 與 top-p 讓同一題不同的推理路徑,再以 self-consistency 選出最常出現的答案,這個方法來自 Wang 等人的 Self-Consistency 論文。我想再多拆解有限樣本 pass@k、固定前 k 筆的實際成功率、多數決和不確定性這幾個概念的差異,因為這幾個概念在口語裡都可能被叫成「多抽幾次」,實際計算時各自回答不同的問題。
Day 07 凍結的 baseline 採 greedy decoding,1,000 道 validation 題各留下唯一一筆 completion,得到 178 題答對,也就是 17.8% pass@1,但尚未實證 pass@2、pass@4 或 pass@8。為了先確認公式、聚合方式與 tie policy,Day 10 將使用 6 個 deterministic synthetic tasks,每題八個已經正規化的 answer key,正解數刻意排成 0、1、2、3、4、8,共 48 筆。
假設一題已經產生 n 個候選,其中 c 個通過 verifier。隨機取一個時,正確機率是 c / n;若從有限候選池裡任取 k 個,pass@k 問的是「至少含有一個正解」的機率。我們沿用 OpenAI Codex 論文使用的估計式:
pass@k = 1 - C(n-c, k) / C(n, k)
分母是從全部候選取出 k 個的組合數,分子是偏偏只取到錯誤答案的組合數;兩者相除得到全錯的機率,再用 1 減掉它。實作很短:
import math
def pass_at_k(n, c, k):
if n - c < k:
return 1.0
return 1.0 - math.comb(n - c, k) / math.comb(n, k)
以八個候選裡有兩個正解為例,隨機取四個時是 1 - C(6,4) / C(8,4) = 55 / 70,約為 0.7857。這和 1 - (1 - c/n)^k 不相同,因為後者假設有放回且每次獨立;此處面對的是已經生成的八筆有限樣本,從中不放回地挑四筆。
每道題目先各自算完,再對六道題取 macro mean,得到:
estimate task-bootstrap 95% interval
pass@1 0.3750 0.1458 - 0.6458
pass@2 0.5238 0.2500 - 0.7798
pass@4 0.7000 0.3926 - 0.9476
pass@8 0.8333 0.5000 - 1.0000
這組區間極寬,畢竟我們只有六道刻意設計的題。程式以題目為單位做 10,000 次 percentile bootstrap,base seed 固定為 20260729,每個 k 使用的衍生 seed 也一併寫進 artifact,同題八個回答來自同樣的 prompt 和題目難度。
固定取前四筆意味著:六題中有四題在 sample ID 0–3 至少出現一次正解,所以 prefix-4 any-success 是 4 / 6 = 0.6667;unbiased pass@4 則是 0.7000,因為它平均了每題八個候選中所有可能的四筆組合。
first four samples, any correct 0.6667
unbiased pass@4 0.7000
Self-consistency 會先把數學上等價的回答收斂成同一個 answer key,再選票數最高的一組,而且平手規則也要在看到結果前紀錄下來;本次採最高票平手時,選 sample ID 最早出現的答案。六道合成題沒有真的遇到 tie,但程式仍保留這條規則:
from collections import Counter
def self_consistency_vote(answer_keys):
counts = Counter(answer_keys)
top_count = max(counts.values())
return next(key for key in answer_keys if counts[key] == top_count)
第三道合成題最能說明兩種指標的距離,正解 A 出現兩次,錯誤的 B 出現四次,C 也出現兩次;pass@8 看到候選池裡確實有 A,所以這題記為成功,多數決最後選中 B,依然答錯。六題合計有五題的候選池藏著正解,pass@8 因而是 5 / 6 = 0.8333;self-consistency 真正選對的只有三題,最後是 3 / 6 = 0.5000。
candidate pool contains a correct answer 5 / 6 = 0.8333
majority vote selects the correct answer 3 / 6 = 0.5000
Pass@k 描述候選池的 oracle ceiling:假如有一位看得到標準答案的裁判,池子裡最多能救回多少題;真正部署時沒有這位裁判,系統仍得靠多數決、分數、另一個 selector,或其他不偷看 reference 的訊號做最後選擇。候選池曾經答對,和系統知道自己哪一次答對,是不一樣的事情。
要重建今天的結果,可以直接執行:
python articles/day09/demo.py
所以我們目前能得到的結論是:有限樣本 pass@k、prefix-4、多數決和 task-level bootstrap 都通過預先寫好的合成案例;83.33% 屬於合成 metric-contract,Qwen3-0.6B-Base 尚未得到 pass@8,五百題 MATH-500 也維持零次模型推論。真正的多抽樣實驗必須固定 model revision、validation split、prompt、temperature、top-p、每題生成數、seed、token 上限與 verifier,並保存每一筆 completion。
所以這意味著更多次抽樣、更多的候選答案,就能提高答對率嗎?會否這建立在更高的 token 消耗、更久的等待時間,更高成本的記憶體、工具呼叫與金錢上?所以我預定 Day 11 的文章會比較:當兩個方法使用的預算不同,accuracy 是否還是合適的評估指標?