一、temperature 调成 0,输出就确定了?
用过 LLM API 的人多少都听过一个说法:temperature 控制随机性,调成 0 就是确定性输出。
这个说法流传很广,以至于很多生产系统的设计直接建立在这个假设上——用 temperature=0 的输出做哈希校验,做精确字符串匹配的回归测试,甚至用单次调用的结果来评估模型的安全性。
但一项针对多个 LLM 的安全评估研究给出了一个不太舒服的数据:在 temperature=0 的贪婪解码下,5-12% 的测试 prompt 在不同运行中产出了不同的安全决策——同一个有害请求,模型有时拒绝、有时配合。不是 temperature=1的情况,就是 temperature=0的时候,纯贪婪解码。
5-12% 意味着什么?意味着如果你用"跑一次、看结果、下结论"的方式做安全评估,每 10 个测试用例里可能有 1 个的结论是不可靠的。
这个不确定性从哪来的?temperature 已经是 0 了,没有任何随机数参与,为什么结果还会变?
要回答这个问题,得先看清楚大模型生成一段文字时,内部到底发生了什么。
二、生成一段文字的两个阶段
大模型的工作过程可以拆成两步,理解这两步的区别是理解后面所有内容的基础。
第一步:Prefill——计算概率分布。 所有输入 token 经过模型每一层的矩阵运算,每个 token 产出 Key 和 Value 两组向量(即 KV 张量)。全部算完后,模型得到一个覆盖整个词表的概率分布:"下一个词是'好'的概率 30%、'嗯'的概率 20%、'可以'的概率 15%……"
这个过程没有任何随机数参与。同样的输入 token × 同样的模型权重 → 同样的矩阵乘法 → 应该得到同样的概率分布。纯数学,纯确定性。
(顺带一提,正因为这步是确定性的,其产物 KV 张量才可以被缓存复用——这是 prompt caching 能够成立的数学基础。关于缓存如何省 80% token,详见《大模型缓存机制完全指南》。)
第二步:Decode——从概率分布中选一个 token。 选完之后,把这个 token 追加到输入末尾,回到第一步算新的概率分布,再选……循环往复,直到输出结束。
所以整个流程是:确定性地算出概率分布 → 从分布中选一个 token → 确定性地算出新的概率分布 → 再选一个 token → ……
不确定性只可能发生在"选 token"的环节。那 temperature=0 的时候,选 token 的规则是"永远选概率最高的那个",没有骰子可掷,按理说应该完全确定。
那第一章提到的 5-12% 翻转率到底怎么来的?
三、temperature 能关掉的随机性,和它关不掉的
先把 temperature 能控制的部分说清楚。
temperature > 0 时,模型按照概率分布采样——30% 的概率选"好"、20% 的概率选"嗯"。这个采样过程使用伪随机数生成器(PRNG),"伪随机"意味着它不是真正的随机,而是由一个初始值(称为 seed,种子)驱动的确定性算法——给定相同的 seed,PRNG 会产出完全相同的随机数序列。
部分 LLM API 允许用户在请求中指定 seed 参数。指定之后,即使 temperature > 0,采样过程使用的随机数序列也是固定的,理论上同样的 seed + 同样的 temperature + 同样的输入 = 同样的输出。这是一个几乎零成本的操作——不影响速度、不影响质量,只是让采样过程变得可复现。不指定 seed 的话,系统每次自动生成一个不同的 seed,输出自然每次不同。
temperature = 0 更简单:直接选概率最高的 token,不需要任何随机数,PRNG 根本不参与,seed 也就无所谓了。
到这里为止,一切都很干净。如果概率分布本身是完全确定的,那 temperature=0 就应该是完全确定的。
问题出在"概率分布本身是完全确定的"这个前提上。在数学定义里它是确定的,但在真实的 GPU 上跑起来,它不是。
四、GPU 不做精确计算
这是整篇文章最核心的一节。
先看一个 Python 实验:
>> (1 + 1e16) - 1e160.0>> 1 + (1e16 - 1e16)1.0
数学上 (a+b)+c 等于 a+(b+c),但浮点数下不等。这不是 Python 的 bug,是 IEEE 754 浮点标准的固有属性——有限位数的二进制表示无法精确承载所有实数运算,运算顺序不同,舍入误差的累积方式就不同。
大模型的每一层 Transformer 都在做大规模矩阵乘法和加法。GPU 为了最大化吞吐量,用数千个线程并行计算这些运算,线程的调度顺序由硬件动态决定。这意味着:同一组浮点加法,在不同运行中的执行顺序可能不同。 执行顺序不同 → 舍入误差不同 → 最终结果不同。
差异有多大?在 FP32(32 位浮点)精度下,差异极小,几乎可以忽略。但生产环境不用 FP32——太贵了,内存占用和计算成本是 BF16(bfloat16,16 位)的两倍。几乎所有商业 LLM 服务都使用 BF16 或类似的低精度格式,在这个精度下,数值差异显著到足以影响输出。
GPU 厂商提供了"确定性模式"(比如 NVIDIA 的 torch.use_deterministic_algorithms(True)),可以强制线程调度顺序一致,消除这类差异。但代价是计算速度显著下降。所以生产环境默认不启用——非确定性不是疏忽,而是用可复现性换吞吐量的主动选择。
这还只是同一台 GPU 上的情况。云服务的负载均衡会把请求路由到不同服务器、不同型号的 GPU;批处理(batching)会把多个用户的请求打包处理,同一批次的其他请求的数量和长度会影响内存访问模式。这些因素叠加在一起,构成了一个用户完全不可控的不确定性来源。
现在可以回答第一章的问题了:那 5-12% 的翻转率,不是因为采样随机性,而是因为 GPU 浮点计算在不同运行间产出了略有不同的概率分布。大多数时候差异太小、不影响最终选择,但有时候恰好够大……
五、蝴蝶效应:0.00002% 的差异如何毁掉整段输出
正常情况下,GPU 浮点噪声导致的概率差异在小数点后很多位,完全不影响贪婪解码的选择——概率 30% 的 token 不会因为 0.00001% 的波动就被概率 15% 的 token 超过。
但有一种临界情况:两个候选 token 的概率非常接近。
"的"是 25.00001%,"很"是 24.99999%。在某次运行中,浮点噪声让"很"的计算概率微微上浮到 25.00002%。贪婪解码忠实地选了"很"。
如果模型只输出一个 token,这无关紧要。但大模型是自回归生成的——每个 token 都取决于前面所有 token。
运行 1: "这个问题" → "的" → "关键" → "在于" → "理解注意力机制的工作原理..."运行 2: "这个问题" → "很" → "好" → "," → "核心要点是注意力计算..."↑概率差距: 0.00002%但从第 3 个 token 开始,两次输出完全分道扬镳
第 2 个 token 不同 → 第 3 个 token 面对的输入就不同 → 概率分布完全改变 → 到第 20 个 token 时两次输出在字面上可能毫无相似之处。一个小数点后第六位的差异,经过 20 步传播,变成了两段完全不同的文字。
这就是为什么日常使用中感觉"大模型每次回答都不一样"。 字面差异确实很大。但如果仔细对比两次输出的含义,往往非常接近。
原因是:两次生成的输入完全相同,阶段一算出的概率分布中,高概率区域(即"合理回答"的语义空间)是一致的。模型对这个问题的"理解"没变,变的只是在多条合理的表达路径中走了不同的一条。"关键在于理解注意力机制"和"核心要点是注意力计算"——不同的词汇路径,同一个语义终点。
这也解释了一个容易验证的现象:短回答的一致性远高于长回答。 "1+1 等于几"永远是"2",因为正确答案的概率远高于竞争者,浮点噪声翻转不了。开放式问题则有多种合理开头,概率彼此接近,蝴蝶效应很容易触发。
六、MoE 架构:还有一层更难控制的不确定性
前面几章讨论的不确定性,无论是浮点噪声还是蝴蝶效应,有一个共同特点:它们只和"你自己的请求"有关。同样的输入在同一台 GPU 上跑两次,因为线程调度不同导致结果不同——但至少这个差异只取决于你的输入和硬件状态,跟其他用户无关。
MoE(混合专家模型)架构打破了这个边界。
什么是 MoE
DeepSeek-V3、Gemini 等模型采用的 MoE 架构,不让每个 token 经过所有参数,而是通过一个路由网络(router)动态分配——每个 token 只被其中几个"专家"子网络处理。比如一个模型有 256 个专家,每个 token 只激活其中 8 个。这样做的好处是:模型总参数量可以很大(知识容量大),但每个 token 的实际计算量很小(推理成本低)。
路由网络会给每个 token 打分:"这个 token 应该去专家 A 的得分 0.35、去专家 B 的得分 0.33、去专家 C 的得分 0.12……"然后把 token 分配给得分最高的 top-k 个专家。这个打分过程本身只看 token 内容,跟其他请求无关。
专家容量限制:问题的根源
每个专家子网络不是无限容量的。在一次前向传播中,每个专家能处理的 token 数量有上限。这个上限通常按"本批次总 token 数 / 专家数 × 容量系数"来设定。
为什么要限制容量?因为硬件效率。MoE 的专家通常分布在不同的 GPU 上,如果 70% 的 token 都涌向同一个专家(同一块 GPU),那块 GPU 过载,其他 GPU 闲置,整体吞吐量反而下降。容量限制的本质是在计算准确性和硬件利用率之间做权衡。
问题出在专家被占满之后的处理方式。假设 200 个 token 都想去专家 A,但专家 A 本轮只能处理 125 个,剩下的 75 个怎么办?不同的实现有不同的策略——有的直接丢弃(token dropping),让这些 token 跳过专家层;有的把它们路由到得分第二高的专家 B。无论哪种方式,这些 token 经过的计算路径都和"本应去专家 A"时不同了。
陌生人的请求如何影响你的输出
云端大模型服务会把同一时刻的多个用户请求打包成一个批次(batch)一起处理。这意味着你的 token 和其他用户的 token 在同一个批次中竞争专家容量。
场景 1: 你的请求 + 50 个短请求(批次共 800 token)专家 A 容量上限: 125 token想去专家 A 的 token: 100 个 → 没超,你的 token 正常被专家 A 处理场景 2: 你的请求 + 3 个长请求(批次共 2000 token)专家 A 容量上限: 312 token想去专家 A 的 token: 400 个 → 超了,88 个被挤到专家 B→ 你的某些 token 可能就在这 88 个之中→ 专家 B 的权重和专家 A 不同 → 计算结果有差异 → 输出不同
你的输入没有变,路由网络给你的 token 打的分也没有变,但因为同一批次里其他人的 token 占了专家 A 的容量,你的 token 被挤到了别的专家。最终输出可能因此不同。
换句话说:你的输出不仅取决于你输入了什么,还取决于"这一刻还有谁在用这个服务"。
影响的边界:计算路径,不是信息内容
这里需要严格限定:其他用户的请求能影响的是你的 token 的计算路径(经过哪个专家),不是信息内容。
注意力计算是按请求隔离的——每个请求只对自己的 token 序列做注意力,不会跨请求做注意力。其他用户看不到你的输入内容,你也看不到他们的。没有信息泄露,没有隐私风险。他们影响的只是你的 token 在 GPU 上的"行走路线",不是你的 token 携带的"身份信息"。
Dense 模型不存在这个问题
Dense 架构的模型(所有参数对每个 token 都参与计算,如 Claude)每个 token 经过的计算路径是固定的——不存在专家选择、不存在容量竞争、不存在路由。你的请求和其他人的请求在 Dense 模型上唯一的交集是共享 GPU 的浮点计算资源(第四章讨论的问题),但那个层面的影响比 MoE 专家路由小得多。
不过 MoE 因为效率优势正在被越来越多的模型采用。对于使用这类模型的开发者来说,这一层不确定性值得纳入系统设计的考量。
七、确定性的代价
把前面所有层次放在一起:
从上到下,可控性递减,消除代价递增。
完美的确定性在理论上可以实现:FP32 精度、GPU 确定性模式、固定 seed、temperature=0、独占推理实例、不用 MoE。但这套配置在生产环境中意味着成本翻倍、吞吐量减半,没有商业 LLM 服务商会这样做。
这是整篇文章的核心结论:大模型输出的不确定性不是一个 bug,也不是一个可以用 temperature=0 简单消除的问题。它是多个层次的工程权衡叠加的结果——每一层都在用可复现性换取性能或成本。
理解了这一点,才能在自己的场景中做出合理的设计决策:
安全评估: 单次测试不够,每个 prompt 至少采样 3 次以上。
回归测试: 不要做精确字符串匹配,在语义层面验证输出的正确性。
关键决策: 不要依赖单次 LLM 调用,多次采样加一致性检查,或者引入人工复核。
日常使用: 同一个问题问两遍得到不同措辞的回答,不是模型"不稳定",而是在同一个语义空间内走了不同的表达路径。知道这一点,就不必为此焦虑。
我建了一个AI学习群,目前几十来人,都是在真正动手学AI的人。如果你也在自己跑模型、写代码、做项目,欢迎加我微信,备注"你在做的AI方向",我拉你进群。纯围观的就不加了,群里大家都在真搞。
