深度学习中的多GPU训练(Pytorch 20)

一 多GPU训练

下面详细介绍如何从零开始并行地训练网络,这里需要运用小批量随机梯度下降算法。后面我还讲介绍如何使用高级API并行训练网络。

我们从一个简单的计算机视觉问题和一个稍稍过时的网络开始。这个网络有多个卷积层和汇聚层,最后可能 有几个全连接的层,看起来非常类似于LeNet (LeCun et al., 1998)或AlexNet (Krizhevsky et al., 2012)。假设我们有多个GPU(如果是桌面服务器则有2个,AWS g4dn.12xlarge上有4个,p3.16xlarge上有8个,p2.16xlarge上 有16个)。我们希望以一种方式对训练进行拆分,为实现良好的加速比,还能同时受益于简单且可重复的设计 选择。毕竟,多个GPU同时增加了内存和计算能力。简而言之,对于需要分类的小批量训练数据,我们有以下选择。

第一种方法,在多个GPU之间拆分网络。也就是说,每个GPU将流入特定层的数据作为输入,跨多个后续层 对数据进行处理,然后将数据发送到下一个GPU。与单个GPU所能处理的数据相比,我们可以用更大的网络 处理数据。此外,每个GPU占用的显存(memory footprint)可以得到很好的控制,虽然它只是整个网络显 存的一小部分。

第二种方法,拆分层内的工作。例如,将问题分散到4个GPU,每个GPU生成16个通道的数据,而不是在单 个GPU上计算64个通道。对于全连接的层,同样可以拆分输出单元的数量。其策略用于处理显存非常小(当时为2GB)的GPU。当通道或单元的数量不太小时,使计算性能有良好的提升。此外,由于可用的显存呈线性扩展,多个GPU能够处理不断变大的网络。

假设一台机器有k个GPU。给定需要训练的模型,虽然每个GPU上的参数值都是相同且同步的,但是每个GPU都 将独立地维护一组完整的模型参数。

一般来说,k个GPU并行训练过程如下:

  • 在任何一次训练迭代中,给定的随机的小批量样本都将被分成k个部分,并均匀地分配到GPU上
  • 每个GPU根据分配给它的小批量子集,计算模型参数的损失和梯度
  • 将k个GPU中的局部梯度聚合,以获得当前小批量的随机梯度;
  • 聚合梯度被重新分发到每个GPU中
  • 每个GPU使用这个小批量随机梯度,来更新它所维护的完整的模型参数集。

1.1 简单网络

在实践中请注意,当在k个GPU上训练时,需要扩大小批量的大小为k的倍数,这样每个GPU都有相同的工作量,就像只在单个GPU上训练一样。因此,在16‐GPU服务器上可以显著地增加小批量数据量的大小,同时可 能还需要相应地提高学习率。

%matplotlib inline
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

我们使用 LeNet,从零开始定义它,从而详细说明参数交换和同步

# 初始化参数
scale = 0.01
W1 = torch.randn(size = (20, 1, 3, 3)) * scale
b1 = torch.zeros(20)
W2 = torch.randn(size = (50, 20, 5, 5)) * scale
b2 = torch.zeros(50)
W3 = torch.randn(size = (800, 128)) * scale
b3 = torch.zeros(128)
W4 = torch.randn(size = (128, 10)) * scale
b4 = torch.zeros(10)
params = [W1, b1, W2, b2, W3, b3, W4, b4]# 定义模型
def lenet(X, params):h1_conv = F.conv2d(input = X, weight = params[0], bias = params[1])h1_activation = F.relu(h1_conv)h1 = F.avg_pool2d(input = h1_activation, kernel_size = (2, 2), stride = (2, 2))h2_conv = F.conv2d(input = h1, weight = params[2], bias = params[3])h2_activation = F.relu(h2_conv)h2 = F.avg_pool2d(input = h2_activation, kernel_size = (2, 2), stride = (2, 2))h2 = h2.reshape(h2.shape[0], -1)h3_linear = torch.mm(h2, params[4] + params[5])h3 = F.relu(h3_linear)y_hat = torch.mm(h3, params[6] + params[7])return y_hat# 交叉熵损失
loss = nn.CrossEntropyLoss(reduction = 'none')

