评价此页

数值精度#

创建时间: 2021年10月13日 | 最后更新时间: 2025年7月16日

在现代计算机中,浮点数使用 IEEE 754 标准表示。有关浮点运算和 IEEE 754 标准的更多详细信息,请参阅 浮点运算。特别需要注意的是,浮点数精度有限(单精度浮点数约 7 位十进制数字,双精度浮点数约 16 位十进制数字),并且浮点数的加法和乘法不满足结合律,因此运算顺序会影响结果。鉴于此,PyTorch 不保证在数学上相同的浮点计算会产生逐位相同的(bitwise identical)结果。同样,在 PyTorch 版本之间、单个提交(commit)之间或不同平台之间也不保证逐位相同的结果。特别是,即使输入逐位相同,并且在控制了随机源的情况下,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() 来检测这种情况。

linalg 中的极端值#

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

求解器逆矩阵 假设输入矩阵 A 是可逆的。如果它接近不可逆(例如,如果它有一个非常小的奇异值),那么这些算法可能会默默地返回错误的结果。这些矩阵被称为 病态(ill-conditioned) 的。如果提供病态输入,这些函数的结果在使用不同设备上的相同输入,或通过关键字 driver 使用不同后端时,可能会有所不同。

svdeigeigh 这样的谱操作,当它们的输入具有非常接近的奇异值时,也可能返回错误的结果(并且它们的梯度可能为无穷大)。这是因为用于计算这些分解的算法在这种输入下难以收敛。

float64 运行计算(正如 NumPy 默认所做的那样)通常有帮助,但这并不能在所有情况下解决这些问题。通过 torch.linalg.svdvals() 分析输入的谱(spectrum),或通过 torch.linalg.cond() 分析其条件数,有助于检测这些问题。

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

在 Ampere(及更高版本)Nvidia GPU 上,PyTorch 可以使用 TensorFloat32 (TF32) 来加速计算密集型操作,特别是矩阵乘法和卷积。当使用 TF32 张量核心执行操作时,仅读取输入尾数(mantissa)的前 10 位。这可能会降低精度并产生意外的结果(例如,将矩阵乘以单位矩阵可能会产生与输入不同的结果)。默认情况下,TF32 张量核心对于矩阵乘法是禁用的,对于卷积是启用的,尽管大多数神经网络在 TF32 下的收敛行为与 fp32 相同。我们建议使用 torch.backends.cuda.matmul.fp32_precision = "tf32"`torch.backends.cuda.matmul.allow_tf32 = True 将被弃用)为矩阵乘法启用 TF32 张量核心,如果您的网络不需要完整的 float32 精度。如果您的网络需要矩阵乘法和卷积的完整 float32 精度,那么 TF32 张量核心也可以通过 torch.backends.cudnn.conv.fp32_precision = "ieee"torch.backends.cudnn.allow_tf32 = False 将被弃用)来禁用卷积的 TF32 张量核心。

有关更多信息,请参阅 TensorFloat32

FP16 和 BF16 GEMM 的降低精度累积#

半精度 GEMM 操作通常在单精度下进行中间累积(reduction),以提高数值精度和鲁棒性。为了性能,某些 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 输入向上转换为 FP32。计算在 FP32/TF32 中进行,然后最终的 FP32 结果会被向下转换为 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 非正规数被冲刷为零而无法收敛。在训练的反向传播过程中,特别是在梯度计算期间,非正规值出现的频率更高。PyTorch 默认将使用 rocBLAS 和 MIOpen 的备用实现进行反向传播。可以通过环境变量 ROCBLAS_INTERNAL_FP16_ALT_IMPL 和 MIOPEN_DEBUG_CONVOLUTION_ATTRIB_FP16_ALT_IMPL 来覆盖默认行为。这些环境变量的行为如下:

forward

backward

Env unset

original

alternate

Env set to 1

alternate

alternate

Env set to 0

original

original

以下是 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