大模型的温度到底是什么?从 token 采样讲到量化

1 阅读11分钟

前面,我们从零训练了一个小语言模型,也看到了它怎么一步步生成文字。

接着就遇到了一个很容易混淆的参数:temperature,温度。

有人把它叫作“创意参数”:温度低,回答稳定;温度高,回答发散。于是,一个自然的问题出现了:

温度高了,回答更容易出错,是不是和量化一样,把模型的精度降低了?

不是。这里混在一起的,其实是两件不同的事:

  • 温度:调整选取下一个 token 的概率。
  • 量化:用更低位数表示模型中的数字。

一个影响“怎么选”,一个影响“数字怎么表示”。下面用一个例子把它们拆开。

1. 温度在生成过程的哪个位置?

先看常见的自回归语言模型生成一个 token 的过程:

image.png

文字先转换成 token 编号,经过词嵌入、多层 Transformer 和输出层 LM Head,得到一组分数。这些还没有归一化成概率的分数,叫作 logits

假设词表有 10,000 个 token,那么在当前生成位置,输出层会给这 10,000 个候选各算一个分数。它不是另外找一个模型评分,而是当前模型的一部分。

接下来,生成程序根据分数选择一个 token。温度通常就在这个阶段起作用:先缩放分数,再转换成概率,最后按概率抽取。Transformers 将温度归入输出 logits 的处理参数,具体说明见文本生成文档

这意味着:调温度不需要重训模型,也不会因此改变层数、参数量或权重的存储位数。它是生成时的配置,不是训练出来的那几亿个权重之一。

图中只画温度采样的主线,省略了候选过滤等细节;量化标注以权重量化为例,也不代表每次生成都重新量化一次模型。

2. 同一组分数,温度怎样改变概率?

假设模型正在续写:

小白在……

为了方便计算,我们构造一个只有三个候选的教学例子:

候选 token原始分数
2
1
0

这里按字符演示,“餐”“公”“家”各是一个 token。真实分词器未必一个汉字对应一个 token;这些分数也不是某个真实模型的测量结果。

当温度 T > 0 时,常见计算方式是:

概率 = softmax(原始分数 ÷ 温度)

pᵢ = exp(zᵢ / T) / Σⱼ exp(zⱼ / T)

其中,zᵢ 是第 i 个候选的分数,exp(x) 就是 e 的 x 次方;分母把所有候选的结果加起来,让概率总和等于 1。

不记公式也没关系,只看这三个步骤:所有分数除以温度 → 取指数 → 除以总和。

用同一组 [2, 1, 0] 计算,可以得到:

温度除以温度后的分数
0.5[4, 2, 0]86.7%11.7%1.6%
1.0[2, 1, 0]66.5%24.5%9.0%
2.0[1, 0.5, 0]50.6%30.7%18.6%

百分比保留一位小数,因此显示值相加可能不是恰好 100%。

image.png

可以看到:

  • 温度降低,分数差距被放大,概率更集中在第一名。
  • 温度升高,分数差距被缩小,其他候选更容易被抽中。
  • T = 1 不进行温度缩放,但后面的采样仍然可以是随机的。

所以,温度高不是“凭空创造一个词”,而是让原本概率较小的候选获得更多机会。TemperatureLogitsWarper 文档也把它定义为对分布随机性的调节。

这里比较的是相同上下文、同一步生成。如果这一步抽中了不同 token,下一步的上下文就变了,模型后续算出的原始分数也可能不同。

3. 一个关键细节:调温度不等于改选第一名

在上面的三个温度下,排名始终是:

餐 > 公 > 家

正温度会改变分数差距,但在理想数学计算中,不改变候选排名。

因此,如果生成代码一直是“取最高分”,而不是“按概率抽取”,那么只除以温度,通常看不出效果。

前面那个 MLX 教学模型,当前恰好使用这样的代码:

# 排除 PAD 补齐标记,在其余 token 中选最高分的编号。
next_id = 1 + int(mx.argmax(logits[1:]).item())

这叫贪心生成。它没有启用随机采样,也没有通过温度改变抽取概率。

理解两种方式的区别,可以想象抽奖:

  • 贪心生成:谁的概率最高,就直接选谁。
  • 随机采样:按概率分配抽签机会,第一名最容易中,但其他候选也可能中。

这不是“平均随机选一个”。86.7% 的候选,比 1.6% 的候选有大得多的机会。

对于 Transformers 的常见单束生成,使用温度采样还需要开启 do_sample=True;如果关闭采样、使用贪心解码,调温度并不能获得预期的随机效果。见生成参数说明

那 temperature = 0 呢?

公式不能直接除以 0。

一些实现约定:T = 0 时走单独的贪心分支,直接选最高分。另一些实现要求温度严格大于 0,需要通过其他配置关闭采样。

因此,“0 表示贪心”是实现约定,不是把 0 代入公式。不同框架和模型 API 的支持范围、默认值也不能一概而论。

4. 不下载模型,也能运行这个实验

下面只用 Python 标准库,模拟“模型已经给出分数以后,如何选 token”。不需要 GPU、不需要 API Key,也没有训练过程。

把代码保存为你自己的 temperature_demo.py,执行 python3 temperature_demo.py 即可。

import math
import random