1.2 数据同步

对于高效的多GPU训练,我们需要两个基本操作。首先,我们需要向多个设备分发参数并附加梯度 (get_params)。如果没有参数,就不可能在GPU上评估网络。第二,需要跨多个设备对参数求和,也就是说,需要一个allreduce函数。

def get_params(params, device):new_params = [p.to(device) for p in params]for p in params:p.requires_grad_()return new_paramsnew_params = get_params(params, d2l.try_gpu(0))
print(f'b1 权重:{new_params[1]}')
print(f'b1 梯度:{new_params[1].grad}')

由于还没有进行任何计算,因此权重参数的梯度仍然为零。假设现在有一个向量分布在多个GPU上,下面 的allreduce函数将所有向量相加,并将结果广播给所有GPU。请注意,我们需要将数据复制到累积结果的设 备,才能使函数正常工作。

def allreduce(data):for i in range(1, len(data)):data[0][:] += data[i].to(data[0].device)for i in range(1, len(data)):data[i][:] = data[0].to(data[i].device)

通过在不同设备上创建具有不同值的向量并聚合它们。

data = [torch.ones((1, 2), device = d2l.try_gpu(i)) * (i + 1) for i in range(2)]
print(f'allreduce 之前:\n', data[0], '\n', data[1])
allreduce(data)
print('allreduce 之后:\n', data[0], '\n', data[1])

1.3 数据分发

我们需要一个简单的工具函数,将一个小批量数据均匀地分布在多个GPU上。例如,有两个GPU时,我们希望每个GPU可以复制一半的数据。因为深度学习框架的内置函数编写代码更方便、更简洁,所以在4 × 5矩阵 上使用它进行尝试。

data = torch.arange(20).reshape(4, 5)
devices = [torch.device('cuda:0'), torch.device('cuda:0')]
split = nn.parallel.scatter(data, devices)
print('input:', data)
print('load into:', device)
print('output:', split)

为了方便以后复用,我们定义了可以同时拆分数据和标签的split_batch函数。

#@save
def split_batch(X, y, devices):assert X.shape[0] == y.shape[0]return (nn.parallel.scatter(x, devices),nn.parallel.scatter(y, devices))

1.4 执行训练

现在我们可以在一个小批量上实现多GPU训练。在多个GPU之间同步数据将使用刚才讨论的辅助函 数allreduce和split_and_load。我们不需要编写任何特定的代码来实现并行性。因为计算图在小批量内的 设备之间没有任何依赖关系,因此它是“自动地”并行执行。

def train_batch(X, y, device_params, devices, lr):X_shards, y_shards = split_batch(X, y, devices)# 在每个GPU上分别计算损失ls = [loss(lenet(X_shard, device_W), y_shard).sum()for X_shard, y_shard, device_W in zip(X_shards, y_shards, device_params)]for l in ls: # 反向传播在每个GPU上分别执行l.backward()# 将每个GPU的所有梯度相加,并将其广播到所有GPUwith torch.no_grad():for i in range(len(device_params[0])):allreduce([device_params[c][i].grad for c in range(len(devices))])# 在每个GPU上分别更新模型参数for param in device_params:d2l.sgd(param, lr, X.shape[0]) # 在这里,我们使用全尺寸的小批量

现在,我们可以定义训练函数。与前几章中略有不同:训练函数需要分配GPU并将所有模型参数复制到所有 设备。显然,每个小批量都是使用train_batch函数来处理多个GPU。我们只在一个GPU上计算模型的精确度, 而让其他GPU保持空闲,尽管这是相对低效的,但是使用方便且代码简洁。

def train(num_gpus, batch_size, lr):train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)devices = [d2l.try_gpu(i) for i in range(num_gpus)]# 将模型参数复制到num_gpus个GPUdevice_params = [get_params(params, d) for d in devices]num_epochs = 10animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs])timer = d2l.Timer()for epoch in range(num_epochs):timer.start()for X, y in train_iter:# 为单个小批量执行多GPU训练train_batch(X, y, device_params, devices, lr)torch.cuda.synchronize()timer.stop()# 在GPU0上评估模型animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu(lambda x: lenet(x, device_params[0]), test_iter, devices[0]),))print(f'测试精度:{animator.Y[0][-1]:.2f},{timer.avg():.1f}秒/轮,'f'在{str(devices)}')train(num_gpus=1, batch_size=256, lr=0.2)

