采样选项:top-k / top-p / temperature / logprobs / penalties
谁该读这一篇? 想用好采样参数、不想瞎设的工程师;做 RL 训练 / Best-of-N 的研究员。 前置阅读:
00-prerequisites.md。 耗时: 30 分钟 学完能: 1. 用数学和直觉解释 temperature / top-k / top-p; 2. 区分 frequency / presence / repetition penalty; 3. 输出 logprobs 并解读; 4. 选对参数给定具体业务(chat / 代码 / RAG / 翻译); 5. 排查"输出突然乱码 / 重复"等采样相关 bug。
1. Logits → Token:完整流程
Hidden state ─▶ LM head ─▶ logits [vocab_size]
│
▼
penalties (freq/pres/rep)
│
▼
temperature scaling
│
▼
top-k filter
│
▼
top-p filter
│
▼
constrained mask (regex/schema)
│
▼
softmax
│
▼
multinomial sample → token
每个滤波器在不同程度上"裁剪"候选 token 分布。
源码:srt/sampling/、srt/layers/sampler.py。
2. Temperature
- $T = 1$:原始分布。
- $T \to 0$:变 argmax(greedy)。
- $T \to \infty$:变均匀分布。
两个方向上的极端:T 越小越确定、越机械;T 越大越随机、越发散。
业务推荐:
| 业务 | T |
|---|---|
| 抽取 / 分类 / 数学 | 0.0 (greedy) |
| RAG / QA | 0.1-0.3 |
| Chat | 0.7-1.0 |
| 创意写作 | 0.8-1.2 |
| 多样性采样(Best-of-N) | 0.8-1.0 |
3. Top-k
只在 logits 最大的 k 个 token 里采样,其他全置 -inf。
top_k_mask = logits < kth_largest(logits, k)
logits[top_k_mask] = -inf
- k=1:等价 greedy。
- k=50:常用 baseline。
- k=vocab_size:等价不裁剪。
适合:
- 词表大(130k+)时防止低概率噪音。
4. Top-p (Nucleus)
按概率累积,取最少集合 S 使 $\sum_{i \in S} p_i \geq p$:
sorted_idx = argsort(-probs)
cumsum = cumsum(probs[sorted_idx])
S = sorted_idx[cumsum < p] # 加 1 让恰好超过
mask everything outside S
- p=0.9:常用。
- p=0.95:保留更多多样性。
- p=0.5:保守。
top-p 比 top-k 更适应分布的尖锐度(尖时少 token,平时多 token)。
通常 temperature + top-p 组合,少用 top-k。
5. Min-p
只保留概率 ≥ threshold 的 token。
例:
- min_p=0.1, p_max=0.5:阈值 0.05,保留概率 ≥ 0.05 的 token。
- min_p=0.1, p_max=0.05:阈值 0.005,保留更多。
适应性强、参数少,渐成 chat 推荐选择。
6. Repetition / Frequency / Presence Penalties
OpenAI 协议三种 penalty:
6.1 Repetition penalty (HF 风格)
logits[token] /= rep_penalty # 已出现过的 token
rep_penalty=1.1 是常用。
问题:对所有出现过 token 一视同仁,没区分次数。
6.2 Frequency penalty (OpenAI 风格)
logits[token] -= freq_penalty * count[token]
次数越多惩罚越大。freq_penalty=0.5 常用。
6.3 Presence penalty (OpenAI 风格)
logits[token] -= pres_penalty if token in seen else 0
只要出现过就扣一次。
三种各有适用:
- Chat 防重复:
presence_penalty=0.5 - 长文创作:
frequency_penalty=0.3 - 代码生成:通常关掉所有 penalty(代码有合理重复)
7. DRY (Don't Repeat Yourself)
更新版的 repetition 抑制:
检测最近 N 个 token 里的 n-gram 重复
对重复 n-gram 的下一 token 大幅降低 logit
适合:长生成中防止"循环"。
SGLang 通过 dry_* 系列参数配。
8. Logprobs 输出
resp = client.chat.completions.create(
model="...",
messages=[...],
logprobs=True,
top_logprobs=5,
)
输出里每个 token 带:
logprob:选中 token 的对数概率。top_logprobs:top-5 候选的 logprob。
用途:
- Self-eval(模型对答案的"信心")。
- 检测幻觉(低信心 token 多 → 可能瞎说)。
- RAG 验证(看是否复述了 context)。
代价:每步多算一些,TPOT 增加 ~5%。
9. Best-of-N(拒绝采样)
# 客户端层:跑 N 次,选 logprob 最高的
candidates = []
for _ in range(N):
r = client.chat.completions.create(..., temperature=1.0, logprobs=True)
score = sum(t.logprob for t in r.choices[0].logprobs.content)
candidates.append((r.choices[0].message.content, score))
best = max(candidates, key=lambda x: x[1])
适合:答案空间小、单次置信度低(数学、规划)。
DSL 等价物(07-hands-on/04-custom-frontend.md §4)用 fork 并 select。
10. 采样参数 cheatsheet
| 场景 | T | top_p | top_k | penalties | 备注 |
|---|---|---|---|---|---|
| 分类 / 抽取 | 0 | 1 | - | - | greedy |
| Chat | 0.7 | 0.9 | - | pres=0.5 | balance |
| 代码 | 0.2 | 0.95 | - | 关 | 代码不要乱 |
| 翻译 | 0.3 | 0.9 | - | - | 准确 |
| 创意 | 1.0 | 0.95 | - | freq=0.3 | 多样性 |
| 数学 / 规划 | 0.0 + best-of-5 | - | - | - | 多次投票 |
| RAG | 0.3 | 0.9 | - | - | 准确性 |
11. 常见 bug
| 现象 | 排查 |
|---|---|
| 输出短得反常 | 检查 EOS 是否被 mask(受约束输出常见) |
| 输出循环(同一短语反复) | rep / dry penalty 没设 |
| 答案全选 A(多选题) | choice 之间 logit 不均;用 select 而非 gen |
| Logprobs 全 0 | 触发了 greedy(T=0),logprob 都是 0(最大概率) |
| 不同 batch 同 seed 不同结果 | 数值不可重现;某些 attention backend 有这问题 |
12. 数值确定性
设 seed=42 + temperature=0:
- 理论上完全确定。
- 实际:不同 batch_size 下 attention kernel 走不同路径,浮点累加顺序不同,可能差 1 个 token。
SGLang 有 batch_invariant_ops(srt/batch_invariant_ops/)让"同输入不同 batch 输出一致",开启后 throughput 略低。
13. 源码
| 内容 | 文件 |
|---|---|
| Sampler | srt/layers/sampler.py |
| SamplingParams | srt/sampling/sampling_params.py |
| BatchInvariant | srt/batch_invariant_ops/ |
| Logits processor | srt/layers/logits_processor.py |
14. 小结
- 采样流程:penalties → temperature → top-k → top-p → constrained → softmax → sample。
- 不同业务采样参数差异大;查 cheatsheet。
- Logprobs 可用于自信度评估、幻觉检测。
- Best-of-N 数学题神器。
- 数值确定性靠 batch_invariant_ops,代价是性能。
15. 自检
- T=0.1 和 T=1.0 在 top_p=0.9 下输出差异主要在哪?
答案
T 缩放 logits 后再 top_p 过滤。 T=0.1:logits 被放大 10×,**概率分布极尖锐**,top_p=0.9 实际只保留 1-2 个 token,输出近似 greedy。同问题反复跑结果几乎相同。 T=1.0:logits 不变,分布平坦,top_p=0.9 保留更多候选 token(可能 5-20 个),输出多样性高。同问题反复跑结果不同。 实际差异:T=0.1 时输出"理性"、保守、可能机械;T=1.0 时输出"有想法"、有惊喜也有错误。 选择:分类 / 抽取 T=0;chat T=0.7;创意写作 T=1.0。- Frequency / presence / repetition penalty 各自的差异?
答案
三种"防重复"机制,公式不同: - **Repetition penalty**(HF 风格):`logits[token] /= rep_penalty if token in seen`。对**所有**出现过的 token 一视同仁惩罚(不区分次数);对代码 / 重复结构不友好。 - **Frequency penalty**(OpenAI):`logits[token] -= freq * count[token]`。**次数越多惩罚越大**;适合长生成防车轱辘话。 - **Presence penalty**(OpenAI):`logits[token] -= pres if token in seen`。出现过就扣固定一次;轻微鼓励多样性。 实战: - Chat:`presence=0.5`。 - 创意:`frequency=0.3`。 - 代码:全关(代码有合理重复)。 - 数学:全关(数字会复用)。 叠加用一种就行,不要三个全开。- Logprobs 怎么用来判断幻觉?
答案
原理:模型对自己"瞎说"的 token 通常 logprob 低(自信度差)。 实战: (1) **平均 logprob**:`mean(logprobs)` 越低越不可信;阈值如 -2.0(每 token 概率 ~13%)。 (2) **最小 logprob**:找最不自信的 token,看是否在关键事实位置(人名、数字、日期)。 (3) **熵估计**:用 `top_logprobs=5` 看候选分布,熵高 = 模型在多个选项里犹豫。 (4) **数字字段 vs 文字**:JSON Schema 输出时数字字段 logprob 低 = 模型不确定具体数值。 局限:模型可能"自信地编"(high logprob 但内容假);logprobs 只是辅助信号,不是绝对判断。 配合 RAG 引用 / fact-check 工具用最稳。- 同一 seed 不同 batch 结果不同怎么解?
答案
原因:attention kernel 在不同 batch size 下走不同代码路径(kernel autotune、tile 大小、tensor core 选择),浮点累加顺序不同,每层引入微小 numerical noise,几层后被放大成 logits 差异,sampler 在 boundary 处选不同 token。 解决: (1) **`--enable-batch-invariant-ops`** ([`srt/batch_invariant_ops/`](../sglang/python/sglang/srt/batch_invariant_ops/)):强制 attention / matmul 走 batch-invariant 路径,相同输入不同 batch 数值完全一致。代价:throughput 降 5-15%。 (2) **更高精度**:FP32 算 attention 噪声小,但成本高。 (3) **业务侧容忍**:很多场景"近似一致"足够(chat 不需要 byte-exact),不强求。 什么时候必须 deterministic:RL 训练、回归测试、合规审计。生产服务通常不需要。- 翻译任务为什么 T 不能太高?
答案
翻译要求**准确 + 一致**: - 同源句应该有稳定译文,T 高会让相同输入每次产出不同译文,破坏一致性。 - 准确性要求选"最可能的"译法,T 低让模型选 argmax。 - 高 T 引入低概率 token,可能选择稀有词汇 / 错误同义词。 实战:T=0.2-0.3 是 sweet spot;T=0 偶尔有机械感(同句固定译,缺少自然变化),加一点点温度。 对比创意 / 自由生成:T=1.0 才能"有想法";翻译需要的是"准确"不是"想法"。 配合 `top_p=0.9` + `frequency_penalty=0` 即可。16. 下一步
02-structured-output.md— 结构化进阶。04-lora.md— LoRA。- 源码:
srt/layers/sampler.py、srt/sampling/。