今天来聊聊 LLM 的 Temperature:从 logits、softmax 到模型如何选出下一个 Token
很多开发者第一次接触大语言模型的 Temperature 参数时,看到的解释通常都差不多:温度越低,输出越稳定;温度越高,输出越有创造力。
这个结论确实没有问题,但如果只记住这一层的话,在真正做 RAG、Agent 或模型 API 接入时其实不太够。因为很快就会碰到一串更具体的问题:Temperature 到底改了什么?logits 和 softmax 分别是什么?模型为什么会选到概率第二高的 token?既然低概率 token 也有机会被抽中,为什么现在的大模型很少突然输出毫不相关的词?还有一个在 RAG 里经常出现的问题------我们只是在 Prompt 里写了一句"如果不知道就回答不知道",模型为什么真的会说"不知道"?
这些问题看着分散,其实都落在同一条链路上:LLM 每生成一个 token,到底经历了什么。
这篇文章就从这条链路讲起,把 Temperature、采样和幻觉之间的关系串清楚。开始!
从 RAG 里的"不知道"说起
在 RAG 系统中,我们经常会写类似这样的 Prompt:
text
请只根据提供的资料回答问题。
如果资料中没有答案,请回答"不知道"。
资料:
Nova 是一个本地 Coding Agent。
问题:
Nova 的作者今年多少岁?
模型很大可能会回复:
text
不知道
这里很容易产生一个错觉:模型是不是先检查了一遍资料,然后在内部得出了"我不知道"这个结论?
如果把它写成代码,大概像这样呢:
python
if answer_not_found:
return "不知道"
LLM 内部其实没有一个可靠的 known = true 或 unknown = true 状态。生成时,它一直在做同一件事:根据当前上下文,预测下一个最合适的 token。
上面的上下文里,模型看到资料没有提供作者年龄,同时 Prompt 明确规定"资料里没有答案就回答不知道"。在这种情况下,"不知道"恰好成为非常合理的一段续写。
模型不会先产生一个"我不知道"的内部结论,再把它翻译成文字。"不知道"本身就是这次生成里概率很高的一段续写。
这也解释了一个实际工程问题:只在 Prompt 里写一句"如果不知道就回答不知道",并不能彻底解决 RAG 幻觉。只要模型认为某个具体答案在当前上下文下同样很合理,它依然可能补出一个并不存在的事实。RAG 里真正可靠的约束,通常还要依赖检索质量、引用证据、相关性判断和结果校验。
一个 token 是怎么被生成出来的
先把整条链路放出来:
text
当前上下文
↓
模型给词表中的候选 token 打分
↓
logits
↓
Temperature 调整 logits
↓
softmax 转成概率
↓
top-p / top-k 等策略筛选候选
↓
sampling(随机抽样)
↓
选出一个 token
↓
把 token 接回上下文
↓
继续预测下一个 token
模型生成回答时,会先选出一个 token,把它接回上下文,再根据新的上下文继续预测下一个 token。几百上千个 token 的回答,就是这个循环不断重复。
Logits:模型先给所有候选 token 打一遍分
假设当前输入是:
text
中国的首都是
模型会根据前面的上下文,对词表里的所有 token 计算一个原始分数。为了方便理解,假设几个候选 token 的分数是:
text
北京 8
上海 4
南京 3
东京 1
这里的 8、4、3、1 就可以理解为 logits。
Logits 不是概率。
北京 = 8 不代表北京有 80% 的概率,上海 = 4 也不代表上海有 40% 的概率。它们只表达一个相对关系:在当前上下文下,模型认为"北京"比"上海"更适合作为下一个 token。
真实模型处理的候选当然远不止几个。词表通常包含几万甚至十几万个 token,模型每生成一步,都要为这些候选计算 logits。
所以 logits 这件事可以先简单记成:
Logits 是模型对所有候选 token 给出的原始评分。
这个分数越高,说明该 token 越符合当前上下文。
Softmax:把原始分数变成概率
有了 logits 之后,还不能直接进行概率采样,因为 logits 本身不是概率。
这时就需要 softmax。
继续使用刚才的例子:
text
北京 8
上海 4
南京 3
东京 1
经过 softmax 之后,它们会被转换成一组总和为 100% 的概率,例如:
text
北京 97%
上海 2%
南京 1%
东京 接近 0%
softmax 的形式可以写成:
text
P(token_i) = exp(logit_i) / Σ exp(logit)
如果只是做 LLM 应用开发,没有必要把这个公式背下来。真正需要理解的是它在做什么:
text
logits
= 原始分数
softmax
= 把原始分数转换成概率分布
而 Temperature,恰好插在这两步之间。
Temperature:它改的是 logits 之间的差距
常见的处理形式是:
text
softmax(logits / T)
其中 T 就是 Temperature。
假设模型原始 logits 是:
text
北京 8
上海 4
南京 3
如果设置:
text
T = 0.5
那么进入 softmax 之前,相当于变成:
text
北京 16
上海 8
南京 6
原来北京和上海差 4 分,现在差 8 分。高分 token 的优势被进一步放大,最后得到的概率分布会更集中。
这就是所谓的"低温更稳定"。
反过来,如果:
text
T = 2
则变成:
text
北京 4
上海 2
南京 1.5
候选 token 之间的差距被压缩了。经过 softmax 后,原来排名靠后的候选也会拿到更多概率。
Temperature 做的事情很具体:改变 logits 之间的相对差距,从而改变最终概率分布的尖锐程度。 "更有创造力"或者"更稳定"只是这个变化最终表现出来的效果。
可以把它概括为:
text
低 Temperature
→ 高分 token 优势被放大
→ 概率更集中
→ 输出更稳定
高 Temperature
→ 候选之间差距被压小
→ 次优 token 获得更多概率
→ 输出变化更多
By the way,需要补充一个细节:有些 API 允许 temperature = 0。数学上当然不能直接做 logits / 0,所以这通常是服务端的特殊处理,效果接近于尽量选择最高概率 token,也就是常说的 Greedy Decoding(贪婪解码)。
高温度为什么会更容易选到"第二名"
这一点很多人容易理解错,我们往下看。
假设模型最终得到:
text
北京 80%
上海 10%
南京 6%
杭州 4%
有些人会把它理解成一个排行榜:
text
第一名:北京
第二名:上海
第三名:南京
然后觉得模型应该永远选第一名,这是不对的。
采样不会保证每次都拿第一名,系统会按照这组概率抽取一次,也就是随机采样
为了直观一点,我们可以把上面的概率想象成 100 张票:
text
北京 80 张
上海 10 张
南京 6 张
杭州 4 张
每次生成就在里面抽一张。尽管北京是最容易被抽到的,但其他 token 依然有机会。
如果把 Temperature 调高,概率分布可能会变成:
text
北京 54%
上海 19%
南京 15%
杭州 12%
排名依然没有变:
text
北京 > 上海 > 南京 > 杭州
北京还是第一名,上海还是第二名。
变化在于,北京从 80% 降到了 54%,后面几个候选合起来已经有接近一半的概率。
所以高温更容易选到第二、第三候选,原因很直接:这些 token 在采样池里的权重变大了。候选的相对排序通常没变,变的是每个候选最终拿到的概率。
那低概率的错误 token 为什么很少被抽中
如果模型是按概率采样,那么只要一个 token 的概率不是 0,它理论上就有机会被选中。比如:
text
这个方案非常......
合理 50%
不错 20%
优秀 10%
有趣 8%
离谱 5%
如果"离谱"真的有 5%,那多生成几次,迟早会抽到。
问题是,现实里的不相关 token 通常根本没有这么高的概率。
上面的数字只是为了方便解释采样。真正训练得比较好的大模型,面对正常上下文时,概率分布往往更加接近:
text
合理 30%
可靠 18%
不错 12%
成熟 8%
......
离谱 0.00000...%
香蕉 0.00000...%
东京 0.00000...%
这些完全不相关的 token,通常早就在模型训练阶段被压到了非常低的位置。
预训练做了什么
预训练阶段,模型反复做的事情其实非常朴素:
text
给一段前文
↓
预测下一个 token
↓
和真实文本比较
↓
预测错了就调整参数
↓
继续下一次
这个过程进行到足够大的规模以后,模型就学到了大量语言、知识和代码模式。
看到:
text
中国的首都是
"北京"的 logit 会很高,而"香蕉""TypeScript""星期三"这类 token 的 logit 会低很多。
现代 LLM 很少突然蹦出完全无关的词,主要原因就在采样之前:训练已经让这类 token 的 logits 低得多,最终概率通常接近于零。
"概率大于 0"也不意味着现实里经常发生。
5% 和 0.000000000001% 都不是 0,但两者在实际体验上完全不是一回事。
后训练还会继续塑造这种概率分布
现代聊天模型通常也不只做预训练。
预训练之后,还会有指令微调、偏好优化、强化学习等后训练过程。不同厂商的具体方案不完全一样,但它们有一个共同效果:继续调整模型在某些上下文下倾向输出什么。
比如用户问:
text
解释一下 React useEffect
一个经过良好后训练的助手模型,会更倾向于:
- 直接回答当前问题;
- 保持上下文一致;
- 遵循用户要求的格式;
- 减少明显无关的内容;
- 在特定情况下拒绝或说明不确定性。
从生成机制上看,这些行为最终仍然会落实到概率分布里:某些 token 的 logits 被提高,另一些 token 的 logits 被压低。
所以"这个模型很会聊天"并不意味着外面套了无数个 if/else。很多看起来像行为规则的东西,最终仍然会反映在 token 的条件概率上。
Top-p 和 Top-k:很多尾部 token 连候选池都进不去
即使模型已经把不相关 token 的概率压得很低,实际推理系统通常还会再做一层筛选。
最常见的就是 top-p 和 top-k。
Top-p
假设当前概率分布是:
text
合理 40%
不错 25%
可靠 15%
优秀 8%
成熟 5%
......
香蕉 0.000001%
如果:
text
top_p = 0.9
系统会从概率最高的 token 开始依次加入候选池,直到累计概率达到大约 90%。
后面那一长串尾部 token 这次就不再参与采样。
到了这一步,尾部 token 甚至不会进入最终抽样集合。
Top-k
top-k 更直接。
比如:
text
top_k = 50
那就只保留概率最高的 50 个 token。
剩下的 token 直接被排除。
所以实际生成过程通常可以理解成:
text
几万 / 十几万个 token
↓
模型计算 logits
↓
Temperature 调整 logits
↓
softmax 生成概率
↓
top-p / top-k 等策略裁剪尾部
↓
在保留下来的候选中进行 sampling
不同模型和不同推理服务的实现细节会有差异,有些服务甚至不会把完整的采样参数暴露给开发者。但从应用开发的角度看,这条思路足够解释大多数现象。
为什么模型不太会"胡言乱语",却仍然会出现幻觉
现代大模型已经很少出现这种错误:
text
React 的 useEffect 香蕉 东京 public 哈哈哈
这种文本在语法和语义上都明显断裂。
但它仍然会出现另一类错误:
text
某个框架在 v3.2 版本加入了 X 功能。
这句话可能写得非常自然,术语也都对,甚至看起来很像官方文档。
问题只是:v3.2 根本没有这个功能。
为什么?
因为从 token 预测的角度看:
text
某个框架
在
v3.2
版本
加入了
X 功能
这些 token 组合在技术文本里完全成立。
模型擅长的是判断:
在当前上下文里,什么样的文本最像一个合理回答?
但它并没有天然拥有一个外部事实数据库,能在每个 token 生成前都验证:
这句话描述的事情在真实世界里到底发生过没有?
这就是语言连贯性和事实真实性之间的差别。
模型可以非常流畅地说错。
而且越强的模型,有时错得越"像真的",因为它组织语言和领域表达的能力本身很强。
所以 RAG、搜索、数据库查询、工具调用、引用证据和结果验证一直很重要。模型负责生成一个看起来合理的回答,外部信息和验证机制负责尽量保证这个回答有事实依据。
Temperature = 0.7 为什么经常被当成默认值
0.7 经常出现在教程、SDK 示例和早期聊天应用中,因此慢慢形成了"比较通用"的印象。
但它不是行业标准,也不存在什么数学结论证明 0.7 是最优点。
它更像一个历史上的经验值。
低温通常会让输出更集中,适合:
text
代码修改
结构化输出
严格 RAG 问答
信息抽取
工具参数生成
高一点的温度则更适合:
text
创意写作
文案构思
方案脑暴
需要多样性的生成任务
如果是在做真正的产品,最稳妥的方法还是拿自己的任务集跑评测。
例如准备 100 个真实任务,分别测试:
text
T = 0.1
T = 0.3
T = 0.7
T = 1.0
然后观察:
text
任务成功率
格式正确率
事实错误率
工具调用成功率
代码测试通过率
输出多样性
最后哪个值在你的任务里表现最好,就用哪个。
Temperature 没有脱离任务场景的"最佳值"。
Temperature 为什么只是一个比较粗的控制参数
即使模型 API 允许我们设置 Temperature,它也有一个天然限制:它通常对整次生成统一生效。
比如一个 Coding Agent 在完成任务时,可能先分析代码,再想方案,然后修改文件,最后检查测试结果。
这些阶段需要的生成风格其实不一样。
分析报错时,希望它稳。
写精确代码时,希望它稳。
脑暴方案时,又希望它有一点发散。
但如果整次请求设置:
text
temperature = 0.7
那基本意味着:
text
第 1 个 token T = 0.7
第 2 个 token T = 0.7
第 3 个 token T = 0.7
......
第 2000 个 token T = 0.7
Temperature 本身不会理解:
text
"现在这一步在写关键代码,应该收一点。"
"现在到了方案阶段,可以多探索几个方向。"
它只是统一调整概率分布。
所以到了复杂 Agent 里,Temperature 只能解释很小一部分行为。
一个 Coding Agent 最终能不能把任务做完,更多取决于:
- 上下文有没有给对;
- 工具有没有设计好;
- 模型能不能拿到真实执行结果;
- 出错后有没有重试或 replan;
- 测试有没有真的跑;
- 完成条件有没有明确检查;
- 长任务中信息有没有被正确保留。
Temperature 会影响输出,但对一个真正跑任务的 Agent 来说,它只是很多变量中的一个。
最后再看一次生成链路
把前面的内容合在一起,模型生成一个 token 时大致会经过这几步:
text
用户输入 + 当前上下文
↓
LLM
↓
为词表中的候选 token 计算 logits
↓
Temperature 调整分数差距
↓
softmax 得到概率分布
↓
top-p / top-k 等策略裁剪候选
↓
sampling
↓
选出一个 token
↓
追加回上下文,继续下一轮
理解这条链路之后,Temperature 就没那么神秘了。它控制的是解码阶段概率分布有多集中,能影响输出的稳定性和多样性,但它解释不了模型能力本身,更替代不了 RAG、工具调用、测试和验证这些工程机制。
如果你在做普通的模型 API 接入,知道它如何影响采样已经够用;如果你在做 Coding Agent,更值得花时间调的通常是上下文、工具、执行流程和完成条件。