数值精度#
创建于:2021 年 10 月 13 日 | 最后更新于:2026 年 1 月 29 日
在现代计算机中,浮点数使用 IEEE 754 标准表示。有关浮点算术和 IEEE 754 标准的更多详细信息,请参阅 浮点算术。特别是,请注意浮点数提供的精度有限(单精度浮点数约为 7 位十进制数字,双精度浮点数约为 16 位十进制数字),并且浮点加法和乘法不具有结合律,因此运算顺序会影响结果。由于这个原因,PyTorch 不能保证为在数学上相同的浮点运算产生逐位相同的结果。同样,也不能保证在 PyTorch 版本、单个提交或不同平台之间产生逐位相同的结果。特别是,即使在控制了随机性来源之后,CPU 和 GPU 的结果也可能不同,即使对于逐位相同的输入也是如此。
批量计算或切片计算#
PyTorch 中的许多运算支持批量计算,其中相同的运算对输入批次中的元素执行。这方面的示例是 torch.mm() 和 torch.bmm()。可以实现将批量计算作为对批量元素进行循环,并将必要的数学运算应用于单个批量元素,但出于效率原因,我们不这样做,而是通常为整个批量执行计算。我们调用的数学库以及 PyTorch 内部的运算实现,在这种情况下,与非批量计算相比,可能会产生略有不同的结果。特别是,设 A 和 B 是维度适合批量矩阵乘法的 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 使用的外部库(后端)对其输入具有非有限值(如 inf 或 NaN)时的行为不作任何保证。因此,PyTorch 也不作任何保证。这些运算可能会返回包含非有限值的张量,或者引发异常,甚至分段错误。
请考虑在调用这些函数之前使用 torch.isfinite() 来检测这种情况。
线性代数中的极值#
torch.linalg 中的函数比其他 PyTorch 函数具有更多的 极值。
求解器 和 逆矩阵 假定输入矩阵 A 是可逆的。如果它接近不可逆(例如,如果它具有非常小的奇异值),那么这些算法可能会悄无声息地返回不正确的结果。这些矩阵被称为 病态矩阵。如果提供病态输入,使用相同的输入在不同设备上或使用关键字 driver 使用不同的后端时,这些函数的结果可能会有所不同。
诸如 svd、eig 和 eigh 等谱运算,当其输入具有彼此接近的奇异值时,也可能返回不正确的结果(并且它们的梯度可能是无限的)。这是因为用于计算这些分解的算法难以收敛于这些输入。
在 float64(如 NumPy 默认情况下所做的那样)中运行计算通常有所帮助,但它并不能在所有情况下解决这些问题。通过 torch.linalg.svdvals() 分析输入的谱或通过 torch.linalg.cond() 分析其条件数可能会帮助检测这些问题。
Nvidia Ampere(及更高版本)设备上的 TensorFloat-32 (TF32)#
在 Ampere(及更高版本)Nvidia GPU 上,PyTorch 可以使用 TensorFloat32 (TF32) 来加速计算密集型运算,特别是矩阵乘法和卷积。当使用 TF32 张量核心执行运算时,仅读取输入尾数的 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 运算通常使用单精度进行中间累积(缩减),以提高数值精度和抗溢出能力。为了提高性能,某些 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_reduction 和 allow_bf16_reduced_precision_reduction
缩放点积注意力 (SDPA) 中 FP16 和 BF16 的降精度缩减#
当使用 FP16/BF16 输入时,朴素的 SDPA 数学后端可能会由于使用低精度中间缓冲区而累积大量的数值误差。为了缓解这个问题,默认行为现在是将 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 矩阵指令会将输入和输出的非正规值刷新为零。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 |
反向传播 |
|
|---|---|---|
环境变量未设置 |
原始 |
替代 |
环境变量设置为 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