数值精度#
创建于: 2021年10月13日 | 最后更新于: 2025年7月16日
在现代计算机中,浮点数使用 IEEE 754 标准表示。关于浮点数运算和 IEEE 754 标准的更多细节,请参阅 浮点数运算。特别需要注意的是,浮点数精度有限(单精度浮点数约为7位十进制数字,双精度浮点数约为16位十进制数字),并且浮点数的加法和乘法不满足结合律,因此运算顺序会影响结果。正因为如此,PyTorch 不保证对数学上相同的浮点数计算产生位精确相同的(bitwise identical)结果。同样,在 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 也不提供保证。操作可能会返回一个包含非有限值的张量,抛出异常,甚至导致段错误(segfault)。
可以考虑在使用这些函数之前使用 torch.isfinite()
来检测这种情况。
线性代数中的极端值#
torch.linalg
中的函数比其他 PyTorch 函数具有更多的 极端值。
求解器 和 逆矩阵 假设输入矩阵 A
是可逆的。如果它接近不可逆(例如,如果它有一个非常小的奇异值),那么这些算法可能会默默地返回错误的结果。这些矩阵被称为 病态(ill-conditioned) 的。如果提供病态输入,这些函数的结果在使用不同设备上的相同输入,或通过 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 相同的收敛行为。如果您不需要完整的 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_reduction 和 allow_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