保持批量大小和学习率不变,并增加为2个GPU,我们可以看到测试精度与之前的实验基本相同。不同的GPU个 数在算法寻优方面是相同的。不幸的是,这里没有任何有意义的加速:模型实在太小了;而且数据集也太小 了。在这个数据集中,我们实现的多GPU训练的简单方法受到了巨大的Python开销的影响。在未来,我们将 遇到更复杂的模型和更复杂的并行化方法。尽管如此,让我们看看Fashion‐MNIST数据集上会发生什么。

train(num_gpus=2, batch_size=256, lr=0.2)

小结:

  • 有多种方法可以在多个GPU上拆分深度网络的训练。拆分可以在层之间、跨层或跨数据上实现。前两者需要对数据传输过程进行严格编排,而最后一种则是最简单的策略。
  • 数据并行训练本身是不复杂的,它 通过增加有效的小批量数据量的大小提高了训练效率
  • 在数据并行中,数据需要跨多个GPU拆分,其中每个GPU执行自己的前向传播和反向传播,随后所有的 梯度被聚合为一,之后聚合结果向所有的GPU广播。
  • 小批量数据量更大时,学习率也需要稍微提高一些

二 多GPU的简洁实现

每个新模型的并行计算都从零开始实现是无趣的。此外,优化同步工具以获得高性能也是有好处的。下面我 们将展示如何使用深度学习框架的高级API来实现这一点。

import torch
from torch import nn
from d2l import torch as d2l

它依然能够容易地和快速地训练。我们选择的是 (He et al., 2016)中的ResNet‐18。因为输入的图像很小,所以稍微修改了一下。我们在开始时使用了更小的卷积核、步长和填充,而且删除了最大汇聚层。

#@save
def resnet18(num_classes, in_channels=1):"""稍加修改的ResNet-18模型"""def resnet_block(in_channels, out_channels, num_residuals,first_block=False):blk = []for i in range(num_residuals):if i == 0 and not first_block:blk.append(d2l.Residual(in_channels, out_channels,use_1x1conv=True, strides=2))else:blk.append(d2l.Residual(out_channels, out_channels))return nn.Sequential(*blk)# 该模型使用了更小的卷积核、步长和填充,而且删除了最大汇聚层net = nn.Sequential(nn.Conv2d(in_channels, 64, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(64), nn.ReLU())net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))net.add_module("resnet_block2", resnet_block(64, 128, 2))net.add_module("resnet_block3", resnet_block(128, 256, 2))net.add_module("resnet_block4", resnet_block(256, 512, 2))net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1,1)))net.add_module("fc", nn.Sequential(nn.Flatten(),nn.Linear(512, num_classes)))return net

我们将在训练回路中初始化网络。

net = resnet18(10)
# 获取GPU列表
devices = d2l.try_all_gpus()
# 我们将在训练代码实现中初始化网络

如前所述,用于训练的代码需要执行几个基本功能才能实现高效并行:

  • 需要在所有设备上初始化网络参数
  • 在数据集上迭代时,要将小批量数据分配到所有设备上
  • 跨设备并行计算损失及其梯度
  • 聚合梯度,并相应地更新参数。 最后,并行地计算精确度和发布网络的最终性能。除了需要拆分和聚合数据外,训练代码与前几章的实现非 常相似。
def train(net, num_gpus, batch_size, lr):train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)devices = [d2l.try_gpu(i) for i in range(num_gpus)]def init_weights(m):if type(m) in [nn.Linear, nn.Conv2d]:nn.init.normal_(m.weight, std=0.01)net.apply(init_weights)# 在多个GPU上设置模型net = nn.DataParallel(net, device_ids=devices)trainer = torch.optim.SGD(net.parameters(), lr)loss = nn.CrossEntropyLoss()timer, num_epochs = d2l.Timer(), 10animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs])for epoch in range(num_epochs):net.train()timer.start()for X, y in train_iter:trainer.zero_grad()X, y = X.to(devices[0]), y.to(devices[0])l = loss(net(X), y)l.backward()trainer.step()timer.stop()animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu(net, test_iter),))print(f'测试精度:{animator.Y[0][-1]:.2f},{timer.avg():.1f}秒/轮,'f'在{str(devices)}')

