采样:temperature / top-k / top-p
一句话定位:模型每一步输出的不是「一个词」而是「全词表的概率分布」,采样就是在这张分布上抽签——temperature 调签筒的胖瘦、top-k/top-p 决定候选池大小;本文讲清机制与参数适配,并回答「temperature=0 真的确定吗」。前置:Tokenizer 与分词——先知道模型以 token 为输出单位。
先从「同一个问题两次答案不同」说起
你问模型同一个问题两次,得到两个措辞不同的答案;把 temperature 设成 0,答案稳定了。
但换台机器或隔天再跑,又出现了细微差别。
客服场景你想要稳定,写作场景你想要惊喜,一个参数真的能两头兼顾吗?
要回答这些,得先看清模型每一步到底输出什么:不是「下一个词」,而是全词表几万到二十几万个 token 各自的概率——比如「今天天气真」之后「好」占七成、「糟」占一成、其余分食剩余概率。采样就是在这张分布上抽签,所有生成参数都在改这张签筒。
顺带点破一件常被忽略的事:这些旋钮全拧在输入侧。模型训练完成、参数冻结之后,对话里发生的一切都只是推理(inference,逐 token 生成),不是再学习——想稳定改变它的行为,改的是输入(提示与上下文),不是它的「脑子」。
抽签怎么抽?temperature、top-k、top-p 各自在改什么?
流程是一条四级流水线:模型先吐出全词表的原始打分(logits),temperature 调整分布的胖瘦,top-k / top-p 剪掉长尾只留候选池,最后按剩余概率抽签定出一个 token——然后从头再来,直到序列结束。先看全景:
图:采样四级流水线——①打分 ②调温 ③剪枝 ④抽签;temperature=0 时旁路②③④直接贪心取最大
渐进拆解:抽签之前先把签筒整形
把④的抽签想成转盘抽奖。temperature 是转盘的重心调整:调低,转盘几乎停在大奖格(高概率词);调高,每个格子被抹匀,冷门格也有机会。top-k 和 top-p 是两种「删格」策略:top-k 说「只留热度前 k 名的格子」,top-p 说「只留加起来占九成热度(p=0.9)的最少格子」——前者格子数固定,后者格子数随分布形状浮动。整条流水线都在回答一个问题:这次该让模型多「稳」或多「野」。
temperature:调分布胖瘦
模型给的 logits 是原始分数,除以温度 T 再做 softmax(把一组分数归一化成总和为 1 的概率)才得到概率:T 越小,高分与低分的差距被放大,分布越来越尖,最终收敛为「永远取最高分」的贪心解码(T=0 的极限,正式名称 argmax,即取最大项);T 越大,差距被抹平,冷门 token 获得与热门 token 接近的机会。
拿个假想分布感受一下(T=1 的输出):「今天天气真」之后,「好」0.6、「不错」0.25、「糟」0.05、其余合计 0.1。T=0.5 时「好」涨到约 0.83,冷门被压扁;T=2 时「好」跌到约 0.43、「不错」「糟」都抬头——模型开始更敢冒险。一句话:temperature 改的是「要不要赌」,不改变「有哪些候选」。
top-k 与 top-p:两种剪枝
temperature 之后再野,长尾里几万个冷门 token 还在池子里——抽到它们多半是胡话。两种剪法:
- top-k 直接保留概率最高的 k 个:k=40 就是只留 40 名。
- top-p(又称 nucleus sampling,nucleus 即「核」——分布的核心候选区)保留「累计概率达到 p 的最小集合」:分布尖时候选可能只有几个,分布平时自动放宽,候选池大小随分布自适应。
2019 年的论文《The Curious Case of Neural Text Degeneration》用实验确立了这类截断采样的地位:固定候选数的 top-k 在尖峰时留太多尾巴、平坦时又砍掉好候选,top-p 的自适应正是解法。实践中两者常叠加使用(先 top-k 后 top-p),再叠一层重复惩罚压制「同一句话绕圈说」——两档惩罚见下节。
三条策略放一张表对照:
| 策略 | 控制什么 | 改概率还是删候选 | 典型值区间 |
|---|---|---|---|
| temperature(温度) | 分布胖瘦(赌性大小) | 只改概率,不删候选 | 提取 0~0.2 · 问答 0.3~0.7 · 写作 0.7~1.0 |
| top-k | 候选池大小(按个数) | 删候选,池内重归一化 | 如 k=40 |
| top-p | 候选池大小(按累计概率) | 删候选,池随分布自适应 | 如 p=0.9,写作可配 0.95 |
表里的机制都能用动手一节的五行代码亲手跑出来。
重复惩罚:两档力度
截断之后还有一层:对已出现过的 token 降概率或降频次,压制「同一句话绕圈说」。两档按「看什么」分:
| 档位 | 看什么 | 力度 | 适用场景 |
|---|---|---|---|
| 存在惩罚 | 出现没有 | 出现一次就罚,力度固定 | 推动模型谈新话题、防卡在同一题材 |
| 频率惩罚 | 出现几次 | 说得越多罚越重 | 防逐字复读 |
OpenAI 官方文档正是按这两档场景定义的。
beam search 为何被弃用
每步只抽一个 token 叫贪心;beam search 则同时保留多条最优路径,最后选总分最高的序列——在翻译、摘要这类答案有标准形态的任务上它确实更强,至今仍是机器翻译的标准配置。
但在开放式生成里它反复被实验打脸,原因有二:
- 倾向产出「最平均的话」——通顺、安全、车轱辘、没有信息量。典型样本是「I don’t know. I don’t know. I don’t know.」式的循环复读,以及放之四海皆准的空话——概率最高的路径往往是最大公约数式的平庸。
- 计算成本是贪心的数倍。
高质量开放文本要的是多样性,不是全局最优路径——这是开放式生成弃用 beam search 转向截断采样的核心原因。
temperature=0 真的确定吗?
理论上 T=0 即贪心解码:永远取最高分,抽签环节是确定的。
但工程上整条推理链路不完全确定。GPU 浮点加法不满足结合律(先加谁后加谁,结果差在小数点后很多位),而服务端为了吞吐把多个请求拼成 batch 并行计算——batch 的组成变了,同一行求和的顺序就变,logits 末位小数就抖动;当两个候选 token 概率本来就半斤八两,这点抖动足以让 argmax(取最大项)翻到另一个。各类缓存(结果缓存、前缀缓存)的命中策略同样算实现差异——同问句命中不同缓存路径,输出也可能不同。
Thinking Machines Lab 的长文把这套「批处理不变性」问题拆得很透:即使 temperature=0、固定随机种子,跨 batch 的输出仍可能不同。结论:T=0 给的是「规则确定」,不是「结果逐字节可复现」;要近似可复现,得约束服务端(固定 batch、确定性内核)或接受「大体一致、偶有微差」。微差的来源链画成图:
图:T=0 微差来源链——浮点/batch/调度三源汇入末位抖动,只在近并列候选处起作用
大白话:模型每步交一张几万格的概率彩票单;temperature 决定单子上号码冷热差多大,top-k/top-p 决定开卖哪些格子,抽签决定这回开哪个。temperature=0 是「不抽奖、直接拿头奖」——规则如此,但头奖是谁,取决于算概率那台算盘当天的拨法。
技术定义:采样在 logits 上依次施加温度缩放、截断(top-k / top-p / 各类惩罚)与概率化抽取(或贪心取 argmax);数学上 temperature 是对 softmax 温度的调节,top-p 即 nucleus sampling。主流推理框架(vLLM 等)还受 batch 调度与算子实现影响,逐字节复现需要专门的确定性推理配置。
动手:五行代码看懂三个参数
用一个五词玩具分布,亲手跑出三种策略的结果(可直接运行):
import numpy as np
logits = np.array([2.0, 1.5, 1.0, 0.5, 0.1]) # 五个候选的原始分数
def sample(logits, T=1.0, top_k=None, top_p=None):
z = logits / T # ② temperature:先除温度
p = np.exp(z - z.max()); p /= p.sum() # softmax 归一化
idx = np.argsort(p)[::-1] # 按概率从高到低排序
keep = idx[:top_k] if top_k else idx # ③ top-k:留前 k 名
if top_p: # ③ top-p:在候选内再按累计 ≥ p 剪
keep = keep[np.cumsum(p[keep]) - p[keep] < top_p]
keep_p = p[keep] / p[keep].sum() # 池内重归一化
return np.random.choice(keep, p=keep_p) # ④ 抽签
跑三组对照(实测输出):T=1.0 时头名概率约 0.43(抽 1000 次实测 0.46,±0.03 属抽样波动);T=0.3 时头名概率升到 0.81——分布明显变尖;top_p=0.9 时第五个候选(概率约 6%)被剪出候选池——分布越尖,截断越早;分布平时候选池自动放宽。最后验证本篇核心问题:T=0 等价于直接取 idx[0](示例代码不受理 T=0,除零会报错;贪心就是这么做的)——注意这一步在「你的进程」里是确定的,但服务端 batch 里的浮点抖动不归你管,这就是「T=0 仍可能微差」的边界所在。
边界与取舍:参数跟着任务走
任务适配是调参的唯一正解
经验区间(以各家官方文档为准):代码与数据提取类任务用低温(0~0.2),削掉随机性换稳定格式;日常问答 0.3~0.7;创意写作 0.7~1.0,甚至配合 top-p 0.95 换取多样性。重复惩罚在长文本里防绕圈,但设太重会「谈过一次的话题永远不再出现」——写代码时把关键词也惩罚没了。没有万能参数,只有任务与参数的匹配。
要稳定输出,别只拧 temperature
生产级「稳定」有三层:采样层(T=0 或低温)、提示层(结构化输出与 schema 约束)、平台层(部分 API 支持固定随机种子,确定性仍受上文 batch 与内核影响)。关键业务要可复现,正确姿势是「低温 + schema 校验 + 失败重试」,把概率系统当工程系统管理。
坏味道预警:参数玄学化
「temperature=0.7 比 0.6 聪明」这类结论多半是过拟合某次体验——采样参数改变的是分布形状,不是模型能力。当任务质量不稳,先查提示与上下文(Context 工程专篇),再动采样参数。参数实验的正确姿势:固定其他一切,一次只动一个参数,用同一批样例对比输出分布——把玄学变成对照实验。
自测
合上文章先自己答,再展开对照:
temperature=0 真的确定性吗?(实现差异、缓存、batch 的影响)
规则确定,结果不保证逐字节复现。T=0 等价于贪心解码——跳过抽签直接取最高分,抽签环节确定性成立。但整条推理链路仍有非确定源:GPU 浮点加法不满足结合律,服务端把多请求拼 batch 并行,batch 组成一变,求和顺序就变,logits 末位小数抖动;当两个候选概率本就接近,argmax 可能翻转。此外部分实现的缓存与内核调度也引入差异。要近似可复现:约束 batch 与确定性内核(推理服务端配置)、固定随机种子(采样模式下),或接受「大体一致、偶有微差」并用 schema 校验兜底。
beam search 为什么在开放式生成里被弃用,截断采样好在哪里?
beam search 每步保留多条高分解路径、最后选全局总分最高的序列——这在翻译、摘要等答案形态固定的任务上仍是标准做法;但开放式生成里它倾向产出「概率上最平均」的文本:通顺、通用、重复、缺信息量,且计算成本数倍于贪心。截断采样(top-k/top-p)每步只抽一个 token,用随机性保留多样性,用截断压制长尾胡话:top-k 按个数固定候选池,top-p 按累计概率自适应池大小,实验证明更能避开重复与生硬。一句话:封闭任务找最优路径,开放任务要抽样多样性——目标不同,工具不同。
参考与延伸
- Thinking Machines Lab《Defeating Nondeterminism in LLM Inference》:temperature=0 仍不确定的批处理不变性分析,本篇核心问题的深读 — thinkingmachines.ai/blog/defeating-nondeterminism-in-llm-inference
- Holtzman 等《The Curious Case of Neural Text Degeneration》(2019):beam search 退化与 nucleus 采样的原始实验 — arxiv.org/abs/1904.09751
下一站:Attention 与 Transformer——采样之上往回看一层:那些概率是怎么被注意力机制算出来的。