PipeDream

PipeDream核心在于解决两个问题:

  1. 对于一个给定的模型与分布式系统,如何划分任务(即哪个节点负责哪些layer,某些layer是数据并行还是模型并行)
  2. 对于流水线模型,如何避免流水线本身带来的训练的问题。

C795AA4E-2947-4F1D-B8DC-AD46E68146E2.webp

如何划分任务

简单的说就是一个动态规划模型:把M层的网络分给N个节点算,最短的时间要么是M-1层分给N-1个节点算,或者M-1层分给N-2个节点算,或者blabla

方程如下
F2AC0B7E-2E7D-4EC0-8F9B-168135E5BB3C.webp

具体步骤:

  1. 对模型进行profiling,得到
    1. 每层layer前向和后向的计算时间
    2. 每层layer的输出的大小
    3. 每层layer参数的大小
  2. 根据profiling的结果,使用动态规划对模型进行划分,将模型划分为不同的stage,以及每个stage的replication(数据并行)数。

如何解决精度问题

目前最主流的分布式深度学习框架用的都是sync SGD,就是每轮iteration结束之后所有机器统一传输一下grad,以此来更新自己的weights从而进行下一轮训练。
坏处是每轮都得等最慢的机器做完
好处是这样彻底模拟了单节点的训练模式,精度最高

但是 pipedream 显然不能使用 sync SGD。

weight stashing

我们来看一下Machine2,发现当他在forward算第5个batch(图中第二行深蓝色的5)的时候,它用的weights是更新两次的(即前面浅绿色的1和2更新了两次参数),而当backward算第5个batch(图作用第二行浅绿色的5)时,用到的weights是更新了4次的(即前面浅绿色的1,2,3,4)。无疑这种做法彻底改变了单节点深度学习的很多假设,自然会降低训练的效果(准确率下降)(出现这种情况有一个假设就是梯度的计算依赖weights,如果是类似relu, max pool这样的,就不会出现这种问题)

针对于这个问题,作者提出了Weight Stashing,思路很简单,就是每个node多备份几个版本的weights,forward用哪个weights算的,backward就还用它

Vertical Sync

353E36AA-0AFA-40E8-B0DF-228A8790B264.webp
显然还是有一些差距:我们现在fwd的时候,经过的每一层计算的时候,它们被更新了不同的次数,有没有什么办法可以更进一步?

也是有的,那就是每次forward的时候,都按照更新最少的那些weights来算。举个例子,我们在算第5个batch的时候(图中深蓝5),Machine1算的时候,weight自然就是更新了一次的。它算完得到的output扔给Machine2的时候,Machine2也用只更新了一次的weight算(就好像浅绿色的2没有用),算完得到的output扔给Machine3,Machine3也用只更新了一次的算(就好像浅绿色的2,3没有用)

不过作者也坦言,这个优化提升效果不太大,最重要的还是第一个优化(即fwd,bwd用同样的weights

Work Scheduling

  1. startup state 在训练开始的阶段,输入的stage的先读入足够多batch的数据,以保证pipeline在稳定阶段时,各个设备上都有相应的工作
  2. 采用 1F1B(one-forward-one-backward) 的调度模式,即每台机器上交替的进行前向后向计算。这种方案可以使得每个GPU上都会有一个batch的数据正在被处理,整个pipeline是比较均衡的,同时也能确保以固定周期执行每个stage上的参数更新。
  3. 对于使用数据并行的stage,采用 round-robin 的方式将任务分配在各个设备上,需要保证一个batch的数据在前向和后向发生在同一台机器上,这套策略称为 1F1B-RR(one-forward-noe-backward-round-robin)。

29A1F655-3B27-440D-9449-A550F7B0E878.webp

https://zhuanlan.zhihu.com/p/336849279
https://zhuanlan.zhihu.com/p/113416860