Dataparallel 和 DataparallelDistributed 的区别

一、Dataparallel(DP)

1.1 Dartaparallel 的使用方式

Dataparallel 的使用方式比较简单,只需要一句话即可: net = nn.Dataparallel(net, device_ids, output_device)

其中,net 就是自己定义的网络实例,device_ids就是需要使用的显卡列表,output_device 表示参数输出结果的设备,默认情况下 output_device = device_ids[0]。因此在使用时经常发现第一块卡所占用的显存会多一些。

1.2 Dataparallel 的基本原理

Dataparallel是数据分离型,其具体做法是:在前向传播过程中,输入数据会被分成多个子部分送到不同的 device 中进行计算,而网络模型则是在每个 device 上都拷贝一份,即:输入的 batch 是平均分配到每个 device 中去,而网络模型需要拷贝到每个 device 中。在反向传播过程中,每个副本积累的梯度会被累加到原始模块中,未指明 output_device 的情况下会在 device_ids[0] 上进行运算,更新好以后把权重分发到其余卡。

1.3 Dataparallel 的注意事项

运行DataParallel模块之前,并行化模块必须在device_ids [0]上具有其参数和缓冲区。在执行DataParallel之前,会首先把其模型的参数放在device_ids[0]上。举个例子,服务器是八卡的服务器,刚好前面序号是0的卡被别人占用着,于是你只能用其他的卡来,比如你用2和3号卡,如果你直接指定 device_ids=[2, 3] 的话会出现模型初始化错误,类似于module没有复制到在 device_ids[0] 上去。那么你需要在运行train之前需要添加如下两句话指定程序可见的devices,如下:

os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "2, 3"

