文章目录

  • DDP原理
  • pytorch中DDP使用
    • 相关的概念
    • 使用流程
    • 如何启动
      • torch.distributed.launch
      • spawn调用方式
  • 针对实例voxceleb_trainer多卡介绍

DDP原理

DistributedDataParallel(DDP)支持多机多卡分布式训练。pytorch原生支持,本文简要总结下DDP的使用,多卡下的测试,并根据实际代码介绍。

voxceleb_trainer: 开源的声纹识别工具,简单好用,适合研究人员。

通俗理解:

  1. DDP模式会开启N个进程,每个进程在一张显卡上加载模型,这些模型相同(被复制了N份到N个显卡),缓解GIL锁的限制。
  2. 训练阶段,每个进程通过Ring-Reduce的方法与其他进程通讯(交换各自的梯度)
  3. 各个进程使用平均后的梯度更新自己的参数,因为每个进程下模型的初始参数、更新梯度是一样的,所以更新后模型的参数也保持一致。

DP模式出现的较早,支持单机多卡的训练,使用方法

model=torch.nn.DataParallel(model)

DP模式中只有一个进程,容易受到GIL的限制。master节点相当于参数服务器,向其他卡广播参数,在梯度反向传播后,每个卡将梯度汇总到master节点,master对梯度进行平均后更新参数,再将参数发送到其他卡上。

显而易见的,这种模式会导致节点的计算任务,通讯量很重,从而导致网络阻塞,降低训练速度。

强烈建议使用DDP

GIL是什么?为什么DDP更快?

GIL(全局解释器锁,可以参考GIL),主要的缺点就是:限制python进程只能利用一个CPU核心,不适合计算密集型的任务。使用多进程,才能有效利用多核的计算资源。DDP启动多进程,一定程度上避免了这个限制。

Ring-Reduce梯度合并:各个进程独立计算梯度,每个进程将梯度依次传给下一个进程,之后再把从上一个进程拿到的梯度传给下一个进程,循环n(进程数量)次之后,所有的进程就可以得到全部的梯度。

快的原因:每个进程只和自己上下游的两个进程进行通信,极大缓解了参数服务器的通讯阻塞现象。

通常讲,神经网络并行有三种:

  • Data parallelism: 数据并行,可以间接增大batch_size。一般常用的DP,DDP都是这种模式
  • Model parallelism: 模型并行,把模型放在不同的显卡上,计算是并行的。可能会加速,需要看实际的通信效率。
  • Workload Partitioning:把模型放在不同的显卡上,计算是串行的,不能加速。

pytorch中DDP使用

DDP推荐使用单进程单卡,就是一个模型放在一个卡上。

也可以单进程多卡。分配有三种情况:

  • 每个进程一张卡。(官方推荐的最佳模式)
  • 每个进程多张卡,复制模式。一个模型复制在不同的卡上,每个进程等同于DP模式。但速度不如单卡单进程,一般不采用
  • 每个进程多张卡,并行模式。一个模型的不同部分分布在不同的卡上。一般用在模型很大,一张卡塞不下batch_size=1的情况。

本文只介绍单卡单进程的情况。(实际没接触到大到一张卡塞不下的模型,小破实验室ε=ε=ε=┏(゜ロ゜;)┛)

相关的概念

先了解下相关的概念:

  • group,进程组。默认情况下只有一个组,

  • world size: 全局的并行数,

    torch.distributed.get_world_size()

  • rank: 表示当前进程的序号,用于进程间通讯。从0开始,rank=0的进程是master进程

    torch.distributed.get_rank()

  • local_rank: 每台机子上的进程的序号。

    一般情况下,用local_rank来手动设置模型是跑在当前机器的哪块GPU上。

    torch.distributed.local_rank()

使用流程

使用很简单,在代码中加入:

model = DDP(model, device_ids=[local_rank], output_device=local_rank)

原本的model是pytorch模型,新得到的model是DDP模型。

https://zhuanlan.zhihu.com/p/178402798

## main.py文件
import torch
import argparse# 新增1:依赖
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP# 新增2:从外面得到local_rank参数,在调用DDP的时候,其会自动给出这个参数,后面还会介绍。所以不用考虑太多,照着抄就是了。
#       argparse是python的一个系统库,用来处理命令行调用,如果不熟悉,可以稍微百度一下,很简单!
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", default=-1)
FLAGS = parser.parse_args()
local_rank = FLAGS.local_rank# 新增3:DDP backend初始化
#   a.根据local_rank来设定当前使用哪块GPU
torch.cuda.set_device(local_rank)
#   b.初始化DDP,使用默认backend(nccl)就行。如果是CPU模型运行,需要选择其他后端。
dist.init_process_group(backend='nccl')# 新增4:定义并把模型放置到单独的GPU上,需要在调用`model=DDP(model)`前做哦。
#       如果要加载模型,也必须在这里做哦。
device = torch.device("cuda", local_rank)
model = nn.Linear(10, 10).to(device)
# 可能的load模型...# 新增5:之后才是初始化DDP模型
model = DDP(model, device_ids=[local_rank], output_device=local_rank)

