GPU 分布式使用教程

GPU 分布式使用教程

GPU 分布式使用教程之 Horovod

Horovod 是实现分布式机器学习的较佳实践之一,支持 Pytorch,TensorFlow,Keras,MXNet,采用 Ring-AllReduce 算法,在 GPU 较多时,算力的发挥能提升近一倍,且代码改动较小。使用镜像即可快速开启单机多卡/多机多卡分布式学习。

选择机器

在租用时,请选择带有如图所示图标的机器。没有这个图标的机器不支持加入分布式网络。
分布式网络图标
分布式网络图标

单机多卡

1.单机多卡使用 Horovod 实现较简单。首先,您需要按正常流程租用GPU,如单节点 4 卡 K80,选择含有 Horovod 镜像,如Pytorch1.8.1-Horovod/TensorFlow 2.4-Horovod。
选择镜像
2.进入运行脚本所在目录,输入命令行,如
shell
horovodrun -np 4 python test.py
运行
即可实现单机多卡计算。-np 指定进程数,推荐设定为总GPU数。test.py 可使用官方范例。普通的脚本如果要使用 Hovorod,修改方法可参考第三部分:脚本修改

多机多卡

1.首先,您需要按正常流程租用 GPU,如两个计算节点,每节点各 8 卡 K80,共计 16 卡。选择相应的 Horovod 镜像,如Pytorch1.8.1-Horovod/TensorFlow 2.4-Horovod。
2.将节点组建集群。进入 【个人中心】 — 【我的租用】 — 【分布式集群】。
分布式集群需要先进行申请,申请通过后,点击 【添加集群】- 【添加机器】— 【确定】。
新建集群
添加机器
选择节点
添加机器成功后,系统会给每个节点分配集群 IP,当状态为已连接时,代表机器间可相互通信。
集群列表
3.登录任一节点。因秘钥由您掌握,故需由您按以下步骤完成节点间的ssh连通:
shell
ssh-keygen -t rsa # 一路默认yes,生成公私钥
ssh-copy-id root@其他节点IP  #分发给其他节点,输入对应秘钥。IP可在我的集群页面查看,如192.168.1.4
4.添加以下环境变量,使用 ifconfig 命令查询各节点网卡名称,如 meth01,meth02。登陆各个节点添加相同变量(可用 ssh 登录)
网卡
shell
export NCCL_SOCKET_IFNAME=meth01,meth02
export GLOO_IFACE=meth01,meth02
export NCCL_DEBUG=INFO #可选,如需获得额外的nccl信息
5.进入 /mnt/ 共享目录,上传运行脚本,如 test.py,运行范例命令:
shell
horovodrun -np 16 -H 192.168.6.01:8,192.168.6.02:8 --network-interface "192.168.6.01/24,192.168.6.02/24" /root/miniconda3/envs/myconda/bin/python test.py 
参数说明
  • np:指定总进程数,
  • H:指定各计算节点所运行卡数,格式为 IP:GPU数,多个节点之间逗号隔开,本机的信息也需要配置,所有节点都需要写入。例如 192.168.6.01:8 代表 IP 为 192.168.6.01,有 8 张GPU。
  • network-interface:指定各计算节点的 IP,需要与 H 的参数对应。
如需进行性能分析,可使用timeline命令,如
shell
horovodrun -np 4 --timeline-filename /path/to/timeline.json python test.py
但使用此命令可能损耗性能。

脚本修改

Horovod 对各模块进行了包裹,使用方便,但仍需对代码进行修改,逻辑和 Pytorch DDP 比较类似,官方流程如下:
python
import torch
import horovod.torch as hvd

# Initialize Horovod
hvd.init()

# Pin GPU to be used to process local rank (one GPU per process)
torch.cuda.set_device(hvd.local_rank())

# Define dataset...
train_dataset = ...

# Partition dataset among workers using DistributedSampler
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=..., sampler=train_sampler)

# Build model...
model = ...
model.cuda()

optimizer = optim.SGD(model.parameters())

# Add Horovod Distributed Optimizer
optimizer = hvd.DistributedOptimizer(optimizer, named_parameters=model.named_parameters())

# Broadcast parameters from rank 0 to all other processes.
hvd.broadcast_parameters(model.state_dict(), root_rank=0)

for epoch in range(100):
   for batch_idx, (data, target) in enumerate(train_loader):
       optimizer.zero_grad()
       output = model(data)
       loss = F.nll_loss(output, target)
       loss.backward()
       optimizer.step()
       if batch_idx % args.log_interval == 0:
           print('Train Epoch: {} [{}/{}]\tLoss: {}'.format(
               epoch, batch_idx * len(data), len(train_sampler), loss.item()))
更详细资料请参考官网