重现性

完全可重复的结果无法在不同的PyTorch版本、单独的提交或不同平台上得到保证。此外,即使使用相同的随机数种子,在CPU和GPU执行之间,结果也可能不可重复。

然而,你可以采取一些措施来限制特定平台、设备和PyTorch版本中的非确定性行为来源。首先,你可以控制可能导致应用程序多次执行结果不同的随机因素。其次,你可以配置PyTorch以避免在某些操作中使用非确定性算法,这样,在给定相同输入的情况下,对这些操作的多次调用将产生一致的结果。

警告

确定性操作通常比非确定性操作慢,因此你的模型的单次运行性能可能降低。然而,确定性可以在开发过程中通过促进实验、调试和回归测试来节省时间。

控制随机性来源

PyTorch 随机数生成器

你可以使用torch.manual_seed()为所有设备(包括CPU和CUDA)设置随机数生成器的种子:

import torch
torch.manual_seed(0)

一些 PyTorch 操作会在内部使用随机数,例如 torch.svd_lowrank()。因此,连续多次调用它并传入相同的输入参数可能会得到不同的结果。然而,只要在应用程序开始时将torch.manual_seed() 设置为一个常量,并且消除了所有其他非确定性的来源,在相同环境中运行该应用程序每次都会生成相同的随机数序列。

通过在连续调用之间将torch.manual_seed() 设置为相同的值,也可以从使用随机数的操作中获得相同的结果。

Python

对于自定义操作符,你可能还需要设置 Python 的随机种子。

import random
random.seed(0)

其他库里的随机数生成器

如果你或你使用的任何库依赖于 NumPy,可以使用以下方法来设置全局 NumPy 随机数生成器 (RNG) 的种子:

import numpy as np
np.random.seed(0)

然而,某些应用程序和库可能会使用 NumPy 的随机生成器对象(而不是全局随机数生成器:https://numpy.org/doc/stable/reference/random/generator.html),这些生成器也需要一致地进行种子初始化。

如果你使用了其他包含随机数生成器的库,请参考这些库的文档,了解如何为它们设置一致的种子。

CUDA卷积基准测试

cuDNN库被CUDA卷积操作使用,可能在应用程序多次执行时导致非确定性结果。当一个cuDNN卷积调用并传入一组新的大小参数时,可选功能可以运行多个卷积算法,并对其进行基准测试以找到最快的算法。然后,在整个过程中,对于相应的大小参数集将始终使用该最快算法。但由于基准测试噪声和不同的硬件环境,即使在同一台机器上,后续的运行中可能会选择不同的算法。

torch.backends.cudnn.benchmark = False 用于禁用基准测试功能,这会使 cuDNN 确定性地选择一个算法,但可能会影响性能。

然而,如果你不需要应用程序在多次执行之间的重复性,那么可以尝试使用torch.backends.cudnn.benchmark = True 来启用基准测试功能,这可能会提高性能。

请注意,此设置与下方讨论的 torch.backends.cudnn.deterministic 设置不同。

避免非确定性算法

torch.use_deterministic_algorithms() 允许你配置 PyTorch 使用确定性算法而不是非确定性算法,并在遇到没有确定性替代方案的非确定性操作时抛出错误。

请参阅 torch.use_deterministic_algorithms() 的文档,获取受影响操作的完整列表。如果某个操作的行为不符合文档说明,或者你需要一个没有确定性实现的操作的确定性版本,请提交一个问题:https://github.com/pytorch/pytorch/issues?q=label:%22module:%20determinism%22

例如,运行torch.Tensor.index_add_()的非确定性CUDA实现会抛出错误:

>>> import torch
>>> torch.use_deterministic_algorithms(True)
>>> torch.randn(2, 2).cuda().index_add_(0, torch.tensor([0, 1]), torch.randn(2, 2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: index_add_cuda_ does not have a deterministic implementation, but you set
'torch.use_deterministic_algorithms(True)'. ...

当调用torch.bmm() 且参数为稀疏-稠密 CUDA 张量时,默认情况下它通常使用非确定性算法。但是,如果启用了确定性标志,则会使用其替代的确定性实现:

>>> import torch
>>> torch.use_deterministic_algorithms(True)
>>> torch.bmm(torch.randn(2, 2, 2).to_sparse().cuda(), torch.randn(2, 2, 2).cuda())
tensor([[[ 1.1900, -2.3409],
         [ 0.4796,  0.8003]],
        [[ 0.1509,  1.8027],
         [ 0.0333, -1.1444]]], device='cuda:0')

此外,如果你使用CUDA张量且CUDA版本为10.2或更高,请根据CUDA文档设置环境变量 CUBLAS_WORKSPACE_CONFIGhttps://docs.nvidia.com/cuda/cublas/index.html#results-reproducibility

CUDA卷积一致性

虽然禁用 CUDA 卷积基准测试(如上所述)确保每次运行应用程序时 CUDA 选择相同的算法,但该算法本身可能是非确定性的。除非设置了 torch.use_deterministic_algorithms(True) 或者 torch.backends.cudnn.deterministic = True,否则这种情况会持续存在。后者设置仅控制此行为,而不同于torch.use_deterministic_algorithms(),它会使其他 PyTorch 操作也表现出确定性行为。

CUDA RNN 和 LSTM

在某些版本的 CUDA 中,RNN 和 LSTM 网络可能会出现非确定性行为。详情和解决方法请参见 torch.nn.RNN()torch.nn.LSTM()

填充未初始化的内存

torch.empty()torch.Tensor.resize_() 这样的操作可能会返回内存未初始化的张量,这些张量包含未定义值。如果需要确定性,则将此类张量用作其他操作输入是无效的,因为输出将是不确定的。但实际上没有任何机制阻止运行这种无效代码。因此出于安全考虑,默认情况下 torch.utils.deterministic.fill_uninitialized_memory 设置为 True,如果设置了torch.use_deterministic_algorithms(True),则会用已知值填充未初始化的内存。这将防止这种不确定行为的发生。

然而,填充未初始化的内存会降低性能。因此,如果您的程序有效并且不会将未初始化的内存用作操作的输入,则可以关闭此设置以提高性能。

DataLoader

DataLoader 将根据多进程数据加载中的随机性算法重新初始化工作进程。使用worker_init_fn()generator来确保可重复性:

def seed_worker(worker_id):
    worker_seed = torch.initial_seed() % 2**32
    numpy.random.seed(worker_seed)
    random.seed(worker_seed)

g = torch.Generator()
g.manual_seed(0)

DataLoader(
    train_dataset,
    batch_size=batch_size,
    num_workers=num_workers,
    worker_init_fn=seed_worker,
    generator=g,
)
本页目录