数值精度

在现代计算机中,浮点数采用IEEE 754标准进行表示。关于浮点运算和IEEE 754标准的详细信息,请参阅浮点运算。特别需要注意的是,浮点运算提供有限精度(单精度约为7位十进制数字,双精度约为16位十进制数字),并且浮点加法和乘法不具有结合性,因此操作顺序会影响结果。由于这一点,对于数学上相同的浮点计算,PyTorch不能保证产生比特级相同的结果。同样,在不同的PyTorch版本、单独的提交或不同平台上,也不能保证比特级相同的结果。特别是,即使控制了随机性的来源,CPU和GPU对位相同输入的结果也可能不同。

批处理计算或切片计算

PyTorch 中的许多操作支持批量计算,即对输入批次中的每个元素执行相同的操作。例如,torch.mm()torch.bmm() 就是这样的操作。虽然可以通过在批次元素上进行循环并分别对每个批量元素应用必要的数学运算来实现批量计算,但从效率考虑我们不会这样做,而是通常会对整个批次执行一次计算。在这种情况下,我们调用的数学库和 PyTorch 内部操作的实现与非批量计算相比可能会产生略微不同的结果。特别是,令 AB 为适合批量矩阵乘法的三维张量,则 (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

非定值

当输入值为非有限值(如 infNaN)时,torch.linalg 使用的外部库(后端)不对行为提供任何保证。因此,PyTorch 也不做此类保证。操作可能会返回包含非有限值的张量,抛出异常,甚至导致程序崩溃。

建议在调用这些函数之前,先使用torch.isfinite()来检测可能出现的情况。

线性代数中的极端值

与其它 PyTorch 函数相比,torch.linalg 中的函数拥有更多极值

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

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

将计算在 float64 精度下运行(如 NumPy 默认设置)通常有所帮助,但并不能解决所有问题。通过torch.linalg.svdvals() 分析输入的频谱或通过 torch.linalg.cond() 计算其条件数,可能有助于检测这些问题。

TensorFloat-32 (TF32) 在 Nvidia Ampere 及后续设备上

在 Ampere(及以后)的 Nvidia GPU 上,PyTorch 可以使用 TensorFloat32 (TF32) 来加速数学密集型操作,特别是矩阵乘法和卷积。当使用 TF32 张量核心执行操作时,只会读取输入尾数的前 10 位,这可能会降低精度并产生意外结果(例如,将一个矩阵与单位矩阵相乘可能得到的结果不同于输入)。默认情况下,TF32 张量核心对于矩阵乘法是禁用的,而对于卷积则是启用的。尽管大多数神经网络工作负载在使用 TF32 时具有与 fp32 相同的收敛行为,但如果您的网络不需要完整的 float32 精度,则我们建议通过设置 torch.backends.cuda.matmul.allow_tf32 = True 启用 TF32 张量核心进行矩阵乘法。如果您需要在矩阵乘法和卷积中都使用完整的 float32 精度,可以通过设置 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

FP16和BF16在缩放点积注意力(SDPA)中的低精度计算

当使用FP16/BF16输入时,一个简单的SDPA数学后端可能会因为低精度中间缓冲区的使用而积累显著的数值误差。为了缓解这个问题,默认行为是将FP16/BF16输入上转换为FP32进行计算,并在完成后将其下转换回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 来更改默认行为。这些环境变量的具体作用如下:

前向传播

向后的

环境变量未设定

原始内容

轮流

环境设为1

轮流

轮流

环境变量设为0

原始内容

原始内容

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

  • torch.addbmm

  • torch.addmm

  • torch.baddbmm

  • torch.bmm

  • torch.mm

  • torch.nn.GRUCell

  • LSTMCell (torch.nn.LSTMCell)

  • torch.nn.Linear

  • torch.sparse.addmm

  • 以下 torch._C._ConvBackend 的实现:
    • 慢Nd

    • 转置后的慢速Nd

    • 慢性和扩张型

    • 慢速膨胀转置

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

  • torch.nn.Conv[Transpose]Nd

  • 以下 torch._C._ConvBackend 的实现:
    • Miopen ConvBackend

    • MiopenDepthwise ConvBackend

    • MiopenTranspose (ConvBackend)

本页目录