def temperature_probs(logits, temperature):
    # 教学例子只接受非空、有限的分数;禁止生成的 token 应先过滤。
    if not logits or not all(math.isfinite(x) for x in logits):
        raise ValueError("logits 必须非空,且全部是有限数字")
    # 本例允许 0,并单独实现贪心;负数、NaN 和无穷大都不接受。
    if not math.isfinite(temperature) or temperature < 0:
        raise ValueError("temperature 必须是有限的非负数")

    # 0 不进入除法;并列最高分时,本例选靠前的候选。
    if temperature == 0:
        winner = max(range(len(logits)), key=lambda i: logits[i])
        return [1.0 if i == winner else 0.0 for i in range(len(logits))]

    # 先减去最大分数,避免指数过大;数学上不改变 softmax 的结果。
    maximum = max(logits)
    # 温度越大,候选间的分数差距越小。
    weights = [math.exp((x - maximum) / temperature) for x in logits]
    # 归一化以后,所有候选的概率总和为 1。
    total = sum(weights)
    return [weight / total for weight in weights]


# 固定候选及分数,只比较温度的作用;这不是模型实测 logits。
tokens = ["餐", "公", "家"]
logits = [2.0, 1.0, 0.0]
# 固定本例的随机种子,方便在相同环境里复现。
rng = random.Random(7)

for temperature in (0.5, 1.0, 2.0):
    # 同一组分数,分别转成三种温度下的概率。
    probabilities = temperature_probs(logits, temperature)
    # 按概率抽一个 token,而不是直接取最大值。
    selected = rng.choices(tokens, weights=probabilities, k=1)[0]
    # 同时打印概率和本次抽取结果,观察两者的区别。
    percentages = [f"{p:.1%}" for p in probabilities]
    print(f"T={temperature}: 概率={percentages}, 抽中={selected}")

这份代码在本地运行得到:

T=0.5: 概率=['86.7%', '11.7%', '1.6%'], 抽中=餐
T=1.0: 概率=['66.5%', '24.5%', '9.0%'], 抽中=餐
T=2.0: 概率=['50.6%', '30.7%', '18.6%'], 抽中=公

三组概率与前面的表格一致。单次抽取可能在不同温度下选到同一个字,这完全正常:概率改变,不代表每次结果一定改变。

注意,这段代码只反复观察一个固定生成位置。如果想生成完整句子,还需要把选中的 token 追加到上下文,再让模型计算下一步的 logits,而不是拿这一组分数一直抽。

示例中的 T = 0 分支把全部概率给一个最高分候选,只是为了统一返回格式;没有执行除以 0,也没有修改模型权重。

5. 量化改变的又是什么?

量化关注的是数字的表示方式。以权重量化为例:原来用 16 位浮点数保存的权重,经过量化,可能用 8 位或 4 位编码来近似表示,从而减少权重占用的空间。具体方法及其适用条件可参考量化概览

它不是简单地“保留几位小数”。一种常见的均匀量化思路,是将数值映射到有限的整数档位,并配合缩放系数等信息还原近似值。不同量化方案的编码方式并不相同。

为了直观理解,可以看一个人为指定步长的例子:

假设只能用步长为 0.1 的档位表示:

原始数值 0.1234 → 最近档位 0.1
原始数值 0.2761 → 最近档位 0.3

这只是“用有限档位近似数字”的示意,不代表真实 4-bit 模型都按 0.1 四舍五入。

这里还要分清三个容易混用的概念:

概念关注的问题
数值精度数字可以表示得多细,有多大的近似误差?
回答正确率模型在具体任务上,答对了多少?
采样随机性生成时,非最高分候选有多少机会被选中?

量化涉及第一项,温度主要影响第三项,两者都可能影响最终回答,但不能直接画等号。

再补一个部署中常见的误区:4-bit 权重不代表所有计算都是 4-bit。 例如,bitsandbytes 支持保存 4-bit 权重,同时选择 BF16 计算类型,见其计算数据类型说明

量化也不一定只针对权重,还有针对激活值、KV Cache 的方案。因此,看见“量化”时,要进一步问:量化的是哪部分,用什么格式,运行时又怎么计算?

把两者放在一起就很清楚了:

对比项温度 Temperature权重量化
主要改变什么选 token 时的概率分布权重数值的表示方式
是否改权重存储位数不改通常降低
是否减少模型参数个数不会单纯量化通常也不会
是否因此缩小权重文件不会通常会,有额外编码信息开销
对回答的可能影响多样性、稳定性、错误概率变化数值近似可能导致输出变化
能否一起使用可以可以

所以,一个量化模型完全可以低温生成,也可以高温生成。温度不是量化等级,也不能把量化损失的数值信息恢复回来。

6. 温度高,就更有创意、更不准确吗?

“创意参数”是方便理解的比喻,但不是能力保证。

提高温度,让不那么常见的候选更有机会出现,可能带来不同表达,也可能带来不相关的词、前后矛盾或错误细节。它不会自动补上模型没学会的知识。

同样,降低温度只是让选择更集中。如果模型本来就把错误答案排在第一,低温可能让它更稳定地答错。

这也解释了为什么前面的教学小模型,不能靠调温度就变成通用问答助手:数据、训练效果和模型能力仍然是基础。

实际比较时,可以固定模型版本、提示词和其他生成设置,只改变温度,多生成几次,分别观察正确率和表达差异。不要只拿一条“刚好写得好”的输出当结论。

还要注意,top_ktop_p 等候选过滤设置也会影响结果:前者限制保留多少个高概率候选,后者按累计概率截取候选集合。它们不是温度的别名,具体组合顺序要看实现。定义见生成配置文档

7. 最后记住这三句话

  1. 模型先根据上下文给候选 token 打分,温度再调整用于采样的概率分布。
  2. 温度高不等于数字精度低;量化才是在改变模型数字的表示方式。
  3. 温度调的是选择方式,不是知识量,更不是“正确率旋钮”。

如果只用一句话总结:

温度决定“按什么概率选下一个 token”,量化决定“用什么精度表示模型中的数字”。