温度、top-p,以及你并不拥有的确定性
温度会在 softmax 前除以 logit,这个事实足以推翻“创意旋钮”的说法。同一次 greedy 调用,两次运行,两个答案。
本页内容
下面是同一个请求发给同一个模型五次。相同权重、相同 prompt、相同机器、相同随机种子。唯一变化的是一个数字。
prompt: "Q: What is the capital of France?\nA:"
T = 0.0 " Paris\nWhat is the question and does the answer answer it? The
question is: What is the capital of France?..."
T = 0.7 " Paris\nWhat is the question: Which city is the capital of
France?..."
T = 1.0 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea..."
T = 1.5 " Paris\nWhat clue from premise allows we to conclude that Godwin
was &, He chose Healing Crimson Colour No:white flour Pure..."
T = 2.0 "安全感金华.ITEMT]]];\naims assume parental.st-importe.valtermination
Screens قطر_Zeroหมายเลข-zA ('$ספטמבר..."没有任何东西坏掉。最后一行里的每个 token 都是从模型自己在 151,936 个词表项上的概率分布中合法抽出来的。那个变化的数字叫温度,大多数文档把它描述成创意旋钮,而这个描述是错的;本章会演示这一点,而不是只做断言。
这也是前三个承诺兑现的一章。第 4 章定义了 logit,但一直没有真正用上它。第 2 章的浮点数框以一句提醒结束——等第 17 章问为什么相同 prompt、模型和种子仍可能产生不同 token 时,请记住这一点。而第 9 章的 mixture-of-experts 框承诺会列出四个非确定性的原因。三者都在下面出现。
整章都悬在这一行公式上
链接到此部分:整章都悬在这一行公式上第 4 章把 logit 介绍为未归一化的实数分数,每个类别一个。第 8 章让语言模型为词表中的每个条目生成一个 logit。softmax 会把这个向量 变成概率:
温度在这里进入——这个名字借自统计物理,在那里同一个参数控制 Boltzmann 分布在低能态上集中得有多尖锐1——而且它是在取指数之前除以 logits:
这个位置就是整个机制,值得用两行代数说明它为什么不可能放在别处。假设你试图把温度应用到概率上——把它们按 缩放再重新归一化。你会得到
常数抵消了。缩放概率完全没有效果;分布原样回来。温度之所以有效,只是因为它作用在指数上:在取指数前除以 ,等价于把每个概率提升到 次方——这是一种非线性重塑,改变的是条目之间的比值,而不是它们共同的尺度。
从这个位置出发,两个极限不需要额外推导就能得到。当 时,最大的 logit 会把其他 logit 远远甩开, 坍缩到单个最高分 token 上:greedy decoding。当 增大时,每个 都趋向于零,每个指数都趋向于 1,分布会向整个词表上的均匀分布变平。在恰好 时,公式会除以零,所以每个实现都会把它 special-case 为算术最大值——包括下面的小组件,它会在 切换到 argmax。
有一点警告,因为重名会造成真正的混淆。机器学习里还有另一个无关的东西也叫温度:temperature scaling,一种校准方法,会在验证集上拟合一个值,让分类器的置信度匹配它的准确率。2 公式相同,但与生成无关。论文里说“temperature”时经常指的是那个;本章从不指它。
下面就是这个分布,算术过程摆在你面前。logits 是固定且合理的,所以你可以把下文里的数字与看到的内容核对:
温度不是创意旋钮
链接到此部分:温度不是创意旋钮␣banana 这个数字就是整个论点的缩影:提高温度不能让模型产生它原本没有的想法。 logits 已经算出来了,排名已经固定,而温度会完全保留这个排名——再多的热量也不会让低分 token 超过高分 token。它所做的只是沿着模型自己给出的排名向下重新分配质量。高温不会让模型更有创造力;它只是让模型更可能输出那些它自己评为糟糕的 token。
在真实词表上,这不再只是个有趣现象,而会成为高温输出不可用的原因。在 Qwen/Qwen2.5-0.5B-Instruct 上测量,一次 forward pass,使用上面的 prompt,统计需要多少 token 才能累积到给定比例的概率质量:
| temperature | top-1 probability | entropy | tokens holding 80 % | 90 % | 95 % | 99 % |
|---|---|---|---|---|---|---|
| 0.5 | 99.98 % | 0.00 nats | 1 | 1 | 1 | 1 |
| 0.7 | 99.65 % | 0.03 nats | 1 | 1 | 1 | 1 |
| 1.0 | 96.01 % | 0.30 nats | 1 | 1 | 1 | 14 |
| 1.2 | 88.20 % | 0.88 nats | 1 | 2 | 13 | 252 |
| 1.5 | 62.83 % | 3.07 nats | 29 | 353 | 2,672 | 26,787 |
| 2.0 | 16.62 % | 8.19 nats | 13,516 | 32,966 | 55,231 | 101,205 |
慢慢读最后一行。在 时,对于一个只有一个正确答案的问题,32,966 个不同 token 共享了前 90 % 的概率质量。这不是更宽阔的创意空间。这是一个模型被算术告知:在 A: 后面的词位上,韩语助词和 C++ 标识符都该被当作可选项。开头代码块里的垃圾就是直接后果,而且这不是模型或库的 bug——这是请求本身要求得到的结果。
有用范围很窄,而且取决于任务,不取决于口味。对于事实性问题,答案就是一个 token,任何高于约 1.2 的热量都只是在无谓地注入错误。对于开放式问题,确实存在不止一个好的续写,适当的热量能换来仍然流畅的多样性:
"Write a two-sentence story about a lighthouse."
T = 0.0 "The lighthouse stood tall and proud, its beacon illuminating the
night sky above. A lone sailor, his eyes fixed on the distant
horizon..."
T = 0.7 "In the quiet, stormy waters of the sea, a lighthouse stood
sentinel over the horizon, its golden dome casting a warm glow
on the fog-shrouded streets below..."
T = 1.0 "In the quiet night, a lone lighthouse stood sentinel over the
sea, its shining beacon a beacon of hope and solace for sailors
and fishermen across the vast and endless ocean..."
T = 1.3 "In the gentle sunlight, now reflecting upon the opening of Jack's
lighthouse, Jim Trahan, a small-time individual difficult to
define in paperwork, wondered about a career where simplicity
reigns..."在 1.3 时,模型发明了一个专有名词和一个无法解析的句子。对这个模型和这个任务来说,从“每次都一样”到“不连贯”的区间大约是 0.6 到 1.1;诚实的建议是,你应该在你的任务上测量得到它,而不是从一篇博客里抄一个数字。
为什么最可能的文本是糟糕文本
链接到此部分:为什么最可能的文本是糟糕文本这一切下面藏着一个显而易见的问题:如果模型有一个概率分布,而某个 token 最可能,为什么不总是选它?Greedy decoding 免费、可复现,而且不需要参数。
因为结果会是这样:
prompt: "In a shocking finding, scientists discovered a herd of unicorns
living in a remote valley."
greedy: " The unicorns were so rare that they were not even recognized by
the local people. The unicorns were so rare that they were not
even recognized by the local people. The unicorns were so rare
that they were not even recognized by the local people. ..."
repeated 4-grams: 87.6 %八个句子,一个句子。几乎十个四-token 窗口里有九个已经在同一段输出的前面出现过。这就是 neural text degeneration,由 Holtzman 等人在提出 top-p 的论文中命名和解释。3 模型并没有坏;最大化序列概率只是不适合作为开放式文本的目标。人类写作不是最可能的词序列——它带有惊讶,每个 token 的概率会游走、下探再恢复——而最大概率路径是一个固定点,一旦进入,就没有离开的理由。
这就是为什么 sampling 存在。但同样重要、却常被省略的一点是:这不是普遍定律。第 12 章测量过,两步文字题用普通 greedy decoding 得到 24/24 全对,而温度 0.8 的 sampling 会降到 81%;self-consistency 随后花掉六倍 token 才爬回 greedy 原本已经达到的位置。两个事实可以同时为真:
开放式生成。 没有单一正确续写,所以最可能的那个是一座陷阱——它会循环,而且 87.6 % 的内容是从自身复制来的。要 sample。
只有一个正确答案的任务。 这里确实存在单一正确续写,所以抽到其他任何东西就是抽到错误。第 12 章的 100 % 变成 81 %,正是这个原因。不要 sample。
大多数生产 prompt 都是第二类,却被配置得像第一类,因为 temperature 被留在了示例代码使用的那个值。
两种裁剪方式,只有一种会自适应
链接到此部分:两种裁剪方式,只有一种会自适应没有人真的会从完整分布中 sampling,因为尾部巨大,而且充满胡言乱语。必须裁掉一些东西。经典答案有两个,它们在一个决定一切的方面不同。
Top-k 保留固定数量的候选。按概率排序,保留前 个,丢弃其余,再重新归一化。4 Top-p,也叫 nucleus sampling,保留固定数量的质量:按降序取 token,直到它们的累积概率达到 ,然后停止。3 形式上,nucleus 是满足下面条件的最小集合 :
这个差异听起来像是表面问题,但并不是,因为你在同一分钟发出的两个 prompt 会有完全不同形状的分布。下面两者都是同一个模型在温度 1 下的结果:
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| top-1 probability | 96.01 % | 25.39 % |
| tokens holding 90 % of the mass | 1 | 467 |
| top-k = 40 keeps | 99.61 % of the mass | 78.87 % of the mass |
| mass in ranks 2 to 40 | 3.61 % | 53.48 % |
| token at rank 40 | ␣Av, 0.0093 % | ␣Dr, 0.128 % |
一个固定的 ,两个方向相反的失败。在事实性 prompt 上, 放进了 39 个 token,它们合起来只值 3.6 %——它在放垃圾通过,包括一个概率只有万分之九个百分点的候选,因为规则数的是槽位而不是证据。在故事 prompt 上,同一个 丢掉了模型真正分配的 21 % 质量,因为那里的真实 nucleus 有 467 个 token 宽。
Top-p 只用一个数字就能同时完成两件事。设 ,它会在第一个 prompt 上保留 1 个 token,在第二个上保留 467 个,因为它问的是分布本身的问题,而不是把一个数量强加给它。直接观察这种自适应——同一个裁剪,四个温度:
这个小组件还澄清了一个值得点名的误解,因为它会让人花冤枉钱。在一个置信分布上,top_p = 0.9 并不是“有一点多样性”。它就是 greedy。 在温度 1 时,这里的领先 token 已经占 96.90 %,超过 0.9,所以 nucleus 宽度为一个 token,其他任何东西都永远抽不到。团队把 top_p 设为 0.9,以为自己放松了某些东西,然后困惑为什么每个回复都一模一样。
改用 top-k,则相反的失败也同样明显:
各种惩罚项,连同公式,因为把它们混淆是常态
链接到此部分:各种惩罚项,连同公式,因为把它们混淆是常态三个不同机制带着相似的名字流传,它们做的事情不同,而且差异可以测量。令 表示 token 已经出现的次数。
Presence penalty
链接到此部分:Presence penalty对任何已经出现过的 token 减去一个常数。出现一次和出现四十次受到的惩罚完全相同。它是开关,不是旋钮。
Frequency penalty
链接到此部分:Frequency penalty按出现次数成比例地减去。一个用过四次的 token 受到的惩罚是只用过一次的四倍,而且随着文本增长,压力会不断叠加。
Repetition penalty (CTRL)
链接到此部分:Repetition penalty (CTRL)原始版本来自 CTRL 论文。7 它是相除而不是相减,并且需要符号分支,因为除以负 logit 会让它变大。因此它的强度取决于 logit 的大小,这意味着同一个 在同一句子的不同位置打击力度不同。
前面那个退化续写,分别应用每一种惩罚项。“Steps altered”统计 120 个生成步骤中,有多少步选择了与未惩罚模型不同的 token。这里的运行是 120 步,而上面的代码块是 140 步,所以未惩罚基线显示为 85.5 % 而不是 87.6 %:
| setting | repeated 4-grams | steps altered |
|---|---|---|
| nothing | 85.5 % | 0 / 120 |
| presence 0.5 | 65.0 % | 3 / 120 |
| presence 1.0 | 3.4 % | 11 / 120 |
| frequency 0.5 | 6.0 % | 12 / 120 |
| frequency 1.0 | 0.0 % | 20 / 120 |
| repetition 1.2 (CTRL) | 0.0 % | 35 / 120 |
可以看出三件事。Presence 为 0.5 时,在 120 个决策中改变了 3 个,把重复减少了四分之一——这个循环是由少数几个 token 维系的。Frequency 为 0.5 时,改变的决策数量是前者的四倍,效果也大得多,因为计数乘数会持续增长,而 presence 常数不会。CTRL 惩罚项在被广泛复制的 1.2 数值下重写了 120 个决策中的 35 个,这不是轻推;这是另一个模型。
最后这个数字铺垫了一个没人提醒你的失败模式。
惩罚项会怎样影响本来就应该重复的文本
链接到此部分:惩罚项会怎样影响本来就应该重复的文本代码会重复。表格会重复。列表会重复。结构化输出按定义就会重复——这就是结构。惩罚项无法区分模型卡在循环里,和模型正在正确输出表格第四行,因为两者看起来都是某个 token 再次出现。
同样三个任务,用三种方式生成:
| task | nothing | frequency 0.5 | repetition 1.2 |
|---|---|---|---|
| markdown table, 6 rows | 0 / 56 steps altered | 0 / 56 | 2 / 62 |
| Python function | 0 / 93 | 0 / 93 | 10 / 110 |
| bulleted list, 1 to 12 | 0 / 50 | 0 / 50 | 0 / 50 |
Frequency penalty 为 0.5 时,对三者都无害,这是一个有用且略令人意外的结果,它说明了一件精确的事:既然没有决策改变,那么结构性 token 在自己的位置上赢出的幅度,一定大于惩罚项减去的量,即使它们已经出现了五六次。CTRL 惩罚项是相除,它确实会把这些 token 撬开,下面是它生成的内容:
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |对齐崩掉了:每个单元格里的 padding 量逐行变化,因为 closing pipe 前的一串空格正是惩罚项要打断的那类重复。这只是外观问题,而且多花了六个 token。Python 例子就不是外观问题了:
nothing / frequency 0.5:
total = 0
for i in range(1, n + 1):
total += i ** 2
return total
repetition 1.2:
# Initialize total_sum with 0
total_sum = 0
# Loop through numbers from 1 to n, incrementing by 2 each time
for i in range(1, n + 1,惩罚项把模型从 total 推开——它已经在 docstring 里用过——转向 total_sum,又用虚构注释填充输出,把预算花在未使用的 token 上,最后走进了一个带 stride 的三参数 range。注释说 incrementing by 2 each time,这对于从 1 到 的平方和是错的。一个 repetition penalty 让原本无需它就能正确回答的 prompt 生成了错误代码。
接下来的规则很短:惩罚项适用于开放式散文,应该在代码、结构化输出、表格数据以及任何带 schema 的内容上关闭。第 18 章讲的正是第二类。
应用顺序,以及为什么它会改变答案
链接到此部分:应用顺序,以及为什么它会改变答案每个真实实现都会按一个具体顺序应用这些机制:
penalties → temperature → top-k → top-p → sample
这不是任意记账,交换两个阶段会产生真正不同的分布。下面两个测量都在事实性 prompt 上进行。
在温度前还是温度后裁剪。 nucleus 会在它拿到的分布上计算,而温度会彻底改变这个分布:
| top-p 0.9 after temperature | top-p 0.9 before temperature | |
|---|---|---|
| 1 token | 1 token | |
| 353 tokens | 1 token | |
| 32,966 tokens | 1 token |
在 时,相同的名义设置会得到 32,966 个候选或 1 个候选,纯粹取决于哪个阶段先运行。如果你曾经疑惑为什么在一家提供商上提高温度“毫无作用”,而在另一家提供商上用同样两个数字却毁掉输出,这张表就是一个合理答案。
在温度前还是温度后惩罚。 先减去惩罚项 再除以 ,会得到有效惩罚 ;先相除再相减,则得到 。对领先 token 应用 1.0 的 presence penalty:
| temperature | penalise, then temper | temper, then penalise |
|---|---|---|
| 0.5 | 99.858 % | 99.948 % |
| 1.0 | 89.839 % | 89.839 % |
| 2.0 | 10.783 % | 6.830 % |
在 时它们完全相同,也必须相同。在 时相差 1.58 倍。“Presence penalty 1.0”并不是一个定义良好的惩罚量,除非你还知道温度在哪里应用,而没有 API 会记录这一点。
查看详情
可选:按上述顺序展示完整 pipeline。
十六行,本章的一切都在里面。这与小组件执行的是同一套计算,只是用真实 logit 向量,而不是十个固定数字。
def sample(logits, counts, presence=0.0, frequency=0.0,
temperature=1.0, top_k=0, top_p=1.0, generator=None):
z = logits.clone()
idx = torch.tensor(list(counts)) # 1. penalties
if len(idx):
z[idx] -= presence
z[idx] -= frequency * torch.tensor([float(c) for c in counts.values()])
if temperature <= 0: # 2. temperature
return int(z.argmax()) # T=0 is argmax
p = torch.softmax(z / temperature, -1)
p, order = p.sort(descending=True)
if top_k: # 3. top-k
p[top_k:] = 0
p = p * ((p.cumsum(0) - p) < top_p) # 4. top-p
p = p / p.sum() # 5. renormalise
return int(order[torch.multinomial(p, 1, generator=generator)])Top-p 行里的 cumsum(0) - p 是不包含当前 token 的累积质量,这使得 nucleus 会包含那个跨过阈值的 token,而不是在它前面就停止。这里差一位,top_p = 0.9 就会悄悄变成比其他实现略紧的裁剪。
这是课程后半部分里少数 Python 是正确语言的地方之一,原因是结构性的,而不是风格性的:上面的每一行都需要你手里有完整的 logits 向量,而通过 HTTP API,这个向量并不存在。你可以把 temperature 和 top_p 发给提供商;你无法实现它们,也看不到它们做了什么。
不存在通用的 sampling API
链接到此部分:不存在通用的 sampling API每家提供商接受这些控制项的不同子集,范围不同,并且会静默忽略其余部分。这不是抽象抱怨。任何提供模型选择的应用,都必须在某处写下这些差异,而写下它的那个文件就是一张不兼容地图。下面是一个这样的目录对其支持的九个文本来源,在单个参数上声明的内容:
| declared temperature range | sources |
|---|---|
| 0 to 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| 0 to 1.5 | Mistral |
| 0 to 2 | OpenAI, DeepSeek, xAI |
词是同一个,尺度不是。某处的“temperature of 1”是未修改分布,另一处则是允许的最高热量,而目录中一半来源无法表达另一半视为“中性再加一点”的值。其他旋钮也一样不均衡:OpenAI、DeepSeek 和 xAI 条目接受 presence 与 frequency penalties,但没有 topK;Google、Meta、Cerebras 和 PaLM 条目接受 topK,但没有 penalties;Anthropic 接受 topK、topP 和 stop sequences,但没有 penalties;而九个里面只有一个——Mistral——接受种子。发送提供商未实现的参数通常完全不会报错:请求成功,旋钮没有作用,然后你得出“这个设置没效果”的结论。
还要注意,这样一个文件是什么:它是在某一天写下的、关于别人 API 的一个声明,之后没有东西验证它。一个目录如果对某家现在已经接受 0 到 2 的提供商仍写着 0 到 1,就会静默截断每个请求。
还有两个控制项属于同一类。logprobs 在可用时,会返回所选 token 的 log-probabilities,通常还会返回前几个替代项——这是你窥见本章所讨论分布的唯一窗口,也是基于闭源模型构建任何置信度启发式的基础。而 maximum tokens 加 stop sequences 会完全不参考概率地结束生成:一个硬上限和一个字符串匹配。两者都会表现为第 14 章里的 finish_reason,其中 length 意味着你的答案是被预算在句中截断的,不是模型自己完成的。
种子,以及你并不拥有的确定性
链接到此部分:种子,以及你并不拥有的确定性设置种子后,sampling 会变得可复现。这部分是真的,而且很容易验证:
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."同一个种子内字节完全一致,不同种子之间不同,正如宣传所说。因此种子固定的是那个 sample 函数最后一行里的随机抽样——在给定分布下选择哪个 token。
它没有固定的是分布。 麻烦就在这里,因为你的模型产生的 logits 向量不是一个数学对象;它是数十亿次浮点加法的输出,而这些加法有顺序。
第 2 章已经准备好了这个实验。同样的一百万个 float32 数字,用不同分组求和:
sequential 998.564270020 error vs float64: 6.393e-03
pairwise (numpy) 998.570556641 error vs float64: 1.061e-04
in 4 chunks 998.570495605 error vs float64: 1.672e-04
in 8 chunks 998.570556641 error vs float64: 1.061e-04
in 16 chunks 998.570678711 error vs float64: 1.594e-05
sequential == pairwise? False
4 chunks == 8 chunks? False看最后一行。chunk 数量会改变答案。 这不是 numpy 的趣闻;这就是机制,因为当推理服务器把 reduction 拆到更多或更少的并行单元上时,它做的正是这件事。而服务器会根据它正在服务多少请求来拆分。
下面是这种影响在模型本身上的表现。同一个 prompt,同一个 forward pass,唯一不同是 batch 里当时碰巧有多少其他请求:
20 identical forward passes, batch of 1: 20 / 20 bit-for-bit identical
the same prompt inside a batch of 2: 147,321 of 151,936 logits differ
the same prompt inside a batch of 4: 146,515 of 151,936 logits differ
the same prompt inside a batch of 8: 146,515 of 151,936 logits differ
the same prompt inside a batch of 16: 147,321 of 151,936 logits differ
largest change to any logit: 2.5e-05单独运行时,模型完全确定——二十次 pass,bit 级相同。把相同 prompt 与无关请求放进同一个 batch,97 % 的 logits 会改变。你的请求没有任何变化。别人的请求到了。
现在说诚实的部分,因为这件事通常被讲得好像故事到这里就结束了。 的变化只有在两个候选 token 的距离也在这个量级内时,才会改变输出。在十二个 prompt 的 717 个生成步骤中,前两个 logits 之间最小的间隔是 ——比扰动大一百倍——而且没有一步接近到足以翻转。所以在这个模型、float32、笔记本电脑上,batching 改变了每个 logit,却没有改变任何 token。
这是对有利条件的描述,不是安慰;只要条件变一个就够了:
same weights, same prompts, greedy decoding, no seed involved
float32 vs bfloat16: 6 of 8 answers diverge
first divergence at step 23, on average
float32: "...it is scattered and dispersed into different colors,
including blue. The blue light is scattered more than other
colors, so it appears to come from the sky."
bfloat16: "...it is scattered and scattered, causing the colors of the
sun to be scattered and scattered, creating the appearance
of a blue color."八个答案里有六个分歧,其中一个严重退化。第 2 章的表解释了原因:bfloat16 保留 7 个尾数位,所以在 logit 大小接近 16 时,可表示值之间相隔 0.125——16.0,然后 16.125,然后 16.25——舍入可以让一个 logit 移动最多 0.0625。与此同时,上面测得的生成步骤中有 4.7 % 的 top-two gap 低于 0.1。这就是两个实验的全部差异:在 float32 中,扰动比最近的决策小一百倍;而在 bfloat16 中,它与决策处于同一量级。生产推理运行在 16-bit 上,硬件使用 fused kernels 和没人承诺保持的 reduction 顺序。“数值噪声是否可以忽略”是关于精度和硬件的问题,不是关于模型的问题。
所以,按第 9 章承诺,四个原因列举如下:
浮点加法不满足结合律
链接到此部分:浮点加法不满足结合律第 2 章的框。求和顺序会改变数值,因此任何 reduction 拆分方式的变化都会改变 logits。这是底层基质;另外三个原因都是改变顺序的方式。
动态 batching 会把你的请求和陌生人的请求分到一起
链接到此部分:动态 batching 会把你的请求和陌生人的请求分到一起来自第 13 章的 continuous batching 是推理变得负担得起的原因——但它也意味着你的 token 流过的矩阵形状取决于流量。上面测得:batch size 改变导致 147,321 个 logits 移动。
Mixture-of-experts routing 取决于 batch
链接到此部分:Mixture-of-experts routing 取决于 batch第 9 章的框已经说过了。router 会对每个 token、每一层做出离散选择,并受制于基于整个 batch 计算的每个 expert 容量限制。一个单独时会去 expert 7 的 token,在有其他请求陪同时会去 expert 12。这不是舍入差异;这是不同的一组权重。
名字背后的模型会变化
链接到此部分:名字背后的模型会变化像 -latest 这样的版本字符串是一个指针,而指针会被重新指向。提供商也会在固定版本标识符下更新 serving stack。两者都不会以足够细的粒度公告,让你能把它与你自己的输出变化关联起来。
OpenAI 的 seed 参数以它唯一能做到的方式诚实面对这一点:它会随同一个标识后端配置的 system_fingerprint 字段一起返回,文档也说明确定性是 best-effort,且 fingerprint 改变意味着结果可能不同。请把它按原意理解——提供商告诉你,以上四个原因都由它控制,你一个都控制不了,而它唯一能提供的是在事后告诉你:有东西变了。
下一步去哪里
链接到此部分:下一步去哪里到这里为止,所有内容都围绕一个旋钮及其后果。退后一层,更难的问题就出现了:我们一直在调的是一个概率分布,而概率分布没有接口。
function call 有接口。数据库行有接口。一个期待带三个必填字段 JSON body 的 POST handler 有接口,而且会拒绝其他任何东西。在模型与你系统里的每个其他组件之间,都坐着一个契约,而其中一方无法做出承诺:模型会产生某些东西,它来自一个你塑形过但没有固定的分布;另一边的代码需要一个已知类型的值,否则就会抛错。
连接这两个世界的桥,不是由 parsing 和 retry 搭起来的,而是由本章的材料搭起来的。如果某个 token 会破坏所需结构,你不应该 sample 它再祈祷——你应该在 softmax 看到它之前,把它的 logit 设为 。Constrained decoding 是覆盖在同一个向量上的 mask,而我们花了一整章重塑的正是这个向量;它把“请用 JSON 回复”从请求变成保证。
第 18 章就是这个契约:tool calling、JSON Schema、structured outputs,以及要在概率系统之上安全构建确定性系统需要什么。
来源与方法
链接到此部分:来源与方法本章所有测量都来自 CPU 上的 Qwen/Qwen2.5-0.5B-Instruct,除非特别说明均为 float32,sampling 按可选小节中的写法实现,而不是委托给库。它们来自一个小模型,具体数值属于它;机制不属于它。Von Platen 的 How to generate text with different decoding methods(Hugging Face,2020)是本文对照测量的文章,也仍然是同一材料最好的简短入门。关于确定性部分:PyTorch 的可复现性说明描述了种子在单机上能固定什么、不能固定什么;OpenAI 关于 seed 和 system_fingerprint 的文档描述了提供商能承诺什么、不能承诺什么;Thinking Machines 在 2025 年关于 batch-invariant kernels 的讨论,是目前最清楚的公开解释:为什么在推理服务器层面修复这件事是可能的,但并不免费。
参考资料
链接到此部分:参考资料-
Ackley, D. H., Hinton, G. E. and Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985),其中 softmax 里的温度来自统计物理。Hinton, G., Vinyals, O. and Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015),第 2 节,是同一个参数在现代深度学习中重新出现的地方——作为暴露 teacher 完整分布的一种方式,也就是第 13 章的 soft labels,而不是本章的 sampling。 ↩
-
Guo, C., Pleiss, G., Sun, Y. and Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). 不要把它和本章的温度混淆。 Temperature scaling 会在验证集上拟合单个值,让模型置信度匹配准确率;它是应用于分类器输出的事后校准方法。Temperature sampling 是运行时控制生成器如何抽取 tokens 的机制。公式相同,目的不同,也没有共享数值。 ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. and Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). 提出了 nucleus sampling,并给出了这样一项测量:基于最大化的 decoding 会产生概率画像完全不像人类文本的文本。 ↩ ↩2
-
Fan, A., Lewis, M. and Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). 让 top-k sampling 流行起来的论文。 ↩
-
Nguyen, M. et al. Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs. arXiv:2407.01082 (2024). ↩
-
Meister, C., Pimentel, T., Wiher, G. and Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022). ↩
-
Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. and Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). 第 4.1 节是原始的 repetition penalty——那个使用相除的版本。 ↩