Horovod, 分布式进阶

Horovod简介

Horovod是Uber开源的又一个深度学习工具,它的发展吸取了Facebook “Training ImageNet In 1 Hour” 与百度 “Ring Allreduce” 的优点,可为用户实现分布式训练提供帮助。本文将简要介绍如何使用Horovod配合pytorch更高效地进行分布式训练。

为什么要用Horovod

随着大家训练机器学习模型越来越多的,项目的数据和计算能力需求正在急剧增加。在大部分情况下,模型是可以在单个或多GPU平台的服务器上运行的,但随着数据集的增大和训练时间的增长,有些时候训练需要一周甚至更长时间。因此,我们不得不寻求分布式训练的方法。

以分布式TensorFlow为例,在试验了一些方法之后,意识到原有方法需要进行一些调整:

  • 首先,在遵循文档和代码示例之后,大家并不总是清楚哪些功能对应着哪些模型训练代码的分布式计算。标准分布式TensorFlow引入了很多新的概念:工作线程、参数服务器、tf.Server()、tf.ClusterSpec()、tf.train.SyncReplicasOptimizer()以及tf.train.replicas_device_setter()等等。它们在某些情况下能起到优化作用,但也让我们难以诊断拖慢训练速度的bug。
  • 第二个问题有关大规模的计算性能。在进行了一些基准测试之后,可以发现标准的分布式TensorFlow机制无法满足需求。例如,在使用 128个GPU进行训练时,因为低效率损失了一半的计算资源。

《Horovod, 分布式进阶》
《Horovod, 分布式进阶》

当使用标准TensorFlow在128块Pascal GPU上进行测试时,无论是Inception V3还是ResNet-101都浪费了将近一半GPU算力。充分利用GPU资源是目前大规模训练的一大课题,此前Facebook的一小时训练ImageNet论文《Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour介绍了使用256块GPU进行ResNet-50网络数据并行训练的方法,引起人们的广泛关注,这也证明了大规模分布式训练可以显著提高生产力。

应对不断增加的TensorFlow程序复杂性:在测试中可以发现,每个使用分布式TensorFlow的案例都需要指定初始工作线程和参数服务器,传递服务发现信息,如所有工作线程和参数服务器的主机和端口,并使用合适的tf.ClusterSpec()构建tf.Server(),进而调整训练程序。此外,用户必须保证所有的操作都正确地使用 tf.train.device_replica_setter(),并使用towers让代码符合服务器中多GPU的设置。这通常导致陡峭的学习曲线和大量的代码重构,压缩了实际建模的时间。

在2017年上半年,百度发表了研究《Bringing HPC Techniques to Deep Learning》,提出使用不同的算法来平均梯度,并让这些梯度在所有节点之间交流,这被称为ring-allreduce。在ring-allreduce算法中,每个N节点与其他两个节点进行2*(N-1)次通信。在这个通信过程中,一个节点发送并接收数据缓冲区传来的块。在第一个N-1迭代中,接收的值被添加到节点缓冲区中的值。在第二次N-1迭代中,接收的值代替节点缓冲区中保存的值。ring-allreduce算法允许工作线程节点平均梯度并将其分散到所有节点——无需参数服务器。百度的文章证明了这种算法是带宽上最优的,这意味着如果缓冲区足够大,它将最大化地利用可用的网络。

《Horovod, 分布式进阶》
《Horovod, 分布式进阶》

Pytorch with Horovod

https://github.com/uber/horovod/blob/master/examples/pytorch_imagenet_resnet50.py
这里,常规的操作就不多叙述了,主要讲一下和Horovod分布式相关的代码部分。

import horovod.torch as hvd
hvd.init()
if args.cuda:
    # Horovod: pin GPU to local rank.
    torch.cuda.set_device(hvd.local_rank())

train_sampler = torch.utils.data.distributed.DistributedSampler(
    train_dataset, num_replicas=hvd.size(), rank=hvd.rank())
train_loader = torch.utils.data.DataLoader(
    train_dataset, batch_size=args.batch_size, sampler=train_sampler, **kwargs)

# Horovod: scale learning rate by the number of GPUs.
optimizer = optim.SGD(model.parameters(), lr=args.base_lr * hvd.size(),
                      momentum=args.momentum, weight_decay=args.wd)

# Horovod: wrap optimizer with DistributedOptimizer.
optimizer = hvd.DistributedOptimizer(
    optimizer, named_parameters=model.named_parameters())

# Horovod: broadcast parameters & optimizer state.
hvd.broadcast_parameters(model.state_dict(), root_rank=0)
hvd.broadcast_optimizer_state(optimizer, root_rank=0)

跟传统的pytorch分布式其实区别不大,基本做到了上手可用的程度,感谢Uber~

    原文作者:lbin
    原文地址: https://zhuanlan.zhihu.com/p/43942234
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