数值精度¶
在现代计算机中,浮点数是使用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类型
a.norm() # 生成tensor(inf)
a.double().norm() # 生成tensor(1.4142e+20, dtype=torch.float64),可以在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)¶
在安培(及更高版本)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_reduction 和 allow_bf16_reduced_precision_reduction
在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
torch.nn.LSTMCell
torch.nn.线性
torch.sparse.addmm
以下 torch._C._ConvBackend 实现:
slowNd
slowNd_转置
slowNd_dilated
slowNd_dilated_transposed
以下是可能使用MIOpen的操作列表:
torch.nn.Conv[Transpose]Nd
以下是 torch._C._ConvBackend 的实现:
ConvBackend::Miopen
ConvBackend::Miopen深度卷积
ConvBackend::MiopenTranspose