数据建模的基本流程 分布式数据处理的概念( 二 )


初始方案足以完成想要的目标,但存在两项性能缺陷 。聚合通信在小型 tensor 上性能表现很差,这种缺陷在带有大量小参数的大型模型上尤为突出 。由于两者之间存在界限,分别进行梯度计算和同步化会造成通信重叠计算机会的缺失 。
梯度分桶(bucketing )
梯度分桶的观点是受聚合通信在大型 tensor 上更加高效的启发而提出的 。
下图 2(a)和 (b) 给出的定量视图展示了在每个 AllReduce 中参数数目不同的情况下,AllReduce 60M torch 的 float32 参数的完整执行时间:

数据建模的基本流程 分布式数据处理的概念


这些实验表明,不用等到每个梯度 tensor 都可用时再启动 AllReduce,DDP 在等待较短的时间并将多个梯度存储到一个 AllReduce 操作中时,就可以实现更高的吞吐量和更短的延迟 。
通信重叠计算
在使用分桶的情况下,DDP 只需在启动通信之前在同一个 bucket 中等待所有的内容 。在这样的设置下,在反向传播的最后触发 AllReduce 就显得不足了 。因此需要对更加频繁的信号做出相应,并且更加迅速地启动 AllReduce 。因此,DDP 为每个梯度累加器都注册了 autograd 钩子 。
下图 3(a)的示例中,两个竖直轴表示时间,虚线代表梯度准备就绪的时间 。进程 1 中,4 个梯度按顺序计算 。进程 2 中,g_2 在 g_3 和 g_4 之后计算;图 3(b)的示例中,梯度 g_3 对应的参数在一次迭代中被跳过了,导致 g_3 的就绪信号缺失 。

数据建模的基本流程 分布式数据处理的概念


为了解决这个问题,DDP 遍历了前向传播的输出 tensor 中的 autograd 图以找到涉及到的所有参数 。涉及到 tensor 的就绪状态足以充当反向传播完成的信号 。
以下算法 1 给出了 DDP 的伪代码:

数据建模的基本流程 分布式数据处理的概念


下图 4 展示了 DDP 在前向传播和反向传播过程中如何与本地模型交互:

数据建模的基本流程 分布式数据处理的概念


梯度累加
此外,DDP 无法分辨应用程序是计划在反向传播之后立即调用 optimizer.step()还是通过多次迭代累加梯度 。因此,研究者需要为这个用例再引入一个接口(即 no sync) 。以下是样例代码片段:

数据建模的基本流程 分布式数据处理的概念


聚合通信
DDP 是在集合通信库基础上建立的,包括 3 个选项 NCCL、Gloo 和 MPI 。DDP 采用了来自这三个库的 API,并将它们封装进同一个 ProcessGroup API 中 。
由于所有的通信都是聚合操作,因此所有的 ProcessGroup 实例上的后续操作必须和其类型匹配并遵循相同的顺序 。对所有的库使用同一个 ProcessGroup API 允许研究者在相同的 DDP 实现上试验不同的通信算法 。
如果单一 NCCL、Gloo 或 MPI 的 ProcessGroup 无法使链路容量达到饱和,通过使用循环的 ProcessGroups,DDP 可以获得更高的带宽利用率 。
具体实现
DDP 的实现在之前的几个版本中已经改进了多次 。研究者介绍了当前 PyTorch v1.5.0 的状态 。DDP 同时在 Python 和 C++ 上都可以实现,Python 开放了 API 并组成了非性能关键因素组件,而 C++ 提供了核心梯度下降算法 。Python API 通过 Pybind11 的 API 调用了 C++ 内核 。
Python 前端
Python 前端中的实现细节决定了 DDP 的行为 。可配置的 Knobs 在 DDP 构造函数 API 中开放 。具体包括:
分组处理以找出 DDP 中运行 AllReduce 的进程组实例,它能够帮助避免与默认进程组混淆;
bucket_cap_mb 控制 AllReduce 的 bucket 大小,其中的应用应调整 knob 来优化训练速度;

推荐阅读