0x00 摘要

工欲善其事,必先利其器,为了更好的剖析代码,咱们先来学习一下相关论文。

PyTorch 开发者在完结的一同,发布了一篇论文:[ PyTorch Distributed: Experiences on Accelerating Data Parallel Training ] Shen Li, Yanli Zhao, Rohan Varma, Omkar Salpekar, Pieter Noordhuis, Teng Li, Adam Paszke, Jeff Smith, Brian Vaughan, Pritam Damania, Soumith Chintal。

其地址为:www.vldb.org/pvldb/vol13…

因为论文较长,所以本文翻译其思路和完结之中的部分内容,在后文之中将以这篇论文为根底,结合源码来进行剖析。本文不彻底依照原论文的次序进行翻译,笔者会对其要点做标示,也会依照自己的理解进行调整,另外,原文是依据 PyTorch 1.5,与最新 PyTorch 有部分出入。

本系列其他文章如下:

[源码解析] PyTorch 分布式(1)——前史和概述

[源码解析] PyTorch 怎么运用GPU

[源码解析] PyTorch 分布式(2) —– DataParallel(上)

[源码解析] PyTorch 分布式(3) —– DataParallel(下)

[源码解析] PyTorch 分布式(4)——分布式运用根底概念

[源码解析] PyTorch 分布式(5) —— DistributedDataParallel 总述&怎么运用

[源码解析] PyTorch分布式(6) —DistributedDataParallel — 初始化&store

[源码解析] PyTorch 分布式(7) —– DistributedDataParallel 之进程组

0x01 原文摘要

深度学习的最新进展证明晰大型数据集和大型模型的价值,这就需求将模型练习扩展到更多核算资源之上。因为其简略的原理和广泛的适用性,数据并行已成为分布式练习的一种盛行处理计划。一般,分布式数据并行技能在每个核算源上仿制模型以在每个worker之上独登时生成梯度,然后在每次迭代中通讯这些梯度以坚持模型副本的共同性。尽管该技能概念简略,但核算和通讯之间的微妙依赖性使得优化分布式练习功率非常重要。从1.5版开端,Pytorch 供给了几种加快分布式数据并行的技能,包括bucketing梯度、通讯堆叠核算和越过梯度同步。评价标明,当恰当装备时,Pyrotch分布式数据并行模块可运用256 GPU完结近似线性的可扩展性。

0x02 引论

练习DNN模型一般重复履行以下三个进程:

  • 向前传递以核算损失。
  • 向后传达以核算梯度。
  • 以及优化器进程以更新参数。

数据并行性的概念普遍适用于此类框架:运用程序能够创立一个模型的多个副本,每个模型副本处理一部分练习数据,并独立履行向前和向后传达。之后,模型副本能够依据算法同步其梯度或更新的参数。

2.1 应战

看起来,彻底在运用程序端构建数据并行的作业版别是或许的,因为它只需求在每次迭代中刺进恰当的通讯。可是,挤出最终一点功用需求在规划和调整方面付出巨大的努力。在渠道端供给本机分布式数据并行API将协助运用程序开发人员专注于优化其模型,而渠道开发团队能够持续透明地进步练习速度。

要供给一个通用的分布式数据并行包,有三个方面的应战。

  • 数学等价:数据并行的意图是加快对大型数据集的练习。运用程序希望获得相同的成果模型,就好像一切练习都是在本地进行,没有模型仿制相同。这就要求尽管它是分布式练习,可是应该数学等价于本地练习。
  • 非侵入式和阻拦式API:运用程序开发一般从本地模型开端,然后在必要时扩展。所以需求有一个从本地模型开端,修正代码以习惯分布式的进程。
    • 为了防止这个从本地模型到分布式模型的过渡期间过分麻烦,API在运用程序代码中有必要是非侵入性的。
    • 另一方面,API也需求答应一个内部完结来及时截获各种信号,以便履行通讯和体系优化。
  • 高功用:数据并行练习受制于核算和通讯之间微妙的依赖关系。规划和完结有必要探索处理计划空间,以有用地将更多资源转化为更高的练习吞吐量。

