关于深度学习:深度学习训练如何更快些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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理