接下来看看这在实践中是如何运作的。我们先在单个GPU上训练网络进行预热。

train(net, num_gpus=1, batch_size=256, lr=0.1)

接下来我们使用2个GPU进行训练。

train(net, num_gpus=2, batch_size=512, lr=0.2)

小结:

  • 神经网络可以在(可找到数据的)单GPU上进行自动评估
  • 每台设备上的网络 需要先初始化,然后再尝试访问该设备上的参数,否则会遇到错误。
  • 优化算法在多个GPU上自动聚合

三 多机训练

新的挑战出现在多台机器上进行分布式训练:我们 需要服务器之间相互通信,而这些服务器又只通过相对较低的带宽结构连接,在某些情况下这种连接的速度可能会慢一个数量级,因此跨设备同步是个棘手的问题。 毕竟,在不同机器上运行训练代码的速度会有细微的差别,因此如果想使用分布式优化的同步算法就需要同 步(synchronize)这些机器。

  1. 在每台机器上读取一组(不同的)批量数据,在多个GPU之间分割数据并传输到GPU的显存中。基于每个GPU上的批量数据分别计算预测和梯度。
  2. 来自一台机器上的所有的本地GPU的梯度聚合在一个GPU上(或者在不同的GPU上聚合梯度的某些部 分)。
  3. 每台机器的梯度被发送到其本地CPU中
  4. 所有的CPU将梯度发送到中央参数服务器中,由该服务器聚合所有梯度。
  5. 然后使用聚合后的梯度来更新参数,并将更新后的参数广播回各个CPU中。
  6. 更新后的参数信息发送到本地一个(或多个)GPU中
  7. 所有GPU上的参数更新完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/333348.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android:将时间戳转换为本地时间格式

