前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Pytorch 多卡并行训练

Pytorch 多卡并行训练

作者头像
为为为什么
发布于 2022-08-09 07:59:03
发布于 2022-08-09 07:59:03
4.1K00
代码可运行
举报
文章被收录于专栏:又见苍岚又见苍岚
运行总次数:0
代码可运行

Pytorch 框架支持多卡分布式并行训练网络,可以利用更大的显存得到更大的 batchsize,同时也会倍增训练速度,本文记录 Pytorch 多卡训练实现过程。

简介

  • Pytorch 支持两种多卡并行训练的方案,DataParallel 和 DistributedDataParallel
  • 主要区别在于 DataParallel 为单一进程控制多个显卡,配置简单但显卡资源利用率不够高,DistributedDataParallel 相对复杂,胜在高效
  • 将单卡训练的 Pytorch 流程修改为多卡并行需要对代码中的关键节点进行调整,Github 上有一个仓库做了很优质的 demo,可以用来参考,在此感谢这位大佬 https://github.com/tczhangzhi/pytorch-distributed
  • 官方文档

DataParallel

DataParallel 可以帮助我们(使用单进程控)将模型和数据加载到多个 GPU 中,控制数据在 GPU 之间的流动,协同不同 GPU 上的模型进行并行训练(细粒度的方法有 scatter,gather 等等)。

DataParallel 使用起来非常方便,我们只需要用 DataParallel 包装模型,再设置一些参数即可。需要定义的参数包括:参与训练的 GPU 有哪些,device_ids=gpus;用于汇总梯度的 GPU 是哪个,output_device=gpus0 。DataParallel 会自动帮我们将数据切分 load 到相应 GPU,将模型复制到相应 GPU,进行正向传播计算梯度并汇总:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model = nn.DataParallel(model.cuda(), device_ids=gpus, output_device=gpus[0])

值得注意的是,模型和数据都需要先 load 进 GPU 中,DataParallel 的 module 才能对其进行处理,否则会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 这里要 model.cuda()
model = nn.DataParallel(model.cuda(), device_ids=gpus, output_device=gpus[0])

for epoch in range(100):
   for batch_idx, (data, target) in enumerate(train_loader):
      # 这里要 images/target.cuda()
      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()

汇总一下,DataParallel 并行训练部分主要与如下代码段有关:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# main.py
import torch
import torch.distributed as dist

gpus = [0, 1, 2, 3]
torch.cuda.set_device('cuda:{}'.format(gpus[0]))

train_dataset = ...

train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=...)

model = ...
model = nn.DataParallel(model.to(device), device_ids=gpus, output_device=gpus[0])

optimizer = optim.SGD(model.parameters())

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()

在使用时,使用 python 执行即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
python main.py

DistributedDataParallel

实现原理
  • 使用 nn.DistributedDataParallel 进行Multiprocessing可以在多个gpu之间复制该模型,每个gpu由一个进程控制。(如果你想,也可以一个进程控制多个GPU,但这会比控制一个慢得多。也有可能有多个工作进程为每个GPU获取数据,但为了简单起见,本文将省略这一点。)这些GPU可以位于同一个节点上,也可以分布在多个节点上。每个进程都执行相同的任务,并且每个进程与所有其他进程通信。只有梯度会在进程/GPU之间传播,这样网络通信就不至于成为一个瓶颈了。
  • 训练过程中,每个进程从磁盘加载自己的小批(minibatch)数据,并将它们传递给自己的GPU。每个GPU都做它自己的前向计算,然后梯度在GPU之间全部约简。每个层的梯度不仅仅依赖于前一层,因此梯度全约简与并行计算反向传播,进一步缓解网络瓶颈。在反向传播结束时,每个节点都有平均的梯度,确保模型权值保持同步(synchronized)。
  • 上述的步骤要求需要多个进程,甚至可能是不同结点上的多个进程同步和通信。而Pytorch通过它的 distributed.init_process_group 函数实现。这个函数需要知道如何找到进程0(process 0),一边所有的进程都可以同步,也知道了一共要同步多少进程。每个独立的进程也要知道总共的进程数,以及自己在所有进程中的阶序(rank),当然也要知道自己要用那张GPU。总进程数称之为 world size。最后,每个进程都需要知道要处理的数据的哪一部分,这样批处理就不会重叠。而Pytorch通过 nn.utils.data.DistributedSampler 来实现这种效果。
