Day 06 裡我們問了模型 9 次同樣的問題,並且觀察 sampling 如何讓文字變得不同,同時也可看到答案正確和格式正確並不是同一件事。不過只靠單一題目,無論重複執行多少次,都很難說明模型究竟有多會解題。因此,Day 07 的目標是把題目增加到 1000 題,先把規則固定下來,再讓尚未經過我們訓練的 Base model 留下一個可以重跑、也可以被後續實驗比較的起點。
這部分可對應到 Sebastian Raschka 原書的第三章。在官方 companion hub裡,第二章完成預訓練模型的文字生成,第三章隨即開始擷取答案、建立 verifier,最後才把模型放到一批數學題上評估。這個順序是合理的,理由是:如果先看到模型的回答,再修改 prompt、答案擷取方式或正確判定規則,最後提升的也可能只是評分方式、而非模型,畢竟直接在測試資料上訓練,向來是讓 benchmark 成績突飛猛進的最快方法——2023 年甚至有人用三頁諷刺論文,替這件事下了一個戲謔的標題:Pretraining on the Test Set Is All You Need。
原書使用 MATH-500 示範完整評估,我則把這 500 題當成最後才使用的測試集,先不讓推理模型看見。我使用的資料來自 MATH 題庫中排除 MATH-500 的來源池,原本共有 12,000 題,然而 4題缺少可用答案,另外 11 題在文字相似度檢查中過於接近封存題,因此這 15 題被我排除。剩下的 11,985 題再依科目和難度分層,10,985 題留給往後訓練,1,000 題固定成今天的 validation split。
上述過程仰賴 Codex 和 GPT-5.6 Sol max 的協助,這種檢查只能抓到完全相同、正規化後相同或文字高度相似的題目,不能證明兩邊沒有語意改寫,更不可能證明預訓練模型從未在網路上看過公開的 MATH 題目,它的用途僅是讓我們不再主動把明顯重複的題目混進訓練資料。
模型固定使用 Qwen/Qwen3-0.6B-Base,連模型 revision 也一併鎖定。這是只有預訓練權重的 Base model,不是前兩天使用的 post-trained Qwen3,也還沒有接受我們後續的 SFT 或 RLVR。輸入採 raw completion,不套 chat template;prompt 則要求保留必要推導,並把最後結果放在最後一行的 \boxed{ANSWER} 裡。
MODEL_ID = "Qwen/Qwen3-0.6B-Base"
MODEL_REVISION = "da87bfb608c14b7cf20ba1ce41287e8de496c0cd"
PROMPT = r"""You are a helpful math assistant.
Solve the problem. Show the necessary reasoning, then put only the final
result on the last line in exactly this form:
\boxed{ANSWER}
Question:
{problem}
Answer:
"""
def render_prompt(problem):
return PROMPT.replace("{problem}", problem.strip())
生成條件也一起固定,這次依然沿用 greedy decoding,因為我們要測的是同一個出發點,而不是比較多條抽樣路徑。seed=42 在 greedy 本身不負責抽 token,但仍寫進共同契約,免得日後切換 sampling 時遺漏。預跑時也觀察到批次方式會影響生成結果,所以 batch size、padding 方向和題目順序都不能在比較途中任意更換。
generation = {
"do_sample": False,
"seed": 42,
"max_new_tokens": 512,
"use_cache": True,
"batch_size": 32,
"padding_side": "left",
"order": "row_id_ascending",
}
接著才是評分規則,程式優先擷取最後一個完整的 \boxed{...};找不到時,才退回最後一個可辨識的數字。候選答案會先處理常見的 LaTeX、分數、根號、次方和空白差異,再進入受限的數學等價判定。答案對不對與格式是否合規分成兩個欄位,因此 fallback 可以救回能力訊號,卻不會把沒有照格式作答的輸出偷偷算成格式通過。
每一道題除了最後的對錯,還保存題目與 prompt 的雜湊、模型原始輸出、擷取到的答案、判定方法、格式狀態、輸出 token 數與錯誤類別。原因在於:如果只留下一個 17.8%,往後就無法知道分數變動來自模型真的多解對幾題,還是輸出格式、parser 支援範圍或其他評估細節改了。
在這套固定條件下,完整的一千題結果如下:
validation examples 1,000
pass@1 178 / 1,000 = 17.8%
Wilson 95% interval 15.55% - 20.29%
strict format validity 45 / 1,000 = 4.5%
mean output length 511.552 tokens
median output length 512 tokens
17.8% 裡面,只有十四題同時答對並遵守最後一行的格式;另外一百六十四題答案可以判定為正確,格式卻不合規。其餘七百一十六題答案錯誤,一百題的表示式超出目前 verifier 能處理的範圍,還有六題完全抓不到候選答案。
correct and compliant 14
correct, noncompliant format 164
wrong answer 716
unsupported verifier expression 100
no extractable answer 6
輸出長度的中位數正好是 512,表示至少一半的回答碰到了 token 上限;平均值又高達 511.552,這個 Base model 幾乎總是一路寫到預算用完。這不等於多寫就會推理,也不能只靠縮短答案便提高正確率,不過它替後續訓練留下了一個可測量的問題:模型能否在同樣的 token budget 內,更常完成推導,也更常把答案寫成我們預先指定的格式?
這裡的 178 題是 validation 結果,而非最終泛化成績;17.8% 也是預訓練模型的起點,並不是已經把 reasoning 能力提高到 17.8%。500 題 MATH-500 還未投入模型推論,避免我們一邊看考卷,一邊修改作答方法。
所以這是這系列鐵人賽的真正起點,從明天開始,無論是換 prompt、做 Best-of-N、加入 self-refinement,還是真的進入推理訓練,我們就能知道推理模型的輸出究竟長什麼樣子:在同一批題目、同一套評分規則和同一筆計算預算下,究竟比這 178 題多答對了多少?推理表現提升多少?不過在那之前,Day 08 會再稍微細談這 178 題,究竟是如何被程式判定為正確的?