PyTorch distributed currently only supports Linux.
这句话是来自 pytorch 官网 的 torch.distributed 部分,说明 pytorch 支持分布式训练,而且只在linux 上支持。
torch.distributed supports three backends, each with different capabilities.
同样来自上述 页面,pytorch 在分布式训练中,支持三种后端(backend)进行集群管理或者通信。
那么,在什么情况下选用什么样的backend?
我这里使用的示例代码来自于 github 上的一段开源代码, 地址是 https://github.com/pytorch/examples/tree/master/mnist 。我对这段代码做了些更改,使其支持起 rank 的操作,测试的时候一台机器上起一个 rank 。你可以在 网上搜索到类似的修改。
但是,这不是一个最佳的测试例子,原因是因为这段代码缺少 类似 all_reduce 这样的操作,缺少运算过程中所有 rank 的权重进行修改、因而引起的 PCI 通信或者 InfiniBand 网络通信(非系统通信)等等。我不明白的是,网上有很多人把这段代码作为一个 非常有效的例子 用来 测试分布式运算,估计都是没有深究,互相抄抄改改。
我找到了一段最佳实验代码, 地址是 https://github.com/seba-1511/dist_tuto.pth/blob/gh-pages/train_dist.py 。奈何不是做 深度学习 出身,修改了几次也没能成功修改成需要符合需求的测试代码(有高手的话,麻烦能修改好,联系我测试测试)。这段代码是自带 创建 rank 的,并且是单机运行的。我想要的是,自己创建每一个 rank,并且支持多机分布式。
# 机器 1 上输入命令
python3 /mnt/pytorch/mnist.py --rank 0 --world-size 2 --init-method tcp://192.168.61.55:9090 --backend gloo
# 机器 2 上输入命令
python3 /mnt/pytorch/mnist.py --rank 1 --world-size 2 --init-method tcp://192.168.61.55:9090 --backend gloo
其实这已经是一个多机训练的例子了。但是我建议在操作之前,先单节点 跑 2 个 rank 试试。
并且 run 一下 官方 示例 的一些代码,玩一下。
这个操作中,以 gloo 作 backend ,以 rank0 作为 master 通信,9090 机器 1 的未被占用通信端口。相关概念可以仔细阅读官方说明。
操作一波
# 机器 1
docker run -it --rm -v /mnt/pytorch/:/pytorch -w /pytorch --net=host loongc/pytorch:pytorch python mnist.py --rank 0 --world-size 2 --init-method tcp://192.168.61.55:9090 --backend gloo
# 机器 2
docker run -it --rm -v /mnt/pytorch/:/pytorch -w /pytorch --net=host loongc/pytorch:pytorch python mnist.py --rank 1 --world-size 2 --init-method tcp://192.168.61.55:9090 --backend gloo
以上部分,测试都是在 多节点CPU 上运行的。如果你需要在GPU 进行运算:
如果,环境已经准备好了,就可以做一些 GPU 相关的测试了。
使用 基于 Docker 的 GPU 分布式运算,如果不需要 诸如 运算性能 特殊的需求,你可以直接使用 上一节 的 操作方法即可。如果 需要 诸如使用 分布式文件系统通信、环境变量通信、InfiniBand通信,使用 nccl 作为 backend 等等,将在下一节做一个总结。
引言中,已经提到 3 种 backend,分别是:MPI、GLOO、NCCL。这 3 种 backend 支持了 3 种 通信 方式:tcp、环境变量、分布式文件系统(当前使用的 pytorch 版本如此,后续的版本就不知道了)。
所以,大致上可以认为不同 rank 之间的通信就是 由 backend 与 通信方式的 组合完成的。当然,在不同的组合方式所带来的 通信速度(影响运算整体性能)是不同的,能实现的功能也是不太一样的(详情见引言中的表格)。
本文前面的内容当中,使用的 backend 都是GLOO,使用的通信方式都是 tcp。
本节内容,主要围绕 NCCL 的 3 种 通信操作方式展开,以及如何使用 InfiniBand。
MPI 的方式我没有测过,也不打算测试,主要原因是需要 额外的依赖 。
nccl 与 gloo 使用 tcp 的方式是一致的,不需要多余的操作。在运行 python 脚本的时候,只需要将传入 backend 的参数 gloo 改为 nccl 即可。
nccl 使用环境变量,相对于 tcp 要复杂一些。
还是在 192.168.61.55 和 192.168.61.56 两台机器上操作,启动大致如下:
# 机器 1
docker run -it --rm --env-file /mnt/pytorch_env/master -v /mnt/pytorch/:/pytorch -w /pytorch --net=host loongc/pytorch:pytorch python mnist.py --rank 0 --world-size 2 --init-method env:// --backend nccl
# 机器 2
docker run -it --rm --env-file /mnt/pytorch_env/master -v /mnt/pytorch/:/pytorch -w /pytorch --net=host loongc/pytorch:pytorch python mnist.py --rank 1 --world-size 2 --init-method env:// --backend nccl
# 文件 /mnt/pytorch_env/master 内容如下
MASTER_ADDR=192.168.61.55
MASTER_PORT=9090
上述 /mnt/pytorch_env/master 文件的内容中 MASTER_ADDR=192.168.61.55 是 rank0 所在机器的 ip ,MASTER_PORT=9090 是该机器上一个未被征用的 端口
nccl 使用分布式文件系统,那么就需要把分布式文件系统 映射进 容器当中,在这里,我是在 挂载目录下 /mnt/pytorch/ 创建了一个文件 backendnccl ,因为本身这个挂载 就是分布式文件系统。
# 以机器 1 上的操作为例
docker run -it --rm -v /mnt/pytorch/:/pytorch -w /pytorch --net=host loongc/pytorch:pytorch python mnist.py --rank 0 --world-size 2 --init-method file:///pytorch/backendnccl --backend nccl
原本,pytorch 是支持 IP over IB 的,所以可操作 InfiniBand 的方式主要有以下几种:
另外,NCCL和Gloo后端都会尝试找到要使用的正确网络接口,如果自动检测到的接口不正确,您可以使用以下环境变量覆盖它(适用于相应的后端):
在使用 过程中,如果确定 使用 nccl,并且打算用 InfiniBand ,推荐通过环境变量(写进 前文提到的 文件 /mnt/pytorch_env/master ) NCCL_SOCKET_IFNAME=ib0
额外说明,如果你的机器上有多张 InfiniBand 卡,你可以通过 NCCL_SOCKET_IFNAME 指定任意一张,如 ib0 或者 ib1 这种
但是,你可以直接使用 NCCL_SOCKET_IFNAME=ib 进行指定,这样所有的 ib 卡都有机会被调用起来用于通信。你可以通过相关工具,如 ibdump 进行监控 ib 卡的发包 和 数据的发送与接受。
经过分析,我们认为在 以发送数据包 的方式进行通信的过程中,除了真正用于计算的数据包,还可能包括 机器通信 的数据。
这里,实现了手动启动多个rank 进行 基于 docker 的分布式训练。那么,自动化进行分布式训练就变得比较容易了。
可以通过 mesos 或者 k8s 启动多个 容器,进行集群管理,完成分布式训练。通过 设计 api 指定启动 指定数目的 rank,指定硬件资源,以及是否使用 InfiniBand 。
如果有更好的实现 分布式训练 PyTorch ,欢迎留言讨论。