除了模型部分,最重要的是数据的分发。简单来说,就是把数据集均分到不同的卡上,保证每个卡的数据不同(如果都拿整个数据,会出现冗余)。

pytorch中使用torch.utils.data.distributed.DistributedSampler实现数据的分发。

my_trainset = torchvision.datasets.CIFAR10(root='./data', train=True)
# 新增1:使用DistributedSampler,DDP帮我们把细节都封装起来了。用,就完事儿!
#       sampler的原理,后面也会介绍。
train_sampler = torch.utils.data.distributed.DistributedSampler(my_trainset)
# 需要注意的是,这里的batch_size指的是每个进程下的batch_size。也就是说,总batch_size是这里的batch_size再乘以并行数(world_size)。
trainloader = torch.utils.data.DataLoader(my_trainset, batch_size=batch_size, sampler=train_sampler)for epoch in range(num_epochs):# 新增2:设置sampler的epoch,DistributedSampler需要这个来维持各个进程之间的相同随机数种子trainloader.sampler.set_epoch(epoch)# 后面这部分,则与原来完全一致了。for data, label in trainloader:prediction = model(data)loss = loss_fn(prediction, label)loss.backward()optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)optimizer.step()

上边两个不做完,基本就可以进行多卡的训练。

模型保存:

# 1. save模型的时候,和DP模式一样,有一个需要注意的点:保存的是model.module而不是model。
#    因为model其实是DDP model,参数是被`model=DDP(model)`包起来的。
# 2. 我只需要在进程0上保存一次就行了,避免多次保存重复的东西。
if dist.get_rank() == 0:torch.save(model.module, "saved_model.ckpt")

注意点:

  1. 理论上,在没有buffer参数(如BN)的情况下,DDP性能和单卡Gradient Accumulation性能是完全一致的。并行8就等于Gradient Accumulation Step为8的单卡。
  2. 速度上,DDP比Gradient Accumulation的单卡快

如何启动

有两种方法:1. torch.distributed.launch启动 2. torch.multiprocessing.spawn

torch.distributed.launch

介绍一些参数:

  • –nnodes 有多少台机器
  • –node_rank 当前是哪台机器
  • –nproc_per_node 每台机器有多少进程

实现方式:在每台机子上都运行一次torch.distributed.launch,每个torch.distributed.launch会启动n个进程,并给每个进程一个--local_rank=i的参数

单机模式:

## Bash运行
# 假设我们只在一台机器上运行,可用卡数是8
python -m torch.distributed.launch --nproc_per_node 8 main.py

多机模式:

–master_address: master进程的网络地址,默认是127.0.0.1(只用用于单机)

–master_port: master进程的一个端口,默认29500,使用前需要确认端口是否被其他程序占用。

## Bash运行
# 假设我们在2台机器上运行,每台可用卡数是8
#    机器1:
python -m torch.distributed.launch --nnodes=2 --node_rank=0 --nproc_per_node 8 \--master_adderss $my_address --master_port $my_port main.py
#    机器2:
python -m torch.distributed.launch --nnodes=2 --node_rank=1 --nproc_per_node 8 \--master_adderss $my_address --master_port $my_port main.py

spawn调用方式

给出一个demo:

https://zhuanlan.zhihu.com/p/178402798

def demo_fn(rank, world_size):dist.init_process_group("nccl", rank=rank, world_size=world_size)# lots of code....def run_demo(demo_fn, world_size):mp.spawn(demo_fn,args=(world_size,),nprocs=world_size,join=True)

相比于launch,spawn使用起来更加复杂一点,但是封装的好,方便其他人直接使用。

DDP实现的原理和细节参考:https://zhuanlan.zhihu.com/p/187610959

针对实例voxceleb_trainer多卡介绍

voxceleb_trainer是一个开源的声纹识别工具,代码简洁。实现了多卡模式,基于spawn启动模式,简单看一下:

和上文介绍的流程类似:

首先设置地址,端口号,初始化进程组,并先将模型放置到单卡上,再封装为DDP模型。

if args.distributed:# 针对单机多卡# 设置本地ipos.environ['MASTER_ADDR']='localhost'# 端口号os.environ['MASTER_PORT']=args.port# 初始化进程组dist.init_process_group(backend='nccl', world_size=ngpus_per_node, rank=args.gpu)torch.cuda.set_device(args.gpu)# 模型传到GPU上s.cuda(args.gpu)# BN同步if args.syncBN:s = torch.nn.SyncBatchNorm.convert_sync_batchnorm(s)print('----syncBN----')s = torch.nn.parallel.DistributedDataParallel(s, device_ids=[args.gpu], find_unused_parameters=False)print('Loaded the model on GPU {:d}'.format(args.gpu))

以上封装在一个main_work函数中,其中数据加载变为:

Datasets->DistributedSampler->BatchSampler->DataLoader

train_dataset = train_dataset_loader(**vars(args))train_sampler = train_dataset_sampler(train_dataset, **vars(args))
# 总的batch_size = args.batch_size * n_gpu (显卡数)
train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=args.batch_size,num_workers=args.nDataLoaderThread,sampler=train_sampler,pin_memory=False,worker_init_fn=worker_init_fn,drop_last=True,
)