2.2 完结和评价

PyTorch以nn.Module类的形式供给分布式数据并行,其间运用程序在构建时以子模块的形式供给其模型。为了确保数学等效性,一切副本都从相同的模型参数初始值开端,并同步梯度,以便在整个练习迭代中坚持参数共同。为了最大限度地降低集成度,该完结(分布式数据并行模型)暴露了与用户模型相同的forward API,这答应运用程序无缝地用分布式数据并行模型对象替换之前出现的用户模型,而无需额定的代码更改。规划中集成了多种技能,以供给高功用练习,包括bucketing gradients,与核算的堆叠通讯和越过同步。

评价是在一个专用的32 GPU集群和一个更大的同享权限中的256 GPU上进行的。咱们开发了基准程序来评价不同规划的分布式包,以深化了解不同优化技能和装备的功用影响。实验还包括NCCL和Gloo通讯库之间的比较。成果标明:

  1. 通讯是影响练习推迟的首要因素,其影响随模型尺度的增大而增大;
  2. 存储桶巨细对通讯功率有很大影响,假如装备正确,或许会导致2倍以上的加快;
  3. 恰当越过同步将明显削减分摊的通讯开支,而不会明显降低收敛速度。

0x03 布景

3.1 PyTorch

PyTorch将值组织成张量,张量是具有丰富数据操作集的通用n维数组。模块界说了从输入值到输出值的转化,其正向传递期间的行为由其 forward 成员函数指定。模块能够包括张量作为参数。例如,线性模块包括权重参数和误差参数,其正向函数经过将输入乘以权重并添加误差来生成输出。

运用程序经过将本机模块(如线性、卷积等)和自界说forward函数中的Function(如relu、pool等)粘合在一同,构成自己的模块。典型的练习迭代包括运用输入和标签生成损失的前向传递,核算参数梯度的后向传递,以及运用梯度更新参数的优化器进程。更具体地说,在向前传达进程中,PyTorch构建了一个autograd图来记载所履行的动作。然后,在后向进程中,运用autograd图进行反向传达以生成梯度。最终,优化器运用梯度来更新参数。练习进程重复这三个进程,直到模型收敛。

3.2 数据并行

PyTorch 供给了多种工具来促进分布式练习,包括:

  • DataParallel,用于在同一台机器上运用多个GPU的单进程多线程进行数据并行练习。

  • DistributedDataParallel,用于跨GPU和机器的多进程数据并行练习。

  • RPC,用于一般分布式模型并行练习(例如,参数服务器)。

论文的其余部分首要重视分布式数据并行。数据并行经过在操作优化进程之前进行梯度通讯来完结分布式练习,这样能够确保运用彻底相同的梯度集来更新一切模型副本的参数,因而模型副本能够在迭代中坚持共同。

参数均匀是扩展模型练习的另一种盛行技能。类似地,它能够跨多台机器发动多个进程,但不是同步梯度,而是直接核算一切模型参数的均匀值。这产生在本地优化器进程之后,这意味着参数均匀能够彻底作为一个辅助进程完结,彻底不需求与本地练习进程交互,这很有吸引力,因为它能够轻松、干净地解耦分布式练习和本地迭代的代码。可是参数均匀有几个注意事项。

  • 与部分练习相比,参数均匀可产生天壤之别的成果,这有时会对模型精度形成晦气影响。根本原因是,参数均匀在数学上并不等同于本地处理一切输入数据,尤其是当优化器依赖于过去的本地梯度值(如动量)时。因为不同的模型副本或许会看到不同的梯度,因而optimizers中的状况或许会逐渐发散,从而导致梯度下降方向冲突。当从部分优化模型切换到大规划部署模型时,这或许会导致功用上莫名其妙的差异。

  • 参数均匀的结构将核算(即反向传递)和通讯(即核算均匀值)协调到非堆叠阶段,运用optimizer step() 函数作为硬分离点。无论咱们怎么大力优化核算或通讯,一种类型的资源在任何给定时刻都将处于闲暇状况,从而放弃大量功用优化时机。

