Google Article
使用 TensorRT 集成实现高性能推断
2019 年 6 月 13 日
作者:Pooya Davoodi (NVIDIA)、Guangda Lai (Google)、Trevor Morris (NVIDIA)、Siddharth Sharma (NVIDIA)

去年,我们介绍了将 TensorFlow 与 TensorRT 集成,以利用 GPU 加速深度学习推断。本文将更深入地探讨并分享一些技巧,以便您在推断过程中充分利用应用程序。即使您不熟悉集成,本文也提供了足够的上下文信息,以便您可以继续学习。

在本文结束时,您将了解到
  • 支持的模型和集成工作流程
  • 新的技术,如量化感知训练,用于使用 INT8 精度
  • 用于衡量性能的分析技术
  • 新的实验性功能以及路线图的简要介绍

使用 TensorFlow-TensorRT 的优化三个阶段

训练完模型后,可以将其部署以执行推断。您可以从TensorFlow GitHub网站上找到几个预训练的深度学习模型作为起点。这些模型使用最新的 TensorFlow API,并定期更新。虽然您可以在 TensorFlow 本身中运行推断,但应用程序通常使用 TensorRT 在 GPU 上提供更高的性能。使用 TensorRT 优化的 TensorFlow 模型可以部署到数据中心的 T4 GPU,以及 Jetson Nano 和 Xavier GPU。

那么什么是 TensorRT 呢?NVIDIA TensorRT 是一种高性能推断优化器和运行时,可用于在 GPU 上以较低精度(FP16 和 INT8)执行推断。它与 TensorFlow 的集成允许您使用几行代码将 TensorRT 优化应用于您的 TensorFlow 模型。与仅使用 TensorFlow 相比,您将获得高达 8 倍的性能提升,同时仍保留在 TensorFlow 环境中。集成会将优化应用于支持的图,而不会触碰不受支持的操作,使其在 TensorFlow 中本地执行。集成解决方案的最新版本始终在NVIDIA NGC TensorFlow 容器中可用。

集成解决方案可以应用于目标检测、翻译、推荐系统和强化学习等应用程序中的模型。正在不断扩展的模型集(包括 MobileNet、NASNet、Inception 和 ResNet)的精度数据已公布,并定期更新。

安装集成并获得经过训练的 TensorFlow 模型后,将其以保存的模型格式导出。然后,集成解决方案会将 TensorRT 优化应用于 TensorFlow 支持的子图。输出是一个 TensorFlow 图,其中支持的子图被替换为 TensorRT 优化的引擎,这些引擎由 TensorFlow 执行。以下是如何实现相同目标的工作流程和代码:TensorFlow 工作流程
图 1(a)在 TensorFlow 中仅使用 TensorFlow 和在 TensorFlow-TensorRT 中使用“保存的模型”格式执行推断时的工作流程
import tensorflow.contrib.tensorrt as trt
trt.create_inference_graph(
    input_saved_model_dir = input_saved_model_dir,
    output_saved_model_dir = output_saved_model_dir)
 workflows when performing inference in TensorFlow only and using TensorFlow-TensorRT using frozen graphs
图 1(b)在 TensorFlow 中仅使用 TensorFlow 和在 TensorFlow-TensorRT 中使用冻结图执行推断时的工作流程
导出 TensorFlow 模型以进行推断的另一种方法是冻结经过训练的模型图以进行推断。以下图像和代码片段展示了如何在使用这种方法时将 TensorRT 优化应用于 TensorFlow 中的图。输出是一个 TensorFlow 图,其中支持的子图被替换为 TensorRT 优化的引擎,这些引擎可以由 TensorFlow 执行。
import tensorflow.contrib.tensorrt as trt
converted _graph_def = trt.create_inference_graph(
     input_graph_def = frozen_graph,
     outputs-[‘logits’, ‘classes’])
我们维护着一个更新的操作列表,这些操作受集成工作流程支持。

