torch.distributed使用总结 单机多卡并行训练demo

转载:https://zhuanlan.zhihu.com/p/468784034

一、代码总览

一段完整的代码以及程序启动命令

训练代码

import os
import argparse
import torch
from torch.nn import SyncBatchNorm
from torch.nn.parallel import DistributedDataParallel
import torch.distributed as dist
from torch.utils.data.distributed import DistributedSampler

parser = argparse.ArgumentParser(description='training')
parser.add_argument('--local_rank', type=int, help='local rank for dist')
args = parser.parse_args()

print(os.environ['MASTER_ADDR'])
print(os.environ['MASTER_PORT'])
world_size = torch.cuda.device_count()
local_rank = args.local_rank

dist.init_process_group(backend='nccl')

torch.cuda.set_device(local_rank)

train_dataset = Dataset(...)
train_sampler = DistributedSampler(train_dataset)
train_loader = Dataloader(dataset=train_dataet, sampler=train_sampler, shuffle=False)

val_set = Dataset()
val_loader = Dataloader(dataset=val_set)

model = MyModel()
model = model.cuda()
model = SyncBatchNorn.convert_sync_batchnorm(model)
model = DistributedDataParallel(model, device_ids=[local_rank], output_devices=local_rank, find_unused_parameters=True)

cls_criterion = nn.CrossEntropyLoss()
optim = optimizer()
scheduler = scheduler()

for epoch in range(start_epoch, end_epoch):
    trainer_sampler.set_epoch(epoch)
    train()
    eval()
    if local_rank==0:
        log()

if local_rank==0:
    torch.save(model.module.state_dict())

运行代码:

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

二、分块总结

2.1 运行代码部分

因为torch.dist对程序的入口有更改,所以先总结运行代码。torch.dist跟DataParallel不同,需要多进程运行程序,以此达到多机训练的效果。在官方的实现中,使用torch.distributed.launch来启动多进程。除此之外,还使用torch.multiprocessing来手动执行多进程程序。在最新的版本中,官方打算将python -m torch.distributed.lanuch用torchun替代,这里暂时不用。

torch.distributed.launch使用方式如下:

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

其中nproc_per_node表示开启的进程数,这里与使用的卡数保持一致。torch.distributed.launch会自动分配一些参数到主程序中,也可以手动修改。这次关注到的有:RANK表示进程的优先级,也可以认为是进程的序列号;MASTER_ADDR和MASTER_PORT分别表示通讯的地址和端口,torch.distributed.launch会将其设置为环境变量;WORLD_SIZE表示gpu*节点数,本例里就是gpu数量。

2.2 训练代码部分

讲解训练代码的细节,细节还不少。

导入模块

import os
import argparse
import torch
from torch.nn import SyncBatchNorm
from torch.nn.parallel import DistributedDataParallel
import torch.distributed as dist
from torch.utils.data.distributed import DistributedSampler

argparse:

parser = argparse.ArgumentParser(description='training')
parser.add_argument('--local_rank', type=str, help='local rank for dist')
args = parser.parse_args()

需要承接torch.distributed.launch给main.py传递的local_rank参数。local_rank参数表示进程的优先级,也可以认为是进程的序列号。

环境初始化:

print(os.environ['MASTER_ADDR'])
print(os.environ['MASTER_PORT'])
world_size = torch.cuda.device_count()
local_rank = args.local_rank

dist.init_process_group(backend='nccl')

torch.cuda.set_device(local_rank)
  • local_rank表示进程的优先级,也可以认为是进程的序列号;MASTER_ADDR和MASTER_PORT分别表示通讯的地址和端口,torch.distributed.launch会将其设置为环境变量;world_size表示gpu*节点数,本例里就是gpu数量。这里代码里print出来展示。
  • dist.init_process_group(backend=‘nccl’)初始化torch.dist的环境。这里backend选择nccl来进行通讯,可以用dist.is_nccl_avaliable()来查看是否可用nccl。除此之外也可以在这里设置一些其他的环境参数。
  • torch.cuda.set_device(local_rank)设置环境CUDA序号

数据集设置:

train_dataset = Dataset(...)
train_sampler = DistributedSampler(train_dataset)
train_loader = Dataloader(dataset=train_dataet, sampler=train_sampler, shuffle=False)

val_set = Dataset()
val_loader = Dataloader(dataset=val_set)
  • 对训练数据集做修改。将dataloader的sampler修改为DistributedSampler,这样保证其每个进程采样的数据是不同的
  • 训练集的dataloader的shuffle只能设置为False,DistributedSampler会进行shuffle,如果dataloader再shuffle的话会打乱次序,导致多进程分配的数据不对
  • batch_size设置的是每个进程的,因此不需要像dataparalle一样乘以卡数
  • 对验证集可以不做修改,如果每个进程不同的话需要再整合所有进程的结果

模型加载:

model = MyModel()
model = model.cuda()
model = SyncBatchNorn.convert_sync_batchnorm(model)
model = DistributedDataParallel(model, device_ids=[local_rank], output_devices=local_rank, find_unused_parameters=True)

cls_criterion = nn.CrossEntropyLoss()
optim = optimizer()
scheduler = scheduler()
  • 跟DataParallel类似的是,加载的模型需要用DDP(DistributedDataParallel)重载一下。这里如果运行时报错有unused_parameters,那么就设置find_unused_parameters=True
  • DDP从原理上应该是多机通讯更新梯度从而保证模型的参数都是一样的,而DataParallel则是在一张卡上集中更新模型权重,再复制到其他卡上
  • 对于损失函数、优化器和sheduler都不需要DDP,如果有需要更新的参数的话还是需要DDP重载

训练代码:

for epoch in range(start_epoch, end_epoch):
    trainer_sampler.set_epoch(epoch)
    train()
    eval()
    if local_rank==0:
        log()
  • sampler.set_epoch(epoch),这样设置一样才能保证数据是乱序的
  • local_rank==0保证只有0进程进行log
  • 每个进程都进行eval,我现在不这么做的话会因为其他进程一直没有收到通讯而超时

保存模型:

if local_rank==0:
    torch.save(model.module.state_dict())
  • 只需要在0号进程进行保存就行了
  • 对DDP用.module

三、运行效果

从我跑的结果来看,训练过程挺好,加速明显(没有展示结果和量化指标)

TODO:

1、可以把验证过程并行化一下加速

问题在于如何合并多进程验证结果。现在想到两套方案:一是用dist的多进程通讯,需要学习dist的其他api;二是将多进程的结果保存到磁盘,再进行合并

2、可以尝试多机多卡

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值