当前位置: 首页 > 科技观察

分布式简介,如何使用PyTorch实现多GPU分布式训练_0

时间:2023-03-15 10:18:43 科技观察

具体来说,本文首先介绍了分布式计算的基本概念,以及如何将分布式计算用于深度学习。然后,列举了配置环境以处理分布式应用程序的标准要求(硬件和软件)。***,为了提供实践经验,本文从理论和实现的角度演示了一种用于训练深度学习模型的分布式算法(同步随机梯度下降,同步SGD)。什么是分布式计算分布式计算是指一种利用网络中多个连接的不同组件编写程序的方式。通常,大规模计算是通过以能够并行处理高密度数值运算的方式安排计算机来实现的。在分布式计算的术语中,这些计算机通常被称为节点(node),这些节点的集合就是一个集群。这些节点通常通过以太网连接,但其他高带宽网络也可以利用分布式架构。深度学习如何从分布式计算中获益?作为深度学习的主力军,神经网络出现在文献中已有一段时间了,但直到最近,还没有人充分发挥其潜力。神经网络异军突起的主要原因之一是巨大的计算能力,这也是我们这篇文章要写的内容。深度学习需要在大量数据的基础上训练一个深度神经网络,其中包含大量的参数。分布式计算是充分利用现代硬件的绝佳工具。这是它的核心思想:精心设计的分布式算法可以:跨多个节点“分布”计算(深度学习模型中的前向和反向传播)和数据以进行连贯处理。为了实现一致性,它能够跨多个节点建立有效的“同步”。MPI:分布式计算标准还有一个您必须熟悉的术语——消息传递接口(MPI)。MPI是几乎所有分布式计算的主力。MPI是一个开放标准,定义了一系列节点间通信的规则,MPI也是一种编程模型/API。MPI不是一个软件或工具,它是一个规范。1991年夏天,一群来自学术界和工业界的组织和个人聚集在一起,最终创建了MPI论坛(MPIForum)。论坛达成共识,起草库的语法和语义规范,为不同硬件供应商提供可移植/灵活/优化实施方案的指导。一些硬件供应商有他们自己的MPI实现——OpenMPI、MPICH、MVAPICH、IntelMPI等。在本教程中,我们将使用IntelMPI,因为它非常高效并且针对Intel平台进行了优化。最初的英特尔MPI是一个C库,而且级别非常低。配置正确的配置对于分布式系统来说非常重要。如果您没有适当的硬件和网络安排,即使对其编程模型有概念性的了解也没有多大用处。以下是需要进行的关键安排:通常由公共网络互连的一系列节点形成一个集群。推荐使用高端服务器作为节点和InfiniBand等高带宽网络。集群中的所有节点都需要具有完全相同用户名的Linux系统。在节点之间建立无缝的SSH连接是必不可少的。必须安装MPI实现。本文仅关注IntelMPI。需要一个通用文件系统,对所有节点可见,分布式应用程序必须驻留在该系统上。网络文件系统(NFS,NetworkFilesystem)是实现这一点的一种方式。ParallelStrategiesTypesofParallelStrategies深度学习模型的并行化有两种流行的方式:ModelParallelDataParallel1.ModelParallelModelParallelModelParallel是指一个模型在逻辑上分为几个部分(例如,一些层在一个部分,其他层在另一部分部分),然后将它们部署到不同的硬件/设备上。尽管在不同设备上部署模型的不同部分在执行时间方面确实有好处,但它通常用于避免内存限制。具有特别多参数的模型受益于这种并行策略,因为此类模型需要高内存占用并且难以适合单个系统。2.数据并行另一方面,数据并行是指通过位于不同硬件/设备上的同一网络的多个副本来处理不同批次的数据。与模型并行性不同,每个副本可能是整个网络,而不仅仅是一部分。正如您可能猜到的那样,此策略可以随着数据增长而很好地扩展。但是,由于整个网络必须部署在一台设备上,因此它可能无法帮助内存占用量大的模型。下图应该可以说明这个问题。模型并行与数据并行事实上,数据并行在大型组织中更受欢迎,更常用于实施生产质量的深度学习训练算法。因此,本教程重点介绍数据并行性。torch.distributedAPIPyTorch提供了一个非常优雅且易于使用的API作为用C编写的底层MPI库的接口。PyTorch需要从源代码编译,并且必须与系统上安装的英特尔MPI链接。现在让我们看看torch.distributed的基本用法以及如何实现它。#filename'ptdist.py'importtorchiimporttorch.distributedasdistdefmain(rank,world):ifrank==0:x=torch.tensor([1.,-1.])#Tensorofinterestdist.send(x,dst=1)print('Rank-0hassentthefollowingtensortoRank-1')print(x)else:z=torch.tensor([0.,0.])#Aholderforrecievingthetensordist.recv(z,src=0)print('Rank-1hasrecievedthefollowingtensorfromRank-0')print(z)if__name__=='__main__':dist.init_process_group(backend='mpi')main(dist.get_rank(),dist.get_world_size())点对点通信使用mpiexec执行上面的代码得到分布式processscheduler,基于任何标准的MPI实现,结果如下:cluster@miriad2a:~/nfs$mpiexec-n2-ppn1-hostsmiriad2a,miriad2bpythoptdist.pyRank-0hassentthefollowingtensortoRank-1tensor([1.,-1.])Rank-1hasrecievedthefollowingtensorfromRank-0tensor([1.,-1.])要执行的第一行是dist.init_process_group(backend),它基本上建立了参与节点之间的内部通信通道。它需要一个参数来指定要使用的后端。由于我们完全使用MPI,因此在我们的例子中backend='mpi'。还有其他后端(例如TCP、Gloo、NCCL)。需要检索两个参数-世界大小和等级。World指的是特定mpiexec调用上下文中所有节点的集合(请参阅mpiexec中的-hosts标志)。rank是由MPI运行时分配给每个进程的唯一整数。它从0开始。它们在-hosts中指定的顺序用于赋值。因此,在此示例中,节点“miriad2a”上的进程将被分配为Rank0,节点“miriad2b”上的进程将被分配为Rank1。x是Rank0打算通过dist发送到Rank1的张量。发送(x,dst=1)完成。z是Rank1在接收张量之前创建的东西。我们需要一个较早创建的具有相同维度的张量作为占位符来接收传递的张量。z的值最终会被x取代。与dist.send(..)类似,对应的负责接收的函数是dist.recv(z,src=0),接收一个张量到z中。CommunicationCollectives我们在上一节中看到的是“点对点”通信的示例,其中rank(s)将数据发送到给定环境中的特定rank(s)。虽然此通信很有用,因为它提供了对通信的细粒度控制,但还有其他经常使用的标准通信模式,称为集合。下面介绍我们在SynchronousSGD算法中比较感兴趣的一个ensemble——all-reduceensemble。1.ALL-REDUCECollectiveAll-reduce是一种同步通信方式。所有rank都进行归约操作,结果对所有rank可见。下图介绍了思路(求和作为归约运算)。all-reducecollectivedefmain(rank,world):ifrank==0:x=torch.tensor([1.])elifrank==1:x=torch.tensor([2.])elifrank==2:x=torch.tensor([-3.])dist.all_reduce(x,op=dist.reduce_op.SUM)print('Rank{}has{}'.format(rank,x))if__name__=='__main__':dist.init_process_group(backend='mpi')main(dist.get_rank(),dist.get_world_size())PyTorch中all-reducecollective的基本用法在worldof3环境启动时,结果如下:cluster@miriad2a:~/nfs$mpiexec-n3-ppn1-hostsmiriad2a,miriad2b,miriad2cpythonptdist.pyRank1hastensor([0.])Rank0hastensor([0.])Rank2hastensor([0.])如果rank==...elif是我们的在分布式计算中多次遇到模式。在此示例中,它用于创建不同等级的张量。它们一起执行all-reduce(如您所见,dist.all_reduce(..)在if...elif块逻辑块之外),求和(dist.reduce_op.SUM)作为归约操作。对每个等级的x求和并将所得总和放入每个等级的x中。转向深度学习假定读者熟悉标准随机梯度下降算法(SGD),该算法通常用于训练深度学习模型。我们现在看到的是SGD的一个变体——同步SGD(synchronousSGD),它使用all-reducecollective来缩放。让我们从标准SGD的数学开始。其中D是样本集(mini-batch),θ是所有参数的集合,λ是学习率,Loss(X,y)是D中所有样本的损失函数的平均值。核心技巧同步SGD所依赖的是将更新规则中的总和拆分为更小的(迷你)批处理子集。D被划分为R个子集D?、D?、...。运算符是分布式的,所以我们得到:我们从中得到了什么?查看上面等式中的各个梯度项(在方括号内)。现在可以独立计算它们并将它们加在一起以获得原始梯度而没有任何损失/近似值。这就是数据并行。下面是整个过程:将整个数据集分成R个大小相等的数据块(子集)。这里的字母R代表副本。使用MPI启动R进程/排名,将每个进程绑定到一个数据块。让每个rank使用一个大小为B的mini-batch(d?)(d?来自分配给rank的数据块D_r)来计算梯度,即rankr计算。将所有等级的梯度相加,并在进一步处理之前使生成的梯度对每个等级可见。最后一点是all-reduce算法。因此,每次在所有等级都使用大小为B的小批量(d?)计算梯度后,必须执行all-reduce。需要注意的一件事是,总结所有R等级的梯度(使用大小为B的小批量计算)产生有效的批量大小:这是实现的关键部分(未显示样板代码):模型=LeNet()#firstsynchronizationofinitialweightssync_initial_weights(model,rank,world_size)optimoptimizer=optim.SGD(model.parameters(),lr=1e-3,momentum=0.85)model.train()forepochinrange(1,epochs+1):fordata,targetintrain_loader:optimizer.zero_grad()output=model(data)loss=F.nll_loss(output,target)loss.backward()#Theall-reduceongradientssync_gradients(model,rank,world_size)optimizer.step()defsync_initial_weights(model,rank,world_size):forparaminmodel.parameters():ifrank==0:#Rank0issendingit'sownweight#toallit'ssiblings(1toworld_size)forsiblinginrange(1,world_size):dist.send(param.data,dst=sibling)else:#Siblingsmustrecievtheparametersdist。recv(param.data,src=0)defsync_gradients(model,rank,world_size):forparaminmodel.parameters():dist.all_reduce(param.grad.data,op=dist.reduce_op.SUM)所有的Rranks使用随机的权重创建自己的模型副本。具有随机权重的单个副本最初可能不同步。建议在所有副本上同步初始权重,而sync_initial_weights(..)就是这样做的。让任何等级将自己的权重发送到它的兄弟等级,它必须接收这些权重并使用它们来初始化自己。从每个等级对应的数据部分中取出一个mini-batch(大小为B),并计算前向和反向传递(梯度)。作为配置的一部分,这里要注意的重要一点是所有进程/等级都应该有自己的可见数据部分(通常在它们自己的硬盘上或共享文件系统中)。将求和视为归约操作,并对每个副本上的梯度进行集体归约。sync_gradients(..)将执行梯度同步。在梯度同步之后,每个副本都可以独立地对自己的权重执行标准的SGD更新。optimizer.step()工作正常。现在问题来了:我们如何确保独立更新保持同步?我们看一下更新方程的最新更新:上面的第2点和第4点确保每个初始权重和梯度是同步的。显然,它们的线性组合也是同步的(λ为常数)。所有未来的更新都遵循类似的逻辑,因此是同步的。性能比较所有分布式算法最大的瓶颈是同步。只有当同步时间明显小于计算时间时,分布式算法才有用。让我们对标准SGD和同步SGD做一个简单的比较,看看后者什么时候更好。定义:我们假设整个数据集的大小为N。网络处理一个大小为B的mini-batch需要时间Tcomp。在分布式情况下,all-reduce同步需要Tsync。对于非分布式(标准)SGD,每个时期花费的时间为:对于同步SGD,每个时期花费的时间为:因此,对于分布式环境,为了比非分布式环境具有显着优势,我们需要满足:我们可以调整影响上述不等式的三个因素,以从分布式算法中获得更多收益。通过将节点与具有高带宽的快速网络连接来减少Tsync。通过增加批量大小B来增加Tcomp。通过连接更多节点和拥有更多副本来增加R。本文清晰地介绍了深度学习背景下分布式计算的核心思想。尽管同步SGD很流行,但还有其他分布式算法也经常使用(例如异步SGD及其变体)。然而,更重要的是并行思考深度学习方法的能力。请注意,并非所有算法都可以开箱即用地并行化,有些算法需要一些近似值,这打破了原始算法给出的理论保证。有效地处理这些近似值取决于算法的设计者和实现者。原文地址:https://medium.com/intel-student-ambassadors/distributed-training-of-deep-learning-models-with-pytorch-1123fa538848【本文为机器之心专栏原文翻译,微信公众号《机器之心(id:almosthuman2014)》】点此阅读作者更多好文