文章

LLM 推理可复现性生产实战:用 Batch Invariance、固定 Seed 与配置指纹控制输出漂移

本文拆解大模型推理在温度为零时仍可能出现输出漂移的原因,给出固定种子、Batch Invariance、环境指纹、回放矩阵与分级确定性门禁的生产落地方法,帮助团队稳定评测、审计、缓存和回归测试结果。

LLM 推理可复现性生产实战:用 Batch Invariance、固定 Seed 与配置指纹控制输出漂移

为什么 temperature=0 仍然不等于”同输入、同输出”

很多团队把大模型的随机性简单归因于采样参数:把 temperature 设为 0、关闭 top_p,再固定一个 seed,似乎就应该得到完全一致的输出。

这套理解只覆盖了最表层的随机性。

temperature=0 通常意味着每一步选择概率最高的 Token,也就是贪心解码。但在真实推理服务中,请求会被动态合批,批次大小随流量变化;推理框架会根据输入 Shape、序列长度和并行度选择不同 Kernel;矩阵乘法、归一化和注意力中的浮点归约次序也可能改变。只要两个候选 Token 的 Logit 足够接近,微小的数值差异就可能改变第一次选择,之后整条生成路径都会分叉。

因此,推理可复现性不是一个采样参数问题,而是一个覆盖模型制品、解码配置、调度器、Kernel、硬件和运行环境的系统工程问题。

Thinking Machines Lab 在 2025 年的分析进一步指出:常见 LLM 前向 Kernel 本身可以是单次运行确定的,但未必具备 Batch Invariance —— 同一个样本放进不同大小或不同形状的批次时,归约策略可能变化,最终数值也会变化。线上并发量决定批次形状,而并发量对单个用户不可控,于是服务从用户视角表现为非确定性。

先区分四种”输出不一致”

生产治理前,必须先把不同来源的漂移拆开,否则很容易把所有问题都归结为 seed。

1. 采样随机性

当 temperature > 0、启用 nucleus sampling 或其他随机采样时,不同结果本来就是分布的一部分。固定 seed 可以在特定实现和固定调用顺序下控制这类随机性,但并不保证跨进程、跨版本或跨硬件复现。

2. Kernel 的运行间非确定性

部分 GPU 操作可能使用原子加法或未固定执行顺序的归约。相同输入连续运行,也可能出现最低有效位上的差异。对典型 LLM 前向过程来说,这未必是主要来源,但仍要关注自定义算子、第三方 Kernel 和特定量化实现。

3. Batch 与调度非不变性

这是在线 Serving 最容易被忽略的来源。同一请求在 Batch Size 1、8 和 32 中,矩阵乘法或 Attention 可能采用不同 Tile、Split-K 或 Split-KV 策略;Chunked Prefill、动态批处理和请求插队也会改变序列的计算切分方式。只要数值结果依赖批次或切分方式,线上流量变化就会转化为输出变化。

4. 环境与版本差异

GPU 型号、CUDA、驱动、cuBLAS、NCCL、PyTorch、vLLM、量化后端、Tensor Parallel 大小以及编译参数都可能改变计算路径。即使某个配置在单机上可复现,也不能直接推导出跨硬件、跨版本的 Bitwise Reproducibility。

NVIDIA 的浮点文档明确说明,运算顺序、线程数量、FMA 使用方式和并行归约结构都会影响最终数值。vLLM 的可复现性文档也注明:默认配置为了性能不保证结果可复现;即使启用相关设置,保证范围仍限定在相同硬件和相同 vLLM 版本内。

核心原理:Seed、确定性调度与 Batch Invariance 各解决什么

固定 Seed:控制随机数,不控制计算形状

Seed 的作用是固定 Python、NumPy、PyTorch 或推理框架内部随机数生成器的状态。它适合控制抽样,但无法保证:

  • 动态批次保持相同;
  • 每次使用相同 Kernel;
  • Tensor Parallel 的归约树保持相同;
  • 升级 CUDA 或框架后数值不变;
  • 同一请求在不同并发负载下输出一致。

所以,固定 Seed 是必要条件之一,但不是充分条件。

确定性调度:固定请求进入计算图的方式

离线评测可以关闭多进程调度、固定 Batch、固定输入排序,让每轮运行使用相同的执行计划。vLLM 当前文档给出的离线方案之一,是关闭 V1 Multiprocessing 以获得确定性调度;在线模式由于真实流量不断变化,单靠固定调度通常不可行。

Batch Invariance:让单个请求不受同批其他请求影响

