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. Element-ui Popconfirm气泡确认框的确认及取消事件不生效

    Element-ui 官方文档对 Popconfirm气泡确认框的一些属性及事件的描述不够详细,导致第一次使用时会遇到各种各样的问题 对确定事件及取消事件描述如下: 但是如果给组件绑定@confirm ...

  2. 项目中添加lib依赖

    Project Structure-->Artifacts

  3. ubuntu设置允许root用户登录

    一.允许ssh登录root用户(命令行) 1.修改root 密码,sudo passwd root 2.修改ssh配置, sudo vim /etc/ssh/shd_config,修改文件中的Perm ...

  4. 关于一类docker容器闪退问题定位

    背景:正在学习docker期间,接到一个任务,通过docker部署一个应用A.该应用A类似于之前部署的应用B,结果很自然地犯了形而上学的错误. 思路:基于dockerfile+docker-compo ...

  5. 达梦数据库产品支持技术学习分享_Week1

    本周主要从以下几个方面进行本人对达梦数据库学习的分享,学习进度和学习情况因人而异,仅供参考. 一.达梦数据库的体系架构 二.达梦数据库的安装 三.达梦数据库的数据类型 四.达梦数据库的DDL.DML. ...

  6. Nucleus-SE迁移:未实现的设施和兼容性

    Nucleus-SE迁移:未实现的设施和兼容性 Nucleus SE migration: Unimplemented facilities and compatibility Nucleus SE的 ...

  7. VB 老旧版本维护系列---迷之集合- dataTable

    迷之集合- dataTable '定义一个datatable,并声明一个空对象 Dim data As DataTable = New DataTable() '获取行数 Dim rows As In ...

  8. mysql表ERROR 144 (HY000)Table 'dede_archives' is marked

    1.故障现象 mysql> select count(*) from dede_archives;ERROR 144 (HY000): Table '.xx' is marked as cras ...

  9. kerberos安装配置

    目录 前言 服务端安装 组件安装 配置krb5.conf 配置kdc.conf 配置kadm5.acl 创建kdc数据库 在server端创建一个管理员账号,方便远程登录管理kerberos 正式启动 ...

  10. Nexus 安装配置教程

    目录 为什么使用 Nexus Docker 模式安装 Nexus 使用 data volume 使用本地目录 Nexus 配置 配置 Blob Stores Nexus 使用 包下载 包上传 参考 为 ...