在深度学习训练中,我们经常遇到 GPU 的内存太小的问题,如果我们的数据量比较大,别说大批量(large batch size)训练了,有时候甚至连一个训练样本都放不下。但是随机梯度下降(SGD)中,如果能使用更大的 Batch Size 训练,一般能得到更好的结果。所以问题来了:

问题来了:当 GPU 的内存不够时,如何使用大批量(large batch size)样本来训练神经网络呢?

这篇文章将以 PyTorch 为例,讲解一下几点:

  1. 当 GPU 的内存小于 Batch Size 的训练样本,或者甚至连一个样本都塞不下的时候,怎么用单个或多个 GPU 进行训练?
  2. 怎么尽量高效地利用多 GPU?

单个或多个 GPU 进行大批量训练

如果你也遇到过 CUDA RuntimeError: out of memory 的错误,那么说明你也遇到了这个问题。

深度神经网络训练の显存过载计算

PyTorch 的开发人员都出来了,估计一脸黑线:兄弟,这不是 bug,是你内存不够…

又一个方法可以解决这个问题:梯度累加(accumulating gradients)。

一般在 PyTorch 中,我们是这样来更新梯度的:

 
1
2
3
4
5
# 前向计算
# 计算损失函数
# 后向计算梯度
# 优化器更新梯度
# 用更新过的参数值进行下一次前向计算

在上看的代码注释中,在计算梯度的 loss.backward() 操作中,每个参数的梯度被计算出来后,都被存储在各个参数对应的一个张量里:parameter.grad。然后优化器就会根据这个来更新每个参数的值,就是 optimizer.step()

而梯度累加(accumulating gradients)的基本思想就是, 在优化器更新参数前,也就是执行 optimizer.step() 前,我们进行多次梯度计算,保存在 parameter.grad 中,然后累加梯度再更新。这个在 PyTorch 中特别容易实现,因为 PyTorch 中,梯度值本身会保留,除非我们调用 model.zero_grad() or optimizer.zero_grad()

下面是一个梯度累加的例子,其中 accumulation_steps 就是要累加梯度的循环数:

 
1
2
3
4
5
6
7
8
9
# 重置保存梯度值的张量
:
# 前向计算
# 计算损失函数
# 对损失正则化 (如果需要平均所有损失)
# 计算梯度
# 重复多次前面的过程
# 更新梯度
# 重置梯度

 

如果连一个样本都不放下怎么办?

如果样本特别大,别说 batch training,要是 GPU 的内存连一个样本都不下怎么办呢?

答案是使用梯度检查点(gradient-checkpoingting),用计算量来换内存。基本思想就是,在反向传播的过程中,把梯度切分成几部分,分别对网络上的部分参数进行更新(见下图)。但这种方法的速度很慢,因为要增加额外的计算量。但在某些例子上又很有用,比如训练长序列的 RNN 模型等(感兴趣的话可以参考这篇文章)。

深度神经网络训练の显存过载计算

 

图片来自:https://medium.com/tensorflow/fitting-larger-networks-into-memory-583e3c758ff9

这里就不展开讲了,可以参考 PyTorch 官方文档对 Checkpoint 的描述:https://pytorch.org/docs/stable/checkpoint.html

多 GPU 训练方法

简单来讲,PyTorch 中多 GPU 训练的方法是使用 torch.nn.DataParallel。非常简单,只需要一行代码:

 
1
2
3
4
5
6
7
# 就是这里!
 
# 前向计算
# 计算损失函数
# 计算多个GPU的损失函数平均值,计算梯度
# 反向传播
)

在使用torch.nn.DataParallel 的过程中,我们经常遇到一个问题:第一个GPU的计算量往往比较大。我们先来看一下多 GPU 的训练过程原理:

深度神经网络训练の显存过载计算

在上图第一行第四个步骤中,GPU-1 其实汇集了所有 GPU 的运算结果。这个对于多分类问题还好,但如果是自然语言处理模型就会出现问题,导致 GPU-1 汇集的梯度过大,直接爆掉。

那么就要想办法实现多 GPU 的负载均衡,方法就是让 GPU-1 不汇集梯度,而是保存在各个 GPU 上。这个方法的关键就是要分布化我们的损失函数,让梯度在各个 GPU 上单独计算和反向传播。这里又一个开源的实现:https://github.com/zhanghang1989/PyTorch-Encoding。这里是一个修改版,可以直接在我们的代码里调用:地址。实例:

 
1
2
3
4
5
6
7
8
9
10
11
DataParallelCriterion
 
# 并行化model
# 并行化损失函数
 
# 并行前向计算
# 并行计算损失函数
# 计算梯度
# 反向传播
)

如果你的网络输出是多个,可以这样分解:

 
1
)

如果有时候不想进行分布式损失函数计算,可以这样手动汇集所有结果:

 
1
)

下图展示了负载均衡以后的原理:

深度神经网络训练の显存过载计算

 

 

(原文链接: https://medium.com/huggingface/training-larger-batches-practical-tips-on-1-gpu-multi-gpu-distributed-setups-ec88c3e51255)

相关文章:

  • 2021-12-07
  • 2021-12-13
  • 2021-11-14
  • 2022-12-23
  • 2021-07-19
  • 2021-10-01
  • 2021-11-23
猜你喜欢
  • 2021-06-07
  • 2021-12-25
  • 2022-01-01
  • 2022-12-23
  • 2021-05-18
  • 2021-04-26
  • 2021-11-19
相关资源
相似解决方案