Sampling策略——Temperature/Top-p/Top-k与Speculative Decoding
Greedy/Beam/Temperature/Top-k/Top-p/Min-p、Speculative decoding、Contrastive search
日期: 2026-09-02 方向: AI系统工程 阶段: Phase 3 - LLM基础与Prompt工程 (Day 121-134) 标签: #Sampling #Temperature #TopP #SpeculativeDecoding #LLM
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | Greedy/Beam/Temperature/Top-k/Top-p/Min-p、Speculative decoding、Contrastive search |
| 实操 | 对同一prompt用5种sampling策略调Claude/GPT,对比创意度、稳定性、长度、cost |
| 产出 | 实验报告 + 不同任务推荐sampling参数表 |
一、理论基础
1.1 LM输出是概率分布
每步model输出logits $\mathbf{z} \in \mathbb{R}^{|V|}$(|V|=vocab size),经softmax变概率:
$$ p_i = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} $$
$T$是temperature:
- $T \to 0$:分布退化为one-hot(greedy)
- $T = 1$:原分布
- $T > 1$:分布更平坦(更随机)
- $T \to \infty$:均匀分布(纯随机)
1.2 主要sampling方法
Greedy decoding
$\arg\max_i p_i$。确定性,但容易重复:"The cat sat on the mat. The cat sat on the mat..."
Beam search
保留top-k个候选序列。机器翻译/摘要常用;creative writing差(输出统一无趣)。
Top-k sampling
从概率最高的k个token里按概率采样。Hard cutoff,k固定可能太严或太松。
Top-p (nucleus) sampling — Holtzman et al. 2020
按概率从大到小累加直到≥p,从这个集合采样。自适应:尖锐分布只考虑1-2个,平缓分布考虑很多。
$$ V_p = \min{V' \subset V : \sum_{i \in V'} p_i \geq p} $$
Min-p sampling — Nguyen et al. 2024
保留$p_i \geq p_{base} \cdot \max_j p_j$的token。比top-p更"尊重"分布形状,high-temperature下不会让低质token突然进入。
Repetition penalty
对已出现token降权:$p_i \leftarrow p_i / \alpha$ if $i \in \text{generated}$。容易over-penalize正常重复("the"会被打压)。
1.3 Speculative Decoding(推理加速)
朴素:每生成1个token要跑一次forward pass —— GPU利用率低(memory-bound)。
Speculative decoding (Leviathan 2023):
- Draft model(小、快)连续生成N个token
- Target model(大)一次forward验证这N个token
- 接受最长前缀;从第一个不接受位置改用target采样
加速比:典型2-4x。Anthropic、OpenAI都在用。
数学:reject的概率 $1 - \min(1, p_{target}/p_{draft})$ 保证最终分布严格等同target distribution。
1.4 Contrastive Search
抑制degeneration:每步选择 $\arg\max_i [(1-\alpha) p_i - \alpha \cdot \max_{x_j \in \text{context}} \cos(h_i, h_j)]$,惩罚和上文hidden state太相似的token。
二、直觉解释
Temperature的本质
T控制模型的"自信度"。低T = "我很确定";高T = "我不确定,谁都可能"。但不影响模型本身知识,只影响表达方式。
为什么creative任务用T=0.7-1.0,code/structured用T=0?
- 创作:希望多样性,T高让模型探索"次优但有趣"的token
- Code:希望确定性,T=0让模型走最高概率路径,bug少
- Structured output:T=0避免JSON格式偏离
为什么top-p比top-k好?
考虑两个场景:
- "I love" → 后接token分布很集中("you", "it", "this"占80%):top-p=0.9只考虑3-4个,top-k=50会让低质token也进来
- "Once upon a time, " → 分布很广:top-p=0.9考虑200个;top-k=50太严
top-p自适应分布形状,top-k一刀切。
三、代码实现
3.1 完整实验:5种sampling对比
# sampling_compare.py
"""
对同一prompt用不同sampling参数调Anthropic API,
量化输出多样性、长度、相关性。
"""
import anthropic
from collections import Counter
client = anthropic.Anthropic()
PROMPT_CREATIVE = "Write a one-sentence story about a robot who discovers emotions."
PROMPT_FACT = "What is the capital of France? Answer with one word."
PROMPT_CODE = "Write a Python function to compute factorial. Use recursion."
CONFIGS = [
("greedy", {"temperature": 0.0, "top_p": 1.0}),
("conservative", {"temperature": 0.3, "top_p": 0.9}),
("balanced", {"temperature": 0.7, "top_p": 0.95}),
("creative", {"temperature": 1.0, "top_p": 0.95}),
("wild", {"temperature": 1.2, "top_p": 1.0}),
]
def sample_n(prompt, config, n=5, max_tokens=200):
outputs = []
for _ in range(n):
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=max_tokens,
temperature=config["temperature"],
top_p=config["top_p"],
messages=[{"role": "user", "content": prompt}]
)
outputs.append(resp.content[0].text)
return outputs
def diversity(outputs):
"""简单多样性指标:unique outputs / total"""
return len(set(outputs)) / len(outputs)
def avg_length(outputs):
return sum(len(o) for o in outputs) / len(outputs)
def run_experiment(prompt_name, prompt, n=5):
print(f"\n=== {prompt_name} ===")
print(f"Prompt: {prompt[:80]}...")
for label, config in CONFIGS:
outs = sample_n(prompt, config, n=n)
d = diversity(outs)
l = avg_length(outs)
print(f" {label:<14} T={config['temperature']:.1f} top_p={config['top_p']:.2f} "
f"diversity={d:.0%} avg_len={l:.0f} example={outs[0][:60]!r}")
if __name__ == "__main__":
run_experiment("Creative", PROMPT_CREATIVE)
run_experiment("Factual", PROMPT_FACT)
run_experiment("Code", PROMPT_CODE)
预期典型输出:
=== Creative ===
greedy T=0.0 top_p=1.00 diversity=20% avg_len=92 example='In the dim glow of the server room...'
conservative T=0.3 top_p=0.90 diversity=80% avg_len=98
balanced T=0.7 top_p=0.95 diversity=100% avg_len=105
creative T=1.0 top_p=0.95 diversity=100% avg_len=110
wild T=1.2 top_p=1.00 diversity=100% avg_len=130
=== Factual ===
greedy diversity=20% avg_len=5 "Paris."
balanced diversity=20% avg_len=5 (well-known fact不受T影响)
wild diversity=40% avg_len=8 ("Paris.", "Paris" with explanation)
=== Code ===
greedy diversity=20% always correct, identical output
creative diversity=100% sometimes引入bugs或alternative styles
洞察:
- 高T在"已确定"问题上没帮助(factual recall)
- 高T在creative任务上明显多样性提升
- 高T在code任务上反而降低正确率——这是反直觉但真实的trade-off
3.2 实现一个简易sampling库
# my_sampling.py
"""
教学用:用numpy实现各种sampling
(实际生产用vLLM/SGLang)
"""
import numpy as np
def softmax(z):
z = z - z.max()
e = np.exp(z)
return e / e.sum()
def greedy(logits):
return int(np.argmax(logits))
def temperature_sample(logits, T=1.0):
if T == 0:
return greedy(logits)
probs = softmax(logits / T)
return int(np.random.choice(len(probs), p=probs))
def top_k_sample(logits, k=50, T=1.0):
top_idx = np.argpartition(logits, -k)[-k:]
masked = np.full_like(logits, -np.inf)
masked[top_idx] = logits[top_idx]
return temperature_sample(masked, T)
def top_p_sample(logits, p=0.9, T=1.0):
probs = softmax(logits / T)
sorted_idx = np.argsort(probs)[::-1]
cumsum = np.cumsum(probs[sorted_idx])
cutoff = np.searchsorted(cumsum, p) + 1
keep = sorted_idx[:cutoff]
masked = np.full_like(logits, -np.inf)
masked[keep] = logits[keep]
return temperature_sample(masked, T)
def min_p_sample(logits, p_base=0.05, T=1.0):
probs = softmax(logits / T)
threshold = p_base * probs.max()
keep = probs >= threshold
masked = np.where(keep, logits, -np.inf)
return temperature_sample(masked, T)
# Demo: 同一logits不同sampling 1000次的分布
logits = np.array([5.0, 4.5, 3.0, 2.5, 1.0, 0.5, -1.0, -2.0])
labels = list("ABCDEFGH")
for name, fn in [
("greedy", lambda l: greedy(l)),
("T=0.7", lambda l: temperature_sample(l, 0.7)),
("top-k=3", lambda l: top_k_sample(l, 3, 0.7)),
("top-p=0.9",lambda l: top_p_sample(l, 0.9, 0.7)),
("min-p=0.1",lambda l: min_p_sample(l, 0.1, 0.7)),
]:
counts = np.bincount([fn(logits) for _ in range(2000)], minlength=8)
print(f"{name:<12}: {dict(zip(labels, counts))}")
输出(典型):
greedy : {'A': 2000, ...}
T=0.7 : {'A': 1100, 'B': 600, 'C': 200, 'D': 80, 'E': 15, ...}
top-k=3 : {'A': 1200, 'B': 600, 'C': 200, ...}
top-p=0.9 : {'A': 1100, 'B': 600, 'C': 200, 'D': 80, ...}
min-p=0.1 : {'A': 1500, 'B': 400, 'C': 100, ...}
3.3 Speculative Decoding 直观演示
# speculative_demo.py
"""
教学:speculative decoding加速原理
不真跑model,用伪概率说明算法
"""
import numpy as np
np.random.seed(0)
def draft_propose(n_tokens=4):
"""draft model快速生成n个token"""
# 假装每个token返回 (token_id, prob)
return [(np.random.randint(0, 100), np.random.uniform(0.3, 0.9)) for _ in range(n_tokens)]
def target_verify(draft_tokens):
"""target model给每个token一个真实prob"""
return [np.random.uniform(0.2, 0.95) for _ in draft_tokens]
def speculative_step():
proposals = draft_propose(n_tokens=4)
target_probs = target_verify(proposals)
accepted = []
for (tok, p_d), p_t in zip(proposals, target_probs):
# accept概率 = min(1, p_target / p_draft)
accept_p = min(1.0, p_t / p_d)
if np.random.random() < accept_p:
accepted.append(tok)
else:
# 第一个reject位置:用调整分布从target采新token
break
return accepted
# 1000个step统计
total_accepted = sum(len(speculative_step()) for _ in range(1000))
print(f"Average accepted tokens per step: {total_accepted / 1000:.2f}")
# 期望2-3,意味着用~1次target call生成2-3个token,2-3x加速
四、Anthropic API最佳实践
4.1 参数设置
Anthropic Messages API支持:
client.messages.create(
model="claude-opus-4-7",
temperature=0.7, # 0-1(不像OpenAI支持>1)
top_p=0.95, # 默认通常不指定
top_k=None, # API也支持,但很少用
max_tokens=4096,
messages=[...]
)
Anthropic特殊:T最大1.0(OpenAI能到2.0)。如果想"更野",建议改prompt(如"be unconventional")而非T>1。
4.2 任务-参数映射推荐表
| 任务类型 | Temperature | Top-p | 备注 |
|---|---|---|---|
| Structured output (JSON) | 0.0 | - | 用structured output API更佳 |
| Code generation | 0.0-0.2 | 0.95 | 低T避免bug |
| Factual Q&A | 0.0-0.3 | - | 准确率优先 |
| Summarization | 0.3-0.5 | 0.95 | 一定多样性 |
| Brainstorming | 0.8-1.0 | 0.95 | 创意优先 |
| Creative writing | 0.7-1.0 | 0.95 | 平衡 |
| Translation | 0.0-0.3 | 0.9 | 准确性 |
| Conversation | 0.5-0.7 | 0.95 | 自然度 |
| Reasoning (extended thinking on) | 1.0 (forced by API) | - | thinking模式T固定1.0 |
4.3 Extended Thinking + Sampling
⚠️注意:当thinking={"type": "enabled"}时,Anthropic 强制 temperature=1.0、top_p=1.0(避免破坏thinking trace)。如果你需要确定性思考,目前只能不开thinking。
五、金融领域应用
案例:量化策略生成 vs 风险报告
| 任务 | T推荐 | 原因 |
|---|---|---|
| 生成量化策略代码 | 0.0 | 避免引入bug;可精确复现 |
| 生成市场情绪分析(风格化) | 0.7 | 多样表达,避免模板化 |
| 提取财报关键数字 | 0.0 | 确定性,可audit |
| 写客户邮件 | 0.5 | 自然但稳定 |
| Trading idea brainstorm | 0.9 | 探索非常规思路 |
实际代码:合规审查(必须T=0)
def compliance_check(transaction_text):
"""
监管审查必须确定性 — 同一输入永远同一输出,便于audit。
"""
resp = client.messages.create(
model="claude-opus-4-7",
temperature=0.0, # 关键
max_tokens=2048,
system=COMPLIANCE_RULES_SYSTEM,
messages=[{"role": "user", "content": transaction_text}]
)
return resp.content[0].text
审计要点:日志record temperature, top_p, model version。同一code+model+T=0输出有确定性(虽然不100%,因infrastructure-level non-determinism)。生产建议:缓存response by hash(input + params)。
六、常见陷阱
- 以为高T等于更聪明:不会。T只改变多样性,不改变知识。难题低T可能更准确。
- structured output忘记T=0:高T导致JSON里key顺序变、value幻觉化。JSON必须T=0。
- temperature=0就是确定:错。GPU non-determinism (parallel reduction order, seed) 让"T=0"也偶尔不一样。要严格确定性需要seed参数(vLLM/SGLang支持,OpenAI部分支持)。
- top_p=0.9一刀切所有任务:factual T=0 + top_p无所谓;creative T=0.8 + top_p=0.95;code T=0即可。无脑设0.9是反优化。
- forgot Speculative decoding 不改变distribution:有人以为spec decoding"会变笨"——错,rejection sampling数学保证最终分布严格等同target。
七、关键速查
Anthropic API sampling参数
{
"temperature": 0.0-1.0, # 默认1.0
"top_p": 0.0-1.0, # 默认1.0
"top_k": int (optional),
"max_tokens": int (required),
"stop_sequences": ["..."], # 自定义停止token
}
Trade-off速查
Greedy: 最快 + 最确定 + 最容易repetition
Top-k: 简单 + 缺乏自适应
Top-p: 主流推荐 + 自适应分布形状
Min-p: 高T场景下比top-p好
Beam: 翻译/摘要好,对话差
Speculative Decoding理论
加速比 ≈ E[accepted tokens per draft]
典型值:2-4x
draft model质量越接近target,加速越大
八、面试题
Q1: Temperature=0是否就完全确定?
算法层面是greedy,理论确定。但实际不100%——浮点累加顺序受GPU并行影响,相同logits可能极小差异翻转argmax。需要seed + deterministic kernels才严格可复现。生产监控里要记录model version + 完整params hash。
Q2: 为什么structured output (JSON) 推荐T=0?
JSON schema有严格语法约束。T>0可能在key list、bracket close位置选错token,破坏可解析性。Anthropic的structured output API底层用constrained decoding强制schema,但仍建议T=0最大稳定。
Q3: Speculative decoding对用户有什么trade-off?
没有质量trade-off(数学保证output distribution相同)。trade-off在infra:(a) 需要部署额外draft model消耗显存;(b) 当draft与target divergent时(如rare phrase),加速比下降甚至变慢;(c) batch大时benefit递减(因为本身就GPU满载)。
Q4: Anthropic API为什么extended thinking强制T=1?
Thinking是模型"探索性思考"的过程,需要分布上的探索而不是greedy。T=0会让thinking trace overly deterministic,丧失self-correction能力。设计上Anthropic锁定T=1保证thinking质量;最终answer的稳定性靠thinking后的converge机制。
九、明日预告
Day 125: Prompt基础模式 — Zero-shot/Few-shot/CoT/ToT/Self-consistency,实测5种模式对比。