一种替代办法就是放弃训练算法的同步性质,去除所有 GPU 在锁步中遍历梯度下降迭代的约束。然而,尽管这可以使得你模型的并行处理更加简便,去除了这些限制的算法(各种异步随机梯度下降)还是会很难调试,因为有些模型会收敛到欠佳的结果上。不过由于这篇博文意不在此,我们就不在这里考虑它了。 不过,我们可以通过使用来自高性能计算领域的分布式简约算法(distributed reduction algorithms)并利用带宽优化环衰减(bandwidth-optimal ring allreduce)来解决通信问题。 Ring Allreduce 上述简单通信策略的主要问题是通信成本随系统中的 GPU 数量线性增长。相反,ring allreduce 是这样一种算法——其通信成本是恒定的,与系统中的 GPU 的数量无关,并且仅由系统中的 GPU 之间的最慢连接来确定。事实上,如果在通信成本上你只考虑带宽这一因素(并忽略延迟),那么 ring allreduce 就是一个最佳的通信算法 [4](当你的模型较大时,这是一个很好的通信成本估算,你需要在较少的次数内发送大量数据)。 Ring Allreduce 中的 GPU 被布置在一个逻辑环路(logical ring)之中。每个 GPU 左右两个各有一个 GPU,并且只从左边的 GPU 接收数据,再把数据发送至右边的 GPU。
被布置在逻辑环中的 GPU 算法的进行分两步:第一步,scatter-reduce;第二步,allgather。在第一步中,GPU 将交换数据,使得每个 GPU 最终都有一个最终结果的数据块。在第二步中,GPU 将交换那些块,使得所有 GPU 最终得到完整的最后结果。 Scatter-Reduce 为了简单起见,让我们假设目标是以元素方式求和浮点数的单个大数组的所有元素。在系统中有 N 个 GPU, 其中每个 GPU 有一个相同大小的数组。在 allreduce 的最后,每个 GPU 都应该有一个同样大小的包含了原数组中数值的和的数组。 一开始,GPU 把数组分割成 N 个较小的块(其中 N 是 GPU 在环中的数量)。
接着,GPU 会执行 N-1 次迭代 scatter-reduce。在每一次迭代中,GPU 将发送其中一个块到右边的 GPU,并从左边的 GPU 接收一个块,把数据累积进该块。在每一次迭代中,被发送的块和被接收的块是不同的。第 n 个 GPU 以发送块 n 和接收块 n – 1 开始,并从那儿接着向后运行。每次迭代发送的块即是上次迭代所接收的块。 例如,在第一次迭代中,上图表中的 5 个 GPU 将会发送和接收以下的块: GPU 发送 接收 0 Chunk 0 Chunk 4 1 Chunk 1 Chunk 0 2 Chunk 2 Chunk 1 3 Chunk 3 Chunk 2 4 Chunk 4 Chunk 3
在 scatter-reduce 的第一次迭代中的数据传输 在第一次的发送和接收完成之后,每个 GPU 会有一个由两个不同 GPU 中的相同块的总和组成的块。例如,第二个 GPU 上的第一块将是来自第二个 GPU 和第一个 GPU 的那个块中的值的和。
scatter-reduce 的第一次迭代完成之后的中间和 在下一次迭代中,进程继续,直到最后,每个 GPU 会有一个块包含所有 GPU 中的那块的所有值的和。下面的图像演示了所有的数据传输和中间结果,从第一次迭代开始,一直持续到 scatter-reduce 结束。
scatter-reduce 数据传输(迭代 1)
scatter-reduce 数据传输(迭代 2)
scatter-reduce 数据传输(迭代 3)
scatter-reduce 数据传输(迭代 4)
所有 scatter-reduce 传输结束之后的最后状态 Allgather (责任编辑:本港台直播) |