鉴于上述根本缺点,咱们决议运用数据并行性来同步梯度而不是参数来施行分布式练习。请注意,运用程序依然能够运用PyTorch轻松构建参数均匀值。事实上,后文中描绘的调集通讯特性是该用例的适宜处理计划。运用程序只需求显式地发动AllReduce操作来相应地核算均匀参数。

3.3 AllReduce

AllReduce是一个根底通讯API,其被 DistributedDataParallel 用于核算一切进程的梯度求和。

多个通讯库都供给了AllReduce ,包括NCCL、Gloo和MPI。AllReduce操作要求每个参加进程都供给一个巨细相等的张量,然后将给定的算术运算(如sum、prod、min、max)运用于一切进程的输入张量,并向每个参加者返回相同的成果张量。

一个 AllReduce 简略的完结能够简略地让每个进程向一切对等进程播送其输入张量,然后独登时运用算术运算。可是,因为AllReduce对分布式练习速度有明显影响,通讯库完结了更杂乱、更高效的算法,如依据环的AllReduce和依据树的AllReduce。因为一个AllReduce操作在一切进程参加之前无法发动,因而它被认为是一种同步通讯,而不是参数服务器中运用的P2P通讯。

0x04 体系规划

PyTorch 供给了分布式数据并行(DDP)模块,这有助于轻松地跨多个进程和机器来进行并行化练习。在分布式练习期间,每个流程都有自己的本地模型副本和本地优化器。就正确性而言,分布式数据并行练习和本地练习有必要在数学上等价。DDP能够经过如下来确保练习正确性:

  • 一切模型副本从彻底相同的模型状况开端,并在每次向后传达之后,得到相同的参数梯度。

  • 因而,即使来自不同流程的优化器都是独立的,它们也应该能够在每次迭代结束时将其本地模型副本置于相同的状况

下图示出了DDP的构建块,它包括Python API前端、C++梯度归并中心算法,并运用 c10d 调集通讯库。以下部分按此仓库图的自上而下次序显现。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

第4.1节介绍了推动DDP API规划的一般准则。第4.2节介绍Pyrotch分布式数据并行包中运用的扩展梯度归并技能。最终,第4.3节评论了DDP的调集通讯后端选项。

4.1 API

在规划API时,咱们界说了两个规划目标来完结必要的功用。

  • 非侵入性:API有必要对运用程序是非侵入的。运用程序开发人员一般从编写本地练习脚本开端,并在单个核算机上达到资源限制时扩展。在这一点上,要求开发人员重写整个运用程序以支撑分布式数据并行练习是不可承受的。相反,开发人员应该能够经过最少的修正来重用本地练习脚本。
  • 阻拦:API需求答应完结阻拦各种信号以便及时触发恰当的算法。分布式数据并行旨在经过运用更多的核算资源来加快练习。这一进程需求在核算和通讯方面进行微妙的优化,以完结最佳功用。因而,API有必要对内部完结供给尽或许多的优化时机。

鉴于上述要求,咱们将分布式数据并行完结为一个nn 模块,该模块将本地模型作为结构函数参数,并透明地同步反向进程中的数据。下面的代码片段显现了运用DDP模块的示例。

  • 本例运用nn.Linear层在第10行创立部分模型。
  • 然后,它在第11行将本地模型转化为分布式练习模型,并在第12行设置优化器。
  • 第14行到第23行是典型的前向传达、后向传达和优化器进程完结。

在这个玩具分布式练习示例中,第11行是将本地练习运用程序转化为分布式运用程序的唯一差异,它满足了非侵入性需求,还满足交互要求。结构器答应DDP查看模型结构和参数。结构完结后,本地模型将被分布式模型替换,然后分布式模型能够很容易地阻拦forward()调用以履行相应的必要操作。关于向后传达,DDP依靠向后钩子触发梯度规约,即,在损失张量上履行backward()时,autograd引擎将履行梯度规约。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

4.2 梯度规约

