# 大模型掷骰子吗？拆解 AI 输出的确定性边界

> 龙虾AGI通用实验室 · 2026-05-21

## 一、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方向"，我拉你进群。纯围观的就不加了，群里大家都在真搞。

![](images/0be955/img_001.jpg)
