详解 Double Binary Tree、Ring Reduce、2D-Torus Reduce、Butterfly Reduce 时间消耗

网上都没有人仔细讲一讲 Double Binary Tree 具体怎么操作的,我来简单讲解一下。具体的细节大家还是需要去看代码。

本文还汇总了各种Reduce方法,只想看 Double Binary Tree 可以直接跳转

目录

前置知识

Reduce + Broadcast

Recursive Halving and Doubling

Butterfly

Ring Reduce

分层Ring AllReduce

2D-Torus

Double Binary Tree

Reduce

Broadcast

All

其它

一些参考连接:

NVIDIA NCCL 源码学习(十二)- double binary tree-CSDN博客

AllReduce 算法的前世今生 – remaper

https://zhuanlan.zhihu.com/p/79030485

Massively Scale Your Deep Learning Training with NCCL 2.4 | NVIDIA Technical Blog

前置知识

Allreduce 操作现在是分布式训练中不可或缺的一步操作。Allreduce 操作可以分解为 Reduce 和 Broadcast 两个操作,所以绝大部分算法中只需要分别考虑这两个操作怎么进行的即可。

Reduce 和 Broadcast 的传输时间都是几乎一样的(所以在计算时间时能看到很多系数2,只是Reduce 在不考虑别的优化情况(如pingpong)下会多出一个计算的时间)

以深度学习模型训练中的数据并行为例需要对梯度进行AllReduce操作(因为要汇总梯度,再在所以设备上同步)。Reduce 就是将所有GPUs上的梯度相加求均值,Broadcast 就是分发给所有 GPUs 同步参数。

图中实线是实际发生了通信;虚线是示意,并没有实际通信。

公式中使用的变量

Reduce + Broadcast

直接reduce汇总在一个节点上,再由该节点 Broadcast

Time Cost = 2(\alpha + \frac{S}{B}) + SNC

缺点:计算和传输的负荷全部都在d上,且随节点数线性增加啊

Recursive Halving and Doubling (Tree)

非常普通的树状,每次都会传输大小为S的数据,Reduce和Broadcast都需要log_2N

Time Cost = log_2N\times(2\alpha+2\frac{S}{B}+SC)

缺点就是带宽利用不充分。比如Reduce时,叶节点只发送数据,不接收,因此只利用了带宽的一半。有一半的节点没有进行接收操作。比如图中第一步,a->b,c->d发送数据的时候,b和d节点的发送带宽没有被利用起来。同理,Broadcast时,叶节点只接收数据,不发送。

并且每一步都需要传输完整的数据块(如图的树是不好对数据分块的。比如d在接受c的第二块数据时,b同时传来了第一块数据,没有办法同时进行)

Butterfly

Time Cost = log_2N\times(\alpha+\frac{S}{B}+SC)

相当于传输和接收一起进行的tree。值得注意的是,该方法不需要再Broadcast了

充分利用了接收和发送的带宽

但是每一步都需要传输完整的数据块(和Tree一样不好切块)

Ring Reduce

RingReduce的讲解太多了,我就不仔细分析了

第一阶段reduce数据

Time Cost Per Chunk= \alpha+\frac{S}{NB} + \frac{S}{N}C

第二阶段broadcast数据

Time Cost Per Chunk= \alpha+\frac{S}{NB}

两个阶段都需要进行N-1

\begin{aligned} Time Cost &= 2(N-1)\times(\alpha+\frac{S}{NB}) + (N-1)\times\frac{S}{N}C \\ & = (N-1)\times(2\alpha+2\frac{S}{NB}+\frac{S}{N}C ) \\ &\approx 2N\alpha+2\frac{S}{B}+SC \end{aligned}

分层Ring AllReduce

https://arxiv.org/pdf/1807.11205

组内reduce->组间AllRuduce->组内broadcast

n :组内的节点个数 

m :组的个数

N=nm

第一阶段组内普通的reduce

论文中一共16个GPU,每个组4个,直接使用Reduce + Broadcast(组内节点少,带宽大)

Time Cost = nSC+\alpha+\frac{S}{B}

第二阶段组间Ring Reduce

Time Cost = 2(m-1)\times(\alpha+\frac{S}{mB}) + (m-1)\times\frac{S}{m}C

