分布式训练 - 多机多卡 (DDP)

起初为调用大规模的模型训练,单卡GPU是不够使用的,需要借用服务器的多GPU使用。就会涉及到单机多卡,多机多卡的使用。在这里记录一下使用的方式和踩过的一些坑。文中若有不足,请多多指正。

由于分布式的内容较多,笔者准备分几篇来讲一次下深度学习的分布式训练,深度学习的框架使用的是Pytorch框架。

----1.分布式训练的理论基础

----2.GPU训练

----3.单机多卡的使用 DP,DDP

----4.多机多卡的使用 DDP

在前边的文章中已经提到了怎样进行单机单卡单机多卡进行分布式训练,那可能有小伙伴会有疑问能不能进行多机多卡的训练,答案是,必须是可以的。实际上,在现在很多顶级实验室发布的很多预训练模型都是在多机多卡上训练出来的。需要大量的显存才能处理的了大规模的参数和模型。那么今天这篇文章就来详细说说多机多卡的使用来进行分布式训练

整个分布式文章系列的链接都在上边啦,有需要的小伙伴点击链接就可以看到其他的知识啦!

在单机多gpu可以满足的情况下, 绝对不建议使用多机多gpu进行训练, 我经过测试, 发现多台机器之间传输数据的时间非常慢, 主要是因为我测试的机器可能只是千兆网卡, 再加上别的一些损耗, 网络的传输速度跟不上, 导致训练速度实际很慢. 我看一个github上面的人说在单机8显卡可以满足的情况下, 最好不要进行多机多卡训练。

在详细介绍多机多卡的使用之前,先说一下基本的使用流程

Pytorch 中分布式的基本使用流程如下:

  1. 在使用 distributed 包的任何其他函数之前,需要使用 init_process_group 初始化进程组,同时初始化
    distributed 包。
  2. 如果需要进行小组内集体通信,用 new_group 创建子分组
  3. 为数据集创建 Sampler
  4. 创建分布式并行模型 DDP(model, device_ids=device_ids)
  5. 使用启动工具 torch.distributed.launch 在每个主机上执行一次脚本,开始训练
  6. 使用 destory_process_group() 销毁进程组

整个的流程跟单机多卡的使用时非常相似的,具体的差别我们会在后边详细讨论。

1.1 初始化–(对于多机多卡分布式很重要,详细说明)

初始化是在程序刚开始的时候运行,在进行多机多卡的训练的时候,要使用torch.distributed.init_process_group() 进行初始化。

函数原型

torch.distributed.init_process_group(backend, 
                                     init_method=None, 
                                     timeout=datetime.timedelta(0, 1800), 
                                     world_size=-1, 
                                     rank=-1, 
                                     store=None)
backend: 后端, 实际上是多个机器之间交换数据的协议
init_method: 机器之间交换数据, 需要指定一个主节点, 而这个参数就是指定主节点的
world_size: 介绍都是说是进程, 实际就是机器的个数, 例如两台机器一起训练的话, world_size就设置为2
rank: 区分主节点和从节点的, 主节点为0, 剩余的为了1-(N-1), N为要使用的机器的数量, 也就是world_size

函数作用
该函数需要在每个进程中进行调用,用于初始化该进程。在使用分布式时,该函数必须在 distributed 内所有相关函数之前使用。

参数详解

1.1.1 backend:指定当前进程要使用的通信后端

在pytorch的官方教程中提供了以下的通信后端,使用分布式时,在梯度汇总求平均的过程中,各主机之间需要进行通信。因此,需要指定通信的协议架构等。torch.distributed 对其进行了封装。

torch.distributed 支持 3 种后端,分别为 NCCL,Gloo,MPI。各后端对 CPU / GPU 的支持如下所示:
在这里插入图片描述
gool后端

gloo 后端支持 CPU 和 GPU,其支持集体通信(collective Communication),并对其进行了优化。

由于 GPU 之间可以直接进行数据交换,而无需经过 CPU 和内存,因此,在 GPU 上使用 gloo 后端速度更快。

torch.distributed 对 gloo 提供原生支持,无需进行额外操作。

NCCL 后端

NCCL 的全称为 Nvidia 聚合通信库(NVIDIA Collective Communications Library),是一个可以实现多个 GPU、多个结点间聚合通信的库,在 PCIe、Nvlink、InfiniBand上可以实现较高的通信速度。

NCCL 高度优化和兼容了 MPI,并且可以感知 GPU 的拓扑,促进多 GPU 多节点的加速,最大化 GPU 内的带宽利用率,所以深度学习框架的研究员可以利用 NCCL 的这个优势,在多个结点内或者跨界点间可以充分利用所有可利用的 GPU。

NCCL 对 CPU 和 GPU 均有较好支持,且 torch.distributed 对其也提供了原生支持。

