1. 确定训练中的瓶颈
低GPU利用率:在模型训练过程中,尽管GPU显存已满,但其利用率却不稳定,有时为0%,有时达到90%,波动较大。

大量训练数据:当训练数据达到百万或千万级别时,训练一个Epoch所需时间较长,从而导致模型迭代周期过长。
2. 提升GPU利用率:CPU与GPU的配合
GPU利用率低的主要原因是CPU的处理效率无法跟上GPU的速度。
2.1 CPU与GPU之间的通信
CPU负责数据加载和预处理,不断在内存与显存之间传输数据,而GPU则专注于模型训练。


2.2 解决方案
可以采用多进程并行处理的方式来提升CPU的数据加载性能。在Keras中,可以通过设置workers和use_multiprocessing来实现数据的多进程并行处理,并将数据推送到队列中进行GPU模型训练。由于进程之间可能相互影响资源,因此workers的数量不宜过大,建议设置为2、4或8。
3. 分布式并行训练
3.1 并行训练模式
当训练数据量庞大时,可以通过多台机器和多个GPU来提升训练效率。与Hadoop和Spark等分布式数据处理框架不同,深度学习训练涉及参数的前向传播和反向传播,主要有两种并行方式:
模型并行(Model Parallelism):在分布式系统中,不同机器(如GPU或CPU)负责网络模型的不同部分,通常将神经网络模型的不同层分配给不同的机器,或将同一层中的不同参数分配给不同机器。此方式适用于超大模型,如自然语言处理中的模型。然而,模型并行存在不同层之间的依赖关系,无法完全并行执行。

数据并行(Data Parallelism):不同机器上有相同模型的多个副本,每台机器处理不同的数据,然后将所有机器的计算结果以某种方式合并。这种方式更适合处理大数据。数据并行需要解决数据的划分和传输,以及参数的更新问题。

3.2 数据并行训练
FACEbook在《AccuRate, LaRge miniBATch SGD: TRAIning imageNet in 1 HouR》中介绍了如何利用256块GPU进行ResNet-50网络的“数据并行”训练。
数据分割:选择较大的batch-size,并根据workers数量进行数据划分,分发到不同的workers执行。参数更新则有两种模式:(1)参数服务器模式;(2)环状更新(无服务器模式)。
3.2.1 参数服务器模式
参数服务器模式如图所示。在每个worker完成一个batch的训练后,所有worker会将参数传递给参数服务器进行汇总求均值,然后再将更新后的参数返回给每个worker,以便进入下一个batch的训练。

参数服务器的结构可以是单个或多个,数据并行模式的效率取决于参数服务器与worker之间的通信效率,包括最慢的worker的训练时间以及参数服务器接收和更新参数后的回传时间。如果worker数量较多,参数服务器可能会成为瓶颈。

3.2.2 环状更新(Ring-Reduce)
百度提出的Ring-Reduce方法摒弃了参数服务器,采用环状结构来进行参数更新。所有worker组成一个环形结构,每个worker仅与相邻的worker交换参数。经过若干次交换后,所有worker将包含其他worker的参数信息,完成更新。

以下几张图展示了其中的一些步骤。Ring-Reduce为了提高速度,并非一次性交换所有参数,而是先对参数进行分割,逐步交换分割后的参数。



4. 实现框架:Horovod
Horovod是Uber开源的深度学习工具,它结合了FACEbook的“一小时训练ImageNet”论文与百度的Ring-AllReduce的优点,为用户提供分布式训练的支持。
Horovod采用NCCL替代百度的Ring-AllReduce实现。NCCL是NVIDIA的集合通信库,提供高度优化的Ring-AllReduce版本,并允许在多台机器间运行。
要将单机训练代码转换为分布式代码,只需几个步骤:
首先安装Horovod,建议使用Docker版本以简化环境配置。Horovod依赖NCCL 2和Open MPI,安装过程如下:
mkdir horovod-docker-gpu
wget -O horovod-docker-gpu/Dockerfile https://raw.githubusercontent.com/horovod/horovod/master/Dockerfile.gpu
docker build -t horovod:latest horovod-docker-gpu
确保worker机器间的SSH连接畅通。
接下来修改训练代码,Horovod支持TensorFlow、Keras、PyTorch和MXNet等多种深度学习框架。以Keras为例,修改的主要步骤包括:
(1)初始化:hvd.init();
(2)分配GPU计算资源:config.gpu_options.visible_device_list = str(hvd.local_rank());
(3)使用分布式优化器进行参数的分布式更新:opt = hvd.DistributedOptimizer(opt);
(4)确保所有worker模型初始化一致:hvd.callbacks.BroadcastGlobalVariablesCallback(0);
(5)将模型保存于某个worker中,以防止其他workers对其造成损坏。
然后,利用HorovodRun执行分布式训练:
horovodrun -np 16 -H server1:4,server2:4,server3:4,server4:4 python train.py
5. 总结
本文讨论了通过提升GPU利用率和利用Horovod进行分布式训练来提高深度学习的训练效率。