实现过程

在 pytorch 1.0 之后,官方终于对分布式的常用方法进行了封装,支持 all-reduce,broadcast,send 和 receive 等等。通过 MPI 实现 CPU 通信,通过 NCCL 实现 GPU 通信。官方也曾经提到用 DistributedDataParallel 解决 DataParallel 速度慢,GPU 负载不均衡的问题,目前已经很成熟。

  • 现在假设我们已经有一套单卡训练 Pytorch 模型的代码,包含模型加载数据加载模型训练模型验证模型保存等模块,此时我们要将该套代码改为分布式多卡并行训练
  • 总体来看需要修改的流程如下

配置好需要用到的显卡id
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "1, 2, 3"

配置全局相同的随机数种子
  • 为了数据集划分时有相同的划分方式,最好开局前配置好统一的随机种子
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
random.seed(rand_seed)
torch.manual_seed(rand_seed)

设计单个进程的工作流程

不同显卡各自需要一个进行进行控制,每个进程由不同 local_rank 区分,单个显卡对应着携带某个 local_rankWorker 函数

  • 需要将训练流程整合到一个函数中(此处命名为 _train
  • 函数内部执行 数据加载,模型加载,前向推理,梯度回传,损失汇总,模型保存 的工作
多进程启动
  • 设计好工作流函数 Worker 后需要用多进程的方式启动
  • nprocs 参数中填入进程数量,一般为需要使用的显卡数量
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import torch.multiprocessing as mp
mp.spawn(self._train, nprocs=device_num, args=(arg1, arg2, ...))

  • 函数会将 0 - (device_num-1)范围内的整数依次作为第一个参数分配给一个进程执行 self._train 函数
  • 如果有其他参数可以放入 args 中
  • 如果在 Windows 下运行需要在 __name__ == '__main__' 下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if __name__ == '__main__':
    main()

初始化分布式进程
  • 此时进入到了 Pytorch 子进程在流程函数 Worker 的工作流程
  • 流程开始需要对当前进程进行初始化
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import torch.distributed as dist
dist.init_process_group(backend='nccl',
    init_method='tcp://127.0.0.1:' + self.process_port,
    world_size=self.device_num,
    rank=rank)

  • 其中 rank 为多进程启动时分配的数值
初始化分布式模型

分布式训练需要将模型转换为分布式模型

  • 初始化正常的模型 model
  • 放入显卡显存中
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
torch.cuda.set_device(rank)
model.cuda(rank)

  • 创建分布式模型
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[rank], find_unused_parameters=True)

加载分布式数据

分布式训练需要分布式数据

  • 正常创建 Dataset 对象 dataset
  • 生成分布式模型采样器
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sampler = torch.utils.data.distributed.DistributedSampler(dataset)

  • 将分布式采样器放入到 Dataloader 初始化参数中,此时 shuffle 参数需要设置为 False
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dataloader = torch.utils.data.DataLoader(dataset, batch_size=self.batch_size, shuffle=False, sampler=sampler, **kwargs)

  • 随后可以正常训练,进行前向推导和反向传播
汇总 Loss
  • 此时不同显卡各自执行着自己的任务,互相没有通信,为了获取更准确的evaluate结果,需要将多卡 validation 的 loss 结果汇总
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def reduce_mean(tensor, nprocs):
    rt = tensor.clone()
    dist.all_reduce(rt, op=dist.ReduceOp.SUM)
    rt /= nprocs
    return rt
torch.distributed.barrier()  # 在所有进程运行到这一步之前,先完成此前代码的进程会等待其他进程。这使得我们能够得到准确、有序的输出。
reduced_loss = reduce_mean(loss, self.device_num)

保存模型

