Langchain-Chatchat/knowledge_base/samples/content/llm/分布式训练技术原理.md

74 lines
8.5 KiB
Markdown
Raw Normal View History

2023-11-23 14:18:00 +08:00
# 分布式训练技术原理
- 数据并行
- FSDP
- FSDP算法是由来自DeepSpeed的ZeroRedundancyOptimizer技术驱动的但经过修改的设计和实现与PyTorch的其他组件保持一致。FSDP将模型实例分解为更小的单元然后将每个单元内的所有参数扁平化和分片。分片参数在计算前按需通信和恢复计算结束后立即丢弃。这种方法确保FSDP每次只需要实现一个单元的参数这大大降低了峰值内存消耗。(数据并行+Parameter切分)
- DDP
- DistributedDataParallel (DDP) **在每个设备上维护一个模型副本并通过向后传递的集体AllReduce操作同步梯度从而确保在训练期间跨副本的模型一致性** 。为了加快训练速度, **DDP将梯度通信与向后计算重叠** ,促进在不同资源上并发执行工作负载。
- ZeRO
- Model state
- Optimizer->ZeRO1
- 将optimizer state分成若干份每块GPU上各自维护一份
- 每块GPU上存一份完整的参数W,做完一轮foward和backward后各得一份梯度,对梯度做一次 **AllReducereduce-scatter + all-gather** **得到完整的梯度G,由于每块GPU上只保管部分optimizer states因此只能将相应的W进行更新,对W做一次All-Gather**
- Gradient+Optimzer->ZeRO2
- 每个GPU维护一块梯度
- 每块GPU上存一份完整的参数W,做完一轮foward和backward后 **算得一份完整的梯度,对梯度做一次Reduce-Scatter保证每个GPU上所维持的那块梯度是聚合梯度,每块GPU用自己对应的O和G去更新相应的W。更新完毕后每块GPU维持了一块更新完毕的W。同理对W做一次All-Gather将别的GPU算好的W同步到自己这来**
- Parameter+Gradient+Optimizer->ZeRO3
- 每个GPU维护一块模型状态
- 每块GPU上只保存部分参数W做forward时对W做一次 **All-Gather** 取回分布在别的GPU上的W得到一份完整的W **forward做完立刻把不是自己维护的W抛弃做backward时对W做一次All-Gather取回完整的Wbackward做完立刻把不是自己维护的W抛弃. 做完backward算得一份完整的梯度G对G做一次Reduce-Scatter从别的GPU上聚合自己维护的那部分梯度,聚合操作结束后立刻把不是自己维护的G抛弃。用自己维护的O和G更新W。由于只维护部分W因此无需再对W做任何AllReduce操作**
- Residual state
- activation->Partitioned Activation Checkpointing
- 每块GPU上只维护部分的activation需要时再从别的地方聚合过来就行。需要注意的是activation对显存的占用一般会远高于模型本身通讯量也是巨大的
- temporary buffer->Constant Size Buffer
- 提升带宽利用率。当GPU数量上升GPU间的通讯次数也上升每次的通讯量可能下降但总通讯量不会变。数据切片小了就不能很好利用带宽了。所以这个buffer起到了积攒数据的作用等数据积攒到一定大小再进行通讯。
- 使得存储大小可控。在每次通讯前,积攒的存储大小是常量,是已知可控的。更方便使用者对训练中的存储消耗和通讯时间进行预估
- unusable fragment->Memory Defragmentation
- 对碎片化的存储空间进行重新整合整出连续的存储空间。防止出现总存储足够但连续存储不够而引起的存储请求fail
- offload
- ZeRO-Offload
- **forward和backward计算量高** 因此和它们相关的部分例如参数Wfp16activation就全放入GPU
- **update的部分计算量低** 因此和它相关的部分全部放入CPU中。例如W(fp32)optimizer statesfp32和gradients(fp16)等
- ZeRO-Offload 分为 Offload Strategy 和 Offload Schedule 两部分,前者解决如何在 GPU 和 CPU 间划分模型的问题,后者解决如何调度计算和通信的问题
- ZeRO-Infinity
- 一是将offload和 ZeRO 的结合从 ZeRO-2 延伸到了 ZeRO-3解决了模型参数受限于单张 GPU 内存的问题
- 二是解决了 ZeRO-Offload 在训练 batch size 较小的时候效率较低的问题
- 三是除 CPU 内存外,进一步尝试利用 NVMe 的空间
- 模型并行
- tensor-wise parallelism
- MLP切分
- 对第一个线性层按列切分,对第二个线性层按行切分
- ![图片](./img/分布式训练技术原理-幕布图片-36114-765327.jpg)
- ![图片](./img/分布式训练技术原理-幕布图片-392521-261326.jpg)
- ![图片](./img/分布式训练技术原理-幕布图片-57107-679259.jpg)
- self-attention切分
- attention的多头计算天然适合tensor并行因为每个头上都可以独立计算最后再将结果concat起来从而 **可以把每个头的参数放到一块GPU上**
- 对线性层, **按照“行切割”** 。切割的方式和MLP层基本一致其forward与backward原理也一致
- 输入层Embedding切分
- 对positional embedding来说max_s本身不会太长因此每个GPU上都拷贝一份对显存的压力也不会太大
- 将word embedding拆分到不同GPU上每块GPU维护一分部词表。当输入X去GPU上查找时能找到的词就正常返回词向量找到不到就把词向量中的全部全素都置0。按此方式查找完毕后每块GPU上的数据做一次AllReduce就能得到最终的输入。
- ![图片](./img/分布式训练技术原理-幕布图片-220157-552735.jpg)
- 输出层Embedding切分
- **输入层和输出层共用一个word embeding**
- **当模型的输入层到输入层都在一块GPU上时即流水线并行深度=1我们不必担心这点实践中大部分用Megatron做并行的项目也是这么做的。但若模型输入层和输出层在不同的GPU上时我们就要保证在权重更新前两块GPU上的word embedding梯度做了一次AllReduce** 。
- ![图片](./img/分布式训练技术原理-幕布图片-42284-124759.jpg)
- cross-entroy
- ![图片](./img/分布式训练技术原理-幕布图片-124076-270516.jpg)
- ![图片](./img/分布式训练技术原理-幕布图片-838373-426344.jpg)
- [pipeline paralelism]("https://zhuanlan.zhihu.com/p/629637468")
- GPipe
- PipeDream
- 1F1B
- 每个 GPU 以交替的方式执行每个 micro batch 的正向和反向过程,以尽早释放其占用的显存,进而减少显存占用
- ![图片](./img/分布式训练技术原理-幕布图片-20096-279847.jpg)
- 1F1B 并不能减少 bubble time **为了进一步减少 bubble timeMegatron 又提出了 interleaved 1F1B 模式** 。也就是原本每个 GPU 负责连续 4 个层的计算,现在变成负责连续两个层的计算,只有原来的一半,从而 bubble time 也变成了原来的一半,即把一个设备上连续的层划分为若干不连续的层,负责的数量不变,但顺序变了。
- ![图片](./img/分布式训练技术原理-幕布图片-618350-869132.jpg)
- DAPPLE
- ![图片](./img/分布式训练技术原理-幕布图片-906937-836104.jpg)
- layer-wise parallelism
- sequence parallelism
- Sequence 并行的好处在于不会增加通信量,并且可以大大减少显存占用
- Layer-norm 和 Dropout 沿着序列的维度是独立的,因此可以按照 Sequence 维度进行拆分
- 使用了 Sequence 并行之后对于超大规模的模型而言其实显存占用量还是很大的。因此Megatron 又引入了激活重计算技术,找到一些计算量很少但显存占用很大的算子,比如 Attention 里的 Softmax、Dropout 等算子,对这些算子进行激活重计算就可以显著减少显存,并且计算开销增加不大
- MoE
- 核心思想:将大模型拆分成多个小模型。每个样本只需要激活部分专家模型进行计算,从而大大节省计算资源。 **MoE 的基本思路是通过宽度换取深度,因为模型深度越深,计算层数越多,进而推理时间越长**
- Hard Gate MoE
- Sparse MoE