在过去几年中,神经网络已被证明是解决各种问题的极其有效的工具,并且在规模和计算要求方面迅速增长。 2012年,用于图像识别的超级卷积网络在物体识别方面取得了巨大进步,花了一周时间用两个GPU,拥有6000万个参数。 2016年,研究人员在语言建模方面取得了突破,该网络拥有超过10亿个参数,在32个GPU上训练了三个星期。在百度研究所的硅谷AI实验室内,2014年我们的深度语音识别系统的第一次迭代大约有1100万个参数,而一年后的下一次迭代已经增长到1亿个参数。
随着神经网络的参数数量和计算需求的增长,在许多节点和许多GPU上高效并行化神经网络训练变得越来越重要,因为等待数月的大型网络训练会减慢实验速度并限制进一步的发展。在这篇文章中,我们介绍了一种来自高性能计算(HPC)领域的技术,并演示了如何将其应用于深度学习以在神经网络训练中实现显着的性能提升。
在将神经网络的训练并行化到许多GPU上时,您必须选择如何将不同的操作分配到可用的不同GPU上。在这里,我们专注于一种称为数据并行随机梯度下降(SGD)的技术。
与模型并行技术不同,模型并行指的是将模型部署到很多设备上(设备可能分布在不同机器上,下同)运行,比如多个机器的GPUs。当神经网络模型很大时,由于显存限制,它是难以在跑在单个GPU上,这个时候就需要模型并行。比如Google的神经机器翻译系统,其可能采用深度LSTM模型,如下图所示,此时模型的不同部分需要分散到许多设备上进行并行训练。深度学习模型一般包含很多层,如果要采用模型并行策略,一般需要将不同的层运行在不同的设备上,但是实际上层与层之间的运行是存在约束的:前向运算时,后面的层需要等待前面层的输出作为输入,而在反向传播时,前面的层又要受限于后面层的计算结果。所以除非模型本身很大,一般不会采用模型并行,因为模型层与层之间存在串行逻辑。但是如果模型本身存在一些可以并行的单元,那么也是可以利用模型并行来提升训练速度,比如GoogLeNet的Inception模块。
在数据并行方式中与标准SGD一样,梯度下降是通过数据子集(小批量)完成的,需要多次迭代才能在整个数据集中进行。然而,在数据并行训练中,每个GPU具有整个神经网络模型的完整副本,并且对于每次迭代,仅分配小批量中的样本的子集。对于每次迭代,每个GPU在其数据上运行网络的前向传播,然后进行反向误差传播以计算相对于网络参数的丢失梯度。最后,GPU彼此通信获取由不同GPU计算出的梯度的平均,将平均梯度应用于权重以获得新的权重。 GPU都在锁定步骤中进行迭代,并且一旦GPU完成其迭代,它必须等待其他所有GPU完成它们自己的迭代以便可以正确地更新权重。这相当于在单个GPU上执行SGD,但我们通过在多个GPU之间分配数据并行执行计算来获得加速。
当您只有两个GPU和以兆字节数据为单位的参数时,这些GPU的通信方式可能无关紧要。 但是,当您的模型具有数十亿个参数时,梯度可能占用千兆字节的空间(因为每个参数都有一个梯度值),并且您正在协调数十个GPU,通信机制变得至关重要。
例如,考虑最直接的通信机制。 每个GPU都在其小批量的子集上计算梯度。 然后,每个GPU将其梯度发送到单个GPU,该GPU获取所有梯度的平均值,并将平均值发送回所有其他GPU。
需要发送的数据越多,发送数据的时间就越长;每个通信信道都具有最大吞吐量(带宽)限制。例如,良好的互联网连接可以提供每秒15兆字节的带宽,而千兆以太网连接可以提供每秒125兆字节的带宽。 HPC群集上的专用网络硬件(如Infiniband)可在节点之间提供每秒几千兆字节的带宽。
在从单个GPU发送和接收数据的直连机制中,该单个GPU必须从所有GPU接收所有参数,并将所有参数发送到所有GPU。系统中的GPU越多,通信成本就越高。
让我们评估一下这种通信策略如何在真实模型上运行,例如以百度深度语音2为模型的语音识别网络,具有三亿个可训练参数。每个参数四个字节的三亿个参数大约是1.2千兆字节的数据。假设您系统上的网络硬件可以支持每秒1千兆字节的带宽;在这种情况下,如上所述将系统并行化到两个GPU上将使每次迭代减慢1.2秒。将您的训练任务并行化到10个GPU将使每次迭代减慢10.8秒;随着GPU数量的增长,每次迭代所需的时间呈线性增长。即使每次迭代花费几秒钟,通信成本的这种线性增长也会使得进一步的并行化变得不切实际并且会降低训练效率。
一种替代方案是放弃训练算法的同步性质,并通过梯度下降的迭代消除所有GPU在锁定步骤中前进的约束。但是,虽然这可以使您的模型更容易并行化,删除此约束的算法(异步SGD的变量)却可能难以调试,并且对于某些模型可以收敛到部分子结果,这不是我们这篇文章的目的。
相反,我们可以通过使用高性能计算领域的分布式reduction算法来解决通信问题,并充分利用带宽最优的ring-allreduce方案。
上述简化通信策略的主要问题是通信成本随着系统中GPU的数量线性增长。 相反,ring allreduce是一种算法,其通信成本是恒定的并且与系统中GPU的数量无关,并且仅由系统中GPU之间的最慢连接确定; 事实上,如果你只考虑带宽作为通信成本的一个因素(并忽略延迟),那么ring allreduce是一种最佳的通信算法。 (当您的模型很大时,这是对通信成本的一个很好的预估,并且您只需要较少数次数发送大量数据。)
Ring allreduce中的GPU排列在一个逻辑环中。 每个GPU应该有一个左邻居和一个右邻居; 它只会向其右邻居发送数据,并从其左邻居接收数据。
该算法分两步进行:第一步是scatter-reduce,然后是all-gather。 在scatter-reduce步骤中,GPU将交换数据,使得每个GPU最终得到最终结果的一部分。 在all-gather步骤中,GPU将交换这些块,以便所有GPU最终得到完整的最终结果。
为简单起见,让我们假设目标是元素层面的,即浮点数的单个大数组的所有元素的总和; 系统中有N个GPU,每个GPU都有一个相同大小的数组,并且在allreduce的末尾,每个GPU都应该有一个相同大小的数组,其中包含原始数组中数字的总和。
首先,GPU将数组分成N个较小的块(其中N是环中的GPU数量)。
接下来,GPU将进行N-1次迭代的scatter-reduce; 在每次迭代中,GPU都会将其中一个块发送到其右邻居,并从其左邻居接收一个块并累积到该块中。 每次迭代发送和接收的块都是不同的; 第n个GPU通过发送块n和接收块n-1开始,然后从那里向后进行,每次迭代发送它在前一次迭代中接收到的块。
例如,在第一次迭代中,上图中的五个GPU将发送和接收以下块:
GPU | Send | Receive |
---|---|---|
0 | Chunk0 | Chunk4 |
1 | Chunk1 | Chunk0 |
2 | Chunk2 | Chunk1 |
3 | Chunk3 | Chunk2 |
3 | Chunk4 | Chunk3 |
在第一次发送和接收完成后,每个GPU将具有一个块,该块由两个不同GPU上的相同块的总和组成。 例如,第二个GPU上的第一组块将是来自第二GPU和第一GPU的该组块中的值的总和。
在scatter-reduce第一次迭代完成以后立刻求和
在接下来的迭代中,该过程继续,并且最后,每个GPU将具有一个块,该块包含所有GPU中该块中的所有值的总和。 下面的图像演示了所有数据传输和中间结果,从第一次迭代开始,一直持续到scatter-reduce完成。
Scatter-reduce data transfers (iteration 1)
Scatter-reduce data transfers (iteration 2)
Scatter-reduce data transfers (iteration 3)
Scatter-reduce data transfers (iteration 4)
Final state after all scatter-reduce transfers
Scatter-reduce步骤完成后,每个GPU都是一个有值的数组,其中一些值(每个GPU一个块)是最终值,包括来自所有GPU的贡献。 为了完成allreduce,GPU必须交换这些块,以便所有GPU都具有所有必需的值。
Ring allgather与scatter-reduce相同(发送和接收的N-1次迭代),除了不是累积GPU接收的值以外,它们只是覆盖块。 第n个GPU首先发送第n + 1个块并接收第n个块,然后在将来的迭代中总是发送它刚收到的块。
例如,在我们的5-GPU设置建立的第一次迭代中,GPU将发送和接收以下块:
GPU | Send | Receive |
---|---|---|
0 | Chunk1 | Chunk0 |
1 | Chunk2 | Chunk1 |
2 | Chunk3 | Chunk2 |
3 | Chunk4 | Chunk3 |
3 | Chunk0 | Chunk4 |
Data transfers in the first iteration of the allgather
第一次迭代完成后,每个GPU将有两个最终数组的块。在接下来的迭代中,该过程继续,并且到最后,每个GPU将具有整个数组的完全累积值。
下面的图像演示了所有数据传输和中间结果,从第一次迭代开始直到全部收集完成。
Allgather data transfers (iteration 1)
Allgather data transfers (iteration 2)
Allgather data transfers (iteration 3)
Allgather data transfers (iteration 4)
Final state after all allgather transfers
回想一下,对于介绍中描述的简单通信算法,通信成本随着GPU的数量线性增长。 allreduce运行良好的主要原因是不再是这种情况。
在我们描述的系统中,N个GPU中的每一个将为scatter-reduce发送和接收N-1次值,为allgather发送和接收N-1次。每次,GPU将发送K / N值,其中K是在不同GPU上求和的数组中的值的总数。因此,传输到每个GPU的数据总量是
Data Transferred = 2 ( N − 1 ) K N \text{Data Transferred} = 2(N-1)\dfrac {K} {N} Data Transferred=2(N−1)NK
这至关重要,与GPU的数量无关。
由于所有传输在离散迭代中同步发生,因此allreduce的速度受到环中相邻GPU之间的最慢(最低带宽)连接的限制。给定每个GPU的邻居的正确选择,该算法是带宽最优的并且是用于执行allreduce的最快算法(假设与带宽相比延迟成本可忽略不计)。通常,如果节点上的所有GPU在环中彼此相邻,则算法最佳地起作用; 这最大限度地减少了网络争用的数量,否则可能会显著降低GPU-GPU连接的有效带宽。
Ring allreduce是高性能计算领域中众所周知的算法,但在深度学习中往往很少使用。在我们的实验室中,我们设法使用此工具作为所有数据并行训练的基础,使我们能够有效地将训练扩展到数十个GPU。
为了最小化通信开销,我们可以利用神经网络的结构。在每次迭代中,每个GPU都前向传播以计算错误,然后运行后向传播以计算神经网络的每个参数的梯度。反向传播从输出层开始计算梯度并向输入层中移动,这意味着输出层参数的梯度先于较早层的梯度之前显著可用。由于allreduce可以一次性对网络参数的子集进行操作,因此我们可以在输出层参数上启动allreduce,同时仍然计算其他梯度。这样做会在反向传播步骤中将通信与其余计算重叠,从而减少每个GPU等待通信完成的总时间。
例如,考虑一个类似于 i n 2 in^2 in2的语言模型,有大约3亿个可学习的参数(因此总梯度大小为1.2千兆字节)。使用allreduce,每个GPU必须发送和接收大约2.4千兆字节的数据。使用支持CUDA的MPI实现(类似OpenMPI),我们可以使用GPU Direct RDMA在GPU之间传输数据,带宽大约为每秒10千兆字节; 但是,我们集群中节点之间的连接速度较慢,Infiniband提供的带宽大约为每秒6千兆字节。由于限制因素是Infiniband连接,因此单次迭代需要大约
2.4 gigabytes 6.0 gigabytes per second ≈ 400 milliseconds per iteration \dfrac{2.4\text{ gigabytes}}{6.0\text{ gigabytes per second}} \approx 400 \text{ milliseconds per iteration} 6.0 gigabytes per second2.4 gigabytes≈400 milliseconds per iteration
由于网络较深的层首先具有可用的梯度,我们可以在整个反向传播通道完成之前开始进行数据传输,因此真正的开销可能小于400毫秒; 通信和计算之间的重叠可以根据被优化的神经网络的性质而变化。
我们实现了上述语言模型,并测试了每次迭代所花费的时间,因为我们从单个GPU(没有通信开销)扩展到40个GPU。 这40个GPU排列成5个节点,每个节点有8个GPU,由Infiniband连接。 我们运行语言模型进行了300次迭代,batch size大小为32,并计算每秒处理的样本数。
使用3亿参数语言模型每秒处理的样本数量与同时进行同步训练的GPU数量呈线性关系。
如您所见,整个系统的吞吐量与GPU的数量呈线性关系; 超过某个点时,添加更多GPU不会导致每次迭代速率显著减慢。 在40个GPU上运行模型每次迭代大约需要650-700毫秒,而在单个GPU上大约需要370毫秒。 由于通过我们的估计,通信将花费400毫秒,我们通过将反向传播与数据传输重叠来每次迭代额外节省70-120毫秒。
Ring allreduce是一种来自高性能计算领域的技术,它允许我们在许多设备和许多节点上有效地平均化神经网络中的梯度。通过在训练期间使用这种带宽优化算法,您可以大幅减少通信开销并扩展到更多设备,同时仍保留同步随机梯度下降的确定性和可预测的收敛特性。该算法是网络架构和深度学习框架无关的,可以为数据并行训练的效率提供切实和直接的好处,同时也是相当简单和易于实现的。
为了让您更容易利用这些技术,今天我们发布了baidu-allreduce,一个C程序库,演示了可以嵌入到任何支持MPI的应用程序中的allreduce算法。此外,优步的优秀Horovod库实现了我们在这里提到的技术。
我们希望其他深度学习框架能够在适当的情况下利用类似的技术,并且使用这些工具,您将能够轻松高效地将神经网络模型扩展到许多机器,而不受您选择的框架的影响。
1.Krizhevsky, Alex, Ilya Sutskever, and Geoffrey E. Hinton. “ImageNet classification with deep convolutional neural networks.” Advances in neural information processing systems. 2012.
2.Jozefowicz, Rafal, et al. “Exploring the limits of language modeling.” arXiv preprint arXiv:1602.02410 (2016).
3.Amodei, Dario, et al. “Deep speech 2: End-to-end speech recognition in english and mandarin.” arXiv preprint arXiv:1512.02595 (2015).
4.Patarasuk, Pitch, and Xin Yuan. “Bandwidth optimal all-reduce algorithms for clusters of workstations.” Journal of Parallel and Distributed Computing 69.2 (2009): 117-124.
5.Hannun, Awni, et al. “Deep speech: Scaling up end-to-end speech recognition.” arXiv preprint arXiv:1412.5567 (2014).
参考:http://andrew.gibiansky.com/blog/machine-learning/baidu-allreduce/