在上面概述的流程的优化阶段,执行三个操作
  1. 图划分。TensorRT 会扫描 TensorFlow 图以查找它可以基于支持的操作进行优化的子图。
  2. 层转换。将每个子图中支持的 TensorFlow 层转换为 TensorRT 层。
  3. 引擎优化。最后,子图将被转换为 TensorRT 引擎,并在父 TensorFlow 图中被替换。

让我们看一个这个过程的例子。

示例演练

以以下图为例。绿色块突出显示了 TensorRT 支持的操作,灰色块显示了不受支持的操作(“Cast”)。

优化的第一阶段将 TensorFlow 图划分为 TensorRT 兼容的子图和非兼容的子图。我们从 Relu 操作(a)开始,向后遍历图,一次添加一个节点,以获得尽可能大的子图。唯一的约束是子图应该是一个直接循环图,并且没有循环。可以创建的最大子图如图 © 所示。集群添加所有节点,直到它到达reshape操作。然后有一个循环(d),所以它返回。我们现在为此添加一个新的集群,所以我们最终得到了 2 个 TensorRT 兼容的子图(e)。


图 2(a)示例图,TensorRT 支持的节点以绿色显示,用于优化的图以橙色框显示(b)子图中的 4 个操作,还没有循环(c)添加 Conv2D 也不会添加循环(d)将 reshape 添加到此子图会创建一个循环(e)创建了 2 个子图,解决了循环

控制 TensorRT 引擎中的最小节点数量

在上面的示例中,我们生成了两个 TensorRT 优化的子图:一个用于reshape操作符,另一个用于除cast之外的所有操作符。小型图(例如,只有一个节点的图)在 TensorRT 提供的优化和构建和运行 TRT 引擎的开销之间存在权衡。虽然小型集群可能不会带来很大的收益,但仅接受非常大的集群会导致可能适用于小型集群的优化被遗漏。您可以使用minimum_segment_size参数来控制子图的大小。将此值设置为 3(默认值)将不会为由少于三个节点组成的子图生成 TensorRT 引擎。在这个例子中,最小段大小为 3 将跳过让 TensorRT 优化 reshape 操作,即使它符合 TensorRT 优化的条件,并且将回退到 TensorFlow 来执行 reshape 操作。
converted_graph_def = create_inference_graph(
    input_saved_model_dir=model_dir,
    minimum_segment_size=3,
    is_dynamic_op=True,
    maximum_cached_engines=1)
最终的图包含 2 个子图或集群(图 3a)。

图 3(a)包含 TensorFlow 操作的子图(b)用 TensorRTEngineOp 替换 TensorFlow 子图。接下来,将 TensorRT 兼容的子图包装到名为 TRTEngineOp 的自定义操作中。然后,使用新生成的 TensorRT 操作来替换 TensorFlow 子图。最终的图包含 3 个操作(图 3b)。

可变输入形状

TensorRT 通常要求模型中的所有形状都完全定义(即,除了批次维度之外,不为 -1 或 None),以便选择最优化的 CUDA 内核。如果模型的输入形状完全定义,则可以使用is_dynamic_op=False的默认设置在初始转换过程中静态地构建 TensorRT 引擎。如果您的模型具有未知形状(例如 BERT 或 Mask R-CNN),则可以将 TensorRT 优化延迟到执行时,此时输入形状将被完全指定。将is_dynamic_op设置为true以使用这种方法。
converted_graph_def = create_inference_graph(
    input_saved_model_dir=model_dir,
    minimum_segment_size=3,
    is_dynamic_op=false,
    maximum_cached_engines=1)
接下来,以拓扑顺序遍历图,将子图中的每个 TensorFlow 操作转换为一个或多个 TensorRT 层。最后,TensorRT 应用优化,例如层和张量融合、用于降低精度的校准以及内核自动调整。这些优化对用户来说是透明的,并针对您计划在其上运行推断的 GPU 进行优化。