DDP中的梯度规约算法在过去的版别中有所发展。为了介绍当时完结的结构,让咱们从一个简略的处理计划开端,逐渐引入更多的杂乱性,并在PyTorch v1.5.0中运用当时版别。这还将解说第 4.1节中描绘的相同简略API怎么答应咱们装置各种功用优化算法。

4.2.1 A Naive Solution

如第4节最初所述,DDP经过让一切练习进程(1)从相同的模型状况开端,以及(2)在每次迭代中运用相同的梯度,来确保正确性。前者能够经过在DDP构建时将模型状况从一个进程播送到一切其他进程来完结。为了完结后者,一个简略的处理计划是:能够在本地向后传达之后和更新本地参数之前刺进梯度同步阶段。可是,第4.1节中显现的API没有为此阶段供给清晰的进口点,因为backward()和step()之间没有任何内容。走运的是,PyTorch autograd引擎承受定制的后向hook。DDP能够注册autograd钩子,以在每次向后传达后触发核算。钩子函数被激发时,每个钩子扫描一切部分模型参数,并从每个参数检索梯度张量。然后,它运用AllReduce 调集通讯操作来核算一切进程中每个参数的均匀梯度,并将成果写回梯度张量。

Naive Solution 作业正常,但存在两个功用问题:

  • 调集通讯在小张量上体现欠安,这在具有大量小参数的大型模型上尤为杰出。

  • 将梯度核算和同步分离会因为两者之间的硬鸿沟而损失核算与通讯堆叠的时机。

4.2.2 Gradient Bucketing

梯度bucketing的思维是依据这样一个观察,即调集通讯在大张量上更有用。下图(a)和(b)供给了定量视图,显现了AllReduce 60M torch.float32的总履行时刻。每个AllReduce的参数数量不同。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

为了最大限度地进步带宽利用率,一切reduce操作都是异步发动的,并一同堵塞等候一切操作,以便模仿DDP的梯度归并算法。实验在一台支撑NVLink[3]的服务器上进行,该服务器带有两个NVIDIA Quadro GP100 GPU。NCCL AllReduce直接在CUDA输入张量上运转,而Gloo AllReduce则在CPU输入张量上运转,以便消除在运用Gloo后端时将CUDA内存仿制到CPU内存的开支。关于NCCL和Gloo,当运用较大的输入张量时,总通讯时刻明显削减。Gloo在每个输入张量约500K参数时达到最高速度,而NVLink上的NCCL乃至没有20M参数GPU张量的明显饱满信号。

这些实验标明,假如DDP在短时刻内等候并将多个梯度存储到一个AllReduce操作中,它能够完结更高的吞吐量和更低的推迟,而不是在每个梯度存储可用时当即发动专用的AllReduce。这关于具有许多小参数的模型尤其有用。可是,DDP不应在一个AllReduce中传输一切数据,不然,在核算结束之前无法发动任何通讯。上图(c)和(d)显现了包括大约60M参数的ResNet152 的GPU和CPU反向核算时刻。X轴是预备好的梯度数量,Y轴是自向后传达开端以来经过的时刻。GPU上的后向传达大约需求250毫秒才能完结,这与NVLink上的NCCL的数量级相同。这一定论也适用于Gloo和CPU后向传达。这些丈量预示着,关于相对较小的bucket巨细,DDP能够在向后传达的一同发动AllReduce操作,以使通讯与核算堆叠,这将改变每次迭代的推迟。

4.2.3 Overlap Computation with Communication

在梯度上的AllReduce操作能够在本地向后传达完结之前开端。运用bucketing,DDP需求等候同一个bucket中的一切内容,然后开端发动通讯。

在这种设置下,只是在向后传达结束时触发AllReduce不再满足。咱们需求对更频繁的信号作出反应,并更迅速地发动 AllReduce。因而,DDP为每个梯度累加器注册一个autograd hook。Hook 在其相应的累加器更新梯度之后被触发,并将查看其所属的bucket。假如相同桶中一切梯度的钩子都已触发,则最终一个钩子将触发该桶上的异步AllReduce。

