模型推理阶段经常被默认是确定性的:没有反向传播,也没有参数更新,输入相同输出就应该相同。但实际部署时,同一份权重和同一条数据,连续请求两次却可能得到不同的结果。这个现象背后并不是玄学,而是随机种子未固定、底层算子选择不确定以及生成式采样策略在起作用。要把推理结果稳定下来,通常需要从训练模式切换、随机数种子和框架的确定性算法配置三个方向同时下手。

随机种子固定不彻底,推理依然会漂移
深度学习框架的随机数并不只有一个来源。Python标准库的random、NumPy的随机数生成器、PyTorch的CPU与CUDA生成器、TensorFlow的图级与操作级种子,彼此相互独立。只调用一次torch.manual_seed(42),并不能约束NumPy产生的随机数,也不会影响CUDA卷积算法在运行时对候选算法的选择。推理阶段如果模型内部包含随机丢弃层、随机深度或变分自编码器中的重参数化采样,任意一个随机源没有固定,输出就可能发生抖动。
一个常见的误区是认为推理时model.eval()已经关闭了dropout,所以不需要再管随机种子。实际上eval()确实会让dropout和BatchNorm进入推理模式,但很多模型结构是在forward内部直接调用torch.randn或torch.rand生成噪声,这类代码不会因为eval模式自动变成确定性的。扩散模型、GAN生成器以及推荐系统中的随机负采样都可能在推理阶段继续消耗随机数。因此,无论模型是否训练完成,只要推理代码路径中存在随机操作,就必须固定随机种子。
下面这段配置可以作为所有推理脚本的入口模板,覆盖主流的随机数来源:
import random
import numpy as np
import torch
def set_seed(seed: int = 42):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
# 让CUDA卷积等算子选择确定性算法
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
# 新版本PyTorch可开启全局确定性算法,但要注意算子兼容性
# torch.use_deterministic_algorithms(True)
set_seed(42)
其中torch.backends.cudnn.benchmark = False非常关键。开启benchmark时,cuDNN会在首次运行时自动测试多个卷积算法并选择当前硬件上最快的一个,这个选择过程带有随机性,也可能因为输入尺寸变化而改变,导致同一模型在不同运行中采用不同卷积实现。关闭benchmark之后,cuDNN会使用默认的确定性算法,虽然可能牺牲少量速度,但结果可复现。
PyTorch与TensorFlow的确定性算子配置
固定随机种子只解决了随机数生成层面的问题,GPU底层算子实现仍然可能引入非确定性。以PyTorch为例,常见的torch.bmm、torch.sum等归约操作在并行计算时会按照不同的线程块顺序累加浮点数,由于浮点加法不满足结合律,累加顺序不同会产生极小误差。当模型层数较深时,误差会被逐层放大,最终导致输出在最后几位甚至更靠前的位置发生差异。
PyTorch提供了torch.use_deterministic_algorithms(True)来强制要求所有支持的算子使用确定性实现。执行后,如果某个算子无法保证确定性,框架会直接抛出错误,而不是默默运行非确定性版本。对于卷积操作,还需要配合torch.backends.cudnn.deterministic = True。此外,CUDA的cuBLAS工作空间也需要通过环境变量CUBLAS_WORKSPACE_CONFIG指定,否则某些算法会因工作空间不足而回退到非确定性路径。启动脚本前可以执行:
export CUBLAS_WORKSPACE_CONFIG=:4096:8
TensorFlow用户也有对应方案。启用tf.config.experimental.enable_op_determinism()可以在会话级别约束算子行为,同时通过环境变量TF_DETERMINISTIC_OPS=1和TF_CUDNN_DETERMINISTIC=1关闭非确定性GPU实现。注意TensorFlow的确定性支持和算子覆盖范围在不同版本中存在差异,启用前建议先在目标模型上做一次全量测试,确认没有触发不支持的算子错误。
性能损失是绕不开的话题。开启确定性算法通常会禁用一些经过高度优化的核函数,例如某些Winograd卷积或原子加归约。实际测试中,ResNet类模型的推理延迟可能增加5%到15%,极端情况下可能更高。如果业务对实时性要求极高,可以只在需要对比实验或生成正式评测结果时开启确定性配置,日常服务使用默认配置并接受微小波动。
生成式推理的采样随机性如何控制
如果是大语言模型或扩散模型,前向计算本身可能已经通过上述配置稳定下来,但输出依然每次不同,那问题大概率出在生成策略上。Hugging Face的generate函数在do_sample=True时会按照temperature、top_k、top_p等参数对logits进行随机采样,采样过程需要消耗随机数。即使全局种子固定了,如果生成代码使用不同的Generator对象,采样序列仍会不同。
正确做法是为生成函数单独传入一个固定的torch.Generator,而不是依赖全局状态。因为多线程或多请求场景下,全局随机数生成器的状态可能被其他调用消耗,导致同一个请求在不同时刻拿到的随机数序列不一样。下面这段示例演示了如何固定一个生成器:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "gpt2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
model.eval()
prompt = "The future of AI is"
inputs = tokenizer(prompt, return_tensors="pt")
# 创建独立生成器并固定种子,确保采样序列可复现
generator = torch.Generator(device="cpu").manual_seed(2024)
outputs = model.generate(
inputs["input_ids"],
max_new_tokens=20,
do_sample=True,
temperature=0.8,
top_k=50,
top_p=0.95,
generator=generator,
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
如果设置do_sample=False,生成过程变为贪心解码或beam search,理论上不依赖随机采样,结果通常是确定的。但在某些框架中,beam search内部的排序操作如果遇到相同分数,选择结果可能受到并行实现影响,因此跨设备复现时仍建议固定种子并保持相同的批量大小和序列长度。温度参数只影响采样分布的形状,本身不会引入随机性,真正消耗随机数的是采样动作。
扩散模型推理同样需要关注随机性。DDPM、DDIM等采样器在每一步去噪时会加入高斯噪声,如果噪声种子不固定,生成的图像就会不同。此时应当把随机种子传给采样循环中的噪声生成器,同时确保数据加载和预处理阶段没有使用未固定的随机增强。
可复现性排查清单与跨设备一致性边界
排查推理结果不可复现时,建议按照从外到内的顺序逐步定位。首先确认模型处于eval()状态,dropout和BatchNorm的统计量不会在推理中更新;然后检查数据预处理是否存在随机裁剪、随机翻转、随机填充等操作,如果有,需要固定数据管线的种子。接着固定Python、NumPy、PyTorch或TensorFlow的全局种子,并关闭cuDNN benchmark。之后开启确定性算法开关,观察是否有算子抛出异常。最后对生成式模型单独设置Generator或采样种子。通常情况下,走完这几步后同一台机器上的推理结果可以做到逐位一致。
跨设备的一致性是另一个需要提前明确的目标。CPU和GPU由于浮点实现不同,即使开启所有确定性配置,推理结果也可能在极小误差范围内不同。NVIDIA不同架构的GPU之间、不同版本的CUDA和cuDNN之间,同样可能存在微小差异。如果业务要求跨硬件逐位一致,单纯靠固定随机种子无法解决,需要引入量化推理或使用更高精度的统一计算路径,例如强制使用CPU计算或采用支持确定性跨平台的推理引擎。
同时要认识到,并不是所有场景都需要逐位一致。线上模型的版本对比、算法离线评测、A/B测试中的消融实验通常要求同一环境内可复现即可;而金融风控、医疗影像、自动驾驶等对结果可审计性要求高的场景,则应当把确定性配置作为发布前检查项,写入推理服务的启动脚本中。
最后给出一个精简的检查表:
- 模型已调用
eval()或training=False - 数据预处理中的随机增强已固定种子
- Python、NumPy、框架全局随机种子均已设置
- 已关闭
cudnn.benchmark并开启cudnn.deterministic - 已通过环境变量配置cuBLAS工作空间
- 生成式推理使用独立的
Generator或采样种子 - 相同输入、相同批量大小、相同设备上验证两次结果一致
把这套配置固化到推理项目的公共初始化模块中,后续新增模型或切换设备时都可以复用,能够大幅减少因随机性导致的线上问题。