乐趣区

关于深度学习:深度学习训练如何更快些GPU性能的IO优化你试过吗

本来,有多少人曾经筹备好最新显卡,足够的硬盘空间,甚至请好年假,只为十天后去那个企慕已久的赛博朋克世界里体验一番……

后果他们又发了一张「黄色背景图」,通知大家要跳票……再一次……

好吧,你有了大量闲工夫,但又没事可做?那就干点闲事吧,玩一玩深度学习,即打发工夫,增长常识,还能把你的新显卡先无效利用起来。

GPU 可能显著放慢深度学习的训练速度,无望将训练周期由几个星期缩短至数小时。但要全面施展 GPU 资源的弱小性能,还须要考量以下因素:

  • 优化代码以保障底层硬件失去充分利用。
  • 应用最新高性能库与 GPU 驱动程序。
  • 优化 I / O 与网络操作,确保数据可能以与计算能力相匹配的速率被送至 GPU。
  • 在多 GPU 或分布式训练期间,优化 GPU 之间的通信。

Amazon SageMaker 是一项全托管服务,可能帮忙开发人员与数据科学家疾速、轻松地构建、训练并部署任意规模的机器学习(ML)模型。在本文中,咱们将重点介绍在 Amazon SageMaker 上进行训练时,可能切实进步 I / O 以优化 GPU 性能的通用型技术。这些技术办法具备良好的普适性,不对基础设施或深度学习框架自身做出任何要求。通过优化 I / O 解决例程,整个 GPU 训练中的性能晋升最多可晋升至 10 倍程度。

基础知识

繁多 GPU 每秒可执行万亿次浮点运算(TFLOPS),意味着其运算执行速度可达到一般 CPU 的 10 到 1000 倍。为了让 GPU 失常执行这些运算,数据必须寄存在 GPU 内存当中。将数据加载至 GPU 内存中的速度越快,运算执行速度也就越快。其中的挑战在于如何优化 I / O 或网络操作,保障 GPU 在计算当中不用重复期待数据的传入。

下图所示,为 I / O 优化架构。

将数据搁置进 GPU 内存通常波及以下操作步骤:

  • 网络操作 —— 从 Amazon Simple Storage Service(Amazon S3)下载数据。
  • 磁盘 I /O—— 将数据从本地磁盘读入 CPU 内存。这里的本地磁盘是指实例存储,相干存储容量位于物理接入主机的磁盘之上。Amazon Elastic Block Store(Amazon EBS)存储卷不属于本地资源,其中波及网络操作步骤。
  • 数据预处理 —— 一般来说,数据预处理工作次要由 CPU 负责实现,包含转换或者调整大小等。这些操作可能包含将图像或文本转换为张量模式、或者调整图像大小等。
  • 数据传输至 GPU 内存 —— 将解决后的数据从 CPU 内存复制到 GPU 内存。

以下各节将对优化步骤做出具体解说。

优化网络中的数据下载操作

本节将介绍一些技巧,探讨如何通过网络操作(例如从 Amazon S3 下载数据、应用 Amazon EBS 以及 Amazon Elastic Files System(简称 Amazon EFS)等文件系统)优化数据传输。

优化文件大小

大家可能以低成本将大量数据存储在 Amazon S3 当中,其中包含来自应用程序数据库的数据,例如通过 ETL 过程提取为 JSON 或 CSV 格局的图像文件。而 Amazon SageMaker 运行中的第一步,就是从 Amazon S3 下载文件 —— 这种默认输出模式被称为文件模式。

即便是并行下载或上传体积极小的多个文件,其速度也要低于总大小雷同的多数较大文件。举例来说,如果领有 200 万个文件,单个文件的大小为 5KB(总大小 = 10GB = 200 万 x 5 x 1024KB),则下载大量小型文件可能须要消耗几个小时。但如果这 10GB 数据量来自 2000 个单个大小为 5MB 的文件(总大小 = 10GB = 2000 x 5 x 1024 x 1024KB),那么下载只须要几分钟就能实现。假设用于大文件与小文件的总存储容量与用于数据传输的线程数量大致相同,同时假如传输块大小为 128KB,那么面对仅为 5KB 的理论文件大小,每个传输块的理论数据传输量也将仅为 5KB—— 而非 128KB。

