• DP
    • 参数服务器
  • DDP
    • 分布式数据并行,即使用ring all-reduce来同步各设备上的梯度。
  • FSDP zero的torch实现

DP与DDP

部分内容摘自 图解大模型训练之:数据并行上篇(DP, DDP与ZeRO) - 知乎

假设模型参数W的大小为 ,GPU个数为 。则梯度大小也为,每个梯度块的大小为

对单卡GPU来说(只算其send通讯量):

  • Reduce-Scatter阶段,通讯量为
  • All-Gather阶段,通讯量为

单卡总通讯量为,为理论最优。(见集合通信)。随着N的增大,可以近似为。全卡总通讯量为

而对前文的DP来说,它的Server承载的通讯量是 ,Workers为 ,全卡总通讯量依然为 虽然通讯量相同,但搬运相同数据量的时间却不一定相同。DDP把通讯量均衡负载到了每一时刻的每个Worker上,而DP仅让Server做勤劳的搬运工。

总结

1、在DP中,每个GPU上都拷贝一份完整的模型,每个GPU上处理batch的一部分数据,所有GPU算出来的梯度进行累加后,再传回各GPU用于更新参数
2、DP多采用参数服务器这一编程框架,一般由若个计算Worker和1个梯度聚合Server组成。Server与每个Worker通讯,Worker间并不通讯。因此Server承担了系统所有的通讯压力。基于此DP常用于单机多卡场景。
3、异步梯度更新是提升计算通讯比的一种方法,延迟更新的步数大小决定了模型的收敛速度。
4、Ring-AllReduce通过定义网络环拓扑的方式,将通讯压力均衡地分到每个GPU上,使得跨机器的数据并行(DDP)得以高效实现。
5、DP和DDP的总通讯量相同,但因负载不均的原因,DP需要耗费更多的时间搬运数据

上面要求单个设备能放下整个模型,但是如果放不下模型应该怎么办呢?可以考虑流水线并行
张量并行,以及下面的ZeRO

ZeRO优化器

paper Zero 论文精读【论文精读】_哔哩哔哩_bilibili

背景知识

混合精度训练

混合精度训练使用fp16来进行前向计算和反向传播,数据,模型参数,中间激活值,以及算出来的梯度,都是fp16. 但是为了能够稳定更新参数,还使用fp32存储了优化器状态和另一份用于更新的模型参数。训练时会用fp16的梯度更新fp32的优化器状态,然后再更新fp32的模型参数。最后复制一份fp16版本的模型参数,用于下一步的梯度计算。

假如模型参数为 7.5B,使用fp16存储模型参数则需要显存 15GB,fp16的梯度同样需要的存储空间。假如使用Adam优化器,需要存储两个32fp的优化器状态,最后还需要存储用于更新的fp32版本的模型参数

模型状态(Model State):包含梯度(fp16),模型参数(fp16),优化器状态(fp32),模型参数(fp32)。一共为

剩余状态(residual states): 除了模型状态之外的显存占用,包括激活值(activation)、各种临时缓冲区(buffer)以及无法使用的显存碎片(fragmentation)。

在单卡训练的时候,上面这部分参数其实没有什么优化的余地。但是在数据并行(DP)时,这些Model State memory每个设备都要存一份,这里就存在冗余。论文中还对residual states memory进行了优化。

ZeRO-DP:对Model State Memory的优化

文中把对Model State Memory的优化称为ZeRO-DP,主要是下面这张图
图中的绿色部分是优化器状态,蓝色部分是fp16的模型参数,红色部分是算出来的梯度
表中的行表示不同的优化方式,列表示不同的设备上存储了什么数据。

分布式数据并行(DDP)

是说给每个设备复制一个模型,然后对每个模型使用对应的数据去训练。
具体过程如下:

  • 每个设备输入数据,根据fp16的模型计算出各自的梯度(fp16)
  • 对梯度使用all-reduce来同步,每个设备获得完整的梯度
  • 每个设备使用完全相同的梯度来更新优化器参数,来更新fp32版本的模型
  • 复制出一份新的fp16模型参数用于下一步训练
    这里对梯度的更新明显可以使用reduce-scatter,即让每个设备只存储一部分fp32的优化器状态,然后通过reduce-scatter来让每个设备手机自己所需要的fp16的梯度数据,最后通过all-gather来在设备之间同步更新后的fp16的模型参数。
    对总参数(fp16)进行了一次reduce-scatter,一次all-gather,因此单卡通信量为

:优化状态(3*fp32)和梯度(fp16)分片

这里所使用的优化就如同上面所说的,每个设备只管理自己的32fp的优化器状态。
具体过程如下

  • 每个设备输入数据,根据fp16的模型计算出各自的梯度
  • 对梯度使用reduce-scatter来同步,每个设备获得各自管理的那部分梯度(fp16)(图中红色部分)
  • 使用各自的梯度更新各自的优化器参数(fp32)(图中绿色部分)
  • 最后通过all-gather来将各自的新的模型参数(fp16)分发到其他设备上去,进行下一步训练