保存模型其实可以正常保存,不过如果不加处理会有额外开销

  • 多进程训练模型时,保存模型在每个进程中都会有这一步操作,如果不加干预每个进程都会保存一遍模型,为了避免这种问题做一些调整即可
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if local_rank == 0:
	save_checkpoint ...

  • 模型保存时仅需保存其中的 module 变量
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
torch.save(m.module.state_dict(), path)

注意
  • DistributedDataParallel 中实际的 BatchSize 数量是显卡数与输入dataloader 的 batch_size 参数的乘积。

DataParallel 与 DistributedDataParallel 的区别

关于 nn.DataParallel (以下简称DP) 和 DistributedDataParallel (以下简称DDP) 的区别

  • DDP通过多进程实现的。也就是说操作系统会为每个GPU创建一个进程,从而避免了Python解释器GIL带来的性能开销。而DataParallel()是通过单进程控制多线程来实现的。还有一点,DDP也不存在前面DP提到的负载不均衡问题。
  • 参数更新的方式不同。DDP在各进程梯度计算完成之后,各进程需要将梯度进行汇总平均,然后再由 rank=0进程,将其 broadcast 到所有进程后,各进程用该梯度来独立的更新参数而 DP是梯度汇总到GPU0,反向传播更新参数,再广播参数给其他剩余的GPU。由于DDP各进程中的模型,初始参数一致 (初始时刻进行一次 broadcast),而每次用于更新参数的梯度也一致,因此,各进程的模型参数始终保持一致。而在DP中,全程维护一个 optimizer,对各个GPU上梯度进行求平均,而在主卡进行参数更新,之后再将模型参数 broadcast 到其他GPU.相较于DP, DDP传输的数据量更少,因此速度更快,效率更高。
  • DDP支持 all-reduce(指汇总不同 GPU 计算所得的梯度,并同步计算结果),broadcast,send 和 receive 等等。通过 MPI 实现 CPU 通信,通过 NCCL 实现 GPU 通信,缓解了进程间通信有大的开销问题。

官方建议使用 DDP,无论是从效率还是结果来看都要稳定一些

错误记录

模型存在不参与梯度计算的变量
  • 报错信息
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RuntimeError: Expected to have finished reduction in the prior iteration before starting a new one. This error indicates that your module has parameters that were not used in producing loss. You can enable unused parameter detection by (1) passing the keyword argument `find_unused_parameters=True` to `torch.nn.parallel.DistributedDataParallel`; (2) making sure all `forward` function outputs participate in calculating loss. If you already have done the above two steps, then the distributed data parallel module wasn't able to locate the output tensors in the return value of your module's `forward` function. Please include the loss function and the structure of the return value of `forward` of your module when reporting this issue (e.g. list, dict, iterable). 
  • 错误原因 使用 DistributedDataParallel 向服务器部署模型时,发现模型中有变量不参与梯度回传,为了不为这部分参数浪费显卡之间的通信资源,报错督促修正这部分参数
  • 解决方案 在 DistributedDataParallel 函数中加入参数 find_unused_parameters=True 例如:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
student_model = torch.nn.parallel.DistributedDataParallel(student_model, device_ids=[rank], find_unused_parameters=True)

模型保存后无法加载
  • 问题复现 经过 DataParallel 部署的模型保存到本地,再次加载权重时报错变量名称不匹配
  • 错误原因 事实上经过 DataParallel 的模型已经不是原来的模型了,原来模型的变量会被放到 dp_model.module 中,因此我们保存时可以仅保存这一部分
  • 解决方案
代码语言:txt
AI代码解释
复制
- 仅保存 module 的部分
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
torch.save(m.module.state_dict(), path)

代码语言:txt
AI代码解释
复制
- 加载时仍使用 DP
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
m=nn.DataParallel(Resnet18())
m.load_state_dict(torch.load(path))
m=m.module

