分布式通信与并行策略
1.通信方式(NVIDIA NCCL标准)
1.1 Broadcast

1.2 AllGather

这里是使用的ring方法,即每个卡在每个时间段接收上一张卡的数据并发送当前卡的数据,每次发送第(k-1)步接收到的数据,如果k=1,即发送自己卡上的数据,经过N-1步(N为rank数),每张卡均可得到全量的信息。
1.3 ReduceScatter



目前比较常用的就是一个ring结构;reduceScater遵循以下规则,假设有n 个rank,首先会将每个rank上的tensor,切分成个块,紧接着将其按照如下规则,每一个时间步,都按以下规则发送和接收块tensor,并且接收的块做reduce,最终rank_i得到了第(rank_i+1)%num_ranks的结果。
规则:
第 k 步 (k 从 0 到 N-2, 这里 N=4):
- 发送:将本地缓冲区的第
(rank - k) % N块发送给下一个邻居(rank+1)。 - 接收:从上一个邻居(
rank-1)接收一个数据块,并把这个数据块与本地缓冲区的第(rank - k - 1) % N块相加(Reduce)。
1.4 Reduce

reduce的ring实现方法是reduce_scatter+p2p,即先进行reduce_scatter每张卡保留部分数据,然后直接进行p2p通信,都把自己卡上的数据发送给root。
1.5 AllReduce

Allreduce的底层一般分为两种实现,一种是ring,一种是tree,当然还有蝶形的,因为前面两者用的比较多,所以主要了解的是前两个。
首先环状结构的实现其实就是reduce_scatter+allgather的组合,先进行(n-1)步的reduce_scatter,每个rank发送(rank-k)%N块的数据给相邻的下一个rank,k表示步数,并且从上一个rank接收数据,并将此数据加在(rank-k-1)%N块数据上,也就是做reduce,最终每个rank都有完整out的部分块,并且合并起来就是完整的out,此时再做一个allgather即可让每个rank都得到完整out,总步数为2(n-1)。特点:带宽利用率高,每一时刻所有rank都在收发数据,但延迟较高。
其次是树状结构的实现,这个主要是解决Ring在大规模节点下延迟过高的问题,适合节点极多或中小数量的场景。它将节点组织成二叉树, 叶子节点把数据发送给父节点,直到根节点,即可拿到全局数据,然后使用Broadcast(向下广播),把总和发送给子节点。特点:带宽利用率低,且越接近根节点,通信压力越大,但延迟低,为2log2(N)。
当tensor小,节点数多,延迟高时,用tree,反之用ring。一般GPU用NV Link通信,都是用ring比较多。
1.6 All-To-All
All-To-All 将每个参与进程将其数据分割成 N 份(N 是参与进程的总数),并将第 i 份发送给第 i 个进程。每个进程都会向其他所有进程发送数据,并从其他所有进程接收数据。如下图,All-to-All的底层实现是用P2P实现,即点对点通信:

1.7 总结

这里怎么理解AllReduce = ReduceScatter + AllGather,主要是因为ReduceScatter 把每张卡的值汇总起来,做reduce(可能是sum,average等),然后把结果分成rank个sub_result部分,分别给每个rank,而AllGather再把这rank个sub_result给聚合起来到每个rank上,则每个rank都有完整的result。其机制就跟直接做allreduce一样,对每个rank的数据汇总做reduce,然后把result分到每个rank上。
reduce_scatter使得每个rank_i获得了第(rank_i+1)%num_ranks的结果。这是需要n-1步,然后all gather需要把每张卡上reduce的结果,分发到其它卡,再走n-1步,所以一共的通信步是2*(n-1)。
2. Paddle三类通信区分动手,动半,静态全自动

同时,在动手的案例中不会出现dist_tensor,shard这些东西,因为这是动半特有的,对通信进行标记使用shard,而tensor被shard处理后,即变成了dist_tensor。
3.分布式并行方式
分布式并行方式包括:数据并行(DP),张量并行(tp),流水并行(pp),专家并行(ep),序列并行(sp),上下文并行(Context Parallel)
3.1 数据并行。

1通信为了让数据并行模型参数保持一致,3通信是为了让梯度累加;在反向过程中,本层的∇x梯度计算完毕后,可以继续计算下一层的∇x梯度,而不需要等待∇w的梯度计算,做∇x梯度的计算时,又可以做∇w的allreduce通信,从而达到通信与计算重叠。
必须注意,∇x不需要做allreduce,只有∇w需要,因为∇w做allreduce是为了同步每一份相同参数的grad,最后更新后的参数也在DP组间保持一致,而∇x两张卡上是分别不同的batch数据计算得来的,没有数学依赖关系。
注意: 数据并行分两种,上面是DDP,即Distribute_DP是基于多进程实现的,还有一种是DP,是单进程多线程实现,如下图所示,它使用一个进程来计算模型权重。

注意: 二者效果是等价的,因为假设我们DP实现batchsize为64的数据量,对于DP来说,它会在计算loss时,汇总64条数据的output,然后和lable计算出loss,并得到一个平均的loss,再传给各GPU进行反向传播,此时每个grad就是平均之后的,即除以了64的。而DDP 则分给两个GPU各自去计算梯度,每个GPU计算的梯度也是mean的,不过是除以32的,所以要再allreduce一下,两个相加除以2,即同样是除以64。