有两点需求注意。

  • 首要,一切进程的归并次序有必要相同,不然,AllReduce内容或许不匹配,导致不正确的归并成果或程序崩溃。可是,PyTorch在每次向前传达时都会动态地构建autograd图,不同的进程或许在梯度安排妥当次序上不共同。下图(a)给出了一个示例,其间两个垂直轴表明时刻,虚线表明梯度预备安排妥当的时刻。在进程1中,四个梯度按次序核算,但梯度g2在进程2的g3和g4之后核算。在这种情况下,假如一切进程都在预备安排妥当后当即AllReduce bucket,则AllReduce内容将不匹配。因而,一切流程有必要运用相同的bucketing次序,而且没有流程能够在装载bucket i之前 就在bucket i+1上发动AllReduce。假如bucket 0是最终一个预备安排妥当的bucket,那么通讯就不或许与核算堆叠【笔者:因为 bucket 0 是最终安排妥当的,所以其他bucket在这之前都不会被履行核算,就不能与通讯堆叠了】。PyTorch v1.5.0经过运用model.parameters()的相反次序作为bucketing次序来处理此问题,咱们做如下假设:层(layers)或许依照正向进程中调用的相同次序进行注册。因而,其反向次序便是反向进程中的梯度核算次序的近似表明。诚然,这并不是一个完美的处理计划,但它是一个近似计划,咱们能够用最少的工程开支来完结它。
  • 其次,一次练习迭代或许只触及模型中的一个子图,而且子图在每次迭代中或许不同,这意味着在某些迭代中或许会越过某些Gradient。可是,因为 gradient-to-bucket 的映射是在构建时确认的,这些缺少的梯度将使一些bucket 永远看不到最终的自动装载hook,而且无法将bucket符号为安排妥当。因而,向后传达或许会暂停。下图(b)示出了一个示例,其间在一次迭代中越过了与梯度g3相对应的参数,导致g3缺少安排妥当信号。为了处理这个问题,DDP从前向传达的输出张量遍历autograd图,以找到一切参加的参数。这些参加张量的预备安排妥当是结束向后传达完结的有用信号。因而,DDP能够经过在向前传达结束时主动符号剩余的参数梯度来防止等候。请注意,此更改并不阻碍咱们开发非侵入式API,因为运用程序能够直接调用DDP上的forward函数,而且DDP能够轻松地将此进程刺进其成员函数中。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

下面算法给出了DDP的伪码。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

Constructor包括两个首要进程,播送模型状况和装置autograd挂钩。DDP的 forwad 函数是本地模型 forwad 函数的简略包装器。它遍历autograd图以相应地符号未运用的参数。autograd钩子将内部参数索引作为输入,这有助于找到参数张量及其所属规模。它将部分梯度写入bucket中的正确偏移量,然后发动异步AllReduce操作。伪代码中省掉了一个附加的结束进程,它等候AllReduce操作,并在反向进程结束时将值写回梯度。

下图阐明晰DDP在向前和向后传达期间怎么与部分模型交互。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

上述处理计划适用于大多数用例。可是,因为DDP总是核算一切梯度的均匀值,并将它们写回parameter.grad字段,因而优化器无法区别梯度是否参加了最终一次向后传达。因为DDP和优化器的解耦规划,DDP没有旁侧通道向优化器暗示该信息。假如没有这些信息,练习进程或许会受到模型精度回归的影响,例如,当优化器运用梯度感知信息越过动量值更新时。为了处理这个问题,DDP应该只接触哪些的确触及向后传达的梯度。可是,因为在对等DDP进程中,前向/后向进程或许依然触及到部分缺失梯度,因而无法仅从部分autograd图中提取该信息。因而,DDP运用位图盯梢本地参数参加者,并发动另外一个AllReduce来搜集大局未运用的参数。不幸的是,因为元素类型或许不匹配,DDP无法将此位图合并到其他梯度AllReduce操作中。只要当运用程序显式地告诉DDP查找未运用的参数时,这种额定的开支才会出现,因而只要在必要时才会付出价值。

4.2.4 Gradient Accumulation

