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 配置 |
| 运行环境 | 当前版本与待升级版本 |
| 重启边界 | 同进程、重启后、重新部署后 |
对每个样本记录以下指标:
- Exact Token Match:完整 Token 序列是否一致;
- First Divergence Position:首次分叉发生在哪个 Token;
- Top-K Agreement:分叉前后候选 Token 集是否稳定;
- Logit Margin:第一名与第二名的差距,识别天然脆弱样本;
- 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 成本。
参考资料
- Thinking Machines Lab, Defeating Nondeterminism in LLM Inference — thinkingmachines.ai
- vLLM Documentation, Reproducibility — docs.vllm.ai
- NVIDIA, Floating Point and IEEE 754 — docs.nvidia.com
- Zhang et al., Deterministic Inference across Tensor Parallel Sizes That Eliminates Training-Inference Mismatch — arxiv.org
- Gond et al., LLM-42: Enabling Determinism in LLM Inference with Verified Speculation — arxiv.org