当前位置: 首页 > news >正文

动手学深度学习12.6. 多GPU的简洁实现-笔记练习(PyTorch)

以下内容为结合李沐老师的课程和教材补充的学习笔记,以及对课后练习的一些思考,自留回顾,也供同学之人交流参考。

本节课程地址:简洁实现_哔哩哔哩_bilibili

本节教材地址:12.6. 多GPU的简洁实现 — 动手学深度学习 2.0.0 documentation

本节开源代码:...>d2l-zh>pytorch>chapter_optimization>multiple-gpus-concise.ipynb


多GPU的简洁实现

每个新模型的并行计算都从零开始实现是无趣的。此外,优化同步工具以获得高性能也是有好处的。下面我们将展示如何使用深度学习框架的高级API来实现这一点。数学和算法与 12.5节 中的相同。本节的代码至少需要两个GPU来运行。

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

[简单网络]

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

#@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(out_channels, use_1x1conv=True, strides=2))else:blk.append(d2l.Residual(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

网络初始化

我们将在训练回路中初始化网络。请参见 4.8节 复习初始化方法。

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)

输出结果:
测试精度:0.90,47.1秒/轮,在[device(type='cuda', index=0)]

接下来我们[使用2个GPU进行训练]。与 12.5节 中评估的LeNet相比,ResNet-18的模型要复杂得多。这就是显示并行化优势的地方,计算所需时间明显大于同步参数需要的时间。因为并行化开销的相关性较小,因此这种操作提高了模型的可伸缩性。

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

输出结果:
测试精度:0.80,25.6秒/轮,在[device(type='cuda', index=0), device(type='cuda', index=1)]

小结

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

练习

1. 本节使用ResNet-18,请尝试不同的迭代周期数、批量大小和学习率,以及使用更多的GPU进行计算。如果使用16个GPU(例如,在AWS p2.16xlarge实例上)尝试此操作,会发生什么?
解:
下面是尝试不同的迭代周期数、批量大小和学习率的实验,结果表明:

  • 测试精度基本上是随迭代周期数的增加而增加,epoch为10或20或30的耗时相差不大,但测试精度明显提高。
  • 测试精度随批量大小的增加先降后升,batch_size为512时的测试精度和耗时都最低,batch_size为1024时的测试精度最高,耗时还可以。
  • 测试精度随学习率的增加也是先降后升,但耗时从lr为0.1开始就几乎不变了,lr为0.4时的测试精度最高。 只有两个GPU,所以GPU数量对于测试精度和耗时的影响没有进行实验。
def train(net, num_gpus, batch_size, lr, num_epochs):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 = d2l.Timer()animator = 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)}')return animator.Y[0][-1], timer.avg()
epochs = [5, 10, 20, 30]
accs_1 = {"epochs": [], "accs": [], "times": []} for epoch in epochs:accs_1["epochs"].append(epoch)acc, time = train(net, num_gpus=2, batch_size=512, lr=0.2, num_epochs=epoch)accs_1["accs"].append(acc)accs_1["times"].append(time)

输出结果:
测试精度:0.93,25.6秒/轮,在[device(type='cuda', index=0), device(type='cuda', index=1)]

 

fig, ax1 = plt.subplots(figsize=(6, 4))
ax1.plot(accs_1["epochs"], accs_1["accs"], marker='o', label='acc')ax1.set_xlabel('epochs')
ax1.set_ylabel('accs')ax2 = ax1.twinx()
ax2.plot(accs_1["epochs"], accs_1["times"], marker='o', label='time', color = 'orange')
ax2.set_ylabel('times')
ax1.legend(loc='upper left')
ax2.legend(loc='upper right')plt.show()

 

batchs = [128, 256, 512, 1024]
accs_2 = {"batchs": [], "accs": [], "times": []} for batch in batchs:accs_2["batchs"].append(batch)acc, time = train(net, num_gpus=2, batch_size=batch, lr=0.2, num_epochs=10)accs_2["accs"].append(acc)accs_2["times"].append(time)

输出结果:
测试精度:0.92,27.1秒/轮,在[device(type='cuda', index=0), device(type='cuda', index=1)]

 

