Shortcuts

可重复性

在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随机生成器对象,而不是全局RNG (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

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

>>> 导入 torch
>>> torch.use_deterministic_algorithms(True)
>>> torch.randn(2, 2).cuda().index_add_(0, torch.tensor([0, 1]), torch.randn(2, 2))
回溯 (最近一次调用最后):
文件 "", 第 1 行, 在  中
RuntimeError: index_add_cuda_ 没有确定性的实现, 但你设置了
'torch.use_deterministic_algorithms(True)'. ...

当使用稀疏-稠密 CUDA 张量调用 torch.bmm() 时,通常会使用非确定性算法,但当确定性标志打开时,将使用其替代的确定性实现:

>>> 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 将根据 多进程数据加载中的随机性 算法重新设置工作进程的种子。 使用 worker_init_fn()生成器 来保持可重复性:

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,
)
优云智算