第 11 章 · 采样

拿到一个 logits 向量,应用 temperature 和 top-p,并能说清每个旋钮为什么会让最终分布发生那样的变化。

每次前向传播都以 logits 结束;模型仍然需要一条规则,把这个向量变成一个具体的下一个 token。

7 分钟
前向传播分词嵌入查表× 24 层最终 RMSNormLM head采样
术语表 · 7 个术语
logit
词表中每个条目对应的一个无界实数分数。模型在任何归一化之前的原始输出。
softmax
exp(l_i / T) / sum_j exp(l_j / T)——把 logits 变成一个总和为 1 的概率分布。
temperature (T)
施加在 softmax 之前的锐度旋钮。T<1 让分布更尖(更接近贪心);T>1 让分布更平(更多样)。
greedy decoding
每一步都取 argmax(logits)。确定性、可复现;但容易陷入重复循环。
top-p (nucleus)
保留概率累加到 >= p 的最小 token 集合,重新归一化后从中采样。把长尾砍掉。
top-K sampling
一种采样规则:只保留概率最高的 K 个 token,重新归一化后从中采样。是 top-p 的固定大小近亲。
top-16 capture (this demo)
这是展示层面的截断,不是采样:inspector 每一步记录 16 个最高的 logits,组件才有东西可画。模型本身仍然是在整个词表上采样的。

采样:把 logits 变成下一个 token

Qwen3.5 的每次前向传播都以同样的方式结束:一个 logits 向量——模型词表(约 248k)中每个 token 一个实数。logit 是一个无界的分数:模型有多强烈地推荐这个 token 作为下一个。采样就是把这个向量变成一个具体选择的那一步。

为什么不直接选最大的?

最简单的规则是贪心解码(greedy decoding):每一步都取 argmax(logits)——argmax 的意思就是“选数值最大的那个位置”,也就是得分最高的单个 token。它确定且可复现,但有一个著名的失败模式——重复。一旦模型找到一个它对续写很有信心的短语,就会不断重新进入同一个循环,因为那个循环永远是局部最优的选择。贪心解码还丢掉了大量信息:如果两个 token 的 logits 几乎相等,选其一而完全无视另一个是一种脆弱的平局裁决。

Softmax:logits → 概率

要采样,我们先用 softmax 函数把 logits 转换成一个真正的概率分布:

这里发生了两件事。指数让每个值都变成正数;除以总和让它们加起来等于 1。每个 logit 在取指数之前还会先除以一个 temperature(温度)T——同一个 T 出现在分子和分母的每一项里。temperature 起到锐度旋钮的作用:

  • T < 1 让分布更尖——高 logit 的 token 更加占主导,输出看起来更像贪心。
  • T > 1 让分布更平——logits 之间的细小差异被抹平,输出更多样但连贯性更差。
  • T = 0 退化为贪心(argmax)。组件会把它钳制到一个极小的正数以避免除以零,数值上等价。

为什么偏偏是这个函数?

softmax 其实是 argmax 的一个平滑、可微的替身。argmax 会硬生生地跳到最大的那个 logit 上,softmax 则在候选之间平滑地滑动。喂给它 [5.0, 4.9, 1.0],你会得到大约 [0.52, 0.47, 0.01]:两个相近的领先者几乎均分了概率质量,而第三个仍然小到可以忽略。把差距拉大到 [8.0, 4.9, 1.0],输出就锐化到大约 [0.96, 0.04, 0.00]——几乎是一个硬 argmax,而这正是贪心解码(T = 0)所趋近的极限。

这种平滑性也正是 softmax 远不止用于采样的原因:因为它处处都有干净的梯度,它既是模型训练时所对照的那个函数,也是注意力内部用来给各个 key 称重的同一个归一化。

要看清分布为什么会长成那个样子,下面把这次计算拆成四个机械的阶段——原始 logits、除以 T、取指数、归一化——一步一步循环播放。用 T 按钮对同样的八个 logits 分别跑 0.2、0.7 和 2.0:归一化阶段的柱子在 0.2 时尖锐地集中到一个 token 上,在 2.0 时摊平到全部八个上。

带温度的 softmax,逐阶段拆解
Temperature
更尖——更接近贪心

z——模型的无界分数,直接来自 LM head。

token原始 logits
sunny3.10
cloudy2.40
warm1.90
cold1.20
nice0.90
rainy0.40
mild-0.20
grey-0.80
胜出 token:sunny(argmax——不受这些单调变换影响)
原始 logits 可能为负,所以还画不出柱子——第 3 步取指数后,所有值都会变成正数。

