发布于: Jan 11, 2022

在本节中,我们将讨论如何提高数据预处理能力,并尽可能精简常用函数以进一步增强数据加载效率。

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

TensorFlowMXNet Gluon 以及 PyTorch 都提供用于并行加载数据的数据加载器库。在以下 PyTorch 示例中,增加工作程序的数量能够让更多工作程序并行处理数据条目。作为一项通行原则,我们可以将工作程序的数量扩展至 CPU 数量减1的水平。这通常代表每个程序对应一个进程,并充分发挥 Python 的多线处理能力,当然具体实现细节因框架而异。多处理机制的引入能够避免 Python 全局解释器锁(GIL)使用全部 CPU 进行完全并行,进而导致资源余量不足;但这同时也意味着内存利用率将与工作程序的数量等比例增加,因为每个进程都需要在内存中保留自己的对象副本。当大家开始增加工作程序数量时,可能会引发内存不足问题。在这种情况下,我们需要选择 CPU 内存容量更高的实例类型。

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

在示例中,我们创建一个只包含一个工作程序的数据加载器实例:

在使用单一工作程序的情况下,大家会看到它在逐一检索各个条目,且每次检索之间的延迟(间隔)为 1 秒钟:

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

在示例数据集中,我们可以看到 3 个工作程序正尝试并行检索 3 个条目,且操作的完成时间大约为 1 秒钟。此后,继续检索接下来的 3 个条目:

在此演示 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 上获得最理想的资源利用率。

优化常用函数
 

尽可能减少资源成本高昂的操作,同时在可能的情况下(使用 GPUCPU )检索各个记录项以提高训练性能。大家可以通过多种方式优化常用函数,例如使用正确的数据结构。

在演示 notebook 示例中,我们这套简单的实现方案会加载图像文件并调整各个条目的大小,具体参见以下示例代码。我们通过预处理 Caltech 256 数据集来优化函数,包括提前调整图像大小并保存处理后的图像文件版本。其中 __getitem__ 函数仅尝试对图像进行随机裁剪,这就让 __getitem__ 函数变得非常精简。 GPU 用于等待 CPU 预处理数据的时间更短,数据也将更快被交付至 GPU 内存。具体参见以下代码:

仅仅凭借这一项简单的更改,我们就成功将每轮处理周期缩短至 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 能够立足训练阶段提供关于 GPUCPU 以及磁盘利用率等多种 Amazon CloudWatch 指标。

以下启发式方法,能够帮助大家通过各类开箱即用的指标发现关于 I/O 的各类性能问题:

  • 如果您的训练作业启动时间很长,则代表大部分时间被用于数据下载。您应该了解从 Amazon S3 下载数据方面的优化方法,具体请参阅前文内容。
  • 如果 GPU 利用率过低,但磁盘或者 CPU 的利用率过高,则代表数据加载或预处理可能正是引发瓶颈的根源。您可能需要在训练之前进行数据预处理;另外,大家也可以遵循前文建议优化各项常用函数。
  • 如果数据集已经非常庞大,但 GPU 使用率低下且 CPU 与磁盘利用率一直很低(但不为零),则可能意味着当前代码未能充分利用基础设施资源。如果您发现 CPU 内存的利用率同样很低,那么最有效的快速解决办法可能是增加深度学习框架数据加载器 API 中的工作程序数量。
总结
 

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

相关文章