一、效果图 图1,中国的时间格式 图2,美国的时间格式 二、StringUtil.kt代码 import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* object StringUtil {fun formatTimestamp(currentTime: Long): String {var sdf Si…

dolphinscheduler standalone安装

官方文档:https://dolphinscheduler.apache.org/en-us/docs/3.1.3/guide/installation/standalone 1.安装(以放在/home为例) 下载见:https://download.csdn.net/download/taotao_guiwang/89311365 tar -xvzf apache-dolphinsche…

美团Java社招面试题真题,最新面试题

如何处理Java中的内存泄露? 1、识别泄露: 使用内存分析工具(如Eclipse Memory Analyzer Tool、VisualVM)来识别内存泄露的源头。 2、代码审查: 定期进行代码审查,关注静态集合类属性和监听器注册等常见内…

C++ 数据结构算法 学习笔记(33) -查找算法及企业级应用

C 数据结构算法 学习笔记(33) -查找算法及企业级应用 数组和索引 日常生活中,我们经常会在电话号码簿中查阅“某人”的电话号码,按姓查询或者按字母排 序查询;在字典中查阅“某个词”的读音和含义等等。在这里,“电话号码簿”和…

nginx文件解析漏洞测试

环境条件:ubuntu14,已安装docker,docker pull ubuntu:14.04.5 一、Nginx配置 1、使用docker启动容器: docker run -itd --name ubuntu -p 8088:80 ubuntu:14.04.5 2、进入容器: docker exec -it ubuntu /bin/bash 3、然后使用以下语句安装相关环境…

(四)手把手教你内网穿透,实现外网主机访问内网服务器

背景:书接上回, 服务器的使用-CSDN博客 课题组成员都有自己的账号,且能通过内网访问服务器,进行远程连接了。我们知道内网中的主机可以访问公网的主机,反之不可以访问。那么如果课题组成员在家不在内网区域内&#x…

ai发展会不会带来企业的员工垄断呢

写代码写累了,写点个人不成熟的想法,作为记录 随着gpt-4o发布,可以预计的是,AI逐渐能够通过各种外接设备和传感器和真实世界实时交互。那么未来一个接上摄像头,键盘,音响,可移动身体的的AI还会…

如何注册Claude3?解决Claude3无海外手机号接收验证码的问题以及如何订阅Claude Pro

原文链接:如何注册 Claude3?解决 Claude3 无海外手机号接收验证码的问题以及如何订阅 Claude Pro 前言 Claude3已经出来有一段时间了,大家有没有体验过呢?不过从目前来看,Anthropic公司总共推出了3个模型&#xff1…

Jenkins安装 :AWS EC2 Linux

1 JDK11 install # 用的yum安装 # 压缩包安装,下载的jdk-11.0.22_linux-x64_bin.tar.gz在EC2解压,配置环境变量,运行jenkins的时候会报错$ yum -y list java-11* Available Packages java-11-amazon-corretto-devel.x86_64 …

用队列实现栈 用栈实现队列 设计循环队列

用队列实现栈 思路 栈的特点:后进先出 队列的特点:先进先出 使用两个队列实现栈: 我们可以使用两个队列,一个队列为:空队列,一个队列为:非空队列 当我们要出队列时: 将 size - …

多态

多态的概念 通俗来说,就是多种形态,具体点就是完成某个行为,当不同的对象去完成时会产生出不同的状态 多态的定义及实现 多态构成的条件 1、必须通过基类的指针或者引用调用虚函数 2、子类必须对基类的虚函数进行重写 虚函数 被关键字vi…

内网横向移动小补充 --->PTK

大家别急,我的基于资源的约束性委派攻击还在写,这个东西一时半会讲不清楚,所以我在这里先来补充一点横向移动以前没说好的东西!!! 在更啦,别催啦~~~~ 还记得我之前在内网渗透里面讲过这个PTK&a…

2024.5.22 关于 SpringCloud —— Nacos 配置管理

目录 Nacos 配置统一管理 Nacos 配置热部署 Nacos 多环境配置共享 配置优先级 Nacos 配置统一管理 实例理解 我们想要利用 Nacos 在 user-service 的 application.yml 配置文件中新增配置项此处我们将新增配置日期格式为 yyyy-MM-dd HH:mm:ss下图为新增 Nacos 配置统一管理…

基于STM32实现智能园艺系统

目录 引言环境准备智能园艺系统基础代码示例:实现智能园艺系统 土壤湿度传感器数据读取水泵控制温湿度传感器数据读取显示系统用户输入和设置应用场景:智能农业与家庭园艺问题解决方案与优化收尾与总结 1. 引言 本教程将详细介绍如何在STM32嵌入式系统…

基于Zynq 7000 SoC的迁移设计

基于Zynq 7000 SoC的迁移设计 Vivado IDE工具使用IP集成器进行嵌入式开发。各种IP Vivado IDE IP目录中提供,以适应复杂的设计。您也可以添加 自定义IP到IP目录。 您可以将基于Zynq 7000平台处理器的设计迁移到Vivado design Suite中 使用以下步骤。 1.生成系统基础…

【搜索方法推荐】高效信息检索方法和实用网站推荐

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…

亚马逊云主管马特·加尔曼面临压力,致力于在人工智能领域赶超竞争对手

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

四川景源畅信:抖音小店新手如何做?

随着短视频平台的兴起,抖音小店成为了许多创业者的新选择。但是,对于新手来说,如何在抖音上开设并经营好自己的小店呢?本文将围绕这一问题展开讨论。 一、明确目标和定位作为抖音小店的新手,首先要明确自己的经营目标和定位。是想…

【CTF Web】CTFShow web4 Writeup(SQL注入+PHP+字符型注入)

web4 1 管理员阿呆又失败了&#xff0c;这次一定要堵住漏洞 解法 注意到&#xff1a; <!-- flag in id 1000 -->拦截很多种字符&#xff0c;连 select 也不给用了。 if(preg_match("/or|\-|\\\|\/|\\*|\<|\>|\!|x|hex|\(|\)|\|select/i",$id)){die(&q…

Pycharm最新安装教程(最新更新时间2024年5月27日)

ps&#xff1a;本教程Pycharm安装&#xff0c;最新更新时间&#xff1a;2024年5月27日&#xff0c;公众号持续更新关注公众号防失联哦 Pycharm 再次更新了一个小版本。又回到老话题&#xff0c;2023.3.2这个版本是否还能安装&#xff0c;笔者也亲测了一下。还是沿用本站之前的…