對應原題(Common ‧ LLM Internals / Coding)
- Compare greedy, beam search, top-k, top-p and temperature sampling. When does each fail?
— Asked at: Google DeepMind, Apple, Perplexity- Implement top-k, top-p and temperature sampling over a logits vector. — Asked at: Google DeepMind, Apple
🎯 什麼情境該想到我
當你「在調 API 的 temperature / top_p 但其實是憑感覺、或模型開始鬼打牆重複同一句」的時候。這是唯一一個不用重訓、不用改架構、改個數字就生效的品質旋鈕——所以也最容易被亂調。
⚙️ 怎麼用(機制)
解碼管線
logits (詞表大小的向量)
│
├─▶ 1. repetition / frequency penalty (懲罰已出現過的 token)
├─▶ 2. temperature z ← z / T
├─▶ 3. 截斷:top-k / top-p / min-p (把不要的設成 -inf)
├─▶ 4. softmax → 機率分布
└─▶ 5. 依分布抽樣 → 下一個 token
★ 順序會改變結果,而且各家實作不同。HuggingFace
generate的預設順序是 penalty → temperature → top-k → top-p。「先截斷再調溫」和「先調溫再截斷」是不同的分布——換框架時行為變了通常是這裡。
各策略
Greedy:永遠取 argmax
next_token = logits.argmax(-1)失效模式:退化重複。 取了最可能的 token 會讓下一步更可能取同一串,形成自我強化的迴圈(“I think that I think that I think that…”)。
Holtzman et al. (2020, The Curious Case of Neural Text Degeneration) 的關鍵發現:人類文本的 token 機率是劇烈起伏的,而最大化似然的解碼產生的文本 perplexity 異常地低——太可預測,反而不像人話。
Beam Search:保留 條候選路徑
每步擴展所有 beam,依累積 log-prob 留前 條。
適合:翻譯、摘要、結構化輸出——有「大致正確答案」的任務。
失效:開放式生成。Beam search 系統性偏好高機率的無聊文本,且會偏好短序列(log-prob 是負的,越長越小),要加長度正規化 才堪用。
今天的 LLM chat 場景幾乎沒人用 beam search——成本是 倍、KV cache 也是 倍,品質還不見得好。
Temperature:縮放,但不截斷
| 效果 | |
|---|---|
| 趨近 greedy | |
| 模型原始分布 | |
| 拉平分布,長尾被放大 |
★ 失效模式:它只重新加權,不移除任何選項。 時,那些機率 的垃圾 token 也被放大了——單獨用高 temperature 一定會偶爾吐出胡言亂語。所以實務上永遠要配截斷。
Top-k:固定取前 個
失效模式是「固定」本身:
- 分布很尖(下一個字幾乎確定,例如
"United States of ___")→ 把 49 個垃圾也放進來 - 分布很平(創意寫作的開頭)→ 把大量合理選項切掉
Top-p / Nucleus:動態大小
由小到大累積機率直到超過 ,只保留這個「核」。
尖分布時核很小(可能只有 1–2 個),平分布時核很大。 這正是 top-k 缺的自適應性。
失效模式:極平坦的分布下,核裡可能有上千個 token,仍然吐垃圾。而且 與 的交互不直覺——兩個都調會互相抵銷。
Min-p:相對閾值
「只要不到最佳選項的 就砍掉」。比 top-p 在高 temperature 下更穩,近年被 llama.cpp / vLLM 等採用。
重複懲罰
| 參數 | 做什麼 |
|---|---|
repetition_penalty | 出現過的 token 除以懲罰值(乘性,作用在 logits) |
frequency_penalty | 依出現次數線性扣分(OpenAI 風格,加性) |
presence_penalty | 只要出現過就扣固定分,不管幾次 |
★ 這三個都會傷害程式碼與結構化輸出——for、}、self 這類 token 本來就該重複。生 JSON / 寫程式時請設 0。
實作(coding 題標準答案)
import torch
def sample(logits, temperature=1.0, top_k=0, top_p=1.0, min_p=0.0):
"""logits: (vocab,) 未經 softmax"""
if temperature == 0: # 退化成 greedy
return int(logits.argmax())
logits = logits / temperature
neg_inf = torch.finfo(logits.dtype).min
if top_k > 0:
kth = torch.topk(logits, min(top_k, logits.size(-1))).values[-1]
logits = logits.masked_fill(logits < kth, neg_inf)
if min_p > 0.0:
probs = logits.softmax(-1)
logits = logits.masked_fill(probs < min_p * probs.max(), neg_inf)
if top_p < 1.0:
sorted_logits, sorted_idx = torch.sort(logits, descending=True)
cum = sorted_logits.softmax(-1).cumsum(-1)
# ★ 右移一格:確保「剛好跨過 p 的那個」被保留,否則 p 很小時會全砍光
remove = cum > top_p
remove[..., 1:] = remove[..., :-1].clone()
remove[..., 0] = False
logits = logits.masked_fill(
remove.scatter(-1, sorted_idx, remove), neg_inf)
return int(torch.multinomial(logits.softmax(-1), 1))面試官會挑的兩個 bug:
- 右移那一行——不移的話,當最高機率 token 本身就 >
top_p(例如 )時會把所有 token 都砍掉,直接崩。 scatter把 mask 從排序空間映射回原始索引——直接用sorted_idx順序的 mask 是錯的。
情境建議(起點,不是定論)
| 場景 | 建議 |
|---|---|
| 程式碼、JSON、抽取 | T=0(greedy),penalty 全 0 |
| 分類 / 判斷 | T=0 |
| 問答、RAG 回答 | T=0.2–0.5, top_p=0.9 |
| 一般對話 | T=0.7–1.0, top_p=0.9–0.95 |
| 創意寫作 | T=1.0–1.2 + min_p=0.05 |
| 要多樣候選(best-of-n) | 高 T + 高 top_p,再用評分挑 |
只調一個。 同時調 temperature 和 top_p 幾乎無法歸因。
⚠️ 注意 / 什麼時候不適用
★ temperature=0 不保證可重現
這是實務上最常被誤解的一點。即使走 greedy,下列因素仍會讓同樣的輸入產生不同輸出:
- 浮點加總順序隨 batch 大小改變 —— GPU 的 reduction 不保證結合律,你的請求和誰同批會影響數值
- 不同的 kernel 路徑 —— 框架依 batch/序列長度選不同 kernel
- MoE 路由 —— 有容量限制時,同批其他請求會影響你的 token 被分到哪
- 服務端版本、硬體、批次策略變動
→ 要可重現就得自己固定:固定 seed、batch=1、鎖版本、鎖硬體。 對外 API 做不到這件事。
→ 評估流程不能假設 T=0 就是確定的,見 工具-AI改動的AB對照評測。
其他
T=0不等於「最正確」。它是「每步最可能」,不是「整句最可能」——這是貪婪法的老問題。- 截斷會改變校準。做不確定性估計、要用 logprob 判斷信心時,不能在截斷後的分布上算。
- 高 temperature 不等於有創意,只等於更隨機。真正的創意來自 prompt 與素材。
- structured output / JSON mode 下取樣參數的效果會被約束解碼蓋過,見 工具-讓LLM產出可驗證的產物。
🧪 我實際套用的紀錄
- 2026-09-20:從面試題庫展開,尚未實際套用。
🔗 相關
- 推測解碼 Speculative Decoding —— 在保持同一個取樣分布的前提下加速
- 分詞與 BPE —— 取樣的對象是 token 不是字
- 工具-讓LLM產出可驗證的產物 —— 結構化輸出時的取樣設定
- 幻覺偵測 —— 高 temperature 是幻覺的放大器