笔记-分布式计算基础
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)大小
- 梯度剪枝
- 量化(会损失精度和信息)
-
压缩通信:梯度剪枝
注意梯度剪枝是一种基于梯度信息的剪枝方法。它通过分析梯度的大小来决定哪些神经元或连接是重要的,哪些可以被移除,区别于梯度裁剪