图 4(a)转换为 TensorRT 层之前的 TensorFlow 子图(b)第一个 TensorFlow 操作转换为 TensorRT 层(c)所有 TensorFlow 操作转换为 TensorRT 层(d)来自图的最终 TensorRT 引擎

TensorRT 引擎缓存和可变批次大小

TensorRT 引擎可以缓存在位于 TRTEngineOp 操作中的 LRU 缓存中。此缓存的键是操作输入的形状。因此,如果缓存为空或对于给定输入形状的引擎不存在于缓存中,则会创建一个新的引擎。您可以使用以下maximum_cached_engines参数来控制缓存的引擎数量。
converted_graph_def = create_inference_graph(
    input_saved_model_dir=model_dir
    minimum_segment_size=3,
    is_dynamic_op=True,
    maximum_cached_engines=1)
将值设置为 1 将强制每次创建新引擎时将任何现有缓存驱逐出去。

TensorRT 使用输入的批次大小作为选择性能最高的 CUDA 内核的参数之一。批次大小作为输入的第一维提供。当is_dynamic_optrue时,批次大小由执行期间的输入形状确定,当is_dynamic_opfalse时,由max_batch_size参数确定。如果满足以下条件,则可以将引擎重新用于新的输入:
  • 引擎批次大小大于或等于新输入的批次大小,并且
  • 非批次维度与新输入匹配

因此,在下面的图 5a 中,我们不需要创建新的引擎,因为新的批次大小(2)小于缓存引擎的批次大小(4),而其他输入维度(在本例中为 [8,8,3] 和 [9,9,5])相同。在 5b 中,非批次大小输入维度不同([8,8,3] 与 [9,9,5]),因此需要生成新的引擎。缓存和引擎的最终示意图表示如图 5c 所示。


图 5(a)、(b)、(c)从左到右
增加maximum_cached_engines变量以尽可能防止重新创建引擎。缓存更多引擎会使用机器上的更多资源,但我们发现对于典型模型来说这不是问题。

INT8 精度下的推断

Tesla T4 GPU 引入了 Turing Tensor Core 技术,该技术为推断提供了全范围的精度,从 FP32 到 FP16 再到 INT8。Tensor Core 在 Tesla T4 GPU 上提供高达 30 teraOPS (TOPS) 的吞吐量。使用 INT8 和混合精度可减少内存占用,从而为推断提供更大的模型或更大的小批量。


图 6 Tensor Core 以降低的精度执行矩阵乘法,并以更高的精度累加
你可能想知道,如何将一个在 32 位浮点数精度下运行的模型(代表数十亿个不同的数字),压缩到一个只有 8 位整数(仅代表 256 个可能的数值)中。通常,在深度神经网络中,权重和激活的值会落在某个较小的范围内。如果我们可以将宝贵的 8 位值集中在这个范围内,那么我们就可以在仅有一些小的舍入误差的情况下保持良好的精度。

TensorRT 使用“对称线性量化”来进行量化,这是一个将 FP32 范围(在图 7 中为 -6 到 6)缩放至 INT8 范围(对我们来说是 -127 到 127,以保持对称性)的操作。如果我们能找到网络中每个中间张量的大多数值所在的范围,我们就可以使用该范围来量化该张量,同时保持良好的精度。
Quantize(x, r) = round(s * clip(x, -r, r))
            where s = 127 / r


图 7:x 是输入,r 是张量的浮点数范围,s 是 INT8 中值的数量的缩放因子。上面的等式接收输入 x 并返回一个量化的 INT8 值。
虽然本文无法全面探讨,但两种技术通常用于确定网络中每个张量的激活范围:校准和量化感知训练。

校准是推荐的方法,适用于大多数模型,精度损失最小(<1%)。对于校准,首先在校准数据集上运行推理。在此校准步骤中,会记录激活值的直方图。然后选择 INT8 量化范围,以最大程度地减少信息损失。量化发生在流程的后期,这成为训练的新误差来源。请参阅下面的代码示例,了解如何执行校准。
import tensorflow.contrib.tensorrt as trt
calib_graph = trt.create_inference_graph(…
    precision_mode=’INT8',
    use_calibration=True)
