深度解读 · 推理系统 / 工程
消除 LLM 推理的不确定性
为什么 temperature=0 了,同一个问题问两次还是不一样?Thinking Machines 揭穿了流传最广的那个"标准答案",找到真凶——并给出让 LLM 推理逐比特可复现的工程方案。
原文:Thinking Machines Lab · Connectionism · 2025.09.10 · 作者 Horace He(PyTorch 核心开发者)
封面 Thinking Machines Lab《Defeating Nondeterminism in LLM Inference(消除 LLM 推理的不确定性)》——一篇把"为什么 temperature=0 还会变"这个老问题的根因彻底讲透的系统好文。
一句话抓住重点
大家都说 LLM 推理不确定是因为"GPU 并行 + 浮点不可结合"。这篇文章说:这个解释是错的。真凶是 缺乏"批次不变性"(batch invariance)——你的请求结果,竟然取决于服务器同时还在处理别人多少请求(即 batch size)。而 batch size 随服务器负载随机变化,于是你的输出就"随机"了。修好三个算子(RMSNorm、矩阵乘、注意力),1000 次采样可以完全一致。
01 · 谜题
调到 temperature=0,为什么还是不确定?
把温度调到 0,理论上模型每次都选概率最高的那个 token(贪心采样),应该完全确定才对。但现实是:无论是 ChatGPT 这样的 API,还是你自己用 vLLM / SGLang 在本地跑,结果依然会变。这是个困扰社区已久的现象。
❌ 流传最广、但站不住脚的解释
"
并行 + 浮点(concurrency + floating point)假说":GPU 高度并行,浮点加法又不满足结合律
(a+b)+c ≠ a+(b+c),哪个核先算完不确定,于是累加顺序变了,结果就变了。
反例(文章原话):在 GPU 上把
同一个矩阵乘法对
同样的数据跑 1000 遍,结果
每次逐比特相同。我们确实在用浮点、GPU 也确实高度并行——可这里为什么没有不确定性?说明这个假说
没抓到要害。
02 · 原罪
先理解:浮点为什么会有数值差异
差异的根源确实是浮点不可结合,但要看清它何时发作。浮点数的精度是"动态"的(固定有效数字位数)。当两个量级差很大的数相加,小数会被"对齐挤掉"——信息就丢了。
$$(a + b) + c \neq a + (b + c)$$
浮点加法不满足结合律:换个累加顺序就可能得到不同的结果。但请注意——这只是背景原罪,不是真凶。真正决定"顺序何时变、为何变"的,是下文的批次不变性。
# 用 3 位有效数字演示
1230 + 23.4 = 1253.4 (精确值,需要 5 位)
→ 只能存 3 位 → 1.25 × 10³ = 1250 (末尾 3.4 被丢弃)
# 极端例子:对同一个数组求和,打乱顺序
There are 102 unique results (顺序不同,结果不同)
所以:每次以不同顺序累加浮点数,就可能得到不同结果。但这只解释了"为什么会有差异",没解释"为什么顺序会变、何时变、怎么避免"。答案藏在算子(kernel)的实现里。
03 · 真凶
关键转折:原子加根本没参与,真凶是"批次不变性"
"并行+浮点"假说依赖原子加(atomic add)——多核往同一位置累加,谁先到不确定,顺序就乱。但文章指出一个惊人事实:
⚡ LLM 的前向传播里,几乎一个原子加都没有
因为 batch 维度本身就有足够并行度(可以一个核处理一整行),加上现代库普遍用"树形/split 归约 + 信号量"保证确定顺序。所以——
LLM 前向传播本身是"逐次运行确定"的(run-to-run deterministic):同样的输入,永远同样的输出。
那不确定从哪来?关键在于"确定"对谁而言。从服务器看是确定的,从用户看却是不确定的——这一对视角的反差,正是全文最核心的洞见:
服务器视角 · 确定 对推理服务器而言一切是确定的:给定完全相同的请求批,它永远产出完全相同的输出。
用户视角 · 不确定 对单个用户而言,"同时在线的其他用户"并不是你能控制的输入,而是系统的一个不确定属性。这正是 LLM 推理对每个用户"显得随机"的根源。
文章用四句看似矛盾、实则都对的话点破了本质:
①
GPU 上有些算子确实不确定
但 LLM 前向用到的算子都是确定的
②
推理服务器的前向也可称"确定"
给定完全相同的请求批,输出永远相同
③
但从用户视角,结果是"不确定"的
因为你的输出取决于同批的其他用户请求
④
真凶 = 缺乏批次不变性
算子结果会随 batch size 变化而变
🎯 一句话锁定真凶
矩阵乘法
本该对 batch 里每个元素独立——别人多少、batch 多大,都不该影响你这一条的数值。但实测
并非如此:同一行向量,单独算 vs 放进大 batch 里算,结果能差到
1669.25。于是:
服务器负载(你控制不了)→ 决定 batch size → 决定你的数值结果。负载随机,你的输出就"随机"了。
04 · 解法
修好三个带"归约"的算子
逐点运算天然就是批次不变的,需要动手的只有三个涉及归约(reduction)的算子,难度递增:
易 · STEP 1
RMSNorm
用"数据并行",一个核包一整行;小 batch 时宁可不拆,保持归约顺序固定
→
中 · STEP 2
矩阵乘法
固定一套 kernel 配置用于所有形状,不用 Split-K;约损失 20% 性能
→
难 · STEP 3
注意力 Attention
用固定 split 大小(而非固定 split 数量),并在算子前先更新好 KV 缓存
① 数据并行 RMSNorm 最理想的并行策略是核之间不通信:把每个 batch 元素(每一行)整个交给一个核,保证归约完全在单核内完成。图中四行配四核刚好打满——只要不"拆行",每个元素的归约顺序就与 batch 大小无关。
② 数据并行矩阵乘 与 RMSNorm 类似,把输出张量切成 2D 小块、每块分给一个核,整段归约保持在单核内。区别在于:受算术强度与 Tensor Core 约束,矩阵乘只能按 2D 瓦片切分,而不能逐个输出元素切。关键是对所有形状固定同一套 kernel 配置,拒绝 Split-K。
③ FlashAttention2 策略 沿 Q 维并行、沿 K/V 维同时归约,使整段归约依旧留在单核内,本质上又是一种数据并行策略。注意力是最棘手的一环——它要同时对特征维和序列维归约,还得应付分块预填充、KV 缓存边界等推理优化。
④ 固定大小 Split-KV(解法关键) 解码时并行度极低,必须沿 KV 维拆分。常规做法是按需求"均分"(如 1000 拆成 4 段各 250),会破坏批次不变性。正确做法是用固定大小的 split——1000 拆成三段 256 加一段 232——这样归约策略不再依赖一次处理多少 query token,批次不变性得以保持。
💡 核心原则就一句话
"每个元素的归约顺序,必须与 batch size 无关。" 注意力最棘手,因为它要同时对特征维和序列维归约,还得应付分块预填充(chunked prefill)、前缀缓存等推理优化——必须保证"处理第 1000 个 token 时的归约顺序,与 KV 缓存里有 0 个还是 999 个 token 无关"。开源在
thinking-machines-lab/batch-invariant-ops,基于 vLLM 的 FlexAttention 后端 + torch.Library 无侵入替换算子。
05 · 数据说话
1000 次采样:从 80 种结果到完全一致
用 Qwen3-235B 在温度 0 下对"Tell me about Richard Feynman"采样 1000 次、各生成 1000 token:
所有补全在前 102 个 token 完全相同,第 103 个 token 处:992 条生成"Queens, New York",8 条生成"New York City"——细微分叉就此放大成 80 种结局。开启批次不变算子后,1000 条补全逐字相同。
| 配置 | 1000 请求耗时 | 说明 |
| vLLM 默认 | 26 秒 | 不确定 |
| 未优化确定性 vLLM | 55 秒 | 确定,但慢 |
| + 改进注意力 kernel | 42 秒 | 确定,性能可用 |
性能损耗主因是 vLLM 的 FlexAttention 集成尚未深度优化,并非方案本身的硬上限。
06 · 杀手级应用
真正的 On-Policy RL:训练与采样逐比特一致
这不只是"复现实验"的洁癖。研究者早就注意到:训练端和推理端数值不一致,会让你以为在做 on-policy RL,实际上偷偷变成了 off-policy——因为采样用的策略和训练的策略其实不是同一个。
🔥 确定性推理直接解锁"真·在线 RL"
一旦采样端和训练端能做到
逐比特相同,两者的 KL 散度就是真正的
0。实验(Bigmath RLVR,Qwen2.5-VL 8B)显示:
·
不做 off-policy 校正(重要性加权)→ 训到一半奖励崩盘(~Step 318 损失尖峰);
·
加重要性加权 → 平稳,KL 在 0.001 附近偶有尖峰;
·
真·On-Policy(逐比特一致)→
KL 全程平 0,不需要任何 off-policy 校正也能平稳训练。
那条"平 0"的蓝线不是 bug——它就是一条直线。
07 · 总评
这篇文章的分量在哪
💡 我的总评
这是一篇
"祛魅"式的系统好文。它的价值不在于发明了什么新算法,而在于
把一个被全行业含糊带过的现象的根因彻底讲清楚——并且推翻了流传最广的错误解释。作者 Horace He 是 PyTorch 核心开发者,从浮点原理 → 算子归约策略 → 推理引擎调度,层层下钻,逻辑极其干净。
对你做训练框架(/projects/trainer)尤其相关:
"训练/采样数值不一致会把 on-policy RL 悄悄变成 off-policy" 是个容易被忽视、却会导致训练崩盘的真坑。这篇给了根治思路。文章结尾那句"
我们拒绝这种'破罐破摔'(defeatism)"——别再用调大 atol/rtol 糊弄过去——是很好的工程价值观。
🔗 与另一篇的呼应
TM 的《在线策略蒸馏》明确引用了本文:要做"真正 on-policy"的蒸馏/RL,
确定性推理是前提。两篇构成组合拳:
本篇打地基(让数值可复现),蒸馏篇盖楼(在地基上高效后训练)。一起读,能完整看到 TM "把后训练做扎实"的技术路线。