@_avichawla:这是 LLM 推理最奇怪的特性:你的 LLM 的输出取决于并非由你发起的请求。同样的提示词……

X AI KOLs Timeline 新闻

摘要

一篇技术解析,解释了为什么 LLM 在收到完全相同的请求时仍可能产生不同的输出:GPU 上浮点数的累加顺序会随批大小而变化,导致得分并列的候选词元的选择发生翻转,即使温度设为 0,生成结果也可能出现分歧。文章重点介绍了 vLLM、SGLang 和 DeepSeek 的 DeepInfer 中用于确保批次不变性的标志选项,并指出,为获得可复现的输出,需要在吞吐量上作出权衡。

这是 LLM 推理最奇特的特性: 你的 LLM 的输出取决于不属于你的请求。 同一个提示词可能会产生不同的 logits,具体取决于 GPU 上有多少其他请求与它处于同一批次。 下面来看看 GPU 内部发生了什么。 LLM 前向传播的大部分时间都花在累加求和上。 每一次矩阵乘法、RMSNorm 和注意力计算,都会将数千个浮点数归约为单个输出,因此这些加法的顺序会直接影响结果。 问题在于,浮点加法不满足结合律。 每次加法都会将结果舍入到最接近的可表示值,因此 (a + b) + c 和 a + (b + c) 可能会产生不同的低位比特。 fp16 的有效数有 11 位,因此在 1024 到 2048 之间,相邻可表示值的间隔为 1.0。 如果从左到右计算 1024 + 0.4 + 0.4,每次相加都会舍入回 1024。如果先将两个较小的值相加,0.4 + 0.4 得到 0.8,而 1024 + 0.8 会舍入为 1025。 累加顺序取决于批次大小,因为内核会选择能最大限度提高 GPU 利用率的并行化策略。 像 H100 这样的 GPU 有 132 个计算单元,而较小的批次无法为每个计算单元提供足够的工作量。 因此,内核会将每个长求和拆分成多个分块,让不同的计算单元同时对各个分块求和,然后再将这些部分结果相加。 所以,不同的批次大小会带来不同的划分方式、不同的累加顺序,以及略有不同的 logits。 通常,这种差异微不足道,除非两个 token 的得分非常接近。 在大小为 8 的批次中: - “Queens”的得分可能为 12.3417 - “New”的得分可能为 12.3415 在大小为 32 的批次中,舍入可能会使它们的得分分别变为 12.3414 和 12.3416。 如果温度为 0,argmax 会在第一种情况下选择“Queens”,在第二种情况下选择“New”。此后的每个 token 都以不同的前缀为条件,因此整个生成结果都会出现分歧。 所以,这种非确定性并非来自采样,而是因为你的请求的数值计算取决于你无法控制的并发流量。 这在温度为 0 时相当明显,但非确定性来自前向传播,而非采样器。 你可以通过让每次归约都具有批次不变性来解决这个问题,也就是说,无论同一批次中有多少行,每一行都按相同的固定顺序进行归约。 这会损失吞吐量,因为内核无法再为每种批次大小选择最优的拆分方式。它会使用一种固定策略,即使这会导致某些计算单元空闲。 如今,主要的推理引擎都提供了相应的开关: → vLLM 提供 VLLM_BATCH_INVARIANT=1,目前仍处于测试阶段。 → SGLang 提供 --enable-deterministic-inference,代价是速度降低 25–45%。 → DeepSeek 的 DeepInfer 提供 DEEPINFER_BATCH_INVARIANT=1。 如果你要运行评估、强化学习 rollout,或任何需要可复现输出的任务,请启用它,并将批次组成视为实验设置的一部分。 而在生产环境中,你的请求所在批次的组成始终在变化。采用连续批处理时,服务器会在每个解码步骤中添加和移除请求。 我写了一篇文章,解释它的工作原理,以及为什么每个主流推理引擎都使用它。 请阅读下方文章。
查看原文
查看缓存全文

缓存时间: 2026/11/01 11:51

这是 LLM 推理最奇怪的特性:

你的 LLM 输出会受到其他人的请求影响。

同一个提示词,可能因为 GPU 上同一批次中其他请求的数量不同,而产生不同的 logits。

下面来看看 GPU 内部发生了什么。

LLM 前向传播的大部分时间都花在累加求和上。

每一次矩阵乘法、RMSNorm 和注意力计算,都会将数千个浮点数归约为一个输出,因此这些加法的顺序会直接影响结果。

问题在于,浮点加法不满足结合律。

每次加法都会将结果舍入到最接近的可表示值,因此 (a + b) + c 和 a + (b + c) 的结果可能在低位比特上有所不同。

fp16 的有效数有 11 位,因此在 1024 到 2048 之间,可表示值的间隔为 1.0。

如果从左到右计算 1024 + 0.4 + 0.4,每次相加都会舍入回 1024。如果先将两个较小的值相加,0.4 + 0.4 得到 0.8,而 1024 + 0.8 会舍入为 1025。

累加顺序取决于批次大小,因为内核会选择能最大限度提高 GPU 利用率的并行策略。

像 H100 这样的 GPU 有 132 个计算单元,而小批次无法为每个计算单元提供足够的工作量。

因此,内核会将每个长求和拆分为多个块,在不同的计算单元上同时对这些块求和,然后再将这些部分结果相加。

所以,不同的批次大小会带来不同的分块方式、不同的累加顺序,以及略有差异的 logits。

通常,这种差异微不足道,除非两个 token 的分数非常接近。

在大小为 8 的批次中:

  • “Queens”的分数可能是 12.3417
  • “New”的分数可能是 12.3415

在大小为 32 的批次中,舍入可能使它们的分数分别变为 12.3414 和 12.3416。

如果 temperature 为 0,argmax 在第一种情况下会选择“Queens”,在第二种情况下会选择“New”。之后的每个 token 都以不同的前缀为条件,因此整个生成结果会走向不同的方向。

所以,这种非确定性并非来自采样,而是因为你的请求的数值计算会受到你无法控制的并发流量影响。

这一点在 Temperature 为 0 时相当明显,但非确定性来自前向传播,而非采样器。

你可以通过让每一次归约都具有批次不变性来解决这个问题,也就是说,无论同一批次中有多少行,每一行都按相同的固定顺序进行归约。

这会牺牲吞吐量,因为内核无法再为每种批次大小选择最优的拆分方式。它会使用一种固定策略,即使这会让部分计算单元闲置。

主流推理引擎如今都通过一个开关提供了这一功能:

→ vLLM 提供 VLLM_BATCH_INVARIANT=1,目前仍处于测试阶段。 → SGLang 提供 –enable-deterministic-inference,代价是速度降低 25-45%。 → DeepSeek 的 DeepInfer 提供 DEEPINFER_BATCH_INVARIANT=1。

如果你要运行评估、强化学习 rollout,或任何需要可复现输出的任务,请启用它,并将批次组成视为实验设置的一部分。

而在生产环境中,与你的请求同批处理的其他请求从来都不是固定的。采用连续批处理时,服务器会在每个解码步骤添加和移除请求。

我写了一篇文章,解释它的工作原理,以及为什么每个主流推理引擎都在使用它。

请在下方阅读。

相似文章