加快分布式数据并行练习的一种常用技能是降低梯度同步频率。在大局同步梯度之前,运用程序能够履行n次部分练习迭代,而不是在每次迭代中发动AllReduce。假如输入批次太大而无法装入设备,这也很有协助,因为运用程序能够将一个输入批次拆分为多个微批次,在每个微批次上运转部分向前和向后传达,而且仅在大批次的鸿沟处发动梯度同步。理论上,这应该产生与大批量数据一次性处理相同的成果,因为梯度将简略地累积到相同的张量。可是,这在某种程度上与第 4.2.3节中评论的梯度归并算法相冲突。该算法将在每次向前传递结束时将未运用的参数符号为安排妥当,而一次迭代中未运用的参数仍能够参加后续迭代。此外,DDP无法区别运用程序是否应该在向后或经过多次迭代累积梯度后当即调用optimizer.step()。因而,咱们需求为这个用例引入一个额定的接口(即,no_sync )。

在内部,no_sync 的完结非常简略。上下文管理器只是在进入和退出上下文时切换一个标志,该标志在DDP的forward 功用中运用。在 no_sync 。大局未运用参数的信息也会累积在位图中,并鄙人次通讯产生时运用。下面是一个示例代码段。

[源码解析] PyTorch 分布式(8) -------- DistributedDataParallel之论文篇

4.3 Collective Communication

分布式数据并行练习运用一种特别的通讯形式:每个参加者供给一个相同尺度的张量,并搜集一切参加者的大局和(global sum)。这能够经过如下方法来完结:首要是一个gather操作,然后运用点对点通讯对每个参加者进行部分归并(local reductions),但这将损失功用优化的时机。

DDP构建在调集通讯库之上,包括三个选项:NCCL、Gloo和MPI。DDPs从三个库中获取API,并将它们包装到同一个ProcessGroup API中。该称号预示着ProcessGroup希望多个进程作为一个组一同作业。

一切ProcessGroup实例经过运用调集服务(rendezvous service)一同结构,其间第一个实例将进行堵塞,一直等候,直到最终一个实例参加。关于NCCL后端,ProcessGroup为通讯保护一组专用的CUDA流,以便通讯不会阻挠默许流中的核算。因为一切通讯都是调集操作,因而一切ProcessGroup实例上的后续操作有必要在巨细和类型上匹配,并遵循相同的次序。对一切库运用相同的ProcessGroup API答应咱们运用相同的DDP完结来实验不同的通讯算法。例如,PyTorch v1.5供给了一个round-robin ProcessGroup完结,它获取ProcessGroup实例列表,并以循环方法向这些ProcessGroup实例发送调集通讯。经过运用round-robin ProcessGroup,在单个NCCL、Gloo或MPI处理组无法饱满链路容量的情况下,DDP能够获得更高的带宽利用率。

0x05 施行

在过去的几个版别中,DDP的完结已经演进了好几次。本节要点介绍PyTorch v1.5.0的当时状况。DDP完结一同存在于 Python和C++文件,Python 部分包括公开API和非功用要害的组件,C++供给中心梯度归并算法。Python API 经过Pybind11来调用C++中心。

5.1 Python前端

DDP nn.module在distributed.py中完结,它包括面向用户的组件。组件包括结构函数、forward 函数和 no_sync 上下文管理器。除了在第4节中强调的一般思维外,Python前端中还有几个塑造DDP行为的完结细节。

DDP结构器API中公开了Configurable Knobs,包括

  • process_group,用于指定DDP运转AllReduce的流程组实例,这有助于防止和默许流程组混淆;

  • bucket_cap_mb,用于操控AllReduce bucket巨细,运用程序应调整此以优化练习速度,

  • find_unused_parameters,来切换DDP是否应检测未运用的参数,DDP是经过遍历autograd图来完结检测的。

本地模型中的模型设备关联性(Model Device Affinity )也操控DDP的行为,特别是当模型跨过多个设备时,这在模型太大而无法装入单个设备时很常见。关于大型模型,运用程序能够将模型的不同层放置在不同的设备上,并运用Tensor.to(device) API将中心输出从一个设备移动到另一个设备。DDP也适用于多设备模型。只要将 device_ids参数设置为None或空列表,DDP就会查看模型,履行健全性查看并相应地运用装备。然后,将多设备模型视为一个整体。

