評價此頁

數值精度#

創建於: 2021年10月13日 | 最後更新於: 2025年7月16日

在現代計算機中,浮點數使用 IEEE 754 標準表示。關於浮點數運算和 IEEE 754 標準的更多細節,請參閱 浮點數運算。特別需要注意的是,浮點數精度有限(單精度浮點數約為7位十進位制數字,雙精度浮點數約為16位十進位制數字),並且浮點數的加法和乘法不滿足結合律,因此運算順序會影響結果。正因為如此,PyTorch 不保證對數學上相同的浮點數計算產生位精確相同的(bitwise identical)結果。同樣,在 PyTorch 的不同版本、不同提交或不同平臺之間,也不保證位精確相同的結果。特別是,即使輸入完全相同,並且在控制了隨機源後,CPU 和 GPU 的結果也可能不同。

批處理計算或切片計算#

PyTorch 中的許多操作都支援批處理計算,即對輸入批次中的元素執行相同的操作。例如 torch.mm()torch.bmm()。理論上,批處理計算可以實現為遍歷批次元素的迴圈,並對單個批次元素應用必要的數學運算,但為了效率,我們通常不會這樣做,而是對整個批次執行計算。我們呼叫的數學庫以及 PyTorch 內部的操作實現,在這種情況下可能會產生與非批處理計算略有不同的結果。特別是,令 AB 為適合批處理矩陣乘法的 3D 張量。那麼 (A@B)[0](批處理結果的第一個元素)不保證與 A[0]@B[0](輸入批次的第一個元素的矩陣乘積)位精確相同,儘管數學上這是相同的計算。

同樣,應用於張量切片的操作不保證產生與對完整張量執行相同操作後切片的結果位精確相同。例如,令 A 為一個二維張量。A.sum(-1)[0] 不保證與 A[:,0].sum() 位精確相等。

極端值#

當輸入包含大值,導致中間結果可能溢位所用資料型別的範圍時,最終結果也可能溢位,即使它在原始資料型別中是可表示的。例如:

import torch
a=torch.tensor([1e20, 1e20]) # fp32 type by default
a.norm() # produces tensor(inf)
a.double().norm() # produces tensor(1.4142e+20, dtype=torch.float64), representable in fp32

線性代數 (torch.linalg)#

非有限值#

torch.linalg 使用的外部庫(後端)在輸入包含 infNaN 等非有限值時,不對其行為提供任何保證。因此,PyTorch 也不提供保證。操作可能會返回一個包含非有限值的張量,丟擲異常,甚至導致段錯誤(segfault)。

可以考慮在使用這些函式之前使用 torch.isfinite() 來檢測這種情況。

線性代數中的極端值#

torch.linalg 中的函式比其他 PyTorch 函式具有更多的 極端值

求解器逆矩陣 假設輸入矩陣 A 是可逆的。如果它接近不可逆(例如,如果它有一個非常小的奇異值),那麼這些演算法可能會默默地返回錯誤的結果。這些矩陣被稱為 病態(ill-conditioned) 的。如果提供病態輸入,這些函式的結果在使用不同裝置上的相同輸入,或透過 driver 關鍵字使用不同後端時,可能會有所不同。

當輸入具有彼此接近的奇異值時,像 svdeigeigh 這樣的譜操作也可能返回錯誤的結果(並且它們的梯度可能為無窮大)。這是因為用於計算這些分解的演算法在處理這些輸入時難以收斂。

使用 float64(如 NumPy 預設所做)進行計算通常有助於緩解這些問題,但並非在所有情況下都能解決。透過 torch.linalg.svdvals() 分析輸入的譜,或透過 torch.linalg.cond() 分析其條件數,可能有助於檢測這些問題。

Nvidia Ampere(及更高版本)裝置上的 TensorFloat-32 (TF32)#

