分布式训练:使用Accelerate进行分布式训练
Accelerate是HuggingFace发布的Pytorch高级库,主要是封装了Pytorch当中训练部分的模块。在之前的了解中,我们了解了Pytorch中包含了大量的分布式训练API,如何灵活的调用他们需要费时费力去记忆,为此Accelerate提供了统一的接口,来配置分布式训练参数。
先看看官方的示例:
1 |
|
可以看到是比较简单易懂的。
使用Accelerate进行DDP训练
下面我们用一个实际例子来对比一下使用Accelerate和不使用的区别。我们使用一个文本分类任务来进行训练。首先是不使用加速库的情况:
我们的数据集示例如下:
1 |
|
先是准备一下Dataset和DataLoader:
1 |
|
随后准备模型和优化器:
1 |
|
我们使用touchrun --nproc_per_node=2 ddp.py
来执行这个训练任务。
下面使用Accelerate来进行同样的训练:
1 |
|
我们使用touchrun --nproc_per_node=2 accelerate.py
来开始训练,或者还能使用accelerate launch accelerate.py
。如果使用后者,还能在终端输入accelerate config
来设置训练的参数。在设置完之后,使用accelerate launch accelerate.py
,就可以直接调用前面设置的参数来进行训练。
两者的差别
我们来对比一下这两种方式分别有什么区别:
- 原生
DDP
中需要在DataLoader
中设置sampler
,使用Accelerate
则不需要。 - 原生
DDP
中需要将模型进行包装model = DDP(model)
,另一个则不需要。 - 原生需要在训练时初始化进程组
dist.init_process_group
,Accelerate
则不需要,只需要实例化Accelerate
。 - 数据,模型,优化器都使用了
accelerate.prepare
来进行分布式的准备。 - 训练中,
trainloder.sampler.set_epoch()
以及后续的batch
发送到不同机器这一步也省略了。 - 打印日志可以使用
accelerate.print()
实现。
使用混合精度进行训练
混合精度训练结合了32位的单精度浮点数和16位半精度来进行训练。首先加载完整的32位的完整精度模型,随后将它复制一份成16位的半精度模型。16bit的低精度模型会被用来前向传播,得到的16bit精度的梯度会被转为32bit,传入优化器。最后在32位的模型上进行参数更新。
通过这种方式,能够加速训练,但是不会减少对显存的需求。
假设模型参数量为M:
混合精度 | 单精度 | |
---|---|---|
模型 | (4+2) Bytes * M | 4 Bytes * M |
优化器 | 8 Bytes * M | 8 Bytes * M |
梯度 | (2 + ) Bytes * M | 4 Bytes * M |
激活值 | 2 Bytes * A | 4 Bytes * A |
汇总 | (16 + ) Bytes * M + 2 Bytes * A | 16 Bytes * M + 4 Bytes * A |
当使用混合精度训练时,不光需要一个完整模型,还需要一个半精度模型,因此模型这占用了4 + 2倍的参数量。优化器占用的参数量不变,梯度这在前向传播时变成了半精度,只在更新时会拿出一组参数提高为单精度,因此可以视作2 + 的参数量。此外,激活值也会变为半精度。
使用Accelerate时,只需要使用以下几种方法就可以进行混合精度训练:
1 |
|
使用梯度累积进行训练
在显卡显存过小的时候,能够使用梯度累积的功能来模拟大Batch Size的训练效果。
梯度累积的流程如下:
- 分割Batch:将大Batch分割为多个Mini Batch
- 计算梯度:每个Mini Batch独立计算梯度
- 累积梯度:将Mini Batch的梯度进行累积,而不是马上更新参数
- 更新参数:当积累到一定数量,统一使用累积的梯度更新参数
示例如下:
1 |
|
在Accelerate的实现代码如下:
1 |
|
然后再训练时计算:
1 |
|
2024/5/2 于苏州