示意用的八个 token 的 logits,为展示四个阶段而写定——并非模型的实时输出。柱子在数值变为非负之后(取指数后)才出现;显示的每个数字都是该阶段的真实值。切换 T 再看一遍归一化阶段:0.2 时几乎所有概率质量都压在最上面的 token 上,2.0 时则摊到全部八个上。

同一个种子,三种 temperature

上面的柱子是因,这里是果。我们把同一段种子文本交给真实的 Qwen3.5-0.8B 检查点——正是这个 playground 运行的那一个——跑了三次,只改 temperature。在 T = 0 时采样器是贪心的:每一步都取概率最高的单个 token,所以每次重跑的输出完全相同,并且倾向于最保险、最平淡的措辞——注意它多快就开始绕着同样几个词打转。在 T = 0.7 时分布被锐化但没有坍缩,采样出的故事——至少这一次运行——读起来很自然;重跑会得到不同的文本。

T = 1.5 的运行展示了高 temperature 为什么劣化得这么快:生成是一个循环,每个采样出的 token 都会被追加到上下文里,去产生下一步的分布。一次低概率的选择本来还能挽回——但在高 temperature 下,低概率的选择会接连发生,每一次都把上下文拖得离模型见过的任何东西更远——在这次运行里,错误不断累积,直到输出变成词语沙拉。

同一个种子,三种 temperature · 真实模型输出
各取一次录制的运行 · 每段 70 个新 token · 正是本站使用的 Qwen3.5-0.8B 检查点
T = 0贪心
种子文本:Once upon a time, there was a very special kind of animal called a "pocketed" animal. These animals are very special because they have a special way of living. They live in a very special place called a "pocketed" habitat. Imagine a pocketed animal as a little explorer who lives in a very special place. This place is called a "pocket

确定性的——重跑一次会得到一模一样的文本。安全但在原地打转:“very special” 出现了四次,“pocketed” 已经在循环。

T = 0.7采样
种子文本:Once upon a time, there was a curious child named Ethan. One sunny morning, Ethan decided to explore the world around him and he found a strange object. Ethan picked up a wooden block and put it in a circular hole. He used a different material for the block, which made it feel like a solid, but he didn't know why. Then, he put the

一点温和的随机性就足以跳出最保险的车辙——有了名字、有了情节。仍然连贯;重跑会得到另一段不同的续写(这只是一次采样,不保证每次都好)。

T = 1.5过热
种子文本:Once upon a time, there was a boy named Benobils who lived loving today though I Homer gonna doodproof way since Dad Giovanni will offer him up to USA pilgrimage I let Homework set come don accept Pescidio submit these"` Do español Paramal Decindo Unless Lupens Journals few holders Bel En долга Cuban Borders programa greatly lario routine gym training regulwoothy Numero dai training So much

每一个低概率的选择都会成为下一步的上下文,损害不断累积——短短几行就漂过三种语言,变成词语沙拉。

真实的预录输出,并非人工编写:每段续写都来自在本地运行与本站完全相同的 Qwen3.5-0.8B 检查点,通过原生 mlx-node 的 Qwen35Model.generate() 裸补全 API(不套 chat 模板),maxNewTokens = 70,纯 temperature 采样——没有 top-p 或 top-K 截断(top-p = 1.0,top-K 关闭)。T = 0 的运行是贪心的、完全可复现;T = 0.7 和 T = 1.5 各是一次采样,重跑会得到不同的文本。

Top-p(核)采样

即便 temperature 取得合理,词表的长尾仍然带着微小但非零的概率质量——偶尔采样器就会落在那里。这些尾部 token 大多在上下文里是胡话。Top-p 采样(也叫核采样,nucleus sampling)负责修剪尾巴:

  • 把 token 按概率从高到低排序。
  • 沿排序后的列表累加概率,直到累计和达到 p
  • 之后的全部丢弃。
  • 把幸存者重新归一化,使它们的总和重新等于 1。
  • 从这个“核”中采样:在 0 到 1 之间抽一个随机数 r,沿列表向下累加概率,停在累计值第一个超过 r 的 token 上——切片越大被命中的次数越多。

常见默认值是 T = 0.7 搭配 top_p = 0.9:temperature 给模型留出一些发挥创造力的空间,top-p 则保证我们永远不会从荒谬的尾部采样。右侧的组件可以让你在一次已捕获的运行上扫动这两个旋钮,看看“本来会发生什么”。

拖动下面的截断滑块,观察“核”如何形成:token 自上而下被保留,直到累计概率达到 p,尾部被丢弃,幸存者被重新归一化到总和为 1。

