Skip to content

Latest commit

 

History

History
267 lines (234 loc) · 12.2 KB

2020-05-31_Pytorch分布式训练.org

File metadata and controls

267 lines (234 loc) · 12.2 KB

在GPU上的分布式训练

CUDA基本使用

查看GPU信息

import torch
print(torch.cuda.is_available()) # 判断GPU是否可用
print(torch.cuda.device_count()) # 返回GPU个数
print(torch.cuda.get_device_name(0)) # 返回GPU名称,默认从0开始
print(torch.cuda.current_device()) # 返回当前设备索引

还可以通过设备类型加上编号,来创建 device 对象:

device = torch.device('cuda', 0)
device = torch.device('cpu', 0)

配置 CUDA 访问限制

可以通过如下方式,设置当前 Python 脚本可见的 GPU.

  • 在终端设置:
CUDA_VISIBLE_DEVICES=1 python my_script.py

实例:

# Environment Variable Syntax    Results
CUDA_VISIBLE_DEVICES=1           Only device 1 will be seen
CUDA_VISIBLE_DEVICES=0,1         Devices 0 and 1 will be visible
CUDA_VISIBLE_DEVICES="0,1"       Same as above, quotation marks are optional
CUDA_VISIBLE_DEVICES=0,2,3       Devices 0, 2, 3 will be visible; device 1 is masked
CUDA_VISIBLE_DEVICES=""          No GPU will be visible
  • 在 Python 代码中设置
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0, 2"
  • 使用函数 set_device
import torch
torch.cuda.set_device(id)

官方建议使用 CUDA_VISIBLE_DEVICES,不建议使用 set_device 函数。

单机单卡训练

默认使用CPU训练模型,使用GPU单机单卡训练时,模型和输入必须位于同一张GPU卡上。

使用GPU方法如下:

device = torch.device('cuda: 1')
model = model.cuda(device)
model = model.to(device) # 必须指定device

使用pytorch内置的 torch.nn.parallel.DistributedDataParallel 来并行

简单方便的 nn.DataParallel [不推荐使用,distributed更高效!]

需要用 torch.nn.DataParallel 来包装模型,包装模型时需要设置一些参数,包括: 参与训练的GPU有哪些, device_ids ;用于汇总梯度的GPU是哪个, output_devicenn.DataParallel 会自动将数据切分load到相应GPU,进行正向传播,并计算梯度,所以 batch_size应设置为 单个GPU上的batch_size * 使用的GPU个数

import torch.nn as nn

model = MyNet()
model = model.cuda() # 需要这一步吗?
net = nn.DataParallel(
                      model, # 要被并行化的模块
                      device_ids=[0, 1, 2], # 整数列表或torch.device类型,默认值为所有的CUDA device
                      output_device=0 # 输出的device位置,默认值为device_ids[0]
                      )
output = net(input_var) # input_var可以在任何device上,包括CPU上。新的变动?

注意:

  • 是否需要手动将输入数据移动到GPU上? 需要!
  • 是否需要 model.to(device)? 需要!

由单进程控制多个GPU,所以在一个节点上只需要运行一次就可以创建一个进程。

torch.distributed 启动器

上面的DataParallel进行数据并行时,使用一个进程来使用单个或多个GPU,若要创建多个进程就 需要代码并运行多次,很不方便。

有了distributed之后,我们可以只写一份代码,torch会自动将其分配给n个进程,分别在n个GPU 上运行。

pytorch为我们提供了torch.distributed.lanch启动器,用于在命令行分布式的执行Python 文件。可以这样获得当前进程(也即GPU的)的index:

parser = ArgumentParse()
parser.add_argument('--local_rank', default=-1, type=int,
                    help='node rank for distributed training')
args = parse.parse_args()

print(args.local_rank)

然后使用 init_process_group 设置GPU通信所作用的后端和端口。初始化进程组主要有三种 方法:

  • 显式指定 store rank word_size
  • 指定 init_method ,来告诉进程如何去发现其他并行进程( init_method 是个URL 字符串),并且指定 rankword_size 或将它们写入URL字符串中。
  • 如果以上两种都没有指定,则假设 init_methodenv://
import torch.distributed as dist
dist.init_process_group(backend='nccl', # nccl的GPU支持目前是最好的,推荐使用
                        init_method='env://'
                        #world_size= , # 整数,可选,任务的进程数。如果store给定,则必须指定
                        #rank= , # 当前进程的rank, 如果store给定,则必须指定
                        #store=  #
                       )

然后,使用DistributedSampler对数据集进行划分

train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset)
train_loader = torch.utils.data.DataLoader(train_dataset,
                                           batch_size=...,
                                           sampler=train_sampler)

然后,使用 DistributedDataParallel 包装模型

model = torch.nn.parallel.DistributedDataParallel(model,
                                                  device_ids=[args.local_rank])

最后把数据和模型加载到当前进程使用的GPU中,进行向前向后传播:

torch.cuda.set_device(args.local_rank)
model.cuda()

for epoch in range(100):
   for batch_idx, (data, target) in enumerate(train_loader):
      images = images.cuda(non_blocking=True)
      target = target.cuda(non_blocking=True)
      # ...
      output = model(images)
      loss = criterion(output, target)
      # ...
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()