Batch Invariance 的目标不是简单固定 Batch Size,而是让某个请求的数值结果不依赖:

  • 同时处理多少请求;
  • 请求位于批次中的哪个位置;
  • Prefill 被切成多少段;
  • Decode 时采用多少个 Split-KV 分片。

实现上通常需要固定归约顺序、统一 Kernel 配置,或使用专门设计的 Batch-Invariant RMSNorm、Matmul 和 Attention。代价是放弃一部分针对特定 Shape 的极致优化,因此应按业务价值启用,而不是全量强制。

工程落地:建立三档可复现性等级

将”确定性”做成统一开关往往过于昂贵。更实用的做法是建立三档服务等级。

等级适用场景核心要求
Level 0:统计一致性普通聊天、创意生成、非关键摘要质量、拒答率、长度分布和安全指标稳定,不要求逐 Token 一致
Level 1:请求级可复现回归测试、提示词调试、缓存构建、问题复盘相同制品、环境、并行度、解码参数下,重复执行得到一致 Token 序列
Level 2:严格确定性合规审计、自动化决策、RL Rollout 对齐、Bitwise 对比实验Batch-Invariant Kernel,固定硬件与软件栈,独立确定性服务池

Level 0:统计一致性

线上保留正常动态批处理和高性能 Kernel。要求同一测试集上的质量、拒答率、长度分布和安全指标稳定,但不要求逐 Token 一致。

Level 1:请求级可复现

要求在相同模型制品、相同环境、相同并行度和相同解码参数下,同一请求重复执行得到一致 Token 序列。可以通过固定 Seed、固定调度、固定 Batch Bucket 和锁定 Kernel 配置实现。

Level 2:严格确定性

要求启用 Batch-Invariant Kernel,固定硬件与软件栈,并对 TP 大小、归约顺序和 Attention 切分建立严格约束。此等级应进入独立的确定性服务池,避免普通流量改变调度形状。

配置指纹:没有环境快照,就无法解释复现失败

每次推理都应生成一个 inference_fingerprint。它不是简单记录模型名称,而是对完整执行契约做哈希。

{
  "model_sha256": "...",
  "tokenizer_sha256": "...",
  "chat_template_sha256": "...",
  "dtype": "bfloat16",
  "quantization": "none",
  "sampling": {
    "temperature": 0,
    "top_p": 1,
    "seed": 42
  },
  "runtime": {
    "vllm_version": "pinned-version",
    "pytorch_version": "pinned-version",
    "cuda_version": "pinned-version",
    "driver_version": "pinned-version"
  },
  "hardware": {
    "gpu_model": "pinned-gpu",
    "tensor_parallel_size": 1
  },
  "scheduler": {
    "batch_invariance": true,
    "chunked_prefill": false,
    "max_num_seqs": 16
  }
}

指纹必须与输出、Token 序列、Finish Reason、请求 ID 和部署版本一起落库。出现差异时,系统首先比较指纹,而不是直接怀疑模型”偶尔不听话”。

回放矩阵:不要只在空载环境重复十次

很多”可复现测试”只是在开发机上串行调用十次,这无法发现生产中的批次敏感问题。

至少应覆盖以下维度:

维度建议测试值
并发度1、8、32、峰值附近
Prompt 长度短、中、接近上下文上限
输出长度16、128、长输出
Batch Shape单一长度、混合长度
Prefill 策略整体 Prefill、分块 Prefill
并行度TP=1 与生产 TP 配置
运行环境当前版本与待升级版本
重启边界同进程、重启后、重新部署后

对每个样本记录以下指标:

  1. Exact Token Match:完整 Token 序列是否一致;
  2. First Divergence Position:首次分叉发生在哪个 Token;
  3. Top-K Agreement:分叉前后候选 Token 集是否稳定;
  4. Logit Margin:第一名与第二名的差距,识别天然脆弱样本;
  5. Semantic Agreement:仅用于 Level 0,不能替代严格 Token 对比。

一个简单的回放检查器可以按如下方式实现:

from dataclasses import dataclass

@dataclass(frozen=True)
class ReplayResult:
    token_ids: list[int]
    fingerprint: str

def first_divergence(a: list[int], b: list[int]) -> int | None:
    for index, (left, right) in enumerate(zip(a, b)):
        if left != right:
            return index
    if len(a) != len(b):
        return min(len(a), len(b))
    return None

def assert_reproducible(results: list[ReplayResult]) -> None:
    baseline = results[0]
    for current in results[1:]:
        if current.fingerprint != baseline.fingerprint:
            raise ValueError(
                "Execution fingerprint changed; results are not directly comparable"
            )
        divergence = first_divergence(baseline.token_ids, current.token_ids)
        if divergence is not None:
            raise AssertionError(
                f"Token output diverged at position {divergence}"
            )

