人工智能/深度学习

利用 NVIDIA DALI 实现快速数据预处理

这篇文章是对之前文章的更新.

深度学习模型需要使用大量数据进行培训,以获得准确的结果。由于各种原因,例如不同的存储格式、压缩、数据格式和大小,以及高质量数据的数量有限,原始数据通常无法直接输入神经网络。

解决这些问题需要大量的数据准备和预处理步骤,从加载、解码、解压缩到调整大小、格式转换和各种数据扩充。

深度学习框架,如 TensorFlow 、 PyTorch 、 MXNet 等,为一些预处理步骤提供了本地实现。由于使用特定于框架的数据格式、转换的可用性以及不同框架之间的实现差异,这通常会带来可移植性问题。

CPU 瓶颈

直到最近,深度学习工作负载的数据预处理才引起人们的关注,因为训练复杂模型所需的巨大计算资源使其黯然失色。因此,由于 OpenCV 、 Pillow 或 Librosa 等库的简单性、灵活性和可用性,预处理任务通常用于在 CPU 上运行。

NVIDIA 伏特和 NVIDIA 安培体系结构中引入的 GPU 体系结构的最新进展显著提高了深度学习任务中的 GPU 吞吐量。特别是,半精度算法与张量核加速某些类型的 FP16 矩阵计算,这对培训DNNs非常有用。密集的多 GPU 系统,如 NVIDIA DGX-2DGX A100训练模型的速度远远快于输入管道提供的数据,使 GPU 缺少数据。

今天的 DL 应用程序包括由许多串行操作组成的复杂、多阶段的数据处理管道。依赖 CPU 处理这些管道会限制性能和可扩展性。在图 1 中,可以观察到数据预处理对 ResNet-50 网络训练吞吐量的影响。在左侧,我们可以看到在 CPU 上运行的用于数据加载和预处理的框架工具时网络的吞吐量。在右侧,我们可以看到相同网络的性能,而不受数据加载和预处理的影响,用合成数据替换。当比较不同的数据预处理工具时,这种测量可以用作理论上限。

Bar chart with 2 bars, annotated with labels: Native MXNet, and Synthetic (no preprocessing). The vertical axis is labelled “Throughput (img/s)”. There are numeric annotations in each of the bars, representing the exact throughput values obtained for each case.
图 1 : ResNet-50 网络的数据预处理对总体训练吞吐量的影响.

大理来营救

NVIDIA 数据加载库( DALI )是我们致力于为上述数据管道问题找到可扩展和可移植解决方案的结果。 DALI 是一组高度优化的构建块和执行引擎,用于加速深度学习( DL )应用程序的输入数据预处理(见图 2 )。 DALI 为加速不同的数据管道提供了性能和灵活性。

A diagram representing DALI’s position in a deep learning application. A chain of elements: Input Data, Decode, GPU-Accelerated Augmentations, Preprocessed Data and Training/Inference is connected by an arrow, representing the flow of the data.

图 2 : DALI 概述及其在 DL 应用程序中作为加速数据加载和预处理工具的使用。

DALI 为各种深度学习应用程序(如分类或检测)提供数据处理原语,并支持不同的数据域,包括图像、视频、音频和体积数据。

支持的输入格式包括最常用的图像文件格式( JPEG 、 PNG 、 TIFF 、 BMP 、 JPEG2000 、 NETPBM )、 NumPy 阵列、使用多种编解码器编码的视频文件( H . 264 、 HEVC 、 VP8 、 VP9 、 MJPEG )以及音频文件( WAV 、 OGG 、 FLAC )。

DALI 的一个重要特性是插件,它可以作为框架本机数据集的插入式替换。目前, DALI 带有 MxNET PyTorch 、 TensorFlow 和 PaddlePaddle 的插件。只要使用不同的数据迭代器包装器,就可以一次性定义 DALI 管道,并与任何受支持的框架一起使用。

除此之外, DALI 本机支持特定框架中使用的不同存储格式(例如, Caffe 和 Caffe2 中的 LMDB 、 MXNet 中的 RecordIO 、 TensorFlow 中的 TFRecord )。这允许我们使用任何受支持的数据格式,而不管使用的是何种 DL 框架。例如,我们可以对模型使用 MXNet ,同时将数据保存在 TFRecord (原生 TensorFlow 数据格式)中。

通过在 Python 中配置外部数据源,或使用自定义运算符进行扩展,可以轻松地为特定项目定制 DALI 。最后,DALI是一个开源项目,因此您可以轻松地对其进行扩展和调整,以满足您的特定需求。

大理关键概念

DALI 中的主要实体是数据处理pipeline。管道由operators连接的数据节点的符号图定义。每个操作符通常获得一个或多个输入,应用某种数据处理,并产生一个或多个输出。有一些特殊类型的运算符不接受任何输入并产生输出。这些特殊操作符就像一个数据源——读卡器、随机数生成器和外部_源都属于这一类。管道定义在 Python 中使用命令式语言表示,与当前大多数深度学习框架一样,但以异步方式运行。