当添加这两行代码后,那么 device_ids[0] 默认的就是第2号卡,你的模型也会初始化在第2号卡上了,而不会占用第0号卡了。设置上面两行代码后,那么对这个程序而言可见的只有2和3号卡,和其他的卡没有关系,这是物理上的号卡,逻辑上来说其实是对应0和1号卡,即 device_ids[0] 对应的就是第2号卡,device_ids[1] 对应的就是第3号卡。(当然你要保证上面这两行代码需要定义在下面两行代码之前:

device_ids = [0, 1]
net = torch.nn.DataParallel(net, device_ids=device_ids)

1.4 Dataparallel 的优缺点

Dataparallel 的优点就是使用起来非常简单,能够使用多卡的显存来处理数据。然而其缺点是:会造成负载不均衡的情况,成为限制模型训练速度的瓶颈。

二、DataparallelDistributed(DDP)

2.1 DDP 的基本原理

DataparallelDistributed 在每次迭代中,操作系统会为每个GPU创建一个进程,每个进程具有自己的 optimizer ,并独立完成所有的优化步骤,进程内与一般的训练无异。在各进程梯度计算完成之后,各进程需要将梯度进行汇总平均,然后再由 rank=0 的进程,将其 broadcast 到所有进程。各进程用该梯度来更新参数。由于各进程中的模型,初始参数一致 (初始时刻进行一次 broadcast),而每次用于更新参数的梯度也一致,因此,各进程的模型参数始终保持一致。而在 DataParallel 中,全程维护一个 optimizer,对各 GPU 上梯度进行求和,而在主 GPU 进行参数更新,之后再将模型参数 broadcast 到其他 GPU。相较于 DataParalleltorch.distributed 传输的数据量更少,因此速度更快,效率更高。

2.2 DDP的使用方式

DDP使用起来比DP要麻烦一些,具体想要了解其中原理的可以参考下面几篇文章:

https://blog.csdn.net/laizi_laizi/article/details/115299263

DataParallel & DistributedDataParallel分布式训练 - 知乎 (zhihu.com)

最后,参考上述文章,整理出来了下面一份可以直接跑的代码,由于个人环境不同,可能在个别环境出现不适配的情况,可以参考上述文章进行修改。

################
## main.py文件
import argparse
from tqdm import tqdm
import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
# 新增:
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP ### 1. 基础模块 ###
# 假设我们的模型是这个,与DDP无关
class ToyModel(nn.Module):
def __init__(self):
super(ToyModel, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
# 假设我们的数据是这个
def get_dataset():
transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
my_trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
# DDP:使用DistributedSampler,DDP帮我们把细节都封装起来了。
# 用,就完事儿!sampler的原理,第二篇中有介绍。
train_sampler = torch.utils.data.distributed.DistributedSampler(my_trainset)
# DDP:需要注意的是,这里的batch_size指的是每个进程下的batch_size。
# 也就是说,总batch_size是这里的batch_size再乘以并行数(world_size)。
trainloader = torch.utils.data.DataLoader(my_trainset,
batch_size=16, num_workers=2, sampler=train_sampler)
return trainloader ### 2. 初始化我们的模型、数据、各种配置 ####
# DDP:从外部得到local_rank参数
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", default=-1, type=int)
FLAGS = parser.parse_args()
local_rank = FLAGS.local_rank # DDP:DDP backend初始化
torch.cuda.set_device(local_rank)
dist.init_process_group(backend='nccl') # nccl是GPU设备上最快、最推荐的后端 # 准备数据,要在DDP初始化之后进行
trainloader = get_dataset() # 构造模型
model = ToyModel().to(local_rank)
# DDP: Load模型要在构造DDP模型之前,且只需要在master上加载就行了。
ckpt_path = None
if dist.get_rank() == 0 and ckpt_path is not None:
model.load_state_dict(torch.load(ckpt_path))
# DDP: 构造DDP model
model = DDP(model, device_ids=[local_rank], output_device=local_rank) # DDP: 要在构造DDP model之后,才能用model初始化optimizer。
optimizer = torch.optim.SGD(model.parameters(), lr=0.001) # 假设我们的loss是这个
loss_func = nn.CrossEntropyLoss().to(local_rank) ### 3. 网络训练 ###
model.train()
iterator = tqdm(range(100))
for epoch in iterator:
# DDP:设置sampler的epoch,
# DistributedSampler需要这个来指定shuffle方式,
# 通过维持各个进程之间的相同随机数种子使不同进程能获得同样的shuffle效果。
trainloader.sampler.set_epoch(epoch)
# 后面这部分,则与原来完全一致了。
for data, label in trainloader:
data, label = data.to(local_rank), label.to(local_rank)
optimizer.zero_grad()
prediction = model(data)
loss = loss_func(prediction, label)
loss.backward()
iterator.desc = "loss = %0.3f" % loss
optimizer.step()
# DDP:
# 1. save模型的时候,和DP模式一样,有一个需要注意的点:保存的是model.module而不是model。
# 因为model其实是DDP model,参数是被`model=DDP(model)`包起来的。
# 2. 只需要在进程0上保存一次就行了,避免多次保存重复的东西。
if dist.get_rank() == 0:
torch.save(model.module.state_dict(), "%d.ckpt" % epoch) ################
## Bash运行
# DDP: 使用torch.distributed.launch启动DDP模式
# 使用CUDA_VISIBLE_DEVICES,来决定使用哪些GPU
# CUDA_VISIBLE_DEVICES="0,1" python -m torch.distributed.launch --nproc_per_node 2 main.py

三、总结

总之Dataparellel和Distribution都是模型训练加速的一种方法。Dataparallel (支持单机多卡),但是速度慢(主要原因是它采用parameter server 模式,一张主卡作为reducer,负载不均衡,主卡成为训练瓶颈),在主GPU上进行梯度计算和更新,再将参数给其他gpu。而DDP则使用多线程进行加速,训练速度得到了明显的提升,但是代码修改起来比较麻烦,需要不断试错积累经验。

Pytorch的模型加速方法:Dataparallel (DP) 和 DataparallelDistributedparallel (DDP)的更多相关文章

  1. [Pytorch]基于混和精度的模型加速

    这篇博客是在pytorch中基于apex使用混合精度加速的一个偏工程的描述,原理层面的解释并不是这篇博客的目的,不过在参考部分提供了非常有价值的资料,可以进一步研究. 一个关键原则:“仅仅在权重更新的 ...

  2. Pytorch指定GPU的方法总结

    Pytorch指定GPU的方法 改变系统变量 改变系统环境变量仅使目标显卡,编辑 .bashrc文件,添加系统变量 export CUDA_VISIBLE_DEVICES=0 #这里是要使用的GPU编 ...

  3. PyTorch保存模型与加载模型+Finetune预训练模型使用

    Pytorch 保存模型与加载模型 PyTorch之保存加载模型 参数初始化参 数的初始化其实就是对参数赋值.而我们需要学习的参数其实都是Variable,它其实是对Tensor的封装,同时提供了da ...

  4. StartDT AI Lab | 视觉智能引擎之算法模型加速

    通过StartDT AI Lab专栏之前多篇文章叙述,相信大家已经对计算机视觉技术及人工智能算法在奇点云AIOT战略中的支撑作用有了很好的理解.同样,这种业务牵引,技术覆盖的模式也收获了市场的良好反响 ...

  5. [炼丹术]使用Pytorch搭建模型的步骤及教程

    使用Pytorch搭建模型的步骤及教程 我们知道,模型有一个特定的生命周期,了解这个为数据集建模和理解 PyTorch API 提供了指导方向.我们可以根据生命周期的每一个步骤进行设计和优化,同时更加 ...

  6. VPS/服务器优化网络、加速方法总结与参考

    在国外的服务器上因为受各种因素影响,即使国外的服务器都是百兆共享或者G口到国内下载速度都不是很让人满意,大部分人购买国外服务器是用作存储下载或者扶墙,速度慢的所以话影响我们的使用体验.所以就搞出了很多 ...

  7. go依赖包下载加速方法及github加速

    go依赖包下载加速方法及github加速 对于https://github.com/kubernetes/kubernetes整个仓库大小为近900M,下载起来那个伤心: 方法一:使用码云 这是码云上 ...

  8. tensorflow中一种融合多个模型的方法

    1.使用场景 假设我们有训练好的模型A,B,C,我们希望使用A,B,C中的部分或者全部变量,合成为一个模型D,用于初始化或其他目的,就需要融合多个模型的方法 2.如何实现 我们可以先声明模型D,再创建 ...

  9. 模型验证方法——R语言

    在数据分析中经常会对不同的模型做判断 一.混淆矩阵法 作用:一种比较简单的模型验证方法,可算出不同模型的预测精度 将模型的预测值与实际值组合成一个矩阵,正例一般是我们要预测的目标.真正例就是预测为正例 ...

随机推荐

  1. GO学习-(28) Go语言操作influxDB

    Go语言操作influxDB 本文介绍了influxDB时序数据库及Go语言操作influxDB. InfluxDB是一个开源分布式时序.事件和指标数据库.使用Go语言编写,无需外部依赖.其设计目标是 ...

  2. Docker学习(4) 守护式容器

    守护式容器 stop - 等待信号 kill - 直接干死

  3. Python+Selenium - 浏览器驱动

    Chrome \ Firefox 下载浏览器版本对应的驱动文件后,放在D:\Program Files (x86)\Python\Python38路径下,Python使用时用 driver = web ...

  4. 华为MDC软件架构

    华为MDC软件架构 平台软件零层逻辑架构如下图,由基础层.功能层.应用层和服务层组成. 零层逻辑架构 从平台软件一层逻辑架构可以看出,MDC用了华为自研的越影操作系统.兼容Autosar标准的软件中间 ...

  5. Deeplearning知识蒸馏

    Deeplearning知识蒸馏 merge paddleslim.dist.merge(teacher_program, student_program, data_name_map, place, ...

  6. 使用NVIDIA A100 TF32获得即时加速

    使用NVIDIA A100 TF32获得即时加速 NVIDIA A100带来了我们公司历史上最大的单代性能增长.这是一个新的结构创新,这是一个多功能的支持,这是一个多功能的结构支持.TF32是用于深度 ...

  7. 【Java实现】剑指offer53.1——在排序数组中查找数字(LeetCode34:在排序数组中查找元素的起始位置)

    序数组中查找元素的起始位置):思路分享 <剑指offer>题目和LeetCode主站本质是一样的,想要找到target数目,也需要找到左右边界 题目解析: 在一个排序数组中,找到targe ...

  8. 性能监控之常见 Java Heap Dump 方法

    一.前言 在本文中,我们总结下抓 Java dump 的几种不同方法. Java Heap Dump 是特定时刻 JVM 内存中所有对象的快照.它们对于解决内存泄漏问题和分析 Java 应用程序中的内 ...

  9. 痞子衡嵌入式:超级下载算法(RT-UFL)开发笔记番外(1) - JLinkScript妙用

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是超级下载算法开发笔记番外篇之JLinkScript妙用. JLinkScript 文件是配套 J-Link 调试器使用的脚本,这个脚本适 ...

  10. 【题解】Luogu p3478 [POI2008]STA-Station 动态规划

    题目描述 给出一个$N$个点的树,找出一个点来,以这个点为根的树时,所有点的深度之和最大 输入输出格式 输入格式 第一行一个数$n$,表示树上共有$n$个点接下来$n-1$行,表示$n-1$条边;每行 ...