文章

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 Inferencethinkingmachines.ai
  2. vLLM Documentation, Reproducibilitydocs.vllm.ai
  3. NVIDIA, Floating Point and IEEE 754docs.nvidia.com
  4. Zhang et al., Deterministic Inference across Tensor Parallel Sizes That Eliminates Training-Inference Mismatcharxiv.org
  5. Gond et al., LLM-42: Enabling Determinism in LLM Inference with Verified Speculationarxiv.org

常见问题

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