1.6脚本 62脚本软件

PyTorch 1.6 即将原生支持自动混合精度训练

PyTorch 1.6 nightly增加了一个子模块放大器,支持自动混合精度训练。值得期待。我们来看看性能,和Nvidia Apex相比有什么优势?

PyTorch 1.6即将发布的混合精度训练模块torch.cuda.amp实现了自己的承诺,只需增加几行新代码,大规模模型训练的速度就可以提升50-60%。PyTorch 1.6中最令人兴奋的附加功能之一是支持自动混合精度训练。混合精度训练(Hybrid precision training)是一种通过对半精度浮点数fp16执行尽可能多的运算来大大减少神经网络训练时间的技术,它取代了PyTorch默认的单精度浮点数fp32。最新一代的NVIDIA GPU配备了专门为快速fp16矩阵运算而设计的专用张量核心。然而,到目前为止,这些张量核仍然难以使用,因为它需要手动将精度降低的操作写入模型。这就是自动混合精确训练的用武之地。即将推出的torc h.cuda.amp API,只需五行代码,就能让你在训练脚本中实现混合精准训练!本文是对开发人员友好的混合精度训练的介绍。我们将:

深入研究混合精度训练技术介绍张量核: 它们是什么以及是如何工作的介绍新的 PyTorch amp APIBenchmark用amp训练的三个不同的网络讨论哪些网络原型从amp中受益最多混合精度是如何工作的

