复现性#
创建日期: 2018年9月11日 | 最后更新日期: 2025年10月3日
在不同的 PyTorch 版本、提交或平台之间,完全可复现的结果无法保证。此外,即使使用相同的种子,CPU 和 GPU 执行之间的结果也可能不复现。
然而,您可以采取一些措施来限制特定平台、设备和 PyTorch 版本中非确定性行为的来源。首先,您可以控制可能导致应用程序多次执行行为不同的随机性来源。其次,您可以配置 PyTorch,使其避免对某些操作使用非确定性算法,从而在给定相同输入的情况下,多次调用这些操作会产生相同的结果。
警告
确定性操作通常比非确定性操作慢,因此单个运行的性能可能会降低。但是,确定性可以通过促进实验、调试和回归测试来节省开发时间。
控制随机性来源#
PyTorch 随机数生成器#
您可以使用 torch.manual_seed() 来为所有设备(CPU 和 CUDA)的 RNG 设置种子。
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 Random Generator 对象,而不是全局 RNG(https://numpy.com.cn/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() 的文档,了解受影响操作的完整列表。如果某个操作未按文档正确工作,或者您需要一个没有确定性实现的操作的确定性实现,请提交一个 issue:pytorch/pytorch#issues
例如,运行 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 卷积基准测试(如上所述)可以确保 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,
)