第三阶段组内Broadcast

Time Cost = \alpha+\frac{S}{B}

总的时间

\begin{aligned} Time Cost &= 2(m-1)\times(\alpha+\frac{S}{mB}) + (m-1)\times\frac{S}{m}C + 2(\alpha+\frac{S}{B})+nSC \\ &\approx 2(m+1)\alpha+4\frac{S}{B}+(n+1)SC \end{aligned}

2D-Torus

https://arxiv.org/pdf/1811.05233

主要思想也是分层,是组内reduce->组间allreduce->组内broadcast。

相当于两个ring(组间ring,组内ring)

n :组内的节点个数 

m :组的个数

N=nm

第一阶段scatter-reduce通信步数n-1

Time Cost Per Chunk= \alpha+\frac{S}{nB} + \frac{S}{n}C

Time Cost = (n-1) (\alpha+\frac{S}{nB} + \frac{S}{n}C)

第二阶段allreduce是m个节点的RingReduce

Time Cost = 2(m-1)\times(\alpha+\frac{S}{mB}) + (m-1)\times\frac{S}{m}C

第三阶段allgather通信步数n-1

Time Cost Per Chunk= \alpha+\frac{S}{nB}

Time Cost = (n-1) (\alpha+\frac{S}{nB})

整体耗时大概是

\begin{aligned} Time Cost &= 2(m-1)\times(\alpha+\frac{S}{mB}) + (m-1)\times\frac{S}{m}C + 2(n-1)\times(\alpha+\frac{S}{nB}) + (n-1)\times\frac{S}{n}C \\ &\approx 2(m+n)\alpha+4\frac{S}{B}+2SC \end{aligned}

Double Binary Tree

基于 double binary tree 的 AllReduce

《Two-Tree Algorithms for Full BandwidthBroadcast, Reduction and Scan》

double binary tree 于 2009 年在 MPI 中引入,并随后在 NCCL2.4 中也引入了此实现:https://developer.nvidia.com/blog/massively-scale-deep-learning-training-nccl-2-4/

内容

基础知识

朴素的树状 Reduce 带宽利用不充分,broadcast时叶节点只接收数据,不发送。reduce时叶节点只发送数据,不接收。并且每一步都需要传输完整的数据块

Double Binary Tree 分别构造两棵树。Tree1和Tree2会同时运行!这样使得双向带宽能被同时利用

不难看出Tree1中的叶节点/中间节点,在Tree2中变成了中间节点/叶节点。这样就能保证所有节点既是叶节点也是中间节点,因此不会出现一棵树时的情况 (所以收发带宽都能用上)

如图,将Tree1和Tree2的边按一定规律染成红色或者黑色(如上图),从而有如下很好的性质:

  • 不会有节点在Tree1和Tree2中连到父节点的边的颜色相同,比如node0在Tree1中通过红色的边连到父节点,那Tree2中node0一定通过黑色的边连到父节点。

  • 不会有节点连到子节点的边颜色相同,比如node3在Tree1中是中间节点,如果node3通过红色的边连到右子节点,那么node3一定通过黑色的边连到左子节点

根据上述性质,就有了两棵树的工作流程,在每一步中从父节点中收数据,并将上一步中收到的数据发送给他的一个子节点,比如在偶数步骤中使用红色边,奇数步骤中使用黑色边,这样的话在一个步骤中可以同时收发,从而利用了双向带宽。

操作

在每一个通信步中

Node x从两棵树中其中一个父节点接受数据;Node x给其作为中间节点的树中的一个子节点传输之前的数据(从哪个父节点和给哪个子节点都是一个固定好的schedule)

Broadcast(需要将node i的数据传输给所有的节点):

开始时,node i 的操作:

  1. 将node i 的数据分为两块,可以记为黑色数据红色数据

  2. 第一时刻:node i 将黑色数据据沿着黑色边传递给子节点

  3. 第二时刻:node i 将红色数据据沿着红色边传递给子节点

任意时刻的所有节点(注意,需要对数据分块流水起来才有这个效果!):

  1. 黑色边的父节点接受黑色数据 i+1,沿黑色边给子节点传输黑色数据 i
  2. 红色边的父节点接受红色数据 i+1,沿红色边给子节点传输红色数据 i
  3. 重复1和2