在理解混合精度训练如何工作之前,我们需要先回顾一下浮点数。在计算机工程中,十进制数,如1.0151或566132.8,传统上表示为浮点数。由于我们可以拥有无限精确的数字(想象π),但是用来存储它们的空的数目是有限的,所以我们必须在精确度(在舍入数字之前我们可以在数字中包含的小数位数)和大小(我们用来存储数字的位数)之间进行折衷。浮点数的技术标准是IEEE 754(更深入的理解,我推荐PyCon 2019 & # 34浮动是朋友:充分利用IEEE 8000 & # 34;10000;10000;20001)设定以下标准:

fp64, 又名双精度或"double" ,最大舍入误差 ~ 2^-52fp32, 又名单精度或"single",最大舍入误差 ~ 2 ^-23fp16, 又名半精度或"half" ,最大舍入误差 ~ 2 ^-10

Python的float类型是fp64,而对内存更敏感的PyTorch使用fp32作为默认的dtype。混合精度训练的基本思路很简单:精度减半(fp32→ fp16),训练时间减半。最难的是怎么安全的做。注意,浮点数越小,舍入误差越大。对一个“足够小”的浮点数的任何操作都会将该值四舍五入为零!这就是所谓的下溢,这是个问题,因为很多甚至大部分梯度更新值都很小,但不为零。反向传播中舍入误差的累积可以将这些数字变成0或nans这将导致梯度更新不准确,并影响您的网络收敛。在2018年ICLR论文混合精度训练中,发现简单地在任何地方使用fp16都会“吞下”不到2-24的梯度更新——约为其示例网络中所有梯度更新的5%:

.6脚本

混合精确训练是一套技术,允许你使用fp16,而不会导致你的模型训练出现偏差。这是三种不同技术的结合。首先维护两个权重矩阵的副本,一个是用fp32的“主副本”,一个是用fp16的半精度副本。梯度更新使用fp16矩阵计算,但在fp32矩阵中更新。这使得应用渐变更新更加安全。第二,不同的向量运算在不同的速度下积累误差,要区别对待。有些操作在fp16中总是安全的,而有些操作只在fp32中可靠。与其用fp16来运行整个神经网络,不如对一些用半精度,对另一些用单精度。这种数据类型的混合就是这种技术被称为“混合精度”的原因。第三,使用损失标度。损失缩放是指在反向传播之前将损失函数的输出乘以一个标量数(论文建议从8开始)。乘法增加的损失值产生乘法增加的梯度更新值,并且许多梯度更新值被“提升”到超过fp16的安全阈值2-24。只需确保在应用梯度更新之前撤销缩放即可,不要选择过大的缩放导致inf权重溢出,从而导致网络向相反方向发散。通过结合这三种技术,作者可以在显著加快的时间内训练各种网络以实现收敛。至于基准,我建议看这篇只有9页的论文!

张量核(tensor cores)是如何工作的

混合精度训练虽然节省内存(fp16矩阵只有fp32矩阵的一半大小),但是没有特殊的GPU支持,无法加速模型训练。芯片上需要有能加快半精密运算速度的东西。在最近几代NVIDIA GPU中,这个东西叫张量核。张量核是一种新的处理单元,它针对一种非常特殊的操作进行了优化:将两个4 × 4 fp16矩阵相乘,然后将结果添加到第三个4 × 4 fp16或fp32矩阵中(一种“融合乘加”)。

更大的fp16矩阵乘法运算可以使用该运算作为它们的基本构建块来实现。由于大多数反向传播可以归结为矩阵乘法,张量核适用于网络中几乎任何计算密集型层。陷阱:输入矩阵必须是fp16。如果你是用张量核的GPU训练,而不是混合精度训练,你不可能从你的显卡得到100%的回报!fp32中定义的标准PyTorch模型永远不会对芯片应用任何fp16数学运算,因此所有这些极其强大的张量核都将处于空空闲状态。张量核是2017年底在上一代Volta架构中引入的。当代图灵有一些改进,在即将到来的安培中会看到进一步的改进。云上常见的两种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 . float 32)y = torch . tensor(y,dtype = torch . float 32)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 . data loader(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 _ epochssqueeze()loss = self . loss _ fn(y _ pred,y_batch) # NEW scaler.scale(loss)。backward() lv = loss.detach()。cpu()。numpy()if I % 100 = = 0:print(f & # 34;epoch { epoch+1 }/{ self . n _ epochs };第{i}批;损失{ lv } & # 34)# new scaler . step(optimizer)scaler . update()scheduler . step()新的PyTorch GradScaler对象是PyTorch实现的损耗缩放。回想一下在“混合精度如何工作”一节中,在训练期间,某种形式的缩放是必要的,以防止梯度变得小于零。最佳损失乘数足够高,以保持非常小的梯度,但不会高到导致非常大的梯度被舍入到inf,从而导致相反的问题。然而,没有一个损失乘数适用于每一个网络。最佳乘数也可能随时间而变化,因为训练开始时的梯度通常比训练结束时的梯度大得多。如何在不给用户另一个超参数调整的情况下找到最佳损失乘数?PyTorch使用指数回退来解决这个问题。Gradscalar从一个小的损失乘数开始,每次都会翻倍。这种逐渐加倍的行为一直持续到GradScalar遇到具有inf值的梯度更新。Gradscalar丢弃这批数据(如跳过梯度更新),将损失乘数减半,并重置其乘法时间。通过以这种方式逐步上下移动损失乘数,PyTorch可以随着时间的推移逼近适当的损失乘数。熟悉TCP拥塞控制的读者应该会发现这里的核心思想非常熟悉!该算法中使用的确切数字是可配置的,您可以直接从docstring中看到默认值:

火炬。cuda . amp . grad scaler(init _ scale = 65536.0,growth _ factor = 2.0,backoff _ factor = 0.5,growth _ interval = 2000,enabled = true) gradscaler需要更新梯度计算(检查是否溢出)和优化器(丢弃的批次)。这就是为什么loss.backwards()被scaler.scale(loss)代替的原因。backwards()和optimizer.step()被scaler.step(optimizer)替换。值得注意的是,GradScalar可以检测和阻止溢出(因为inf总是不好的),但它不能检测和阻止下溢(因为0通常是合法的值)。如果您选择的初始值太低,而增长间隔太长,您的网络可能会在GradScalar介入之前下溢和发散。因此,选择一个非常大的初始值init _ scale = 65536(

)是PyTorch的默认值。最后,注意GradScalar是一个有状态对象。使用此函数保存模型检查点需要与模型权重一起写入和读取到磁盘。使用state _ dict和load _ state _ dict对象方法可以很容易地做到这一点(在PyTorch文档中有描述)。自动混合精度训练难题的另一半是torch.cuda.amp.autocast上下文管理器。Autocast实现fp32->;Fp16转换。回想一下“混合精度如何工作”中的内容,并非所有操作都可以在fp16中安全运行,因为不同的操作会以不同的速率累积误差。以下截图来自amp模块文档,显示了autocast如何处理PyTorch中的各种操作:

这个列表主要包括矩阵乘法和卷积,以及简单的线性函数。

这些操作在fp16中是安全的,但是在fp16和fp32混合的情况下,这些操作有一个向上强制转换的规则来保证它们不会出错。注意,这个列表还包括另外两个基本的线性代数运算:矩阵/向量点积和向量叉积。

对数、指数、三角函数、正态函数、离散函数和(大)和在fp16中是不安全的,必须在fp32中执行。通过浏览这个列表,在我看来,大多数层都将受益于自动造型,这要归功于它们对基本线性代数运算的内部依赖,但大多数激活函数却不是这样。卷积层是最大的赢家。启用sutocasting非常简单。您所需要做的就是使用autocast上下文管理器来打包模型的向前传播:

用火炬。cuda。安培。autocast():y _ pred = model(x _ batch)。挤()损=自。loss _ fn (y _ pred,y _ batch)以这种方式打包前向传播,可以自动打开后向传播(如loss.backwards())。只要遵循PyTorch的最佳实践(例如,避免就地操作),autocasting基本上可以“正常工作”。它甚至可以使用多GPU DistributedDataParallel API(只要遵循推荐的策略,每个GPU只使用一个进程)。稍加调整,也可以使用多GPU数据并行API。Pytorch文档中自动混合精度示例页面的“使用多个GPU”部分是关于此主题的方便参考。个人观点,有个重点要记住:& # 34;用二元交叉熵代替二元交叉熵& # 34;。

Benchmarks性能

至此,我们已经了解了什么是混合精度,什么是张量核,PyTorch API如何实现自动混合精度。唯一剩下的就是看一些真实世界的性能基准测试!我曾经训练过三个非常不同的神经网络,具有自动混合精度,还有一次是没有用的。我通过Spell API调用了V100s(以前的张量核)和T4s(当代的张量核)。我用的是AWS EC2实例,分别是p3.2xlarge和g4dn.xlarge,还有最近的PyTorch 1.6 nightly和CUDA 10.0。所有模型的收敛性是一致的,即没有模型发现混合精度网络和原始网络之间的训练损失有任何差异。培训网络如下:

前馈, 一个前馈神经网络,训练数据来自Kaggle比赛Rosan Store Samples。UNet, 一个中等大小的原版UNet 图像分割网络, 在数据集Segmented Bob Ross Images 上训练。 BERT, 一个大的 NLP transformer 模型,使用bert-base-uncased 骨干(通过 huggingface),及数据来自Kaggle竞赛 Twitter Sentiment Extraction 。

结果如下:

因为前馈网络很小,混合精度训练对它不好。UNet是一个具有7703497个参数的中尺度卷积模型,从混合精度训练中获得了显著的效益。有趣的是,虽然V100和T4都受益于混合精确训练,但T4的好处要大得多:节省5%的时间,而不是高达30%的时间。BERT是一个非常大的模型,其中混合精度训练用于节省时间,从中等模型的“非常好”到“必须有”。在Volta或者图灵GPU上训练,自动混音精度会让大模型的训练时间减少50%到60%!这是一个巨大的优势,尤其是当您考虑到增加的复杂性是最小的时候——修改模型训练脚本只需要4到5行代码。在我看来:

混合精度应该是模型训练脚本的首要性能优化之一。

内存呢?

正如我在“混合精度如何工作”一节中解释的那样,fp16矩阵在内存中的大小是fp32矩阵的一半,因此混合精度训练的另一个优势是内存利用率。GPU内存的瓶颈远不及GPU的计算能力,但仍然有很大的优化价值。你的内存效率越高,你就可以在GPU上使用越多的批量。PyTorch在模型训练过程的开始会预留一定量的GPU内存,在训练过程中也会预留这些内存。这可以防止其他进程在训练期间占用过多的GPU内存,从而迫使PyTorch训练脚本崩溃并导致OOM错误。以下是启用混合精度训练对PyTorch记忆保持行为的影响:

有趣的是,虽然两个较大的模型都看到了转换到混合精度的好处,但UNet比BERT从转换中受益更多。PyTorch的内存分配行为对我来说是非常不透明的,所以我不知道为什么会出现这种情况。

总结

在即将发布的PyTorch 1.6版本中,自动混合精度训练是一个简单易用、功能强大的新功能,它承诺将运行在最新NVIDIA GPU上的大型模型的训练速度提高60%。虽然这种技术已经存在了一段时间,但对于普通用户来说并不容易理解,因为直到现在它还没有一个原生的PyTorch API。要直接从源代码中了解有关混合精度训练的更多信息,请参考PyTorch主文档中的自动混合精度包和自动混合精度示例页面。想自己测试一下这个功能?每晚安装最新的PyTorch非常简单:查看PyTorch主页上的说明,了解如何安装它。想自己重现这些基准?所有的模型源代码都可以在GitHub上的residen Mario/Spell-Feed Forward-Ross man、residen Mario/Spell-Unet-Bob-Ross和residen Mario/Spell-Tweet-perspective-Extraction库中获得。

原文:

https://spell . run/blog/mixed-precision-training-with-py torch-xuk 7 ybeaacaasjam

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论