因为在实现上 ring all-reduce=ring reduce-scatter + ring all-gather,而且它们传输的数据大小也是一样的,所以通信时间相比baseline并没有增大!太爽了

假如有p个设备,通信带宽为。训练一步的通信量为,通信时间为
集合通信的分析

内存开销:
其中设备数为(为了和图上符号一致)

:对fp16的模型参数也进行分片

思路就是用额外的通信换显存。具体来说就是在需要fp16的模型参数的时候,再向其它设备要,也就是在backward计算前,多一次all-gather操作。通讯开销变为1.5倍。有人说只要梯度计算和异步更新做的好,通讯时间大部分可以被计算时间隐藏,因此这样的额外通讯开销,也是划算的。不知道是不是真是这样

计算流程如下:

  • 每个设备通过all-gather获得最新的fp16模型参数,用于前向传播
  • 前向传播计算,会产生大量中间激活值。同时我把不需要的fp16模型参数扔掉,这样就能增大batch size
  • 反向传播前,通过all-gather获得对应层的fp16模型参数
  • 反向传播时,每层计算输入梯度fp16,计算参数梯度fp16,同时删除用过的fp16的模型参数(可选,因为本来反向传播就是消耗激活值嘛,显存应该够用)。
  • 对梯度fp16进行reduce-scatter,每个设备获得各自管理参数的梯度,更新优化状态fp32。更新参数进行下一步训练。

这样,在forward完毕后,也就是内存峰值时,我其实不需要存储完整的模型参数,也就是说理论上内存开销:
其中设备数为(为了和图上符号一致)

(相对于流水并行,通信量要求是更多还是更少了呢?流水并行是传递激活值吧?似乎不太容易直接比较。不同设置下的MFU是多少呢?)

ZeRO-R: Optimizing Residual States Memory

下面是从图解大模型训练之:数据并行下篇( DeepSpeed ZeRO,零冗余优化) - 知乎那复制过来的,珠玉在前,复制粘贴

:Partitioned Activation Checkpointing

前面说过,对activation的存储是灵活的。不像optimizer states,gradients和parameters对模型更新是必须的,activation只是起到加速梯度计算的作用。因此,在哪几层保存activation,保存哪些activation都是可以灵活设置的。同样,我们也可以仿照以上切割方式,每块GPU上只维护部分的activation,需要时再从别的地方聚合过来就行。需要注意的是,activation对显存的占用一般会远高于模型本身,通讯量也是巨大的,所以这块要灵活、有效地实验设计。

:Constant Size Buffer

固定大小的内存buffer,它的目的在于:

  • 提升带宽利用率。当GPU数量上升,GPU间的通讯次数也上升,每次的通讯量可能下降(但总通讯量不会变)。数据切片小了,就不能很好利用带宽了。所以这个buffer起到了积攒数据的作用:等数据积攒到一定大小,再进行通讯。
  • 使得存储大小可控。在每次通讯前,积攒的存储大小是常量,是已知可控的。更方便使用者对训练中的存储消耗和通讯时间进行预估。

Memory Defragmentation

在前文提过,设置机制,对碎片化的存储空间进行重新整合,整出连续的存储空间。防止出现总存储足够,但连续存储不够而引起的存储请求fail

ZeRO-Offload与ZeRO-Infinity

ZeRO-Offload如图

  • forward和backward计算量高,大部分是矩阵乘法,计算量正比于batch size和模型大小。因此和它们相关的部分,例如参数W(fp16),activation,就全放入GPU。
  • update的部分计算量低,正比于模型参数大小,是逐点运算。因此和它相关的部分,全部放入CPU中。例如W(fp32),optimizer states(fp32)和gradients(fp16)等。
  • 从gpu到cpu,需要传送gradients(fp16)。从cpu到gpu,需要传送W(fp16)

很好的思路,但是问题是通信延迟有多大?可以被掩盖吗?cpu逐个参数更新状态速度如何(绿色部分)?整体上看能增加吞吐吗?
这些问题在文章中应该有回答
显存offload到cpu内存相关

ZeRO-Infinity

论文中文解读,代码就是DeepSpeed开源项目

ZeRO-Infinity结合了ZeRO系列的论文,直接把分布式训练的方案推到一个目前来说接近终点的位置,改论文探讨的模型大小甚至直接到达了Trillion参数量级别(1 Trillion=1000 Billion),因此有很多之前不用考虑的场景在这里都涉及到了。强烈推荐自行阅读论文或者解读,下面本文简单介绍下该论文的思路

从啥也不会到DeepSpeed————一篇大模型分布式训练的学习过程总结 - 知乎 - ZeRO-Infinity部分

牛的,有时间要看看比较好todo