在使用时,调用 torch.distributed.launch 启动器启动:

CUDA_VISIBLE_DEVICES=0,1,2,3 python -m torch.distributed.launch --nproc_per_node=4 main.py

使用 torch.multiprocessing 取代启动器

上面的launch使用过于繁琐,可以用更方便的 torch.multiprocessing 取代启动器。 下面的spawn 开启了 nprocs=4 个线程,每个线程执行 main_worker 并向其中传入 local_rank和args(即 4 和 myargs)作为参数:

import multiprocessing as mp
mp.spawn(main_worker, nprocs=4, args=(4,myargs))

将原本需要 torch.distributed.launch 管理的执行内容,封装进 main_worker 函数中, 其中 proc 对应 local_rank(当前进程 index),ngpus_per_node 对应 4, args 对应 myargs:

def main_worker(proc, ngpus_per_node, args):
   dist.init_process_group(backend='nccl', init_method='tcp://127.0.0.1:23456', world_size=4, rank=gpu)
   torch.cuda.set_device(args.local_rank)
   # ...

由于没有 torch.distributed.launch 读取的默认环境变量作为配置,我们需要手动为 init_process_group 指定参数:

dist.init_process_group(backend='nccl', init_method='tcp://127.0.0.1:23456', world_size=4, rank=gpu)

在使用时,直接使用 python 运行就可以了:

python main.py

应该选择哪一种信息交换后端?

其中backend为并行时GPU信息交换使用的后端,目前可选的有NCCL, Gloo, MPI. 对CPU和GPU的支持如下图:

images/pytorch_backends.png

目前Pytorch的 distributed 仅支持Linux平台。Gloo被包含在Pytorch安装文件中,NCCL则 在安装CUDA时被包含在内,而使用MPI则需要在用源码安装Pytorch时打开MPI支持。

如何选择后端:

  • 分布式GPU训练:用NCCL。
  • 分布式CPU训练:用Gloo。
  • 用InfiniBand互连的GPU节点:用NCCL。只有NCCL支持IfiniBand和GPUDirect
  • 用以太网互连的GPU节点:用NCCL,因为它的性能最佳。如果NCCL不能使用,再考虑Gloo。目前 Gloo后端比NCCL要慢。
  • 用InfiniBand互连的CPU节点:如果InfiniBand可以使用IP,使用Gloo,否则使用MPI。 正在计划在未来版本中添加Gloo对InfiniBand的支持(时间点<2020-05-12 Tue>)。
  • 用以太网互连的CPU节点:除非你有使用MPI的特殊理由,否则使用Gloo。

并行后端的初始化

使用 init_process_group 设置GPU通信所作用的后端和端口。初始化进程组主要有三种方法:

  • 显式指定 store rank word_size
  • 指定 init_method ,来告诉进程如何去发现其他并行进程( init_method 是个URL 字符串),并且指定 rankword_size 或将它们写入URL字符串中。
  • 如果以上两种都没有指定,则假设 init_methodenv://
import torch.distributed as dist
dist.init_process_group(backend='nccl', # nccl的GPU支持目前是最好的,推荐使用
                        init_method='env://'
                        #world_size= , # 整数,可选,任务的进程数。如果store给定,则必须指定
                        #rank= , # 当前进程的rank, 如果store给定,则必须指定
                        #store=  #
                       )

使用建议

强烈建议用该方式来使用 DistributedDataParallel,使用多进程,每个进程使用一个 GPU。 这是目前 Pytorch 中,无论是单节点还是多节点,进行数据并行训练最快的方式。 在单节点多 GPU 上进行训练,该方式比 torch.nn.DataParallel 更快。这是因为分布是并行不需要 broadcast 参数。

假设每个主机有 N 个 GPUs,那么需要使用 N 个进程,并保证每个进程单独处理一个 GPU。 为了在每个主机(node)上使用多进程,可以使用 torch.distributed.launch 或 torch.multiprocessing.spawn 来实现。

在将数据移动到GPU上时,要将 non_blocking 设置为 False ,否则会导致不同GPU之间 不同步,从而卡进程,不能正常结束任务。

在 DataParallel 中,batch size 设置必须为单卡的 n 倍,但是在 DistributedDataParallel 内,batch size 设置于单卡一样即可。可见代码中设置的均为单个GPU所使用的batch size。

使用方法:

#on master node
python mnist-distributed.py -n 2 -g 4 --nr 0 --epoch 30
#on node 1
python mnist-distributed.py -n 2 -g 4 --nr 0 --epoch 30

pytorch lightning

一般一个新的项目需要定义两个文件,一个 LightningModule 文件,一个 Trainer 文件。 这里有许多示例

LightningModule

LightningModulenn.Module 的严格超类。它提供了一个与 Trainer 交互的标准 界面。要开始自己的项目,可以从示例文件上修改,也可以自己定义 LightningModule 文件。

自定义的 LightningModule 文件要实现以下方法(参考这里):

  • 必需的方法:training_step, tng_dataloader, configure_optimizer
  • 可选的方法:validation_step, validation_end, test_step, test_end, val_dataloader test_dataloader, on_save_checkpoint, on_load_checkpoint, update_tng_log_metrics, add_model_specific_args

horovod

distributed training on slurm cluster