在另一方面,如果文件太大,则无奈应用并行处理放慢文件的数据上传或者下载速度 —— 除非应用 Amazon S3 Range gets 等选项并行下载多个不同的数据块。

通过 MXNet RecordIO 与 TFRecord 等格局,咱们能够将多个图像文件压缩并密集打包至繁多文件当中,从而防止这种性能节约。例如,MXNet RecordIO 在解决图像时会倡议对多幅图像进行尺寸压缩,确保将至多一批图像包容到 CPU/GPU 内存当中;另外,通过将多幅图像密集打包至繁多文件中,也能彻底消除 I / O 操作中因小文件传输导致的传输瓶颈。

作为一项通行准则,最佳文件大小个别在 1 到 128MB 之间。

实用于大型数据集 Amazon SageMaker 的 ShardedByS3Key Amazon S3 数据分布

在分布式训练期间,大家还能够跨多个实例对超大规模数据集进行分片。咱们能够将 S3DataDistributionType 参数设置为 ShardedByS3Key,轻松实现 Amazon SageMaker 训练工作的数据分片。在此模式下,如果 Amazon S3 输出数据集总计蕴含 M 个对象,而训练作业有 N 个实例,则每个实例都将解决 M / N 个对象。对于更多详细信息,请参阅 S3DataSource。在这种用例下,各设施上的模型训练将只应用训练数据中的一个子集。

实用于大型数据集的 Amazon SageMaker Pipe 模式

相较于 SageMaker 文件模式,Pipe 模式可帮忙咱们间接将大量数据从 Amazon S3 流式传输至训练实例,而无需先将其下载至本地磁盘。Pipe 模式容许咱们的代码间接拜访数据,这就防止了后行残缺下载对速度产生的影响。因为数据永远不会被下载至磁盘,且只在内存中保留绝对较小的占用空间,因而该模式会继续一直地从 Amazon S3 下载数据,这十分实用于解决 CPU 内存无奈一次性包容的超大规模数据集。要应用局部原始字节在流式传输带来的可用性劣势,大家须要在代码中依据记录格局(例如 CSV)对字节进行解码,并找到记录开端局部将局部字节转换为逻辑记录。Amazon SageMaker TensorFlow 就为文本文件及 TFRecord 等常见格局提供内置的 Pipe 模式数据集读取器。对于更多详细信息,请参阅 Amazon SageMaker 为 TensorFLow 容器提供批量转换性能与 Pipe 输出模式。如果应用的框架或库不具备内置数据读取器,也能够应用 ML-IO 库或者编写本人的数据读器,从而失常应用 Pipe 模式。

Pipe 模式流传输带来的另一个后果,则是要求对数据进行重新整理,即通过 ShuffleConfig 重新整理 manifest 文件及 augmented manifest 文件中的 Amazon S3 键前缀匹配或行后果。如果文件体积很大,则不能依附 Amazon SageMaker 实现重整;这里必须预取“N”个批次,并依据 ML 框架编写代码以实现数据重整。

如果能够间接将整个数据集放入 CPU 内存当中,那么文件模式的执行效率可能比 Pipe 模式更高。这是因为如果内存足以包容整个数据集,则须要一次性将残缺数据集下载至本地磁盘,再一次性将整个数据集加载至内存中,并在训练流程中的各个阶段重复从内存中读取数据。从内存中读取数据,在速度上往往远高于网络 I /O,可能带来显著的性能晋升。

在下一节中,咱们将探讨如何解决超大规模数据集。

应用 Amazon FSx for Lustre 或 Amazon EFS 解决大型数据集

对于超大型数据集,能够应用分布式文件系统以缩短 Amazon S3 的下载工夫。

大家能够在 Amazon SageMaker 上应用 Amazon FSx for Lustre 以缩短启动工夫,并将数据驻留在 Amazon S3 当中。更多详细信息,请参阅应用 Amazon FSx for Lustre 与 Amazon EFS 文件系统放慢 Amazon SageMaker 上的训练速度。