当一个层需求盯梢运转方差和运转均匀值(例如BatchNorm)等状况时,模型缓冲区(Model Buffers)是必要的。DDP经过让rank 0 的进程获得支撑模型缓冲区的权限。假如模型包括缓冲区,DDP在本地模型上开端前向传递之前,将缓冲区值从rank 0进程播送到一切其他进程。此行为也与no_sync形式兼容。当启用no_sync形式时,它会在正向进程中正确设置一个标志,以指示它是否希望鄙人一个反向进程中履行梯度规约。假如通讯产生,DDP将在随后的前向传递之前播送缓冲区。

5.2 Core Gradient Reduction

首要的开发作业花费在gradient reduction上,因为这是DDP中与功用最相关的进程。该完结存在于reducer.cpp中,它由四个首要组件组成,即:

  • 构建参数到桶的映射。
  • 装置autograd hook。
  • 发动bucket AllReduce
  • 检测大局未运用的参数。

咱们接下来阐述这四个组成部分。

参数到桶映射(Parameter-to-Bucket Mapping)对DDP速度有相当大的影响。在每次向后传达中,将一切参数梯度中的张量仿制到桶中,并在AllReduce之后将均匀梯度仿制回桶中。为了加快仿制操作,存储桶始终与参数在同一设备上创立。假如模型跨过多个设备,DDP会考虑设备关联性,以确保同一存储桶中的一切参数都坐落同一设备上。AllReduce的次序也会对成果产生影响,因为它决议了多少通讯能够与核算堆叠。DDP按model.parameters()的相反次序发动AllReduce。

Autograd Hook是DDP在后向传达中的切入点。在构建进程中,DDP遍历模型中的一切参数,在每个参数上找到梯度累加器,并为每个梯度累加器装置相同的post hook函数。梯度累加器将在相应的梯度预备安排妥当时,会触发post hooks,DDP将核算出整个桶何时全部安排妥当,这样能够发动AllReduce操作。可是,因为无法确保梯度预备的次序,DDP不能挑选性地挑选装置挂钩的参数。在当时的完结中,每个bucket都保存一个挂起的梯度计数。每个post-hook函数都会递减计数,当计数为零时,DDP会将一个桶符号为安排妥当。鄙人一次向前传达中,DDP会为每个桶补齐待定的累积计数。

Bucket AllReduce是DDP中通讯开支的首要来历。一方面,在同一个桶中装入更多的梯度将削减通讯开支的摊销体系。另一方面,因为每个桶需求等候更多的梯度,因而运用较大的桶尺度将导致更长的归并等候时刻。因而,桶巨细是要害的权衡。默许情况下,每个存储桶的巨细为25MB。运用程序应该依据经验丈量其影响,并将其设置为其用例的最佳值。

大局未运用参数(Globally Unused Parameters)的梯度在向前和向后进程中应坚持不变。检测未运用的参数需求大局信息,因为在一个DDP进程中,一个参数或许在一次操作中不存在,但或许在另一个进程的同一次迭代中参加练习。因而DDP在位图中保护本地未运用的参数信息,并发动额定的AllReduce以搜集大局位图。因为位图比张量尺度小得多,因而模型中的一切参数同享同一位图,而不是创立每桶位图(per-bucket bitmaps)。位图坐落CPU上,以防止为每次更新发动专用CUDA内核。可是,某些ProcessGroup后端或许无法在CPU 张量上运转AllReduce。例如,ProcessGroupNCCL仅支撑CUDA张量。此外,因为DDP应该与任何定制的ProcessGroup后端一同作业,它不能假设一切后端都支撑CPU张量。为了处理这个问题,DDP在同一设备上保护另一个位图作为第一个模型参数,并调用非堵塞拷贝操作(non-blocking copy)将CPU位图移动到设备位图以进行调集通讯。

0xEE 个人信息

★★★★★★关于生活和技能的思考★★★★★★

微信公众账号:罗西的思考

0xFF 参考

www.vldb.org/pvldb/vol13…