在线架构:把确定性流量单独分池

最稳妥的架构不是让所有请求都走最严格模式,而是增加 reproducibility_tier 进行路由:

普通生成请求     ──> 高吞吐动态批处理池
回归与审计请求   ──> 固定配置可复现池
严格确定性请求   ──> Batch-Invariant 专用池

网关负责校验:

  • 是否携带固定 Seed;
  • 模型和 Tokenizer 指纹是否匹配;
  • 请求是否被路由到正确硬件池;
  • 当前部署是否允许 Chunked Prefill、Speculative Decoding 或动态 Kernel;
  • 结果是否需要保存完整 Token 序列和 Logprob 证据。

这样既能保留普通流量的吞吐,也能为评测和审计提供稳定执行环境。

适用场景

推理可复现性最有价值的场景包括:

  • 模型与提示词回归测试:避免把系统噪声误判为质量退化;
  • 离线评测与排行榜:保证不同版本在同一执行契约下比较;
  • 合规审计:能够解释某次输出由哪个制品和环境生成;
  • 结果缓存:确认缓存键不仅覆盖 Prompt,还覆盖执行指纹;
  • Agent 自动化:关键工具参数生成需要稳定复盘;
  • RL Rollout:训练和推理并行策略变化时,减少数值不一致带来的偏差。

常见误区

误区一:temperature=0 就是确定性

它只消除了采样层的主要随机性,没有消除动态批次和数值路径差异。

误区二:固定 Seed 就能跨版本复现

Seed 不会固定 CUDA Kernel、GPU 指令、量化后端或归约树。升级任何一层后都必须重新运行回放矩阵。

误区三:答案语义相同就算完全复现

对聊天业务可以接受,但对回归定位、缓存、审计和 RL 来说,Token 级差异仍然重要。应先定义服务等级,再决定比较标准。

误区四:关闭动态批处理是唯一方案

关闭动态批处理可以简化离线复现,但会显著牺牲线上吞吐。生产环境更适合专用确定性池或 Batch-Invariant Kernel。

误区五:只记录模型名称

同一模型名称可能对应不同权重、Tokenizer、Chat Template、量化配置和推理版本。没有完整指纹,复现失败几乎无法定位。

上线检查清单

  • 明确定义 Level 0、Level 1、Level 2 的验收标准;
  • 固定并记录模型、Tokenizer 与 Chat Template 哈希;
  • 固定采样参数并记录 Seed;
  • 记录 GPU、驱动、CUDA、PyTorch、推理框架和 Kernel 配置;
  • 在多并发、多 Batch Shape 和多 Prompt 长度下执行回放;
  • 统计 Exact Match、首次分叉位置和 Logit Margin;
  • 将严格确定性请求路由到独立服务池;
  • 对框架、驱动、GPU 和并行度变更设置发布门禁;
  • 输出与 inference_fingerprint 一起留存;
  • 为确定性模式单独评估吞吐、P95 延迟和单位 Token 成本。

参考资料

  1. Thinking Machines Lab, Defeating Nondeterminism in LLM Inference — thinkingmachines.ai
  2. vLLM Documentation, Reproducibility — docs.vllm.ai
  3. NVIDIA, Floating Point and IEEE 754 — docs.nvidia.com
  4. Zhang et al., Deterministic Inference across Tensor Parallel Sizes That Eliminates Training-Inference Mismatch — arxiv.org
  5. Gond et al., LLM-42: Enabling Determinism in LLM Inference with Verified Speculation — arxiv.org

常见问题

temperature 设置为 0,为什么相同提示词仍可能得到不同答案?
temperature=0 只关闭采样随机性。动态批次、请求调度、浮点归约顺序、并行度、Kernel 选择和软硬件版本变化,仍可能让接近的 Logit 发生细微变化,并在某个 Token 位置产生分叉。
固定 seed 是否足以实现可复现推理?
不够。seed 只能约束随机数生成器,不能消除 Batch 不变性缺失、不同归约顺序、不同 GPU 或推理框架版本带来的数值差异。
开启 Batch Invariance 会不会降低推理性能?
可能会。固定归约策略和 Kernel 配置会减少针对不同 Shape 的优化空间。建议将严格模式用于高价值流量,并通过压测量化吞吐与延迟代价。
是否应该追求跨 GPU 型号的 Bitwise 一致?
除非业务确实需要,否则成本通常过高。更合理的目标是先锁定同硬件、同版本内的请求级可复现,再通过回放评测保证跨环境的质量兼容性。