可復現性¶
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() 設定為相同的值,也可以從使用隨機數的操作中獲得相同的結果。
其他庫中的隨機數生成器¶
如果您或您使用的任何庫依賴於 NumPy,您可以使用以下方法為全域性 NumPy RNG 設定種子
import numpy as np
np.random.seed(0)
然而,一些應用程式和庫可能使用 NumPy Random Generator 物件,而不是全域性 RNG (https://numpy.org/doc/stable/reference/random/generator.html),這些也需要一致地設定種子。
如果您正在使用任何其他使用隨機數生成器的庫,請參閱這些庫的文件以瞭解如何為它們設定一致的種子。
CUDA 卷積基準測試¶
CUDA 卷積操作使用的 cuDNN 庫可能是應用程式多次執行之間非確定性的一個來源。當使用一組新的尺寸引數呼叫 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: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)'. ...
當使用稀疏-密集 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_CONFIG:https://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 的種子。使用 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,
)