with tf.session() as sess:
    tf.import_graph_def(calib_graph)
    for i in range(10):
        sess.run(‘output:0’, {‘input:0’: my_next_data()}) 
# data from calibration dataset
converted_graph_def = trt.calib_graph_to_infer_graph(calib_graph)
当使用 INT8 的校准时,量化步骤发生在模型训练完成后。这意味着没有办法在该阶段调整模型以解决误差。量化感知训练试图解决这个问题,但它仍处于早期阶段,并作为实验性功能发布。量化感知训练在训练的微调步骤期间模拟量化误差,并且量化范围是在训练期间学习的。这使得你的模型能够补偿误差。在某些情况下,这可以提供比校准更高的精度。

用量化节点增强图,然后像往常一样训练模型以执行量化感知训练。量化节点将通过剪切、缩放、舍入和反缩放张量值来模拟由于量化造成的误差,从而使模型能够适应误差。你可以使用固定量化范围,也可以将其设置为可训练变量。你可以使用tf.quantization.fake_quant_with_min_max_vars,并将narrow_range=Truemax=min设置为匹配 TensorRT 的激活量化方案。

图 8 在 TensorFlow 图中插入橙色的量化节点。
其他更改包括设置precision_mode=”INT8”use_calibration=false,如下所示。
calib_graph_def = create_inference_graph(
    input_saved_model_dir=input_saved_model_dir,
    precision_mode=”INT8",
    use_calibration=False)
这会从图中提取量化范围,并为你提供用于推理的转换模型。误差是使用伪量化节点模拟的,对于每个节点,可以使用梯度下降法学习范围。TF-TRT 会自动从你的图中吸收学习到的量化范围,并创建一个可供部署的优化的 INT8 模型。

请注意,INT8 推理必须在训练期间尽可能地进行建模。这意味着你不应该在推理期间不会被量化的位置(由于发生了融合)引入 TensorFlow 量化节点。Conv > Bias > Relu 或 Conv > Bias > BatchNorm > Relu 等操作模式通常会由 TensorRT 合并在一起,因此,在这些操作之间插入量化节点将是错误的。在量化感知训练文档中了解更多信息。

用于 TensorFlow-TensorRT 应用程序的调试和分析工具

你可以找到许多用于分析 TensorFlow-TensorRT 应用程序的工具,从命令行分析器到 GUI 工具,包括nvprof、NVIDIA NSIGHT Systems、TensorFlow 分析器和 TensorBoard。最容易上手的是nvprof,这是一种适用于 Linux、Windows 和 OS X 的命令行分析器。它是一个轻量级分析器,它概述了应用程序中的 GPU 内核和内存复制。你可以按如下所示使用nvprof
nvprof python <your application name>
NVIDIA NSIGHT Systems 是一种系统范围内的性能分析工具,旨在可视化应用程序的算法,帮助用户调查瓶颈,以更高的性能提升概率追求优化,并在任何数量或大小的 CPU 和 GPU 上进行高效调整。它还提供了对 PyTorch 和 TensorFlow 等深度学习框架的行为和负载的宝贵见解;允许用户调整他们的模型和参数以提高整体单 GPU 或多 GPU 利用率。

让我们看看将这两个工具结合使用的一个用例以及你可以从它们那里收集的一些信息。在命令提示符下,使用以下命令。
nvprof python run_inference.py
下面的图 9 显示了按计算时间降序排列的 CUDA 内核列表。前五名内核中有四个是 TensorRT 内核,在 Tensor Core 上运行的 GEMM 操作(关于如何在下一节中使用 Tensor Core 的更多信息)。理想情况下,你希望 GEMM 操作占据此图表中的前几名位置,因为 GPU 非常擅长加速这些操作。如果它们不是 GEMM 内核,那么这将是进一步调查以删除或优化这些操作的线索。


图 9 命令提示符中 nvprof 的输出,显示按计算时间排序的前几个内核。


