龙虾AGI通用实验室Lobster AGI Lab · est. 20262026 · 09 · 16
首页 / 文章 / AI持续学习
AI持续学习

大模型掷骰子吗?拆解 AI 输出的确定性边界

格里灰丝2026-05-21约 6054 字Markdown 原文

一、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 因为效率优势正在被越来越多的模型采用。对于使用这类模型的开发者来说,这一层不确定性值得纳入系统设计的考量。

七、确定性的代价

把前面所有层次放在一起:

不确定性来源
性质
能否消除
消除代价
采样随机性(temperature)
有意设计
可以(设为 0)
失去输出多样性
采样随机数序列(seed)
有意设计
可以(固定 seed)
无,仅在 temperature > 0 时有意义
GPU 浮点噪声
工程权衡
可以(FP32 + 确定性模式)
推理速度减半,显存翻倍
服务端批处理 / 负载均衡
基础设施层面
理论上可以(独占实例)
成本极高
MoE 专家路由竞争
架构层面
几乎不能
放弃 MoE 或完全隔离

从上到下,可控性递减,消除代价递增。

完美的确定性在理论上可以实现:FP32 精度、GPU 确定性模式、固定 seed、temperature=0、独占推理实例、不用 MoE。但这套配置在生产环境中意味着成本翻倍、吞吐量减半,没有商业 LLM 服务商会这样做。

这是整篇文章的核心结论:大模型输出的不确定性不是一个 bug,也不是一个可以用 temperature=0 简单消除的问题。它是多个层次的工程权衡叠加的结果——每一层都在用可复现性换取性能或成本。

理解了这一点,才能在自己的场景中做出合理的设计决策:

安全评估: 单次测试不够,每个 prompt 至少采样 3 次以上。

回归测试: 不要做精确字符串匹配,在语义层面验证输出的正确性。

关键决策: 不要依赖单次 LLM 调用,多次采样加一致性检查,或者引入人工复核。

日常使用: 同一个问题问两遍得到不同措辞的回答,不是模型"不稳定",而是在同一个语义空间内走了不同的表达路径。知道这一点,就不必为此焦虑。

我建了一个AI学习群,目前几十来人,都是在真正动手学AI的人。如果你也在自己跑模型、写代码、做项目,欢迎加我微信,备注"你在做的AI方向",我拉你进群。纯围观的就不加了,群里大家都在真搞。

上一篇大模型缓存机制完全指南:一篇讲透 KV Cache 到 Claude Code 的省钱工程下一篇正本清源说 Agent:OpenClaw 和 Hermes 到底有啥区别?