spawn启动:

torch.multiprocessing.spawn(fn, args=(), nprocs=1, join=True, daemon=False, start_method='spawn')

  • fn: 传入的函数,定义main(rank, *args),在定义的时候,第一个参数留rank, 程序自动分配rank的值,告诉函数当前是在哪块GPU上进行的。
  • args: 传入的fn参数,tuple类型
  • nprocs: 进程个数
  • join:是否加入同一进程组

例如:

mp.spawn(main_worker, nprocs=n_gpus, args=(n_gpus, args))

看到这里点个大拇指,关注一下~

后边更新:DDP的一些细节

简单介绍pytorch中分布式训练DDP使用 (结合实例,快速入门)相关推荐

  1. PyTorch 分布式训练DDP 单机多卡快速上手

    PyTorch 分布式训练DDP 单机多卡快速上手 本文旨在帮助新人快速上手最有效的 PyTorch 单机多卡训练,对于 PyTorch 分布式训练的理论介绍.多方案对比,本文不做详细介绍,有兴趣的读 ...

  2. 简单介绍Java中Comparable和Comparator

    转载自 简单介绍Java中Comparable和Comparator Comparable 和 Comparator是Java核心API提供的两个接口,从它们的名字中,我们大致可以猜到它们用来做对象之 ...

  3. python中len用法_简单介绍Python中的len()函数的使用

    简单介绍Python中的len()函数的使用 函数:len() 1:作用:返回字符串.列表.字典.元组等长度 2:语法:len(str) 3:参数:str:要计算的字符串.列表.字典.元组等 4:返回 ...

  4. 简述python中的几种数据类型,简单介绍Python中的几种数据类型

    简单介绍Python中的几种数据类型 python 里面分为 基本数据类型 和 复合数据类型 基本数据类型包括:数值 字符串 布尔 和 none 复合数据类型包括:列表 元组 字典 和集合怎么算是深情 ...

  5. 简单介绍Python中的几种数据类型

    大体上把Python中的数据类型分为如下几类: Number(数字) 包括int,long,float,complex String(字符串) 例如:hello,"hello",h ...

  6. len函数python返回值类型_简单介绍Python中的len()函数的使用

    01状态机介绍 游戏中的状态机一般都是有限状态机,简写为FSM(有限状态机),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型. 状态机的每一个状态至少需要有以下三个操作: ...

  7. 简单比较python语言和c语言的异同-Python快速入门之与C语言异同

    原标题:Python快速入门之与C语言异同 代码较长,建议使用电脑阅读本文. 10分钟入门Python 本文中使用的是Python3如果你曾经学过C语言,阅读此文,相信你能迅速发现这两种语言的异同,达 ...

  8. pytorch GPU分布式训练 单机单卡、单机多卡

    可以用"watch -n 0.1 nvidia-smi"来查看gpu状态,我用的是3块12G的GPU进行实验 本实验将使用一个简单的瞎写的网络进行,网络训练一个分类任务,当然这个不 ...

  9. 简单介绍Tomcat中catalina.out 和 catalina.log的区别和用途

    本文主要介绍了Tomcat中catalina.out 和 catalina.log的区别和用途详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 catalina. ...

最新文章

  1. 网络协议图形化分析工具EtherApe
  2. 关于json格式字符串解析并用mybatis存入数据库
  3. oracle性能优化求生指南_oracle性能优化:高水位线(HWM)详解--如何计算HWM
  4. pppoe拨号中的server name和service name
  5. C#面向对象名词比较(三)
  6. 拓端tecdat|R语言分位数回归预测筛选有上升潜力的股票
  7. 【Markdown小技巧】 整理小图标和表情符号
  8. 【大话设计模式】第0章 面向对象基础
  9. hdf heg 批量拼接_MODIS处理工具MRT已被HEG代替
  10. 【转】用生命之花制定自己的月计划
  11. 5分绩点转4分_gpa5分制换算4分制(5分绩点转4分)
  12. c语言中mul的用法,MUL指令(无符号数的乘法指令)
  13. dB,dBi和dBm的区别
  14. apache实验报告 linux_linux实验报告心得
  15. python pandas库作用_python pandas库的一些使用总结
  16. 将时谐电磁场引入工程电磁场的意义_《工程电磁场》复习提纲
  17. Opencv图像识别常用的处理算法
  18. 禁止输入空格键demo效果示例(整理)
  19. labview学习笔记之快捷菜单使用
  20. dos重启计算机命令行,命令行中,重启计算机的命令是什么

热门文章

  1. point类型的数组java_Java基础学习之引用类型数组访问NullPoint问题
  2. C++之memcpy的用法
  3. 莫比乌斯入门:bzoj 1101 Zap(Mobius)
  4. 计算机取证(Computer Forensic)
  5. 一、什么是JWT?了解JWT,认知JWT
  6. Delphi 2009发布
  7. 四、全国计算机三级数据库考试——操作题(6—10套)
  8. 查看路由器拨号的宽带密码
  9. 7 openVINO 反光背心和安全帽检测
  10. 李嘉诚能否再续神话?“长科版”上市内幕