Skip to content

可复现性、训练稳定性和随机种子 #48

Description

@THUdsg

我在训练时发现尽管设置了相同的随机种子,但多次跑出来的结果仍然不一样。然后去问了豆包。给出的回答如下(亲测可行):

在深度学习中,要确保实验结果可重复,仅设置随机种子是不够的,还需要处理多线程、CUDA 操作等非确定性因素。以下是对代码的分析和改进建议:

1. 代码分析

你的代码已经设置了主要的随机种子,但仍存在可能导致结果不一致的因素:

def setup_seed(seed):
    torch.manual_seed(seed)  # 设置PyTorch CPU随机种子
    torch.cuda.manual_seed(seed)  # 设置当前GPU随机种子
    torch.cuda.manual_seed_all(seed)  # 设置所有GPU随机种子
    np.random.seed(seed)  # 设置NumPy随机种子
    random.seed(seed)  # 设置Python内置随机种子
    torch.backends.cudnn.deterministic = True  # CuDNN确定性模式
    torch.backends.cudnn.benchmark = False  # 关闭CuDNN自动调优

2. 可能的原因及解决方案

2.1 多线程/数据加载器问题

原因

  • DataLoader 的 num_workers > 0 时,子进程可能使用不同的随机状态
  • 某些数据增强操作可能未被完全固定

解决方案

# 在主函数中添加以下代码
g = torch.Generator()
g.manual_seed(seed)  # 固定数据加载器的随机种子

# 创建DataLoader时使用
DataLoader(..., worker_init_fn=worker_init_fn, generator=g)

# 定义worker_init_fn函数
def worker_init_fn(worker_id):
    np.random.seed(seed + worker_id)

2.2 CUDA 操作的非确定性

原因

  • 某些 CUDA 操作(如原子操作)本身具有非确定性
  • 使用了非确定性的算法(如某些卷积实现)

解决方案

# 确保所有操作使用确定性算法
torch.use_deterministic_algorithms(True)

# 如果使用了分布式训练,还需要设置:
os.environ['NCCL_P2P_DISABLE'] = '1'

2.3 环境变量设置

原因

  • 某些库或框架可能忽略种子设置

解决方案

# 在代码开头添加以下环境变量设置
os.environ['PYTHONHASHSEED'] = str(seed)
os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'  # CUDA 10.2及以上版本

3. 完整的种子设置函数

以下是一个更完善的种子设置函数:

import os
import torch
import numpy as np
import random

def setup_seed(seed):
    # 设置Python哈希种子
    os.environ['PYTHONHASHSEED'] = str(seed)
    
    # 设置CUDA环境变量
    os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'  # 对于CUDA 10.2及以上版本
    os.environ['NCCL_P2P_DISABLE'] = '1'  # 禁用NCCL点对点通信(如果使用分布式训练)
    
    # 设置Python随机种子
    random.seed(seed)
    
    # 设置NumPy随机种子
    np.random.seed(seed)
    
    # 设置PyTorch随机种子
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    
    # 设置PyTorch确定性选项
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    torch.use_deterministic_algorithms(True)

4. 使用示例

# 在程序入口处调用
setup_seed(42)

# 创建数据加载器时使用固定的生成器
g = torch.Generator()
g.manual_seed(42)

train_loader = DataLoader(
    train_dataset,
    batch_size=32,
    shuffle=True,
    num_workers=4,
    worker_init_fn=lambda x: np.random.seed(42 + x),
    generator=g
)

5. 注意事项

  1. 性能影响:确定性模式可能会降低训练速度
  2. 库兼容性:某些第三方库可能仍有非确定性操作
  3. 硬件依赖性:即使设置了所有种子,不同GPU型号或驱动版本仍可能导致细微差异

通过以上设置,你的实验结果应该会更加稳定。如果仍然存在差异,建议检查代码中是否有未被固定的随机操作,或尝试在单GPU环境下测试。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions