Distributed Computing
划分
数据并行(DataParallelism)
将数据分为n份,发送到n个GPU上,每个GPU上都存在一个完整的大模型
缺点: 模型太大
Pipeline Parallelism(串行的)
将模型做split,每个GPU负责一部分,GPU1运行完后将结果发送给GPU2
缺点:
- 不好分割
- 分割成差不多的计算量是个大的问题
- 速度受限于最慢的GPU模块
- 可能会出现许多空闲状态的GPU
Tensor Parallelism
更细化的,在tensor维度上
会造成额外的通信
Data Parallelism
parameter server
Parameter server
分为两个部分
- Parameter Server : recieive gradients from workers and send back the aggregated results
- workers: compute gradients using splitted dataset and send to parameter server
这种方式不太适合大模型
步骤
replicate models to workers
split data to workers
compute gradient
Aggregate and synchronize gradient
Gradient update and update model parameters
All in one picture
Parameter server:代码
parameter server 通信-Communication:reduce and broadcast
one-to-many communication: a type of operations performed across all workers
- Reduce : 类似聚合,但是在聚合过程中进行平均或求和
- Broadcast: 向所有的workers发送相同的复制
Parameter server 的bottle neck(瓶颈)
parameter server主要起的作用就是同步信息的作用,不希望有类似server的节点:All-Reduce
Naive All reduce implementation
需要循环,每次传输所有的数据
Better All reduce implementation
每个节点只和旁边的节点做交互,也需要循环三次,但每次只传输旁边的一部分
更聪明的方式: Recursive Halving reduce(递归减半规约)
同上面的类似,也是临近的workers交换,对于8个worker来说,做了3次的iteration,然后交换间隔是20,21,232^0,2^1,2^320,21,23,这样可以将时间复杂度从O(N)降到O(logN)O(N)降到O(\log N)O(N)降到O(logN)
Zero-1/2/3 and FADP
如果我们训练一个非常大的大模型,那么即使是最好的GPU也没法完全将模型权重完全加载到内存中,然而,训练需要存储梯度和优化器
在fp32精度下,如果模型的weight占2bytes,那么其gradients大概也占2bytes左右,如果优化器使用Adam,其optimizer states因为要存储parameters, momentum 和variance,所以大概需要6倍(这个倍数取决于配置,再怎么配置一般也都是weight的三到四倍),即使是使用A100或者H100显卡(80G)来训练,最多也只能训练5.0B的模型
第一种方式
ZERO-1
没个GPU存放完整的额weight和gradients,分割optimizer states 到N个不同的GPU卡上,假设N=64,则这时候用80G的显卡,大概能训练19B参数量的模型
第二种方式
ZERO-2
相比zero-1,除了optimizer states,我们还将gradients也分布在不同的GPU上,假设N=64, 则这时候用80G的显卡,大概能训36B参数量的模型
第三种方式
ZERO-3
将optimizer states,gradients and weights都分布在不同的GPU上,假设N=64, 则这时候用80G的显卡,大概能训320B参数量的模型
在pytorch中,ZERO-3等价于
FSDP
(FullyShardedDataParallel),即所有的参数都做parallelism难点在于GPU之间的通信,如何将GPU前后向传播联合起来计算
Pipeline Parallelism
与数据并行不同,Pipeline直接对模型进行分割
Naive Implementation
下图表示的是4层网络在训练的时候,使用F代表Forward,B代表Backward,下面图中的(b)Training timeline,其横轴为时间轴,假设这4层网络分别存放在4个GPU上
所以计算的顺序为GPU0->GPU1->GPU2->GPU3->GPU3->GPU2->GPU1->GPU0,那么这四个GPU没个都使用了两个时间单元,占有率都是 28=0.25\frac{2}{8}=0.2582=0.25,这意味着其他75%的时间都是空闲的,而且这25%还是在假设没个pipeline的执行时间是一样的情况下,否则这个占有率还可能更低,这个是pipeline并行的一大问题,没有办法很好的利用到GPU的资源
同一时间点只有一个设备在计算,其他的都在等待。
Micro-batch
让它多跑一跑不断地将计算给到流水线,如下图,将batch为16的分为4个batch为4的(Micro-batch技术),下图下面的部分,这时候T=14, 那么每个GPU的使用率就是4∗84∗14=47\frac{4*8}{4*14}=\frac{4}{7}4∗144∗8=74,这样空闲的时间实际上就下降了很多,当然如果再将任务拆解的更小,还可以提升使用
注意,红色为空闲时间
如何提高Pipeline Paralisem的效率?尽量将任务拆解的更小,然后做micro-batch
Tensor Parallelism
在pipeline Parallelism中再做tensor Parallelism,还可以提高pipeline Parallelism的效率
tensor并行的核心关键点:如何把运算拆解
注意,这里后续还需要进行一个类似reduce的操作
MLP
MLP和Self-Attention的tensor并行
partition in First FFN Layer,注意这里用两个GPU设备来举例
partition in Second FFN Layer,注意这里用两个GPU设备来举例
self-attention
假设这里是用三个GPU来举例 ,每个GPU分别来存储QKV,先在各GPU上分别计算QKV
softmax计算
计算Z
所以tensor parallelism核心是怎么将这些操作设计出来
不同并行方法的总结
总结
Data Oarallelism
- 分割数据
- copy数据到N的设备上
- 高利用率,高内存开销,设备间低通信
- 优化:ZeRO 1/2/3,FSDP
Pipeline Parallelism
- 按层分割模型
- 低利用率,低内存开销,适中的通信要求
Tensor Parallelism
- 按tensor维度分割模型
- 高利用率,低内存开销,高通信要求(有许多all-reduce操作)
3D并行
将上面的三种并行方法都混在一起
下面的相同的颜色表示同一个server里面的GPU(Model Parallel是Tensor Parallelism)
需要注意的是:
为什么同一个server中用 ModelParallel(Tensor Parallelism)?
因为tensor并行是高通信的,GPU之间需要经常交互,同一个server中交互更快
如何设计并行?
当模型太大,无法加载到一个GPU上:使用pipeline parallelism来拆分模型
当layer太大,无法加载到一个GOU上:使用tensor parallelism来拆分layer
带宽:bandwith
通信的时间可能比计算的时间更长,所以我们需要降低通信的开销
在同一个数据中心,数据通信网络延迟可能是1毫秒到10毫秒,无线wifi连接数据通信延迟是100ms,地球间的通信网络延迟大概是500毫秒到1秒,但是在同一个机架内(同一个GPU集群上)那么延迟1纳秒,非常小
减小传输的数据大小
在worker之间,或者在GPU之间,减小传输的数据(gradient,parameters)大小
- 梯度剪枝
- 量化(会损失精度和信息)
压缩通信:梯度剪枝
注意梯度剪枝是一种基于梯度信息的剪枝方法。它通过分析梯度的大小来决定哪些神经元或连接是重要的,哪些可以被移除,区别于梯度裁剪