MASTER_ADDR / MASTER_PORT
  • 分布式训练需要地址和端口进行通信,如果没有配置则会报错
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
raise _env_error("MASTER_ADDR")
ValueError: Error initializing torch.distributed using env:// rendezvous: environment variable MASTER_ADDR expected, but not set

  • 可以在环境变量中配置
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export MASTER_ADDR=localhost
export MASTER_PORT=5678

  • 也可以写死在代码中
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dist.init_process_group(backend='nccl',
    init_method='tcp://127.0.0.1:' + self.process_port,
    world_size=self.device_num,
    rank=rank)

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年3月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Pytorch中的分布式神经网络训练
随着深度学习的多项进步,复杂的网络(例如大型transformer 网络,更广更深的Resnet等)已经发展起来,从而需要了更大的内存空间。经常,在训练这些网络时,深度学习从业人员需要使用多个GPU来有效地训练它们。在本文中,我将向您介绍如何使用PyTorch在GPU集群上设置分布式神经网络训练。
deephub
2021/01/12
1.4K0
Pytorch中的分布式神经网络训练
PyTorch多GPU并行训练方法及问题整理
以下都在Ubuntu上面进行的调试, 使用的Ubuntu版本包括14, 18LST
公众号机器学习与AI生成创作
2020/09/14
14.9K0
PyTorch多GPU并行训练方法及问题整理
当代研究生应当掌握的并行训练方法(单机多卡)
每天给你送来NLP技术干货! ---- 排版:AI算法小喵 1. Take-Away 笔者使用 PyTorch 编写了不同加速库在 ImageNet 上的使用示例(单机多卡)。需要的同学可以当作 quickstart 将所需要的部分 copy 到自己的项目中(Github 请点击下面链接): nn.DataParallel[1] 简单方便的 nn.DataParallel torch.distributed[2] 使用 torch.distributed 加速并行训练 torch.multiprocessi
zenRRan
2022/08/26
1.7K0
当代研究生应当掌握的并行训练方法(单机多卡)
pytorch使用DistributedDataParallel进行多卡加速训练
在上文我们介绍了如何使用多线程在数据模块中进行模型训练加速,本文我们主要介绍在pytorch中如何使用DistributedDataParallel,torch.multiprocessing等模块来进行多卡并行处理提升模块训练速度。
languageX
2021/11/02
3.1K0
Pytorch分布式训练
简单来说,如果数据集较小时推荐尽量使用Map式Dataset,数据量过大、数据量未知、训练内存无法满足时只能使用Iterable式构建Dataset。
iResearch666
2023/09/13
1.3K0
Pytorch分布式训练
Pytorch中的Distributed Data Parallel与混合精度训练(Apex)
Distributed data parallel training in Pytorchyangkky.github.io
磐创AI
2021/01/12
1.2K0
Pytorch中的Distributed Data Parallel与混合精度训练(Apex)
[源码解析] PyTorch分布式(5) ------ DistributedDataParallel 总述&如何使用
本文是 PyTorch 分布式系列的第五篇,以几篇官方文档的翻译为基础,加入了自己的一些思考,带领大家进入DistributedDataParallel,在后续会用5~6篇左右做深入分析。
罗西的思考
2021/11/18
2.2K0
[源码解析] PyTorch分布式(5) ------ DistributedDataParallel 总述&如何使用
Pytorch多GPU训练
torch.nn.DataParallel()这个主要适用于单机多卡。个人一般比较喜欢在程序开始前,import包之后使用os.environ['CUDA_VISIBLE_DEVICES']来优先设定好GPU。例如要使用物理上第0,3号GPU只要在程序中设定如下:
烤粽子
2021/07/07
2.5K0
Pytorch 分布式训练
即进程组。默认情况下,只有一个组,一个 job 即为一个组,也即一个 world。
肉松
2020/09/07
2.5K0
Pytorch 分布式训练
PyTorch分布式训练简介
分布式训练已经成为如今训练深度学习模型的一个必备工具,但pytorch默认使用单个GPU进行训练,如果想用使用多个GPU乃至多个含有多块GPU的节点进行分布式训练的时候,需要在代码当中进行修改,这里总结一下几种使用pytorch进行分布式训练的方式。
狼啸风云
2020/02/13
5K0
【Ubuntu】分布式训练/pycharm远程开发
摸到了组里配备的多卡服务器,对于一个习惯单卡环境的穷学生来说,就像是鸟枪换炮,可惜这炮一时还不会使用,因此就有了此番学习。
zstar
2022/10/08
2K0
【Ubuntu】分布式训练/pycharm远程开发
Pytorch中多GPU训练指北
在数据越来越多的时代,随着模型规模参数的增多,以及数据量的不断提升,使用多GPU去训练是不可避免的事情。Pytorch在0.4.0及以后的版本中已经提供了多GPU训练的方式,本文简单讲解下使用Pytorch多GPU训练的方式以及一些注意的地方。
老潘
2023/10/19
1.8K0
Pytorch中多GPU训练指北
PyTorch中的多GPU训练:DistributedDataParallel
在pytorch中的多GPU训练一般有2种DataParallel(DP)和DistributedDataParallel(DDP) ,DataParallel是最简单的的单机多卡实现,但是它使用多线程模型,并不能够在多机多卡的环境下使用,所以本文将介绍DistributedDataParallel,DDP 基于使用多进程而不是使用多线程的 DP,并且存在 GIL 争用问题,并且可以扩充到多机多卡的环境,所以他是分布式多GPU训练的首选。
deephub
2022/11/11
1.2K0
PyTorch中的多GPU训练:DistributedDataParallel
深入理解Pytorch中的分布式训练
作者:台运鹏 (正在寻找internship...) 主页:https://yunpengtai.top
zenRRan
2023/01/12
1.4K0
深入理解Pytorch中的分布式训练
PyTorch分布式训练进阶:这些细节你都注意到了吗?
导语 | pytorch作为目前主流的深度学习训练框架之一,可以说是每个算法同学工作中的必备技能。此外,pytorch提供了极其方便的API用来进行分布式训练,由于最近做的工作涉及到一些分布式训练的细节,在使用中发现一些之前完全不会care的点,现记录于此,希望对有需求的同学有所帮助。 本文包含: pytorch分布式训练的工作原理介绍。 一些大家平时使用时可能不太注意的点,这些点并不会导致直观的bug或者训练中断,但可能会导致训练结果的偏差以及效率的降低。 同时结合某些场景,介绍更为细粒度(group)的
腾讯云开发者
2022/05/13
1K0
PyTorch分布式训练进阶:这些细节你都注意到了吗?
DataParallel里为什么会显存不均匀以及如何解决
鉴于网上此类教程有不少模糊不清,对原理不得其法,代码也难跑通,故而花了几天细究了一下相关原理和实现,欢迎批评指正!
zenRRan
2023/03/03
1.4K0
DataParallel里为什么会显存不均匀以及如何解决
PyTorch 中的多 GPU 训练和梯度累积作为替代方案
在本文[1]中,我们将首先了解数据并行(DP)和分布式数据并行(DDP)算法之间的差异,然后我们将解释什么是梯度累积(GA),最后展示 DDP 和 GA 在 PyTorch 中的实现方式以及它们如何导致相同的结果。
数据科学工厂
2023/08/10
5470
PyTorch 中的多 GPU 训练和梯度累积作为替代方案
[源码解析] PyTorch 分布式(2) ----- DataParallel(上)
DataParallel 从流程上来看,是通过将整个小批次(minibatch)数据加载到主线程上,然后将子小批次(ub-minibatches)数据分散到整个GPU网络中来工作。
罗西的思考
2021/11/11
1.1K0
[源码解析] PyTorch 分布式(2) ----- DataParallel(上)
Pytorch 分布式模式介绍
数据较多或者模型较大时,为提高机器学习模型训练效率,一般采用多GPU的分布式训练。
狼啸风云
2020/02/13
5.3K1
PyTorch 2.2 中文官方教程(十七)
在本教程中,我们想要强调一个新的torch.nn.functional函数,可以帮助实现 Transformer 架构。该函数被命名为torch.nn.functional.scaled_dot_product_attention。有关该函数的详细描述,请参阅PyTorch 文档。该函数已经被整合到torch.nn.MultiheadAttention和torch.nn.TransformerEncoderLayer中。
ApacheCN_飞龙
2024/02/05
1.2K0
PyTorch 2.2 中文官方教程(十七)
相关推荐
Pytorch中的分布式神经网络训练
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验