数值精度¶
在现代计算机中,浮点数使用 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()
来检测这种情况。
linalg 中的极值¶
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 张量核被启用,尽管大多数神经网络工作负载在使用 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.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