在首次运行训练作业时,FSx for Lustre 会主动从 Amazon S3 处复制数据,并将其交付至 Amazon SageMaker。另外,大家也能够将雷同的 FSx for Lustre 文件系统用于对 Amazon SageMaker 上的训练作业进行后续迭代,以避免反复下载应用频率较高的 Amazon S3 对象。从这个角度看,对于将训练数据集搁置在 Amazon S3 中的训练工作、以及须要应用不同训练算法或参数屡次运行训练作业能力失去最佳成果的工作流当中,FSx for Lustre 具备十分显著的比拟劣势。

如果曾经在 Amazon Elastic File System(Amazon EFS)上领有训练数据,还能够将 Amazon EFS 与 Amazon SageMaker 联合应用。更多详细信息,请参阅应用 Amazon FSx for Lustre 与 Amazon EFS 文件系统放慢 Amazon SageMaker 上的训练速度。

在应用这个选项时,请务必关注文件的大小。如果文件体积过小,因为传输块大小的限度,I/ O 性能可能因而受到影响。

装备本地 NVMe SSD 存储的 Amazon SageMaker 实例

一部分 Amazon SageMaker GPU 实例(例如 ml.p3dn.24xlarge 与 ml.g4dn)提供基于 NVMe 的本地 SSD 存储,用以代替 EBS 存储卷。例如,ml.p3dn.24xlarge 实例就提供 1.8 GB 的本地 NVMe SSD 存储容量。应用基于 NVMe 的本地 SSD 存储,意味着在将训练数据从 Amazon S3 下载至本地磁盘之后,磁盘的 I / O 要远远快于 Amazon S3 或 EBS 存储卷等的网络资源读取速度。如此一来,只有训练数据的大小与本地 NVMe 存储相适应,即可大大放慢训练速度。

优化数据加载与预处理

在上一节中,咱们探讨了如何高效从 Amazon S3 等源处下载数据。在本节中,咱们将探讨如何进步并行化水平,并尽可能精简罕用函数以进一步加强数据加载效率。

应用多个工作程序执行数据加载与预处理

TensorFlow、MXNet Gluon 以及 PyTorch 都提供用于并行加载数据的数据加载器库。在以下 PyTorch 示例中,减少工作程序的数量可能让更多工作程序并行处理数据条目。作为一项通行准则,咱们能够将工作程序的数量扩大至 CPU 数量减 1 的程度。这通常代表每个程序对应一个过程,并充分发挥 Python 的多线解决能力,当然具体实现细节因框架而异。多处理机制的引入可能防止 Python 全局解释器锁(GIL)应用全副 CPU 进行齐全并行,进而导致资源余量有余;但这同时也意味着内存利用率将与工作程序的数量等比例减少,因为每个过程都须要在内存中保留本人的对象正本。当大家开始减少工作程序数量时,可能会引发内存不足问题。在这种状况下,咱们须要抉择 CPU 内存容量更高的实例类型。

为了跟踪工作程序的运行成果,咱们参考以下示例数据集。在这套数据集中,__get_item__操作的休眠时长为 1 秒,用于模仿读取下一条记录时的对应提早:

class MockDatasetSleep(Dataset):
 """Simple mock dataset to understand the use of workers"""
 def __init__(self, num_cols, max_records=32):
 super(MockDatasetSleep).__init__()
 self.max_records = max_records
 self.num_cols = num_cols
 # Initialising mock x and y
 self.x = np.random.uniform(size=self.num_cols)
 self.y = np.random.normal()
 print("Initialised")
 def __len__(self):
 return self.max_records
 def __getitem__(self, idx):
 curtime = datetime.datetime.now()
 # Emulate a slow operation
 sleep_seconds = 1
 time.sleep(sleep_seconds)
 print("{}: retrieving item {}".format(curtime, idx))
 return self.x, self.y

在示例中,咱们创立一个只蕴含一个工作程序的数据加载器实例:

# One worker
num_workers = 1
torch.utils.data.DataLoader(MockDatasetSleep(), batch_size=batch_size, shuffle=True, num_workers=num_workers)

在应用繁多工作程序的状况下,大家会看到它在逐个检索各个条目,且每次检索之间的提早(距离)为 1 秒钟:

15:39:58.833644: retrieving item 0
15:39:59.834420: retrieving item 6
15:40:00.834861: retrieving item 8
15:40:01.835350: retrieving item 5

如果将该实例上的工作程序数量减少到 3 个,则该实例至多须要 4 个 CPU 以保障并行处理的顺畅执行,具体参见以下代码:

# You may need to lower the number of workers if you encounter out of memory exceptions or move to a instance with more memory
num_workers = os.cpu_count() - 1
torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)

在示例数据集中,咱们能够看到 3 个工作程序正尝试并行检索 3 个条目,且操作的实现工夫大概为 1 秒钟。尔后,持续检索接下来的 3 个条目:

1

6:03:21.980084: retrieving item 8
16:03:21.981769: retrieving item 10
16:03:21.981690: retrieving item 25
16:03:22.980437: retrieving item 0
16:03:22.982118: retrieving item 7
16:03:22.982339: retrieving item 21

在此演示 Noteobok 示例中,咱们应用 Caltech-256 数据集,其中蕴含约 30600 张图像。在 Amazon SageMaker 训练任务中,咱们应用繁多 ml.p3.2xlarge 实例,其中蕴含 1 个 GPU 与 8 个 vCPU。在只应用 1 个工作程序的状况下,每轮解决周期约为 260 秒,期间由繁多 GPU 每秒解决大概 100 张图像。而在 7 个工作程序的状况下,每轮解决周期为 96 秒,每秒可能解决约 300 张图像,相当于性能进步了 3 倍。

下图所示,为繁多工作程序在峰值利用率为 50% 时捕捉到的 GPUUtilization 指标。

下图所示,为多工作程序、均匀资源利用率为 95% 时的 GPUUtilization 指标。

对 num_workers 做出的轻微调整可能进一步放慢数据加载速度,缩短数据的期待时长,从而让 GPU 的训练速度失去晋升。这表明优化数据加载器中的 I / O 性能,的确可能进步 GPU 资源利用率。

在繁多 GPU 上实现优化调整之后,接下来大家应抉择在多 GPU 或多主机分布式 GPU 上进行模型训练。因而,在理论进行分布式训练之前,首先要保障可能在繁多 GPU 上取得最现实的资源利用率。

优化罕用函数

尽可能减少资源老本昂扬的操作,同时在可能的状况下(应用 GPU 或 CPU)检索各个记录项以进步训练性能。大家能够通过多种形式优化罕用函数,例如应用正确的数据结构。

在演示 Notebook 示例中,咱们这套简略的实现计划会加载图像文件并调整各个条目标大小,具体参见以下示例代码。咱们通过预处理 Caltech 256 数据集来优化函数,包含提前调整图像大小并保留解决后的图像文件版本。其中__getitem__函数仅尝试对图像进行随机裁剪,这就让__getitem__函数变得十分精简。GPU 用于期待 CPU 预处理数据的工夫更短,数据也将更快被交付至 GPU 内存。具体参见以下代码:

# Naive implementation
def __getitem__(self, idx):
 curtime = datetime.datetime.now()
 self.logger.debug("{}: retrieving item {}".format(curtime, idx))
 image, label = self.images[idx], self.labels[idx]
 # Convert to PIL image to apply transformations
 # This could be faster if handled in a preprocessing step
 image = Image.open(image)
 if image.getbands()[0] == 'L':
 image = image.convert('RGB')
 # Apply transformation at each get item including resize, random crop
 image = self.transformer(image)
 self.logger.debug("{}: completed item {}".format(datetime.datetime.now(), idx))
 return image, label
