这篇文章基于官方文档的第二节 TensorRT’s Capabilities,不要认为这节没有用啊,其实知道一个工具的capability还是比较重要的,学习一个工具你得知道这个工具有啥用,能干啥,这样你在后面遇到某个问题的时候能评估出来那些工具能够解决你的问题,哪些不能,这也是我们常说的工作效率中比较重要的一环。正如这一节官方文档开头所说:这一节给你个提供一个你能用TensorRT来干什么的overview,对于Tensorrt用户来说是非常重要的。
😍😍😍老规矩看前点个赞😍😍😍
2. TensorRT’s Capabilities
2.1 C++ and Python APIs
TensorRT的API具有C++和Python的语言绑定,这两者具有几乎相同的功能。Python API促进了与Python数据处理工具包和库(如NumPy和SciPy)的互操作性。C++ API可以更高效,并且可以更好地满足一些灵活的需求,例如在汽车应用程序中。(Python API并不是适用于所有平台,具体请参考NVIDIA TensorRT Support Matrix,进去搜索一下python关键字就好)
2.2 The Programming Model
我感觉这个官方文档不够精炼,很多地方都重复描述了,但是我为了更贴近原文档,所以也有一定程度的重复描述,大家见谅。
TensorRT在两个阶段进行操作,分别是在第一阶段,通常情况下你是离线执行的,就是单独执行,你将模型提供给TensorRT进行针对GPU的优化(build phase),在第二阶段,你使用优化后的模型来进行推理(runtime phase)。
2.2.1 The Build Phase
对于build phase的高阶结构就是TensorRT的Builder
(C++,Python都有对应的API),builder负责优化模型并且生成一个engine,为了构建engine,你必须:
- 创建网络定义
- 为这个builder进行特定配置
- 调用这个builder来创建engine
NetworkDefinition
接口(C++, Python)用来定义模型,最常见的将一个模型转换为Tensorrt的方法就是从框架中导出ONNX格式的文件,然后使用TensorRT 的 ONNX解析器来填充这个网络,但是你也可以使用TensorRT的Layer
(C++, Python)和 Tensor
(C++, Python)这些接口(之前遇到过一个利用TensorRT部署YOLO网络的工程,就是自己通过这些API来进行网络构建和推理的,也不要忽视这种方法哟)
不管你选择哪一种方式,你都必须定义这个网络的输入输出是哪个tensor,未标记为输出的Tensor会被认为是可以被builder优化掉的临时值(transient values),输入和输出的tensor必须被命名,这样在运行的时候,TensorRT知道如何将输入输出buffer(这些buffer是由你之前单独进行定义的)绑定到模型上。(所以你明白为什么你看到的很多TensorRT的程序都会标记输入输出了吧)
BuilderConfig
接口(C++, Python)被用来指明TensorRT应该如何优化模型,在可选的配置选项中,你可以控制TensorRT对于减少计算的精度、控制内存和运行速度的平衡和限制CUDA kernels的选择的能力。由于构建器可能需要几分钟甚至更长时间才能运行,因此您还可以控制构建器搜索内核的方式,以及缓存搜索结果以供后续运行使用。
在定义了网络和设置了builder configuration后,你可以调用builder来创建engine。builder消除了死计算(dead computations),折叠常量(folds constans,本质就是一种特殊的常量),并且对操作进行重新排序和组合,这样便可以在GPU上更有效地运行。它可以选择性地降低浮点计算的精度,通过简单地在16位浮点数中运行它们,或者通过量化浮点值以便可以使用8位整数执行计算。它还使用不同的数据格式对每层的多个实现进行计时,然后计算执行模型的最佳调度,从而最大限度地减少内核执行和格式转换的综合成本。(这个很重要,在面试的时候会问你,为什么使用TensorRT能够加速和优化计算呢?这就是标准答案,你如果是大佬,还可以反问面试官这个问题,看他怎么回答,哈哈
)
这个时候,builder以一个序列化(serialized)的形式创建了engine,我们称之为plan(每次看到这个名字我都觉得很奇怪,为啥叫plan),plan可以立即被反序列化(deserialized)或者存到磁盘中后续再使用。
注意:
- 默认情况下,TensorRT创建的engine是针对当前的TensorRT和GPU的,如果为其他版本或者硬件做更多更改,请参考: Version Compatibility 和 Hardware Compatibility
- 为了减少内存开销,TensorRT的网络定义对一些参数数组(比如卷积的权重)进行的是浅拷贝。因此,在构建阶段完成之前,一定不要释放这些数组的内存。当使用ONNX解析器导入网络时,解析器拥有权重,因此在构建阶段完成之前不能销毁它,不然数据没了,构建就出问题了。
- builder对算法进行计时,以确定最快的那一组参数。如果这时候与其他GPU工作并行执行builder,可能会扰乱时间,导致优化失败或者无法获得最好的结果,简而言之,就是在优化的时候不要使用多卡啦。
2.2.2 The Runtime Phase
对于执行阶段的TensorRT高级接口是Runtime
(C++, Python),当使用runtime时,你肯定会按照下面的步骤:
- 反序列化一个plan来创建一个engine
- 从这个engine中创建一个执行上下文(
execution context
)
然后,重复进行:
- 填充用于推理的输入缓冲区(就是将数据拷贝到input buffer里面去)
- 调用
execution context
的enqueueV3()
函数来运行推理(其实现在很多还在用enqueue()
和enqueueV2()
)
Engine
(C++, Python)接口表示一个优化后的模型,你可以从一个engine中来查询很多信息,如输入输出的tensors、需要的dimensions、数据类型、数据格式等等(这个很重要啊)
ExecutionContext
接口(C++, Python)是从engine中创建来的,是推理过程中非常重要的接口。execution context
包含了所与有特定调用相关联的所有状态,因此你可以拥有与单个引擎相关联的多个上下文,并且并行地运行它们。在调用推理时,必须在适当的位置设置输入和输出缓冲区,根据数据的性质,这可能在CPU或GPU内存中。如果根据模型很难判断,那可以通过查询engine来确定在哪个内存空间中提供缓冲区。
设置缓冲区后,可以对推理进行排队(enqueueV3
,排到你的任务就执行你的任务)。所需的内核在CUDA流上排队,并尽快将控制返回给应用程序(否则可能导致长时间的阻塞)。一些网络需要在CPU和GPU之间进行多次控制传输,因此控制可能不会立即返回。要等待异步执行完成,请使用cudaStreamSynchronize
在流上同步。
2.3 Plugins
TensorRT对于一些没有原生支持的操作,提供了一个Plugin接口来允许自己实现相应的操作。Plugin通过TensorRT的PluginRegistry
创建和注册,这样就可以被ONNX解析器在翻译网络的时候找到。TensorRT附带了一个插件库,其中许多插件和一些附加插件的源代码可以在这里找到(算子不支持的话,优先建议你来这里搜搜看)。你也可以实现你自己的plugins并且和engine一起进行序列化操作。
2.4 Types and Precision
TensorRT 支持 FP32、FP16、INT8、INT32、UINT8 和 BOOL 数据类型。有关层 I/O 数据类型规范, 请参阅TensorRT Operator 文档。
- FP32、FP16:未量化的高精度类型
- INT8
- 隐式量化:解释为量化整数类型,一个INT8的tensor必须有一个关联的缩放因子(要不是通过calibration要不是通过
setDynamicRange
API)(多解释一点,calibration就是通过放入一堆数据,比如几十上百张图片来决定量化的max和min再确定sacle factor,这些数据被称之为calibration set,这也是面试可能会问的问题。还有基于这个的另一个问题,就是1. 如何选择calibration set中的数据,答案是注意和真实场景的数据的分布尽量保持一致 2. 如果calibration中的数据出现了问题,比如严重不正确的异常值怎么办呢?这个我也不知道答案是什么,个人理解是加强数据的筛选和清洗,以及后期进行验证) - 显式量化:解释为有符号整型,从INT8转换或者转换到INT8需要显式地经过Q/DQ层(这个没见过,我们后面有机会再回来补充)
- 隐式量化:解释为量化整数类型,一个INT8的tensor必须有一个关联的缩放因子(要不是通过calibration要不是通过
- UINT8
- 一种只用在网络I/O的数据类型
- 在其他操作中使用数据之前,必须使用
CastLayer
将网络级输入从UINT8转换为FP32或FP16(也就是网络内部是没有UINT8这个数据类型的,同上一条)。 - UINT8的网络级输出必须由一个明确插入到网络中的
CastLayer
产生(只支持从FP32/FP16到UINT8的转换,就是说内部如果是INT8的话,输出不可能是UINT8的?)。 - UINT8量化目前还不支持
-
ConstantLayer
不支持将UINT8作为输出数据类型
- BOOL:常规的布尔类型,没啥特殊的
TensorRT选择CUDA kernels来完成浮点运算的时候,默认是使用FP32,有两种配置不同级别精度的方法:
- 为了在模型级别控制精度,
BuilderFlag
选项(C++, Python)可以向TensorRT指示,在搜索最快的实现时,它可能会选择较低精度的实现(因为较低精度通常更快)。因此,你可以轻松地指示TensorRT对整个模型使用FP16计算。对于输入动态范围近似为1的正则化模型,这通常会产生显著的加速,而精度的变化可以忽略不计。 - 对于细粒度控制,由于部分网络对数字敏感所以需要高的动态范围,因此某一层必须以更高的精度运行,这个时候可以为该层指定算术精度。
对于更多的精度内容,可以参考:Reduced Precision
2.5 Quantization
TensorRT支持量化浮点值,其中浮点值被线性压缩并四舍五入为8位整数。这大大提高了算术吞吐量,同时降低了存储需求和内存带宽。当量化一个浮点张量时,TensorRT必须知道它的动态范围(dynamic range)——也就是说,哪个值的范围是重要的——在量化时,超出这个范围的值将被限制。
动态范围信息可由builder根据代表性输入数据计算(这称为校准,也就是我上面说的你选择出来的那部分数据)。或者您可以在框架中执行量化感知训练(quantization-aware training, QAT, 这个挺重要的,但是我没有接触过,后面有机会补上),并将带有必要动态范围信息的模型导入到TensorRT中。
其他细节部分,请参考: Working with INT8
2.6 Tensors and Data Formats
在定义网络时,TensorRT将张量当做一个多维的C风格数组。每个层对其输入都有特定的解释:例如,2D卷积将假设其输入的最后三个维度是CHW格式(没有选择使用例如WHC格式),请参阅NVIDIA TensorRT Operator’s Reference,了解每个层如何解释其输入。注意张量被限制在最多2^31-1个元素(也就是最多32位能存储的值)。
在优化网络的同时,TensorRT在内部执行转换(包括到HWC,但也包括更复杂的格式),以使用最快的CUDA内核。通常,选择格式是为了优化性能,应用程序无法控制这些选择(也就是说完全是内部的,我们无法选择)。然而,底层数据格式在I/O边界(网络输入和输出,以及向插件传递数据和从插件传递数据)上是暴露出来的,以允许应用程序将不必要的格式转换最小化。(这里我又想到一个面试题,就是对于这种并行的处理,CHW和HWC哪个更高效呢?考虑到这种高度并行的处理,数据之间可以并行化是最重要的,所以HWC是更高效的,因为可以拆分为m*n*c,m和n就是对应卷积核的大小,这样可以在多个kernel中进行并行计算,但是对于CHW却是不好拆分的,也就是不好进行并行计算的
)。
对于其他的细节部分,请参考: I/O Formats
2.7 Dynamic Shapes
默认情况下,TensorRT是基于已经定义好的输入的形状进行优化的(batch size、image size等),但是builder也可以被配置为允许在运行的过程中进行维度的调整。为了进行这种设置,你需要在builder configuration中指定一个或多个OptimizationProfile实例(C++, Python)包含每个输入的最小和最大形状,以及该范围内的优化点(optimization point)。
TensorRT为每个配置文件创建一个优化的引擎,选择在[minimum,maximum]范围内的所有shapes的CUDA kernels,并且在优化点是最快的——通常每个配置文件有不同的内核,你可以在运行时选择配置文件。(这里也有一个面试题:如何给TensorRT设置Dynamic Shapes
)
对于其他的细节部分,请参考:Working with Dynamic Shapes
2.8 DLA
TensorRT 支持 NVIDIA 的深度学习加速器 ( NVIDIA’s Deep Learning Accelerator, DLA),这是许多 NVIDIA SoC 上存在的专用推理处理器,支持 TensorRT 层的子集。 TensorRT 允许你在 DLA 上执行网络的一部分,而在 GPU 上执行其余部分;对于可以在任一设备上执行的层,你可以在构建器配置中逐层选择目标设备。(就是当有DLA可用的时候,你设置某些层运行在DLA上,会让算法变得更强)
对于其他的细节部分,请参考:Working with DLA
2.9 Updating Weights
构建engine时,你可以指定稍后更新其权重。如果你经常更新模型的权重而不更改结构,例如在强化学习中或在保留相同结构的情况下重新训练模型时,这可能会很有用。权重更新是使用Refitter (https://docs.nvidia.com/deeplearning/tensorrt/api/c_api/classnvinfer1_1_1_i_refitter.html、https://docs.nvidia.com/deeplearning/tensorrt/api/python_api/infer/Core/Refitter.html)接口。(这也是一个很实用的功能接口)
对于其他的细节部分,请参考:Refitting an Engine
2.10 trtexec Tool
包含在samples
文件夹中的是一个命令行包装工具,称为trtexec。trtexec是一个可以直接使用的 TensorRT 的工具,无需开发自己的应用程序就可以实现很多功能。(这个工具的确挺有用,我们后面应该会用到他)。trtexec有三个主要用途:
- 根据随机或用户提供的输入数据对网络进行基准测试(测试整体效果如何,是否符合要求)。
- 从模型生成序列化引擎(直接创建engine,不需要自己定义
layer
和tensor
那些)。 - 从构建器生成序列化时序缓存。
对于其他的细节部分,请参考: trtexec
2.11 Polygraphy
Polygraphy 是一个工具包,旨在帮助在 TensorRT 和其他框架中运行和调试深度学习模型。它包括一个Python API和 a command-line interface (CLI) 。(这个工具我目前还没有用过,比如下面提到的精度对比,我都是随机采样一些层的信息存在日志里面进行对比的,的确比较低级和繁琐,这个工具大家有空可以好好研究一下)
除此之外,通过 Polygraphy,还可以:
- 在多个后端(例如 TensorRT 和 ONNX-Runtime)之间运行推理,并比较结果(例如API、CLI)。
- 将模型转换为各种格式,例如后量化 TensorRT engine(例如API、CLI)。
- 查看有关各种类型模型的信息(例如CLI)
- 在命令行修改 ONNX 模型:
- 提取子图(例如CLI)。
- 优化和清理(例如CLI)。
- 隔离 TensorRT 中的错误策略(例如CLI)。
对于其他的细节部分,请参考:Polygraphy repository
第二节的内容差不多就是这些,好累呀,不过能系统的学习一下也是非常好的,学习就是要系统,不可以东一下西一下,这一节基本就把TensorRT的特性都讲完了,后面哪些工具或者特性忘记了,可以快速的到这里来浏览一下。文章来源:https://www.toymoban.com/news/detail-827194.html
😍😍😍老规矩看后也点个赞😍😍😍文章来源地址https://www.toymoban.com/news/detail-827194.html
到了这里,关于三、深入学习TensorRT,Developer Guide篇(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!