调 API 的时候,temperature 和 top_p 几乎是每个人都会碰到的两个参数。文档通常只说一句「值越高越随机」,但「随机」到底是怎么来的?这篇文章从模型输出的最后一步讲起,把温度采样背后的直觉拆开来看。
模型输出的其实是 logits
语言模型每生成一个 token,最后一层都会给词表里的每一个候选 token 打一个分,这个分叫 logit。它可以是任意实数,没有上下界,也不需要加起来等于 1。
假设词表只有 4 个 token,模型在某一步给出的 logits 是:
| token | logit |
|---|---|
| 猫 | 3.0 |
| 狗 | 2.0 |
| 鸟 | 1.0 |
| 鱼 | 0.2 |
分数越高,说明模型越倾向于选它,但我们还需要把它变成真正的概率分布,才能「按概率抽签」。
Softmax:把分数变成概率
Softmax 做的事情很简单:先对每个 logit 取指数,保证都是正数,再除以总和做归一化:
其中 是第 个 token 的 logit。指数函数会把差距放大:logit 只差 1,概率就差了 倍。
用代码算一遍
下面是一个不依赖任何库的实现。注意第 4–5 行:先减去最大值再取指数,是为了避免 exp 溢出,这在实际工程里几乎是必须的。
import math
def softmax(logits, temperature=1.0): scaled = [z / temperature for z in logits] m = max(scaled) # 数值稳定:减去最大值 exps = [math.exp(z - m) for z in scaled] total = sum(exps) return [e / total for e in exps]
print(softmax([3.0, 2.0, 1.0, 0.2], temperature=0.5))Temperature:给分布调「锐度」
温度 的作用发生在 softmax 之前:把所有 logits 都除以 。
- :logits 之间的差距被放大,分布变「尖」,高分 token 几乎包揽所有概率;
- :就是原始的 softmax;
- :差距被压缩,分布变「平」,低分 token 也有机会被抽中。
光看公式不够直观,拖动下面的滑块试试(也可以用键盘的方向键):
拖动滑块,观察 T 如何改变分布
T = 1.0:适中:保留主次,也有一点随机
把第一名的概率随温度的变化画出来,会看到一条单调下降的曲线:
- 猫(logit 3.0)
- 鱼(logit 0.2)
Top-p:只在「靠谱」的候选里抽
温度调的是分布的形状,但无论怎么调,长尾里那几万个低概率 token 始终都有非零的概率。Top-p(也叫 nucleus sampling)的思路是:把 token 按概率从高到低排序,累加到总和刚好超过 为止,只在这一小撮「核心」里重新归一化再抽样。
以 、 为例:猫 63.9% + 狗 23.5% = 87.4%,还不到 0.9;再加上鸟 8.7% 就超过了,所以鱼被直接排除。
完整的采样流程
flowchart LR
A[logits] --> B[除以 T]
B --> C[softmax]
C --> D[top-p 截断]
D --> E[随机采样]
E --> F[token] 小结
- logits 是模型给每个 token 打的原始分;softmax 把它变成概率。
- temperature 在 softmax 之前缩放 logits,控制分布的尖锐程度。
- top-p 在 softmax 之后截掉长尾,控制候选集合的大小。
下次再看到「值越高越随机」,你就知道它具体是在调哪一步了。