# Optimised implementation
def __getitem__(self, idx):
 curtime = datetime.datetime.now()
 self.logger.debug("{}: retrieving item {}".format(curtime, idx))
 image, label = self.images[idx], self.labels[idx]
 # Apply transformation at each get item - random crop
 image = self.transformer(image)
 self.logger.debug("{}: completed item {}".format(datetime.datetime.now(), idx))
 return image, label

仅仅凭借这一项简略的更改,咱们就胜利将每轮解决周期缩短至 96 秒,每秒解决的图像增长至 300 张,速度相当于繁多工作程序处理未优化数据集时的 3 倍。另外,即便进一步减少工作程序的数量,GPU 受到的影响也不大,因为数据加载过程不再形成性能瓶颈。

在某些状况下,大家可能须要一直减少工作程序数据并优化代码,借此实现 GPU 资源利用率最大化。

下图所示,为优化后数据集配合繁多工作程序时的 GPU 资源利用率:

下图所示,为应用未优化数据集时的 GPU 资源利用率:

理解你的 ML 框架

不同深度学习框架中的数据加载库能够提供多种数据加载优化选项,TensorFlow 数据加载器、MXNet 以及 PyTorch 数据加载器都有相干性能。咱们应该摸索最适宜以后用例与取舍方向的数据加载器与库参数。其中局部相干选项包含:

  • CPU 固定内存 —— 容许咱们放慢从 CPU(主机)内存到 GPU(设施)内存的数据传输速度。之所以可能实现减速,是因为咱们间接调配页面锁定(或者称固定)内存(而非先调配页面内存、再将数据从 CPU 分页内存传输至 CPU 固定内存、再到 GPU 内存)以实现性能晋升。在 PyTOrch 与 MXNet 中,咱们能够在数据加载器内启用 CPU 固定内存。这里的取舍在于,相较于分页内存,固定 CPU 内存机制更可能引发内存不足异样。
  • Modin—— 这是一种轻量化并行处理数据框,容许大家以并行形式执行相似于 Pandas 数据框的操作,从而充分利用设施上的 CPU 资源。Modin 能够应用多种不同类型的并行处理框架,包含 Dask 与 Ray。
  • CuPy—— 该开源矩阵库与 NumPy 相似,可能配合 Python 实现 GPU 计算减速。

以启发式办法找到 I / O 瓶颈

Amazon SageMaker 可能针对训练阶段提供对于 GPU、CPU 以及磁盘利用率等多种 Amazon CloudWatch 指标。对于更多详细信息,请参阅应用 Amazon CloudWatch 监控 Amazon SageMaker。

以下启发式办法,可能帮忙大家通过各类开箱即用的指标发现对于 I / O 的各类性能问题:

  • 如果训练作业启动工夫很长,则代表大部分工夫被用于数据下载。应该理解从 Amazon S3 下载数据方面的优化办法,具体请参阅前文内容。
  • 如果 GPU 利用率过低,但磁盘或者 CPU 的利用率过高,则代表数据加载或预处理可能正是引发瓶颈的本源。可能须要在训练之前进行数据预处理;另外,也能够遵循前文倡议优化各项罕用函数。
  • 如果数据集曾经十分宏大,但 GPU 使用率低下且 CPU 与磁盘利用率始终很低(但不为零),则可能意味着以后代码未能充分利用基础设施资源。如果发现 CPU 内存的利用率同样很低,那么最无效的疾速解决办法可能是减少深度学习框架数据加载器 API 中的工作程序数量。

总结

到这里,置信大家曾经理解了数据加载与解决如何影响 GPU 资源利用率,以及该如何通过解决 I / O 或与网络相干的瓶颈以进步 GPU 性能。在进一步探讨多 GPU 或者分布式模型训练等高级主题之前,咱们应该首先解决这些最根本但也极为要害的瓶颈。

对于 Amazon SageMaker 应用方面的更多详细信息,请参考以下资源:

  • Amazon SageMaker 示例–GitHub repo
  • TensorFlow–应用 tf.data API 实现性能晋升
  • MXNet–设计用于深度学习的高效数据加载器
  • PyTorch 数据加载器–TORCH.UTILS.DATA

退出移动版