只设 torch.manual_seed 不够,因为还需控制 CUDA、NumPy、Python random 及 DataLoader 多进程的随机源;必须配合 torch.cuda.manual_seed_all、numpy.random.seed、random.seed 和 worker_init_fn,并关闭 cuDNN 非确定性优化。

为什么只设 torch.manual_seed 不够?
只调用 torch.manual_seed(42) 只能固定 PyTorch CPU 张量的生成行为,但实际训练中常遇到结果仍不可复现——因为还有多个独立的随机源没被控制。比如 NumPy、Python 内置 random、CUDA 随机数生成器(GPU 训练时)、甚至数据加载器里的多进程采样。
常见错误现象:torch.manual_seed(42) 后模型初始化看起来一致,但 DataLoader shuffle 结果每次不同,或 GPU 上 dropout/outcome 波动明显。
- 必须同时设置
torch.cuda.manual_seed_all(42)(即使只用单卡,也要用manual_seed_all) -
numpy.random.seed(42)和random.seed(42)不能漏 - 若用了
num_workers > 0的DataLoader,需额外设worker_init_fn避免子进程随机状态漂移
如何正确配置 DataLoader 的随机性?
当 DataLoader 开启多进程(num_workers > 0),每个 worker 会继承主进程的随机种子,但 Python 的 random 和 NumPy 的随机状态在 fork 后不会自动重置,导致每个 worker 用的是“同一份初始状态”的副本,shuffle 和采样依然不可控。
解决方案是显式为每个 worker 初始化独立且确定的随机状态:
立即学习“Python免费学习笔记(深入)”;
def worker_init_fn(worker_id):
np.random.seed(42 + worker_id)
random.seed(42 + worker_id)
然后在创建 DataLoader 时传入:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
dataloader = DataLoader(dataset, num_workers=4, worker_init_fn=worker_init_fn)
- 不要用
torch.seed()替代——它不作用于 worker 子进程 -
worker_id从 0 开始,加偏移是为了让各 worker 状态互不干扰 - 如果
num_workers=0,这个函数不执行,可省略,但建议统一加上便于迁移
训练前必须关闭哪些非确定性操作?
即使种子全设对,某些底层 CUDA 操作默认启用非确定性算法(如 cuDNN 的卷积优化),会导致相同输入输出略有差异。
强制使用确定性行为需在训练开始前设置:
torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False
-
deterministic = True会让 cuDNN 选择确定性算法(可能略慢) -
benchmark = False是必须配套的——否则 cuDNN 会先 benchmark 多种实现,选最快的,而该过程本身非确定 - 这两个设置只影响 CUDA 后端,CPU 训练无需关心
完整可复现实验的种子设置模板
把所有关键点打包成一个函数,每次训练前调用一次即可:
def set_seed(seed=42):
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
注意:这个函数必须在导入 PyTorch 之后、创建任何张量或模型之前调用;如果用了分布式训练(torch.distributed),还需额外调用 torch.distributed.init_process_group 前设好种子。
最容易被忽略的一点:如果你在 Jupyter 中反复运行 cell,而没重启 kernel,torch.cuda 的状态可能残留,导致第二次运行时 seed 设置失效——此时应显式加 torch.cuda.empty_cache() 或直接重启 kernel。

















