2024年1月

三种方式:数据并行、模型并行、3D并行

参考:https://juejin.cn/post/7254001262646738981

一、数据并行DP (Data Parallel)

数据并行是最常见的并行形式,因为它很简单。在数据并行训练中,数据集被分割成几个碎片,每个碎片被分配到一个设备上。这相当于沿批次(Batch)维度对训练过程进行并行化。每个设备将持有一个完整的模型副本,并在分配的数据集碎片上进行训练。在反向传播之后,模型的梯度将被全部减少,以便在不同设备上的模型参数能够保持同步。典型的数据并行实现:PyTorch DDP。

72746.png

二、模型并行

通常有两种类型的模型并行:张量并行和流水线并行

  • 流水线并行是在各层之间进行并行计算。
  • 张量并行是在一个操作中进行并行计算,如:矩阵-矩阵乘法。

1、流水线并行 PP(Pipeline Parallelism)

流水线并行的核心思想是,模型按层分割成若干块,每块都交给一个设备。
在前向传播过程中,每个设备将中间的激活传递给下一个阶段。
在后向传播过程中,每个设备将输入张量的梯度传回给前一个流水线阶段。
这允许设备同时进行计算,从而增加训练的吞吐量。

微信截图_20240123173407.png

流水线并行训练的一个明显缺点是训练设备容易出现空闲状态(因为后一个阶段需要等待前一个阶段执行完毕),导致计算资源的浪费,加速效率没有数据并行高。

典型的流水线并行实现:GPipe、PipeDream、PipeDream-2BW、PipeDream Flush(1F1B)

2、张量并行TP(Tensor Parallelism)

3、优化器并行 ???

目前随着模型越来越大,单个GPU的显存目前通常无法装下那么大的模型了。那么就要想办法对占显存的地方进行优化。

通常来说,模型训练的过程中,GPU上需要进行存储的参数包括了模型本身的参数、优化器状态、激活函数的输出值、梯度以及一些零时的Buffer。各种数据的占比如下图所示:
微信截图_20240123174631.png

可以看到模型参数仅占模型训练过程中所有数据的一部分,当进行混合精度运算时,其中模型状态参数(优化器状态 + 梯度+ 模型参数)占到了一大半以上。因此,我们需要想办法去除模型训练过程中的冗余数据。

而优化器相关的并行就是一种去除冗余数据的并行方案,目前这种并行最流行的方法是 ZeRO(即零冗余优化器)。针对模型状态的存储优化(去除冗余),ZeRO使用的方法是分片,即每张卡只存 1/N 的模型状态量,这样系统内只维护一份模型状态。ZeRO有三个不同级别,对模型状态进行不同程度的分片:

ZeRO-1 : 对优化器状态分片(Optimizer States Sharding)
ZeRO-2 : 对优化器状态和梯度分片(Optimizer States & Gradients Sharding)
ZeRO-3 : 对优化器状态、梯度分片以及模型权重参数分片(Optimizer States & Gradients & Parameters Sharding)

微信截图_20240123175131.png

三、3D并行

总的来说,3D并行是由数据并行(DP)、张量并行(TP)和流水线并行(PP)组成

微信截图_20240123180734.png

参考:
https://zhuanlan.zhihu.com/p/598714869

1、DataParallel

如果当前有4个GPU,batch_size=16,那么模型将被复制到每一个GPU上,在前向传播时,每一个gpu将分到4个batch,每个gpu独立计算依据分到的batch计算出结果的梯度,然后将梯度返回到第一个GPU上,第一个GPU再进行梯度融合、模型更新。在下一次前向传播的时候,将更新后的模型再复制给每一个GPU。

1、DP在每个训练批次(batch)中,因为模型的权重都是在 一个进程上先算出来 然后再把他们分发到每个GPU上,所以网络通信就成为了一个瓶颈,而GPU使用率也通常很低。

2、因为它在每一次的前向传播的时候把模型也复制了(即每次更新都复制一遍模型),并且单进程多线程会造成GIL contention(全局解释器锁争用) 这里进程计算权重使通信成为瓶颈造成了大量的时间浪费,因此引入了DDP。

2、DistributedDataParallel

DDP采用多进程控制多GPU,共同训练模型,一份代码会被pytorch自动分配到n个进程并在n个GPU上运行。 DDP运用Ring-Reduce通信算法在每个GPU间对梯度进行通讯,交换彼此的梯度,从而获得所有GPU的梯度。对比DP,不需要在进行模型本体的通信,因此可以加速训练。

参考https://zhuanlan.zhihu.com/p/489011749

在所有节点上运行命令来初始化上面创建的 DDP 作业:

torchrun --nnodes=2 --nproc_per_node=8 --rdzv_id=100 --rdzv_backend=c10d --rdzv_endpoint=$MASTER_ADDR:29400 elastic_ddp.py

这里torchrun将启动8个进程并调用elastic_ddp.py 其启动的节点上的每个进程,但用户还需要应用slurm等集群管理工具才能在2个节点上实际运行此命令。

srun --nodes=2 ./torchrun_script.sh

启动脚本
无论 DDP 应用程序如何启动,每个进程都需要一种机制来了解其rank等,使用torch提供的分布式脚本可以通过环境变量将世界大小、全局等级、主地址和主端口以及本地等级作为命令行参数传递给每个实例,初始化的时候选择环境变量初始化就很方便 (就不应该使用启动子进程torch.multiprocessing.spawn 了)。

torch.distributed.launch
python -m torch.distributed.launch --nproc_per_node 8 test.py
————————————————
版权声明:本文为CSDN博主「www_z_dd」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/www_z_dd/article/details/132020726

参考:
PyTorch 分布式训练和启动脚本torch.distributed.launch torchrun slurm