我们在生产环境中发现,即使把 Temperature 设为0,同样的输入,有时输出还是不一样。这是什么原因呢?
很多同学第一反应都会说”浮点数精度问题”。但是如果答案停在这里,只是触及了这个现象,不是根本原因。这个问题真正考察的是对大模型推理阶段确定性到底是如何被一步步破坏的理解深度。下面我们完整的分析一下整个问题。
从 Temperature=0 的预期说起
首先我们需要明确,在Temperature=0 的时候,在理论上意味着什么。当我们把温度参数设为0时,模型会切换到贪心解码模式,也就是在每一步生成时,都选择概率最高的那个token,从数学上讲,这就是在做argmax操作,不涉及任何概率采样。按理说给定相同的输入和模型权重,每次推理的logits应该是完全一样的,那输出自然应该一致。但现实是,即使我们把Temperature设为0,seed也固定了,同样的prompt发给GPT,很可能还是得到不同的输出。
这种现象让很多工程师抓狂,也让OpenAI的技术人员在早期roundtable上表示过困惑。那问题到底出在哪呢。
浮点运算的非结合性是真相
我们来看第一个关键因素,也是最容易被误解的一个: 浮点数运算。很多人知道浮点数有精度问题,但是不知道浮点数的精度问题会在并行计算中造成什么后果。有一个经典的例子,在Python中,$ (1 + 1e16) - 1e16$ 得到的是 $0。0$,而 $1 + (1e16 - 1e16) $ 得到的是 $ 1。0 $。同样的三个数,不同运算顺序,结果却不一样,这就是浮点数的非结合性问题。
好,接下来我们把这个问题放到GPU并行运算的场景里,当我们的模型在GPU上跑forward pass的时候,大量的矩阵运算是并行执行的。比如说在计算Attention scores的时候,可能需要把成千上万个数值加起来。GPU为了提速,会把这个操作分配给不同的线程并行处理,然后再把各个线程的结果合并。问题就出在这个合并的顺序上,由于线程调度是不确定的,有时候可能是线程A的结果先加到累加器里,有时候可能是线程B先加进去。虽然从算数角度看这没区别,但由于浮点数的非结合性,不同的相加顺序会导致最后的结果有微小差异。这个差异通常很小,可能就是小数点后十几位的变化。但当两个token的logits非常接近的时候,这点差异就足以改变argmax的结果了。
更关键的是,现代深度学习框架为了性能优化,默认是不保证这些操作的确定性的。Pytorch文档里专门有个 reproducibility guide 列出了哪些操作在默认情况下时非确定性的,我们可以通过设置特定的flag强制使用确定性算法,但代价是性能会明显下降,所以生产环境里基本没人这么干。
Sparse MoE 架构带来的批次级非确定性
现在我们聊聊更微妙但是印象更大的一个因素: Sparse MoE 架构。这个可能是生产环境中GPT表现的特别不确定的真正原因。
MoE模型的特点是,它不是一个整体的大网络,而是由多个专家网络组成的,在推理的时候,有个gating机制会决定每个token应该路由到哪些专家。为了效率考虑,这些专家通常都有容量限制,一个专家同时只能处理有限数量的token。这在单个请求的时候还好说,但API服务商为了提高GPU利用率,会把多个用户的请求打包成batch一起处理。问题就来了,当我们prompt中的某个token和别人prompt里的token同时竞争同一个专家的时候,谁能进去、谁被路由到备选专家,这个决策过程可能受到batch构成的影响。用更直白的话说,你同样的输入,如果这次恰好和用户A的请求打包在一起,某个关键token进了专家1; 下次跟用户B打包在一起,这个token肯呢个就被挤到专家2去了。虽然这两个专家都能工作,但他们的输出肯定不一样。这就导致了整个生成结果的分叉。
这种批次级的非确定性是MoE架构的固有特征,有篇论文里明确提到,当下某一模型不再是序列级确定的,而只是凭自己的。也就是说,只有当你能保证每次推理时的整个 batch 完全相同,模型才会给出相同的输出。但作为 API 用户,你根本无法控制你的请求会跟谁打包在一起。所以从你的视角看,输出就是随机的。此前就有人通过实验验证了这个猜想。他发现 GPT 在 temperature = 0 的情况下30次调用能产生11~12种不同的输出,这个非确定性程度远远超过了浮点误差能解释的范围。相比之下,不使用MoE架构的早期davinci模型就稳定的多。
硬件异构性的影响
再往底层走一点,我们还要考虑硬件层面的因素。云服务商通常有一个异构的GPU集群。你的请求可能这次落在H100上,下次落在A100上。虽然这些GPU都兼容,但它们的架构、细节、计算时间甚至驱动版本都可能不同,这导致同样的计算在不同硬件上可能产生略微不同的结果。
更复杂的情况是模型并行。像 GPT 这种超大规模模型,单个 GPU 放不下,必须切分到多个 GPU 甚至多台机器上。这种分布式推理涉及跨设备通信问题,不同设备之间的数据聚合顺序、通信时延的差异都可能引入额外的非确定性。当然,这种硬件层面的影响相对于贸易的批次效应来说要小得多,但它确实让问题雪上加霜的一个因素。
Tie-breaking 机制的隐藏影响
还有一个容易被忽略的细节,当两个TOKEN的概率几乎完全相等时,模型怎么决定选哪个?理论上 argmax 应该有一个固定的 tie-breaking 规则,比如选 TOKEN ID 较小的。但在实际实现中,这个规则可能因为上面提到的各种因素变得不稳定。
更有意思的是,有些 API 在遇到极端情况时会自动微调 temperature 。Openai文档里就暗示过,即使你设了 temperature = 0 ,系统在某些阈值条件下,也可能悄悄提高一点温度来避免系统卡死或进入死循环。这种自适应机制虽然提高了生成质量,但也进一步削弱了确定性。
实际应对策略
讲到这里大家应该明白了,temperature = 0 并不能保证完全的确定性,这是系统架构和实现细节综合作用的结果。那在实际工作中怎么办呢?
对于关键业务场景,不要依赖字符串精确匹配,应该设计更robust的体系逻辑,关注语义的正确性,而不是字面一致性。比如,如果你要从 LLM 输出里提取结构化信息,用正则表达时或者专门的Parser,而不是期望每次格式都完全相同。其次,如果是自己获取开源模型,可以考虑使用 batch-variant 的算子库,强制保证批次的无关性。(但是这个通常会牺牲一些性能)。最后在评估和测试LLM应用的时候,要考虑到这种内在的不确定性,多跑几次取中位值,或是用 self-consistency 方法来提高稳定性,不是假设单次输出就是唯一正确答案。