對應原題(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:

  1. 右移那一行——不移的話,當最高機率 token 本身就 > top_p(例如 )時會把所有 token 都砍掉,直接崩。
  2. 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,下列因素仍會讓同樣的輸入產生不同輸出:

  1. 浮點加總順序隨 batch 大小改變 —— GPU 的 reduction 不保證結合律,你的請求和誰同批會影響數值
  2. 不同的 kernel 路徑 —— 框架依 batch/序列長度選不同 kernel
  3. MoE 路由 —— 有容量限制時,同批其他請求會影響你的 token 被分到哪
  4. 服務端版本、硬體、批次策略變動

→ 要可重現就得自己固定:固定 seed、batch=1、鎖版本、鎖硬體。 對外 API 做不到這件事。
→ 評估流程不能假設 T=0 就是確定的,見 工具-AI改動的AB對照評測。

其他

  • T=0 不等於「最正確」。它是「每步最可能」,不是「整句最可能」——這是貪婪法的老問題。
  • 截斷會改變校準。做不確定性估計、要用 logprob 判斷信心時,不能在截斷後的分布上算。
  • 高 temperature 不等於有創意,只等於更隨機。真正的創意來自 prompt 與素材。
  • structured output / JSON mode 下取樣參數的效果會被約束解碼蓋過,見 工具-讓LLM產出可驗證的產物。

🧪 我實際套用的紀錄

  • 2026-09-20:從面試題庫展開,尚未實際套用。

🔗 相關