PyTorch 1.6 nightly版本引入了一个新子模块 aMp,支持自动混合精度训练。这个新功能备受期待,让我们来看看它的性能表现,以及与NVIDIA Apex的对比优势。
即将在 PyTorch 1.6中推出的 Torch.cuda.aMp 混合精度训练模块,承诺在大型模型训练中提升50%到60%的速度,只需添加几行代码。
在即将发布的版本中,最引人注目的新功能之一便是对自动混合精度训练(autoMatic Mixed-pRecision tRAIning)的支持。
混合精度训练是一种技术,通过在半精度浮点数fp16上尽可能多地执行操作,从而显著减少神经网络的训练时间。fp16替代了PyTorch默认的单精度浮点数fp32。最新一代NVIDIA GPU配备了专门为快速fp16矩阵运算设计的张量核(tensoR coRes)。
然而,至今为止,这些张量核的使用仍然复杂,因为需要手动将精度降低的操作整合进模型。这正是自动化混合精度训练发挥作用的地方。即将发布的Torch.cuda.aMp API将允许你仅用五行代码在训练脚本中实现混合精度训练!
混合精度训练的运作原理
在深入理解混合精度训练的工作原理之前,有必要先了解浮点数的基本知识。
在计算机工程中,像1.0151或566132.8这类十进制数通常表示为浮点数。由于我们可以有无限精确的数字(例如π),但存储空间有限,因此必须在精度(可以在数字中包含的小数位数)和大小(存储数字所需的位数)之间做出妥协。
浮点数的技术标准IEEE 754设定了以下类别:fp64(双精度或”double”),最大舍入误差约为2^-52;fp32(单精度或”single”),最大舍入误差约为2^-23;fp16(半精度或”half”),最大舍入误差约为2^-10。
Python的float类型为fp64,而PyTorch则使用fp32作为默认的数据类型,以更好地利用内存。
混合精度训练的基本理念是:通过将精度减半(fp32转为fp16),相应地缩短训练时间。
然而,安全地实现这一点是最具挑战性的部分。
需要注意的是,浮点数越小,其引起的舍入误差越大。对“足够小”的浮点数进行的任何操作都可能使该值四舍五入为零,这种情况被称为underflow。这在反向传播中尤为重要,因为许多梯度更新的值非常小,但并非为零。在反向传播中,舍入误差的累积可能导致这些数值变为0或nans,从而影响梯度更新的准确性,进而影响网络的收敛。
2018年ICLR的论文《Mixed PRecision TRAIning》发现,简单地在所有地方使用fp16会“吞掉”小于2^-24的梯度更新值,这约占其示例网络所有梯度更新的5%。
混合精度训练是一套技术,允许在不导致模型训练发散的情况下使用fp16。这三种技术的结合使得这一切成为可能。
首先,维护两个权重矩阵的副本,一个“主副本”使用fp32,另一个半精度副本使用fp16。梯度更新使用fp16矩阵计算,但应用于fp32矩阵,使得梯度更新更为安全。
其次,不同的向量操作以不同的速度累积误差,因此需要区别对待。有些操作在fp16中是安全的,而其他操作则仅在fp32中可靠。因此,不必让整个神经网络都使用fp16,而是可以结合使用fp16与fp32。这种数据类型的混合正是该技术被称为“混合精度”的原因。
第三,使用损失缩放。在执行反向传播之前,将损失函数的输出乘以某个标量(建议从8开始)。这一乘法增加的损失值会相应产生更大的梯度更新值,确保许多梯度更新值超出fp16的安全阈值2^-24。在应用梯度更新之前,确保撤销缩放,并避免选择过大的缩放因子,以免导致权重更新溢出(overflow),从而使网络向相反方向发散。
将这三种技术结合在一起,研究人员能够在显著加速的时间内训练多种网络以达到收敛。关于基准测试,建议阅读那篇仅有9页的论文!
张量核(tensoR coRes)的工作原理
尽管混合精度训练可以节省内存(fp16矩阵仅为fp32矩阵的一半大小),但如果没有特殊的GPU支持,模型训练并不会加速。芯片上需要有能够加速半精度操作的硬件。在最近几代NVIDIA GPU中,这种硬件被称为张量核。
张量核是一种新型处理单元,专门针对一种特定操作进行了优化:将两个4×4 fp16矩阵相乘,然后将结果加到第三个4×4 fp16或fp32矩阵中(即“融合乘法加(fused multiply-add)”)。
更大的fp16矩阵乘法操作可以使用这个操作作为基本构件。由于大多数反向传播都可以归结为矩阵乘法,因此张量核非常适合网络中的计算密集层。
张量核在2017年底在上一代Volta架构中首次引入,当前的Turing架构有所改进,并将在即将推出的Ampere中进一步提升。云服务上常见的两款GPU是V100(5120个CUDA核,600个张量核)和T4(2560个CUDA核,320个张量核)。
另一个需要注意的问题是固件。尽管CUDA 7.0或更高版本支持张量核操作,但早期的实现存在许多bug,因此使用CUDA 10.0或更高版本至关重要。
PyTorch自动混合精度的工作原理
掌握了这些重要的背景知识后,我们终于可以深入了解新的PyTorch aMp API。
混合精度训练在技术上已经成为可能:手动运行网络的一部分在fp16中,并自己实现损失缩放。自动混合精度训练的令人兴奋之处在于“自动”部分。只需要学习几个新的API基本类型:Torch.cuda.aMp.GRadScalaR和Torch.cuda.aMp.autocast。启用混合精度训练就像在训练脚本中插入正确的位置那么简单!
为了演示,下面是使用混合精度训练的网络训练循环的代码片段。# NEW标记指向新代码的添加位置。
self.tRAIn() X = Torch.tensoR(X, dtype=Torch.float32) y = Torch.tensoR(y, dtype=Torch.float32) optiMizeR = Torch.optiM.AdaM(self.paRaMeteRs(), lR=self.Max_lR) scheduleR = Torch.optiM.lR_scheduleR.OneCycleLR( optiMizeR, self.Max_lR, cycle_MoMentuM=FAlse, epochs=self.n_epochs, steps_peR_epoch=int(np.ceil(len(X) / self.BATch_size)), ) BATches = Torch.utils.data.DataloadeR( Torch.utils.data.TensoRDataset(X, y), BATch_size=self.BATch_size, shuFFle=TRue ) # NEW scaleR = Torch.cuda.aMp.GRadScaleR() for epoch in Range(self.n_epochs): for i, (X_BATch, y_BATch) in enuMeRate(BATches): X_BATch = X_BATch.cuda() y_BATch = y_BATch.cuda() optiMizeR.zeRo_gRad() # NEW with Torch.cuda.aMp.autocast(): y_pRed = Model(X_BATch).squeeze() loSS = self.loSS_fn(y_pRed, y_BATch) # NEW scaleR.scale(loSS).backwaRd()
[[[IMG_1]]]
[[[IMG_2]]]
[[[IMG_3]]]