对于每台主机均使用多进程的情况,使用 NCCL 可以获得最大化的性能。每个进程内,不许对其使用的 GPUs 具有独占权。若进程之间共享GPUs 资源,则可能导致 deadlocks。

MPI 后端:

MPI 即消息传递接口(Message Passing Interface),是一个来自于高性能计算领域的标准的工具。它支持点对点通信以及集体通信,并且是 torch.distributed 的 API 的灵感来源。使用 MPI 后端的优势在于,在大型计算机集群上,MPI 应用广泛,且高度优化。

但是,torch.distributed 对 MPI 并不提供原生支持。因此,要使用 MPI,必须从源码编译 Pytorch。是否支持GPU,视安装的 MPI 版本而定。

根据官网的介绍, 如果是使用cpu的分布式计算, 建议使用gloo, 因为表中可以看到
gloo对cpu的支持是最好的, 然后如果使用gpu进行分布式计算, 建议使用nccl, 实际测试中我也感觉到, 当使用gpu的时候, nccl的效率是高于gloo的. 根据博客和官网的态度, 好像都不怎么推荐在多gpu的时候使用mpi

基本原则:
用 NCCL 进行分布式 GPU 训练
用 Gloo 进行分布式 CPU 训练

具体详细的分布式通信包底层内容内容可以查看以下链接:

  • https://ptorch.com/docs/8/torch-distributed
  • https://www.jianshu.com/p/5f6cd6b50140
  • https://zhuanlan.zhihu.com/p/76638962

1.1.2 init_method:指定当前进程组初始化方式

分布式任务中,各节点之间需要进行协作,比如说控制数据同步等。因此,需要进行初始化,指定协作方式,同步规则等。

torch.distributed 提供了 3 种初始化方式,分别为 tcp、共享文件 和 环境变量初始化 等。

推荐使用环境变量初始化,就是在你使用函数的时候不需要填写该参数即可,默认使用环境变量初始化。

环境变量初始化(推荐使用)

默认情况下使用的都是环境变量来进行分布式通信,也就是指定 init_method=“env://”。通过在所有机器上设置如下四个环境变量,所有的进程将会适当的连接到 master,获取其他进程的信息,并最终与它们握手(信号)。

  • MASTER_PORT: 必须指定,表示 rank0上机器的一个空闲端口(必须设置)
  • MASTER_ADDR: 必须指定,除了 rank0 主机,表示主进程 rank0 机器的地址(必须设置)
  • WORLD_SIZE: 可选,总进程数,可以这里指定,在 init 函数中也可以指定
  • RANK: 可选,当前进程的 rank,也可以在 init 函数中指定

配合 torch.distribution.launch 使用(详细使用问题请点击该篇文章)。

使用实例:

Node 1(第一台机器): (IP: 192.168.1.1, and has a free port: 1234)
>>> python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE
           --nnodes=2 --node_rank=0 --master_addr="192.168.1.1"
           --master_port=1234 YOUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3
           and all other arguments of your training script)
Node 2 (第二台机器)
>>> python -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE
           --nnodes=2 --node_rank=1 --master_addr="192.168.1.1"
           --master_port=1234 YOUR_TRAINING_SCRIPT.py (--arg1 --arg2 --arg3
           and all other arguments of your training script)

TCP初始化

看代码:

TCP 方式初始化,需要指定进程 0 的 ip 和 port。这种方式需要手动为每个进程指定进程号。

import torch.distributed as dist

dist.init_process_group(backend, init_method='tcp://10.1.1.20:23456',
                        rank=rank, world_size=world_size)

说明:

不同进程内,均使用主进程的 ip 地址和 port,确保每个进程能够通过一个 master 进行协作。该 ip 一般为主进程所在的主机的 ip,端口号应该未被其他应用占用。

实际使用时,在每个进程内运行代码,并需要为每一个进程手动指定一个 rank,进程可以分布与相同或不同主机上。

多个进程之间,同步进行。若其中一个出现问题,其他的也马上停止。

使用实例:

Node 1
python mnsit.py --init-method tcp://192.168.54.179:22225 --rank 0 --world-size 2
Node 2
python mnsit.py --init-method tcp://192.168.54.179:22225 --rank 1 --world-size 2

共享文件系统初始化

该初始化方式,要求共享的文件对于组内所有进程可见!

看代码:

import torch.distributed as dist

# rank should always be specified
dist.init_process_group(backend, init_method='file:///mnt/nfs/sharedfile',
                        world_size=4, rank=args.rank)

说明:

其中,以 file:// 为前缀,表示文件系统各式初始化。/mnt/nfs/sharedfile 表示共享的文件,各个进程在共享文件系统中通过该文件进行同步或异步。因此,所有进程必须对该文件具有读写权限。

每一个进程将会打开这个文件,写入自己的信息,并等待直到其他所有进程完成该操作。在此之后,所有的请求信息将会被所有的进程可访问,为了避免 race conditions,文件系统必须支持通过 fcnt

  • 17
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值