一、分布不是答案

从第 1 讲开始,这门课一直在说:语言模型每一步输出的是一个概率分布——“好"35%、“热"18%、“冷"12%……

但用户看到的不是分布,是一个个具体的字。从分布里挑出一个词,这一步到现在都没有讲过。

它有一个很容易被忽略的特点:这一步不在模型里面。第 7–12 讲拼出的 Transformer,到 softmax 输出分布就结束了。之后怎么挑,是一段独立的小算法,没有参数,不需要训练,可以随时更换。同一个模型,换一种挑法,写出来的东西可以截然不同。

第 17 讲说"让模型对同一个问题生成两个不同的回答”,第 19 讲说"对同一道题生成 4 个解答”,当时都注明了"第 21 讲会讲清楚为什么能做到”。答案就在这一步:模型本身是确定的,同样的输入永远算出同样的分布;不同的回答,来自挑词时的随机性。

这一讲讲这一步的几种做法,以及每种做法在修什么问题。在推理与部署这个单元里,它是最便宜的一环,却直接决定了用户看到什么。

二、打个比方:照着人气榜点菜

一家餐厅贴了一张人气榜:每道菜旁边写着"有百分之多少的客人点了它"。你照着这张榜点菜。

永远点第一名:稳妥,但每次都吃一样的。而且"大多数人第一道点它"不代表"整顿饭最好吃的组合里有它"。

按人气比例随机点:有变化,但菜单有几百道菜,每道冷门菜的比例都很小,加起来却不小——点上七八道,几乎一定会碰上一道你根本不想吃的。

只在前几名里、按人气比例随机点:既有变化,又不容易踩雷。问题只剩下:“前几名"到底取几名?

这三种点法,就是这一讲要讲的三类采样策略。

三、几种挑法

贪心解码:永远挑概率最大的

最直接的做法:每一步都挑概率最大的那个词。这叫贪心解码(greedy decoding)。它完全确定,同一个输入永远得到同一个输出。

它有两个问题。

第一,每一步最好,不等于整句最好。 第 4 讲的链式法则说过,一整句话的概率是每一步概率的乘积。贪心只看眼前这一步,不看乘起来的结果。看一个两步的例子:

第一步:   "A" 0.6       "B" 0.4
             │              │
第二步:   A 之后最可能的词 0.3     B 之后最可能的词 0.9
             │              │
整句概率: 0.6 × 0.3 = 0.18     0.4 × 0.9 = 0.36

贪心在第一步选了 A(0.6 > 0.4),于是走进了一条整体概率只有 0.18 的路;B 那条路虽然开头差一点,整体却是 0.36,高出一倍。A 之后的概率只有 0.3,说明 A 之后的可能性很分散,没有一个特别合适的续写。贪心一旦走错,就回不了头,因为它从不回看。

一个改进叫束搜索(beam search):每一步不只保留一条路,而是同时保留概率最高的几条(比如 4 条),一路往下展开,最后挑整体概率最高的那条。它在机器翻译这类"答案相对确定"的任务上很常用。但在开放式的对话和写作里,人们发现一个反直觉的现象:整体概率最高的文本,往往是最平淡、最套话、最容易重复的文本——因为这类文本在语料里出现得最多。人写的东西,恰恰不总是挑最可能的词。

第二,容易陷入重复。 如果模型某一刻写出了"我觉得这个很好,我觉得这个很好”,下一步最可能的续写很可能又是"我觉得这个很好":这个短语已经在上下文里出现了两次,第 9 讲的注意力会让它显得更加"合理"。贪心每一步都挑最可能的词,一旦进入这种循环就出不来。

纯采样:完全按概率随机抽

另一个极端:严格按照分布的比例随机抽。“好"有 35% 的机会被抽中,“热"有 18%,以此类推。这叫纯采样(pure sampling)。

它解决了贪心的两个问题:有随机性,不容易卡进循环,也不会总写最套话的句子。但它引入了一个新问题:长尾。

词表有几万个词(第 7 讲)。在任何一步,绝大多数词的概率都极小,但它们加在一起并不小。假设每一步,最合理的十来个词占了 90% 的概率,剩下几万个词分享另外 10%——每个都极不合适,但合起来有 10% 的机会被抽中。

写一段 50 个 token 的回答,一次都没有抽到长尾词的概率是:

0.9⁵⁰ ≈ 0.005

也就是说,有 99.5% 的可能,这段话里至少出现一个莫名其妙的词。而第 13 讲讲过,生成是自回归的:这个莫名其妙的词会被接进上下文,后面的每一步都要在它的基础上继续写,一次抽错,后面可能越走越偏。

所以贪心太死板,纯采样太随意。下面三个旋钮,都是在这两个极端之间找位置。

温度:调节分布的尖锐程度

温度(temperature) T 的做法是:在 softmax 之前,把模型输出的原始分数(logits,第 5 讲)统一除以 T:

P(词ᵢ) = exp(zᵢ / T) / Σⱼ exp(zⱼ / T)