核(top-p)截断
0.90
核:10 个 token 中保留 6重新归一化前保留的概率质量:0.925(截断:累计概率 ≥ 0.90 的最小集合)
原始分布——已排序,截断之后的尾部变暗
·store
0.330Σ0.33
·park
0.220Σ0.55
·beach
0.140Σ0.69
·gym
0.110Σ0.80
·office
0.070Σ0.87
·movies
0.055Σ0.93
·airport
0.035Σ0.96
·doctor
0.025Σ0.99
·bank
0.010Σ1.00
·moon
0.005Σ1.00
虚线就是截断线:它下方的一切都是被丢弃的尾部。
核——重新归一化到总和为 1,再从中采样
·store
0.357
·park
0.238
·beach
0.151
·gym
0.119
·office
0.076
·movies
0.059
·airport
·doctor
·bank
·moon
幸存者按 ÷ 0.925 重缩放,使总和重新等于 1——这次重缩放正是被保留的柱子集体跳高的原因。

示意用的十个 token 的分布,已排序、预先写定——并非模型的实时输出。

这个组件如何工作

按下 Run 会生成 6 个 token,inspector 在每一步捕获 top-16 个原始 logits——捕获本身永远是在 temperature=0(也就是贪心)下运行的。temperature 和 top-p 滑块随后在缓存的 logits 上重新应用 softmax + 截断——不会重新运行模型。因为把每个 logit 都除以同一个 T 不会改变它们的相对顺序,top-p 也永远不会丢掉排名第一的 token,所以被高亮的柱子在任何滑块设置下都必然是最高的那根——它永远是模型在这一步的贪心选择。随着你拖动滑块,真正变化的是它与第二名柱子之间的置信度差距:差距大,说明真正的采样器几乎总会认同这个贪心选择;差距小,说明它常常会不认同,落到另一个 token 上。

关于柱子高度的一点说明:这些柱子只在捕获的 top-16 个 logits 上做 softmax/top-p 重新归一化,而不是在完整的 248,320 个 token 的词表上。被省略的尾部仍然携带真实的概率质量,所以每根柱子读起来都比它真实的全词表概率略高——在高 temperature 下最明显,因为那时模型把更多质量摊进了被丢弃的尾部。

当采样出问题时

有两种失败模式值得并排观看:一个低 temperature 的贪心运行陷入循环,一个高 temperature 的运行变成胡言乱语。中间的面板展示的是一个合理的生产环境设置。

采样失败模式 · 同一提示词,三种配置
预先录制的续写 · 每段 10 个 token
提示词:"Once upon a time, in a forest far away"
贪心
T = 0, top-p = 1.0
Once upon a time, in a forest far away there lived a small forest there lived a small forest

重复陷阱。模型找到一个高置信短语,便不断绕回去重复它。

过热
T = 2.0
Once upon a time, in a forest far away a frgg moo whirr the of bicycle banana very

胡言乱语。分布平得离谱,采样器几乎是均匀地挑中罕见 token。

恰到好处
T = 0.7, top-p = 0.9
Once upon a time, in a forest far away a small village where everyone knew each other and shared their stories

连贯又有变化。Top-p 修剪掉荒谬的尾部,temperature 防止分布坍缩。

生产环境的 LLM 服务通常落在 T = 0.7-1.0 搭配 top-p = 0.9(或一个适中的 top-K)附近。两个旋钮分工不同:temperature 重塑整个分布,top-p 截断长尾。两者合力避开你在上面看到的贪心循环和高温胡话这两种失败模式。

提示:在同一个置信的步骤上分别试试低 temperature(0.2)和高 temperature(1.5)。柱状图肉眼可见的“形状”变化,就是采样参数对 LLM 至关重要的全部原因。

工程要点
  • logits 只有经过 softmax 才变成概率。在同一步之内,重要的是它们的相对顺序——最大的 logit 就是贪心选择——但绝对值在不同运行或不同模型之间不可比。
  • temperature 重塑分布;top-p 截断长尾。常见默认值是 T=0.7 搭配 top_p=0.9。
  • 被高亮的柱子永远是模型在这一步的贪心(argmax)选择——捕获这次运行时用的是 temperature=0,之后无论怎样重新缩放或截断都不可能改变哪根柱子最高。滑块真正改变的是它和第二名柱子之间的置信度差距:差距大,说明真正的采样器几乎总会选出和贪心一样的结果;差距小,说明它常常会选到别的 token。
动手练习

自动运行结束后,保持 temperature 为 1.0,慢慢把 top-p 从 1.0 降到 0.3,同时观察第 1 步的柱状图。降到哪个值时图表明显坍缩成一两根柱子?重新归一化后的柱高告诉你什么?

随堂测验
1. 为什么生产环境的 LLM 服务不总是用贪心解码?
2. 把 temperature 设为 T = 0.2 会对概率分布产生什么影响?
3. p = 0.9 的 top-p 采样意味着:

动手试试

互动演示加载中……