三、深入学习TensorRT,Developer Guide篇(二)

news/2024/9/23 9:35:39/

这篇文章基于官方文档的第二节 TensorRT’s Capabilities,不要认为这节没有用啊,其实知道一个工具的capability还是比较重要的,学习一个工具你得知道这个工具有啥用,能干啥,这样你在后面遇到某个问题的时候能评估出来那些工具能够解决你的问题,哪些不能,这也是我们常说的工作效率中比较重要的一环。正如这一节官方文档开头所说:这一节给你个提供一个你能用TensorRT来干什么的overview,对于Tensorrt用户来说是非常重要的。

文章目录

  • 2. TensorRT’s Capabilities
    • 2.1 C++ and Python APIs
    • 2.2 The Programming Model
      • 2.2.1 The Build Phase
      • 2.2.2 The Runtime Phase
    • 2.3 Plugins
    • 2.4 Types and Precision
    • 2.5 Quantization
    • 2.6 Tensors and Data Formats
    • 2.7 Dynamic Shapes
    • 2.8 DLA
    • 2.9 Updating Weights
    • 2.10 trtexec Tool
    • 2.11 Polygraphy

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 contextenqueueV3()函数来运行推理(其实现在很多还在用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层(这个没见过,我们后面有机会再回来补充)
  • 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,不需要自己定义layertensor那些)。
  • 从构建器生成序列化时序缓存。

对于其他的细节部分,请参考: 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的特性都讲完了,后面哪些工具或者特性忘记了,可以快速的到这里来浏览一下。


http://www.ppmy.cn/news/1356746.html

相关文章

Linux下多核CPU指定程序运行的核

设置程序在指定CPU核心运行 一、如何查看程序运行的CPU信息 1.1 查看当前系统CPU有几个核心 查看CPU核心数量:lscpu 1.2 查看程序的PID ps aux|grep cpu_test1.3 查看程序可运行的CPU taskset -c -p pid1.4 设置程序在指定核心上运行 1.4.1 通过运行时的参数设…

基于java的眼镜店仓库管理系统

源码获取,加V:qq2056908377 摘要: 随着电子商务的兴起,越来越多的商家选择在线销售他们的产品。眼镜店作为零售业的一种,也不例外。随着市场需求的不断增加,眼镜店需要更加高效的管理他们的仓库和库存&…

Promise学习之路(一):Promise() 构造函数

文章目录 1 Promise() 语法2 Promise() 的参数3 Promise() 的返回值4 Promise() 的使用时机5 Promise 流程概述 Promise() 构造函数可创建 Promise 对象,主要用于封装尚未支持 Promise 的基于回调的 API。 1 Promise() 语法 Promise() 构造函数接收一个函数类型的…

【大数据面试题】007 谈一谈 Flink 背压

一步一个脚印,一天一道面试题(有些难点的面试题不一定每天都能发,但每天都会写) 什么是背压 Backpressure 在流式处理框架中,如果下游的处理速度,比上游的输入数据小,就会导致程序处理慢&…

opencv安装介绍以及基本图像处理详解

文章目录 一、什么是OpenCV ?二. OpenCV 安装1. 下载地址2.安装命令:pip install opencv-python 三、图像基础1. 基本概念2. 坐标系3. 基本操作(彩色图片)(1)读取图片:cv2.imread( )&#xff08…

vue中使用jsx语法

请注意,在 Vue 中使用 JSX 时,你仍然需要通过 h 函数(通常是一个别名,对应于 createElement 函数)来创建虚拟 DOM 元素。在下面的例子中,h 函数作为 render 函数的参数传入,但在 JSX 语法中你通…

【PyQt】12-滑块、计数控件

文章目录 前言一、滑块控件 QSlider运行结果 二、计数器控件 QSpinBox运行结果 总结 前言 1、滑块控件 2、计数控件 一、滑块控件 QSlider #Author :susocool #Creattime:2024/2/15 #FileName:28-滑块控件 #Description: 通过滑块选择字体大小 import sys from PyQ…

mysql删除idb文件,或者idb文件损坏后的修复

由于使用docker磁盘已满,导致建立表过程中,数据的插入存在问题,进而导致后续启动时读取该表的idb存在问题,导致无法启动 现在提供一种思路处理该种情况 innodb_force_recovery 选项可以让你在某些类型的错误发生时仍然启动 MySQL。…