直接用第 5 讲那个例子。“好"“热"“香蕉"三个词的 logits 是 [2.0, 1.0, −1.0]:

温度 T 除以 T 之后的 logits 好 热 香蕉
0.1 [20, 10, −10] ≈100% ≈0% ≈0%
0.5 [4, 2, −2] 87.9% 11.9% 0.2%
1 [2, 1, −1] 70.5% 25.9% 3.5%
2 [1, 0.5, −0.5] 54.7% 33.1% 12.2%
10 [0.2, 0.1, −0.1] 37.8% 34.2% 28.0%

T = 1 那一行就是第 5 讲算过的结果,模型原本的分布。

为什么"除以一个数"会有这种效果?第 17、18 讲反复用到过一个性质:softmax 只看各个分数之间的差距。除以 T < 1,差距被放大,大的更大,分布变尖;除以 T > 1,差距被缩小,分布变平。

两个极端:

  • T 趋近于 0:差距被无限放大,概率全部集中到最大的那个词上——就是贪心解码。
  • T 趋近于无穷:差距被抹平,所有词概率趋于相等——比纯采样还要随意,长尾词和最合理的词机会均等。

⭐ 温度不改变词的排名,只改变排名靠前的词和靠后的词之间的差距。它是一个连续的旋钮,从"完全确定"一直拧到"完全随机”。

两处和前面几讲的连接。

第 20 讲的 β 表格显示,对齐之后的分布本来就被推尖了:β = 0.5 时,最好的回答已经占到 97%。在这样一个分布上再把温度调低,两种"变尖"会叠加,输出几乎完全确定,多样性基本消失。

第 19 讲的 RLVR 需要对同一道题生成好几个不同的解答,只有"有对有错"时才有学习信号。如果温度太低,4 个解答几乎一模一样,要么全对、要么全错,“得分减平均"全是 0——训练什么也学不到。所以强化学习训练时的采样温度,通常不会设得太低:它需要足够的随机性去探索。

top-k:只在前 k 个里抽

温度调平了分布,但没有解决长尾:T = 2 时,“香蕉"的概率反而从 3.5% 涨到了 12.2%。要真正对付长尾,得直接把它砍掉。

top-k 的做法:每一步只保留概率最高的 k 个词,其余的概率全部清零,再把保留下来的概率重新归一化(让它们加起来等于 1),然后在这 k 个里面抽。

它简单有效,但有一个问题:k 是固定的,而每一步分布的形状差别很大。

情况一:  "中华人民共和国的首都是 ___"
          北京 97%,其余所有词加起来 3%
          → 合理的选项只有 1 个

情况二:  "我最喜欢的颜色是 ___"
          蓝色、红色、绿色、黑色、白色、紫色……都很合理
          → 合理的选项有十几个

如果取 k = 50:情况一里,“北京"之外的 49 个垃圾选项全被保留了下来,一起分享那 3%。如果取 k = 3:情况二里,大量合理的颜色被硬生生砍掉。没有一个 k 能同时适合这两种情况。

top-p:按累计概率决定保留多少

top-p(也叫 nucleus sampling,核采样)换了一个思路:不固定保留几个,而是从概率最高的词开始往下累加,累计概率一旦达到 p,就停止,只在这些词里抽。

用第 1 讲的例子,补全成一个完整的分布,取 p = 0.8:

词 概率 累计概率 保留?
好 35% 35% ✓
热 18% 53% ✓
冷 12% 65% ✓
糟糕 9% 74% ✓
舒服 6% 80% ✓ 到这里达到 0.8,停止
闷 5% 85% ✗
其余几万个词 共 15% 100% ✗