构建完成后,管道实例可以通过调用管道的 run 方法显式运行,也可以使用特定于目标深度学习框架的数据迭代器包装。

DALI 为各种处理操作员提供 CPU 和 GPU 实现。 CPU 或 GPU 实现的可用性取决于运营商的性质。确保检查文档中是否有支持的操作的最新列表,因为每个版本都会对其进行扩展。

DALI 运营商要求将输入数据放置在与运营商后端相同的设备上。具有混合后端的运算符是一种特殊类型的运算符,用于接收 CPU 内存中的输入和 GPU 内存中的输出数据。出于性能原因,无法访问 DALI 管道中从 GPU 到 CPU 内存的数据传输。

虽然 DALI 的大部分好处是在将处理卸载到 GPU 时实现的,但有时在 CPU 上保持部分操作运行是有益的。特别是在 CPU 与 GPU 比率较高的系统中,或在 GPU 完全被模型占用的情况下。用户可以尝试 CPU / GPU 位置,以逐个找到最佳位置。

A diagram with boxes representing the different operators in the example pipeline: Loader, Decode, Resize, Augment, Training. Boxes are connected with arrows representing the data flow in the pipeline. Colors are used to present the device placement of each operator in the pipeline: CPU (blue), Mixed (blue to green gradient), GPU (green).
图 3 : DALI 管道的示例。数据加载到 CPU 上,然后使用混合后端操作符进行解码,该操作符在 GPU 内存上输出解码图像,然后在 GPU 上对其进行大小调整和扩充。

如前所述, DALI 的执行是异步的,这允许数据预取,也就是说,在请求批数据之前提前准备批数据,以便框架始终为下一次迭代准备好数据。 DALI 使用可配置的预取队列长度为用户透明地处理数据预取。数据预取有助于隐藏预处理的延迟,当处理时间在迭代中发生显著变化时,这一点很重要(见图 4 )。

Diagram representing data prefetching. Preprocessing is represented by boxes of different length (Batch 1, Batch 2, etc), representing the time it takes to process each of the batches. A prefetch queue is represented as two slots, either empty or holding data. Training iterations are shown on the bottom. There are arrows representing the flow of data from the preprocessing to the prefetch queue, and from the prefetch queue to the training iterations.
图 4 :数据预取示例,预取队列深度为 2 。较长迭代(第 4 批)的延迟因提前计算而被隐藏。

如何使用大理

定义 DALI 管道的最简单方法是使用pipeline_def Python 装饰器。为了创建管道,我们定义了一个函数,在该函数中实例化并连接所需的运算符,并返回相关的输出。然后用pipeline_def来装饰它。

from nvidia.dali import pipeline_def, fn
@pipeline_def
def simple_pipeline():
    jpegs, labels = fn.readers.file(file_root=image_dir,
                                    random_shuffle=True,
                                    name="Reader")
    images = fn.decoders.image(jpegs)

    return images, labels

在这个示例管道中,没有什么值得注意的事情。第一个操作符是文件读取器,它发现并加载目录中包含的文件。读取器输出文件的内容(在本例中为编码的 JPEG )和从目录结构推断的标签。我们还启用了随机洗牌并为 reader 实例命名,这在稍后与框架迭代器集成时非常重要。第二个运算符是图像解码器

下一步是实例化simple_pipeline对象并构建它以实际构建图形。在管道实例化过程中,我们还定义了批大小、用于数据处理的 CPU 线程数以及 GPU 设备序号。有关更多选项,请参阅文件。

pipe = simple_pipeline(batch_size=32, num_threads=3, device_id=0)
pipe.build()

此时,管道已准备好使用。我们可以通过调用 run 方法获得一批数据。

images, labels = pipe.run()

现在,让我们添加一些数据增强,例如,以随机角度旋转每个图像。要生成随机角度,我们可以使用random.uniform,并旋转rotation

@pipeline_def()
def rotate_pipeline():
    jpegs, labels = fn.readers.file(file_root=image_dir,
                                    random_shuffle=True,
                                    name="Reader")
    images = fn.decoders.image(jpegs)
    angle = fn.random.uniform(range=(-10.0, 10.0))
    rotated_images = fn.rotate(images, angle=angle, fill_value=0)
    return rotated_images, labels
Two rows with four pictures each, showing pictures rotated by different angles, representing the results of the rotate pipeline example.

图5:EZX19的示例结果。每个样本以不同的角度旋转。

将计算卸载到 GPU

我们现在可以修改我们的简单_管道,以便它使用.gpu()执行扩充。 DALI 使这种转变非常容易。唯一改变的是rotate运算符的定义。我们只需要将device参数设置为“gpu”,并确保通过调用 GPU 将其输入传输到 GPU 。

self.rotate = fn.rotate(images.gpu(), angle=angle, device="gpu")

为了使事情更简单,我们甚至可以省略device参数,让 DALI 直接从输入位置推断出运算符。

self.rotate = fn.rotate(images.gpu(), angle=angle)