在 Ampere(及更高版本)Nvidia GPU 上,PyTorch 可以使用 TensorFloat32 (TF32) 來加速數學密集型操作,特別是矩陣乘法和卷積。當使用 TF32 張量核執行操作時,只讀取輸入尾數的首 10 位。這可能會降低精度併產生意外的結果(例如,將矩陣乘以單位矩陣可能產生與輸入不同的結果)。預設情況下,TF32 張量核在矩陣乘法中是停用的,在卷積中是啟用的,儘管大多數神經網路工作負載在使用 TF32 時具有與 fp32 相同的收斂行為。如果您不需要完整的 float32 精度,我們建議使用 torch.backends.cuda.matmul.fp32_precision = "tf32"`torch.backends.cuda.matmul.allow_tf32 = True 將被棄用)來為矩陣乘法啟用 TF32 張量核。如果您的網路需要對矩陣乘法和卷積都使用完整的 float32 精度,那麼 TF32 張量核也可以透過 torch.backends.cudnn.conv.fp32_precision = "ieee"torch.backends.cudnn.allow_tf32 = False 將被棄用)來停用卷積的 TF32 張量核。

更多資訊請參閱 TensorFloat32

FP16 和 BF16 GEMM 的低精度累加#

半精度 GEMM 操作通常會以單精度進行中間累加(縮減),以提高數值精度和對溢位的魯棒性。為了提高效能,某些 GPU 架構,尤其是較新的架構,允許中間累加結果截斷為低精度(例如,半精度)。從模型收斂的角度來看,這種變化通常是良性的,儘管它可能導致意外的結果(例如,當最終結果應為半精度可表示時出現 inf 值)。如果低精度累加存在問題,可以透過 torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = False 來關閉。

對於 BF16 GEMM 操作存在一個類似的標誌,並且預設開啟。如果 BF16 低精度累加存在問題,可以透過 torch.backends.cuda.matmul.allow_bf16_reduced_precision_reduction = False 來關閉。

更多資訊請參閱 allow_fp16_reduced_precision_reductionallow_bf16_reduced_precision_reduction

縮放點積注意力 (SDPA) 中 FP16 和 BF16 的低精度累加#

一個樸素的 SDPA 數學後端,在使用 FP16/BF16 輸入時,可能會由於使用低精度中間緩衝區而累積顯著的數值誤差。為了緩解這個問題,預設行為現在包括將 FP16/BF16 輸入提升(upcasting)到 FP32。計算在 FP32/TF32 中進行,然後將最終的 FP32 結果向下轉換(downcasted)回 FP16/BF16。這將提高 FP16/BF16 輸入的數學後端的最終輸出的數值精度,但會增加記憶體使用量,並可能導致數學後端在從 FP16/BF16 BMM 到 FP32/TF32 BMM/Matmul 的計算轉換中出現效能迴歸。

對於傾向於使用低精度累加以提高速度的場景,可以透過以下設定啟用:torch.backends.cuda.allow_fp16_bf16_reduction_math_sdp(True)

AMD Instinct MI200 裝置上的 FP16 和 BF16 GEMM 及卷積的低精度累加#

在 AMD Instinct MI200 GPU 上,FP16 和 BF16 的 V_DOT2 和 MFMA 矩陣指令會將輸入和輸出的非歸一化值(denormal values)刷(flush)為零。FP32 和 FP64 的 MFMA 矩陣指令不會將輸入和輸出的非歸一化值刷為零。受影響的指令僅被 rocBLAS (GEMM) 和 MIOpen (卷積) 核心使用;所有其他 PyTorch 操作都不會遇到此行為。所有其他支援的 AMD GPU 也不會遇到此行為。

rocBLAS 和 MIOpen 為受影響的 FP16 操作提供了備用實現。BF16 操作沒有提供備用實現;BF16 數字具有比 FP16 數字更大的動態範圍,並且不太可能遇到非歸一化值。對於 FP16 備用實現,FP16 輸入值被轉換為中間 BF16 值,然後在累積 FP32 操作後轉換回 FP16 輸出。透過這種方式,輸入和輸出型別保持不變。

使用 FP16 精度進行訓練時,某些模型在使用 FP16 非歸一化值刷為零的情況下可能會無法收斂。非歸一化值在訓練的後向傳播(backward pass)中計算梯度時更頻繁地出現。PyTorch 預設將使用 rocBLAS 和 MIOpen 的備用實現進行後向傳播。可以透過環境變數 ROCBLAS_INTERNAL_FP16_ALT_IMPL 和 MIOPEN_DEBUG_CONVOLUTION_ATTRIB_FP16_ALT_IMPL 來覆蓋預設行為。這些環境變數的行為如下:

forward

後向傳播

環境變數未設定

原始

備用

環境變數設定為 1

備用

備用

環境變數設定為 0

原始

原始

以下是 rocBLAS 可能使用的操作列表:

  • torch.addbmm

  • torch.addmm

  • torch.baddbmm

  • torch.bmm

  • torch.mm

  • torch.nn.GRUCell

  • torch.nn.LSTMCell

  • torch.nn.Linear

  • torch.sparse.addmm

  • 以下 torch._C._ConvBackend 實現:

    • slowNd

    • slowNd_transposed

    • slowNd_dilated

    • slowNd_dilated_transposed

以下是 MIOpen 可能使用的操作列表:

  • torch.nn.Conv[Transpose]Nd

  • 以下 torch._C._ConvBackend 實現:

    • ConvBackend::Miopen

    • ConvBackend::MiopenDepthwise

    • ConvBackend::MiopenTranspose