fig, ax1 = plt.subplots(figsize=(8, 4))
ax1.plot(accs_2["batchs"], accs_2["accs"], marker='o', label='acc',)ax1.set_xlabel('batchs')
ax1.set_ylabel('accs')
ax1.legend(loc='upper left')ax2 = ax1.twinx()
ax2.plot(accs_2["batchs"], accs_2["times"], marker='o', label='time', color = 'orange')
ax2.set_ylabel('times')
ax2.legend(loc='upper right')plt.show()

 

lrs = [0.05, 0.1, 0.2, 0.3, 0.4]
accs_3 = {"lrs": [], "accs": [], "times": []} for lr in lrs:accs_3["lrs"].append(lr)acc, time = train(net, num_gpus=2, batch_size=512, lr=lr, num_epochs=10)accs_3["accs"].append(acc)accs_3["times"].append(time)

输出结果:
测试精度:0.88,25.6秒/轮,在[device(type='cuda', index=0), device(type='cuda', index=1)]

 

 

fig, ax1 = plt.subplots(figsize=(8, 4))
ax1.plot(accs_3["lrs"], accs_3["accs"], marker='o')ax1.set_xlabel('lrs')
ax1.set_ylabel('accs')
ax1.legend(['acc'], loc='upper left')ax2 = ax1.twinx()
ax2.plot(accs_3["lrs"], accs_3["times"], marker='o', color = 'orange')
ax2.set_ylabel('times')
ax2.legend(['time'], loc='upper right')plt.show()

2. 有时候不同的设备提供了不同的计算能力,我们可以同时使用GPU和CPU,那应该如何分配工作?为什么?
解:
根据GPU和CPU的计算能力和特性,可以如下分配:
1)数据预处理和后处理分配给CPU,包括:数据加载、数据预处理(如归一化、裁剪、翻转等)、数据后处理。因为CPU在处理I/O和控制流方面更高效,而数据预处理和后处理通常涉及大量的I/O操作和简单的数值计算,所以这些任务更适合在CPU上运行。
2)模型训练分配给GPU,包括:模型的前向传播、后向传播和权重更新。因为GPU在处理大规模矩阵运算和并行计算方面具有显著优势,能够加速模型的训练过程。

http://www.xdnf.cn/news/582193.html

相关文章:

  • OpenCV图像平移示例
  • Linux笔记---信号(下)
  • RabbitMQ可靠传输——持久性、发送方确认
  • LangFlow可视化Agent编排
  • 监控易代理合作“自助餐”模式上线:战略/OEM/集成,总有一款适合你
  • 【视频】使用海康SDK保存的MP4无法在浏览器(html5)中播放
  • VPLC (VPLCnext) K8S
  • (1)深度学习基础知识(八股)——常用名词解释
  • # 深入解析BERT自然语言处理框架:原理、结构与应用
  • SSL/TLS证书申请与管理技术指南
  • 【QT】QT6设置.exe文件图标
  • 华为2025年校招笔试手撕真题教程(二)
  • C++ 日志系统实战第五步:日志器的设计
  • 搜维尔科技VR+5G教室建设方案,推动实现教育数字化转型
  • 5G基站选择±10ppm晶振及低相噪技术解析
  • 云原生微服务的前世今生
  • 5G 网络寻呼的信令及 IE 信息分析
  • paddlehub搭建ocr服务
  • 关于vue彻底删除node_modules文件夹
  • JMeter-Websocket接口自动化
  • Python 学习笔记
  • React19 项目开发中antd组件库版本兼容问题解决方案。
  • ubuntu中上传项目至GitHub仓库教程
  • 【数据结构与算法】LeetCode 每日三题
  • LeetCode 3356.零数组变换 II:二分查找 + I的差分数组
  • 精益数据分析(78/126):问题-解决方案画布的实战应用与黏性阶段关键总结
  • 华为云Flexus+DeepSeek征文 | 基于ModelArts Studio 的 DeepSeek API 实现行业深度搜索和分析
  • 平时使用电脑,如何去维护
  • VideoMAE论文笔记
  • 游戏引擎学习第305天:在平台层中使用内存 Arena 的方法与思路