保留 5 个词,概率各自除以 0.8 重新归一化(“好"变成 43.75%,以此类推),再从中抽。

同样 p = 0.8,放到上面的情况一:“北京"一个就占了 97%,第一个词累加就超过 0.8 了——只保留 1 个。放到情况二:颜色的概率很分散,可能要累加十几个词才到 0.8——保留十几个。

⭐ top-p 保留的词数,会随着这一步分布的形状自动伸缩:模型很确定时只留一两个,模型不确定时多留一些。这正是 top-k 做不到的。

实践中怎么组合

实际使用时,这几个旋钮通常一起用:先用温度调整分布的尖锐程度,再用 top-p(或 top-k)砍掉长尾,最后在剩下的词里按比例抽一个。

⚠️ 关于时效性:除了这三种,还有一些变体,比如按"相对于最高概率词的比例"来决定截断位置的方法,以及对已经出现过的词降低概率、专门对付重复的"重复惩罚”。具体用哪种、默认参数设多少,各家服务不同,也在不断变化。但它们都在回答同一个问题:在"太死板"和"太随意"之间,截断在哪里、分布要多尖。

所以,为什么同一个问题会得到不同的回答

现在可以完整回答前几讲的预告了:

  • 模型本身是确定的:同样的输入,算出同样的分布。
  • 只要温度大于 0、且没有截断到只剩一个词,每一步挑词都带有随机性。
  • 第一个不同的词出现之后,后面的上下文就不一样了,自回归会把这个差别一路放大下去。

⚠️ 一个实践中的补充:理论上 T = 0(贪心)应该完全确定,但实际部署中,即使设成 0,同一个问题偶尔也会得到略有不同的回答。原因不在采样,而在计算本身:GPU 上大规模并行的浮点运算,加法的先后顺序可能随批次安排而变化,结果会有极微小的差别;当两个词的概率几乎相等时,这点差别就足以改变谁排第一。

四、代价

⭐ 没有一组参数适合所有任务。温度和截断在"准确、一致"和"多样、有创意"之间做取舍:

任务 倾向 原因
写代码、算数学、抽取信息 低温度 答案基本唯一,随机性只会带来错误
写故事、头脑风暴、起名字 较高温度 需要多样性,最可能的往往最平淡
强化学习训练时采样(第 19 讲) 不能太低 需要多样的尝试,才有"有对有错"的学习信号

⚠️ 采样修不了模型本身的问题。top-p 砍掉的是低概率的长尾。但如果模型在某个问题上本身就搞错了——把最高的概率给了一个错误的答案——那么无论怎么调温度、怎么截断,都只是在几个错误选项之间挑。反过来,正确答案如果恰好落在长尾里(第 20 讲讲过,对齐后的模型校准可能变差),截断会把它和垃圾一起砍掉。采样只能在模型给出的分布里做选择,不能创造一个更好的分布。 第 29 讲讲幻觉时会回到这一点。

⚠️ 随机性让测试和评估变得麻烦。同一个问题跑两次结果不同,意味着"这个模型能不能答对这道题"不能只看一次输出。评估时要么固定随机种子,要么多次采样看比例——第 19 讲的多数投票,就是把这种随机性反过来利用。

⚠️ 采样本身几乎不花计算,但它挑出的每一个词,都要再做一次完整的前向传播。从分布里抽一个词,只是一次排序和一次随机数;真正贵的是下一步:这个词被接到序列末尾,整个模型要再跑一遍,才能算出下一个分布。一段 500 个 token 的回答,就是 500 次完整的前向传播,而且必须一次接一次地串行做。这正是第 13 讲那个"训练并行、推理串行"的不对称性。这一步能不能省?下一讲讲。

五、和后面课程的关系

  • 第 22 讲(KV Cache、量化、批处理) 接着第四节最后一条:每生成一个词都要再跑一遍整个模型,其中有大量重复计算可以省掉。
  • 第 24 讲(测试时计算的代价) 会给第 19 讲"多次采样再投票"算一笔账:并行采样 N 次,成本和延迟各是多少。
  • 第 29 讲(幻觉) 会接着第四节的"采样修不了模型本身的问题”:当模型把高概率给了错误答案时,发生了什么。

六、本讲小结

  • 从分布里挑词这一步不在模型里:它没有参数、不需要训练,但同一个模型换一种挑法,行为可以截然不同。不同的回答来自挑词时的随机性,模型本身是确定的——这回答了第 17、19 讲的预告。
  • 贪心解码每步挑最大:手算显示,它在第一步选了 0.6 的 A,走进整体只有 0.18 的路,错过了整体 0.36 的 B;还容易卡进重复循环。束搜索能缓解,但整体概率最高的文本往往最平淡。
  • 纯采样有长尾问题:每步只有 10% 的概率抽到不合适的词,50 步下来一次都没抽到的概率只有约 0.5%。
  • ⭐ 温度把 logits 除以 T:T < 1 放大差距、分布变尖,T > 1 缩小差距、分布变平;T → 0 就是贪心。它和第 20 讲对齐后变尖的分布会叠加;第 19 讲的 RLVR 需要温度不能太低,否则没有学习信号。
  • top-k 只保留前 k 个词,但固定的 k 适应不了分布形状的变化;⭐ top-p 按累计概率截断,保留的词数随分布形状自动伸缩。
  • ⚠️ 代价:没有通用的最佳参数;采样只能在模型给出的分布里挑,修不了分布本身;随机性让评估变麻烦;采样本身很便宜,但它挑出的每个词都要求再跑一遍完整的模型——这是下一讲的起点。

思考题

  1. 用第三节的温度公式算一算:logits 为 [2.0, 1.0, −1.0]、T = 0.25 时,“好"的概率大约是多少?和 T = 0.5 相比,变化是大还是小?
  2. 在第三节贪心解码的例子里,如果用束宽为 2 的束搜索(每一步保留 2 条路),它会选中哪条路?如果把 B 之后最可能的词的概率从 0.9 改成 0.4,结果会变吗?
  3. 第三节的长尾计算里,如果把回答长度从 50 个 token 改成 500 个,一次都没抽到长尾词的概率大约是多少?这对"长回答用纯采样"意味着什么?
  4. 用第三节 top-p 的表格,把 p 改成 0.6,会保留几个词?“好"重新归一化之后的概率是多少?
  5. 一个用户抱怨:“我把温度设成 0 了,为什么两次问同一个问题,答案还是有一个字不一样?“结合第三节最后的说明,你会怎么向他解释?