图 10 NSIGHT Systems 显示了利用 GPU 良好,没有重大间隙的程序的时间线视图。
图 10 突出显示了用 (1) 标记的 CUDA 内核的时间线。这里的目标是确定时间线中最大的间隙,这表明 GPU 在那个时间没有执行计算。GPU 可能在等待数据可用或等待 CPU 操作完成。由于 ResNet-50 已得到很好的优化,因此你会注意到内核之间的间隙非常小,大约几微秒。如果你的图有更大的间隙,这将是调查哪些操作导致间隙的线索。你也可以在上面的图片中看到 CUDA 流及其对应的 CUDA 内核。黄色对应于 TensorRT 层。对调试工作流程的全面探讨超出了本博客的范围。

你可以在图 11 中看到,在计算时间线上有一个间隙,对应于 GPU 没有被利用的时间。你需要进一步调查类似的模式。


图 11 NSIGHT Systems 显示了 GPU 利用率有间隙的程序的时间线视图。
视觉和 NLP 代表需要这些工具来处理应用程序输入的常见用例。如果这些应用程序的预处理速度慢,因为数据不可用或网络瓶颈,那么使用上面的工具将帮助你识别需要优化应用程序的区域。我们经常看到,TF-TRT 中推理流程的瓶颈是加载来自磁盘或网络的输入(例如 jpeg 图像或 TFRecords)并在将其馈送到推理引擎之前对其进行预处理。如果数据预处理是瓶颈,你应该探索使用 I/O 库(例如nvidia/dali)来加速它们,使用多线程 I/O 和图像处理等优化,并在 GPU 上执行图像处理。

TensorFlow 分析器 是另一个随 TensorFlow 一起提供的工具,它非常方便于通过在 Python 脚本中添加额外的参数来可视化内核计时信息。示例包括提供给会话运行的额外optionsrun_metadatasess.run(res, options=options, run_metadata=run_metadata)。执行后,将生成一个包含分析数据的 .json 文件,该文件采用 Chrome 跟踪格式,可以通过 Chrome 浏览器查看。

你也可以使用 TensorFlow 日志记录功能以及 TensorBoard 来查看你的应用程序的哪些部分被转换为 TensorRT。要使用日志记录,请将 TensorFlow 日志中的详细程度级别提高,以打印来自选定 C++ 文件集的日志。你可以在调试工具文档中了解更多有关详细日志记录和允许的级别的信息。请参阅下面的示例代码,了解如何提高详细程度级别。
TF_CPP_VMODULE=segment=2,convert_graph=2,convert_nodes=2,trt_engine_op=2 python run_inference.py
另一种选择是在TensorBoard(TensorFlow 的一系列可视化工具)中可视化图。TensorBoard 允许你检查 TensorFlow 图,其中包含哪些节点、哪些 TensorFlow 节点被转换为 TensorRT 节点、哪些节点附加到 TensorRT 节点,甚至图中张量的形状。在使用 TensorBoard 可视化 TF-TRT 图中了解更多信息。

你的算法是否使用 Tensor Core?

你可以使用nvprof来检查你的算法是否使用 Tensor Core。上面的图 9 显示了一个使用nvprof和推理 Python 脚本测量性能的示例:nvprof python run_inference.py 当使用 FP16 累加的 Tensor Core 时,字符串 ‘h884’ 会出现在内核名称中。在 Turing 上,使用 Tensor Core 的内核可能在它们的名称中包含 ‘s1688’ 和 ‘h1688’,分别代表 FP32 和 FP16 累加。

如果你的算法没有使用 Tensor Core,你可以做几件事来调试并了解原因。为了检查 Tensor Core 是否在我的网络中使用,请执行以下步骤。
  1. 在命令行中使用命令nvidia-smi来确认当前硬件架构是 Volta 还是 Turing GPU。
  2. 全连接、MatMul 和 Conv 等操作可以使用 Tensor Core。确保这些操作中的所有维度都是 8 的倍数,以触发 Tensor Core 的使用。对于矩阵乘法:M、N、K 大小必须是 8 的倍数。全连接层应该使用 8 的倍数的维度。如果可能,将输入/输出字典填充为 8 的倍数。

