Day 05 最後提到要把隨機性加回來,聽起來貌似會新增一堆幻覺,實際比較像讓模型在每一個決策點,允許它偶爾不要走那條分數最高的路。
前一篇使用 greedy decoding 時,151,936 個 logits 裡誰最大,下一個 token 就選誰。只要模型、prompt 和執行環境沒有改變,整段生成也會沿著同一條路往前。Sampling 則先把 logits 轉成機率分布,再依照機率抽出一個 token。一次抽樣的差異可能很小,只是把 has 換成 owns;但這個 token 會立刻成為下一輪的 context,後面的 logits 也跟著改變。幾十步以後,兩段文字可能已經變得截然不同。
Reasoning model 並不會跳過 decoding 這一關,未來做 Best-of-N、自洽性或其他 inference-time scaling 時,會刻意讓同一題產生多條路徑;所以在開始評估以前,得先知道這些差異究竟從哪裡來。
Temperature 的作用最容易從這行程式碼裡看懂:
probabilities = softmax(logits / temperature)
當 temperature 小於 1,logits 之間原有的差距會被放大,分布變得尖銳,高分 token 更容易勝出。temperature 大於 1 時,分布會攤平,原先機率較低的候選也多了一些被抽中的機會。它不會替模型增加新知識,或者把錯誤推理修好,而是改變模型在現有候選答案之間冒險嘗試不同可能的程度。
當 temperature 攤平以後,詞彙表尾端仍有一大群機率微小的 token。Top-k 的作法是只留下分數最高的 k 個,top-p 則不固定數量,而是從最高機率開始累加,保留累積機率剛好達到門檻的最小集合。假如前三個候選加起來已經超過 0.8,top_p=0.8 可能只留下三個;碰到比較平坦的分布,同樣的 0.8 也可能需要更多候選。因此 top-p 的 0.8 指的是機率質量,不是保留 80% 的 vocabulary。
Hugging Face 的 generate() 文件將這些選項直接放在生成參數裡。do_sample=False 會回到 greedy decoding;開啟 sampling 後,temperature、top-p 與 top-k 才真正參與選擇。
output_ids = model.generate(
**inputs,
do_sample=True,
temperature=0.7,
top_p=0.8,
top_k=20,
max_new_tokens=96,
use_cache=True,
)
這組 0.7 / 0.8 / 20 直接取自 Qwen3-0.6B 官方模型頁對 non-thinking mode 的建議。官方替 thinking mode 準備了另一組設定,也明確提醒不要使用 greedy decoding。這才是為什麼我前幾天會說 greedy 被現代主流模型捨棄,是因為鐵人賽要當作教學和一致性檢查才使用,真正生成推理內容時,還是要回到模型自身的設定。
Seed 處理的是另一件事。電腦裡的抽樣依賴偽隨機數產生器,固定 seed,等於在洗牌以前把牌疊回同一個順序。只要模型、輸入、軟硬體與程式路徑也保持一致,同一個 seed 通常就能重現同一串抽樣結果。
import random
import torch
def seed_everything(seed):
random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
Seed 不會改變模型權重,也不會把機率分布鎖死,而是決定這次抽樣使用哪一串偽隨機數。因此 greedy decoding 根本沒有進行隨機抽取,seed 42 和 seed 7 理應輸出相同 token;sampling 使用相同 seed 重跑時,則應該回到同個實驗路徑。
這次我改用 post-trained Qwen/Qwen3-0.6B,套用 chat template 並關閉 thinking mode,並使用一個簡單的問題,而不是那種稍有難度的邏輯推理題:Nora 有 4 盒鉛筆,每盒 6 枝,送出 5 枝後還剩多少?答案是 19,而且我要求最後必須寫成 Answer: <number>。讓我們來觀察模型是否能計算正確,以及確實依照格式來回答。
我總共跑了九次,greedy 使用 seed 42 與 7,官方 non-thinking sampling 使用 seed 42、42、43、44,其中重複的 42 專門檢查重現性;最後再把 temperature 拉到 1.4、top-p 調成 0.95、top-k 放寬到 50,以 seed 42、43、44 做一組刻意偏高的探索(最後這組屬於 stress test,不是 Qwen 官方推薦設定)。
結果顯示:Greedy 的兩個 seed 產生完全相同的 61 個 token;官方 sampling 的 seed 42 連跑兩次,也逐 token 相同。固定 seed 的用途很窄,它能讓同個抽樣條件可以再執行一遍,卻不會讓不同 seed 的生成彼此相同。
不同 seed 則會把文字引導去不同方向,官方 sampling 的四次執行得到三種 token sequence,三個不同 seed 的遣詞略有差異,有的寫 has,有的改成 totaling,但四次都算出 19,也都遵守了最後一行的格式。高 temperature 的三次執行產生三種序列,答案同樣全部正確,格式卻只通過兩次。
runs unique correct format valid
greedy 2 1 2/2 2/2
recommended sampling 4 3 4/4 4/4
high-temperature probe 3 3 3/3 2/3
失敗的是 high-temperature、seed 44,模型最後寫成:
Nora has 4 boxes with 6 pencils in each box, totaling 24 pencils.
She gives away 5 pencils, so the number of remaining pencils is
**Answer: 19**.
計算結果、數值是正確的,程式也能從中抽出 19,只是不符合我們要求的格式:
Answer: <19>
所以這次結果是答案正確,格式失敗。模型輸出不能只留下一個「對」或「錯」。有時候數學答案對了,格式卻讓後續程式無法可靠接手;有時候格式看起來很工整,內容反而可能是錯的。把兩件事併成同一個分數,很容易在平均值裡漏掉模型究竟錯在哪裡。
當然,單一題目跑九次仍然不是 benchmark。這裡只能確認 greedy 不受 seed 影響、相同 seed 能在本次環境重現抽樣,以及較寬鬆的 sampling 確實可能帶來不同措辭和格式漂移。
單一題目顯然不夠用了,明天開始我們將建立一個可以重現的 reasoning baseline,一次固定整批題目的 prompt、sampling、seed 與 token budget,並事先決定答案如何擷取、結果要如何判定正確,屆時我們將可更明確地比較模型在每次的推理之間有何不同。