也就是说,simple_pipeline现在在 GPU 上执行旋转。请记住,生成的图像也会分配到 GPU 内存中,这通常是我们想要的,因为模型需要 GPU 内存中的数据。在任何情况下,运行管道后将数据复制回 CPU 内存都可以通过调用Pipeline.run返回的对象as_cpu轻松实现。

images, labels = pipe.run()
images_host = images.as_cpu()

框架集成

与不同深度学习框架的无缝互操作性代表了 DALI 的最佳功能之一。例如,要将您的管道与 PyTorch 模型一起使用,我们可以通过使用DALIClassificationIterator包装它来轻松实现。对于更一般的情况,例如任意数量的管道输出,请使用DALIGenericIterator

from nvidia.dali.plugin.pytorch import DALIGenericIterator
train_loader = DALIClassificationIterator([pipe], reader_name='Reader')

注意参数reader_name,该值与reader实例的 name 参数匹配。迭代器将使用该读取器作为一个历元中样本数的信息源。

我们现在可以枚举train_loader实例并将数据批提供给模型。

for i, data in enumerate(train_loader):
    images = data[0]["data"]
    target = data[0]["label"].squeeze(-1).long()
    # model training

关于框架集成的更多信息可以在文档的框架插件部分中找到。

推理中的达利

为训练和推理提供数据处理步骤的等效定义对于获得良好的精度结果至关重要。多亏了 NVIDIA Triton 推理服务器及其专用的大理后端,我们现在可以轻松地将 DALI 管道部署到推理应用程序,使数据管道完全可移植。在图 6 所示的体系结构中, DALI 管道作为 Triton 集成模型的一部分进行部署。这种配置有两个主要优点。首先,数据处理是在服务器中执行的,通常是一台比客户机功能更强大的机器。第二个好处是数据可以被压缩后发送到服务器,这节省了网络带宽。

Two boxes, Triton Client and Triton Server. There are arrows connecting the two of them: Inference request from client to server, and Inference result from server to client. Inside the server there is a box named DALI Backend, with labels Decode, Resize, and Normalize, and another box labelled image classification.
图 6 : DALI 在推理配置中,带有 NVIDIA Triton 推理服务器和用于服务器端预处理的 DALI 后端。

请务必查看我们的专用文章使用 NVIDIA Triton 推理服务器和 NVIDIA DALI 加速推理,详细介绍此主题。

达利对绩效的影响

NVIDIA 展示了 DALI 对 SSD 、 ResNet-50 和 RNN-T 的实现,这是我们的MLPerf基准成功中的一个促成因素。

让我们比较一下使用 DALI 和使用框架的本机解决方案时 ResNet-50 网络的训练吞吐量。在图 7 中,我们可以看到与图 1 中所示类似的比较,这一次显示了将 DALI 作为选项之一用于数据加载和预处理的结果。我们可以看到 DALI 的训练吞吐量如何更接近理论上限(合成示例)。

Bar chart with 3 bars, annotated with labels of the three preprocessing methods compared: Native MXNet, DALI, and Synthetic (no preprocessing). The vertical axis is labelled “Throughput (img/s)”. There are numeric annotations in each of the bars, representing the exact throughput values obtained for each case.
图 7 : ResNet-50 网络不同预处理方法的训练性能比较。

现在让我们看看 DALI 如何影响 Triton 服务器中 Resnet50 推理的性能。图 8 显示了脱机预处理的平均推断请求延迟,这意味着在启动请求之前数据已经过预处理,以及联机服务器端预处理。所花费的时间细分为通信开销、数据预处理和模型推理。由于解码数据的大小较大,预处理请求的延迟会受到通信开销的严重影响。因此,服务器端预处理比离线预处理快,即使前者在度量中包含数据预处理时间。

Bar chart with two groups of two bars. The vertical axis is labelled “Request latency [ms]” and the groups of bars are labelled as batch size 32 and 128, respectively. The bars in each group are labelled “No preprocessing” and “With DALI preprocessing”. The bars are divided in segments of different colors, used to denote the time spent in the following parts: Model inference time (yellow), preprocessing time (red), and communication overhead (blue).
图 8 : Resnet50 模型推断的平均请求延迟(越低越好)比较。这些数字是使用 NVIDIA / Triton 服务器在 DGX A100 机器上使用单个 GPU 收集的: 21 . 07-py3 容器。

今天就从 DALI 开始吧

您可以下载预构建和测试的 DALI pip 包的最新版本。]:MXNetMXNet的 NVIDIA GPU 云( NGC )容器已集成 DALI 。您可以查看许多examples并阅读最新的发行说明,以获取新功能和增强功能的详细列表。

Triton DALI 后端包含在 Triton 推理服务器容器中,从 20 . 11 版本开始。

了解 DALI 如何帮助您加快深度学习应用程序的数据预处理。最好的访问地点是我们的文档页,包括许多示例和教程。您也可以观看我们的 GTC 2021 年谈大理。 DALI 是一个开源项目,我们的代码可以在GitHub上找到。我们欢迎您的反馈和贡献。

 

Tags