请注意,在某些情况下,如果 TensorRT 选择的算法对于所选数据和操作而言执行速度更快,则它可能会选择不基于 Tensor Core 的替代算法。你始终可以在TensorRT 论坛中报告错误并与 TensorFlow-TensorRT 社区互动。

性能和基准测试脚本

TensorRT 最大限度地提高了推理性能,加快了推理速度,并在各种网络(用于图像分类、目标检测和分割)中提供了低延迟。例如,ResNet-50 在使用 TensorFlow 中的 TensorRT 的 GPU 上实现了高达 8 倍的吞吐量。由于支持 INT8 量化,你可以在保持高精度的同时实现高吞吐量。在深度学习产品性能页面上找到 NVIDIA GPU 平台上的最新性能结果。

下面的表 1 显示了我们在 2019 年 3 月的容器中验证的每个模型的精度数字。我们的验证在整个 ImageNet 验证数据集上运行推理,并提供 top-1 精度。在 TensorFlow-TensorRT 文档的已验证模型部分中找到一组基准测试模型和精度数字。


表 1:文档中常见模型的精度数字,用于 19.03 容器。
你可以使用我们在tensorflow/tensorrt github 存储库中的脚本下载和基准测试这些模型,这些脚本使用来自TF slimTF official的公开可用模型(ResNet、MobileNet、Inception、VGG、NASNet L/M、SSD MobileNet v1)。

接下来是什么?

TensorFlow 2.0 于 2019 年 4 月在 TensorFlow 开发者峰会上发布,在撰写本文时,它已在 alpha 版中提供。TensorRT 已从 contrib 区域移动到核心编译器存储库。API 已经略有改变,但旧 API 将继续得到支持。请参阅更新后的代码片段,以将 TensorRT 优化应用于 TensorFlow 2.0 中的 TensorFlow 图。
from tensorflow.python.compiler.tensorrt import trt_convert as tru 
params = trt.DEFAULT_TRT_CONVERSION_PARAMS._replace(
    precision_mode='FP16')
converter = trt.TrtGraphConverterV2(
input_saved_model_dir=input_saved_model_dir, conversion_params=params)
converter.convert()
converter.save(output_saved_model_dir)
预计很快发布的 TensorFlow 1.14 将使用TrtGraphConverter函数,其余代码保持不变。

交给你

我们预计 TensorFlow-TensorFlowRT 集成将确保在使用 NVIDIA GPU 时实现最高性能,同时保持 TensorFlow 的易用性和灵活性。随着 TensorRT 支持更多网络,开发人员将自动受益于更新,而无需对现有代码进行任何更改。

本文基于 2019 年在圣何塞举行的 GPU 技术大会的演讲。请查看 “使用 TensorFlow 进行 TensorRT 推理” 演讲的完整录音以了解更多信息。

该集成还将提供在 NVIDIA GPU 云 (NGC) TensorFlow 容器中。我们相信您在使用 GPU 进行推理时,将看到将 TensorRT 集成到 TensorFlow 的巨大优势。 TensorRT 页面 提供了有关 TensorRT 的更多信息,以及指向更多文章和文档的链接。

NVIDIA 致力于不断改进其技术和产品,因此请在下方留言告诉我们您的想法。
下一篇
High performance inference with TensorRT Integration

作者:Pooya Davoodi (NVIDIA),Guangda Lai (Google),Trevor Morris (NVIDIA),Siddharth Sharma (NVIDIA)

去年,我们 介绍了 TensorFlow 与 TensorRT 的集成,以加快使用 GPU 的深度学习推理速度。本文将深入探讨并分享一些技巧和窍门,以便您在推理过程中充分利用您的应用程序。即使您不熟悉集成,本文也…