耗时

Reduce

reduce相当于就是反向的broadcast(子给父节点传数据)。假设node i需要汇聚所有节点的结果,Tree1把一半的的数据加上,同时Tree2 加另一半的数据

Time Cost Per Step = \alpha+\frac{S}{B}+{S}C

如果将数据切分为2k块

Time Cost Per Step = \alpha+\frac{S}{2kB}+\frac{S}{2k}C

Broadcast

Time Cost Per Step = \alpha+\frac{S}{B}

如果将数据切分为2k块

Time Cost Per Step = \alpha+\frac{S}{2kB}

Time Cost = 2\alpha log_2N+\frac{S}{B}+\sqrt{8log_2N\alpha\frac{S}{B}}

All

Reduce和Broadcast都最多需要执行2h+2k次,h = \lceil log_2N \rceil

Time Cost \leq (2h+2k) (2\alpha+2\frac{S}{2kB}+\frac{S}{2k}C)

k=\sqrt{\frac{Sh}{2\alpha B}}带入上式(这个值是论文中给的),可得:

\begin{aligned} Time Cost &\leq 4h\alpha + 2\frac{S}{B} + 2\sqrt{8h\alpha\frac{S}{B}} + (2h+2k)\frac{S}{2k}C \\ & \leq 4\alpha log_2N + 2\frac{S}{B} + 2\sqrt{8log_2N\alpha\frac{S}{B}} + (\sqrt{2log_2N \alpha S}+S)C \end{aligned}

 

普通的树耗时

Time Cost = log_2N\times(2\alpha+2\frac{S}{B}+SC)

因为充分利用了带宽,可以明显看到比普通的树在数据传输上耗时少很多(直接与卡的数量无关了)。计算的耗时也少了很多。

其它

基于 spanning tree 的 AllReduce

《Blink: Fast and Generic Collectives for Distributed ML》

2D-Mesh

TPU节点可以同时进行2路send和2路recv,而我们普通的服务器都是只有一张网卡,只能同时进行1路send和1路recv

在TPU上,耗时2*(m+n-2)*( α+S/B+S*C)。

BlueConnect (3D-Torus)

基于异构环境下、拓扑感知的 AllReduce 框架 BlueConnect

《BlueConnect: Decomposing All-Reduce for Deep Learning on Heterogeneous Network Hierarchy》

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Java中的reduce()方法是Stream API中的一个方法,它可以将流中的元素按照给定的规则进行合并并返回一个结果。reduce()方法的参数是一个函数式接口,它定义了合并的规则。 reduce()方法有两种重载形式: 1. T reduce(T identity, BinaryOperator<T> accumulator) 这个方法的作用是将流中的元素按照给定的二元操作函数进行合并,并返回合并后的结果。它接受两个参数: - identity:初始值,合并元素之前的初始值。 - accumulator:二元操作函数,用于将两个元素合并。 例如,下面的代码将一个字符串流中的所有元素连接起来: ``` String[] words = {"hello", "world", "java"}; String result = Arrays.stream(words).reduce("", (a, b) -> a + b); System.out.println(result); //输出helloworldjava ``` 2. Optional<T> reduce(BinaryOperator<T> accumulator) 这个方法的作用是将流中的元素按照给定的二元操作函数进行合并,并返回一个Optional对象,表示合并后的结果。它接受一个参数: - accumulator:二元操作函数,用于将两个元素合并。 例如,下面的代码将一个数字流中的所有元素相加: ``` int[] nums = {1, 2, 3, 4, 5}; Optional<Integer> sum = Arrays.stream(nums).reduce((a, b) -> a + b); System.out.println(sum.get()); //输出15 ``` 需要注意的是,reduce()方法返回的结果是一个Optional对象,因为流中可能没有元素。如果流中没有元素,则返回一个空的Optional对象。因此,在使用reduce()方法时,需要判断Optional对象是否为空,以避免空指针异常。 总之,reduce()方法是一个非常有用的方法,可以将流中的元素按照给定的规则进行合并,并返回一个结果。它是函数式编程中的一个重要概念,也是Java 8中Stream API的核心方法之一。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值