原创作品,转载时请务必以超链接形式标明文章原始出处: http://www.dapalm.com/?p=206,作者:大数据,怕了么?
本手册为TensorRT 4.0.1.6 GA版英文手册翻译而来,博主英文水平一般般,主要作为备忘所用,分享出来以供更多开发者使用。TensorRT Developer Guide手册一共分为四个章节,主要内容在第二、三章,看懂这两章,写代码够用了。第一章为TensorRT综述,就是自吹有多牛逼。第四章为示例,介绍demo的代码结构及功能。开篇是目录,前三章每章为两到三篇,最后第四章示例,会拆分几个关键示例进行详细说明。
这是第二章最后一部分内容,关于自定义层、混合精度校准、部署。前两个非常重要需要通过具体编程来体会。尤其是自定义层一个新的模型是否能够部署,现阶段一定要保证常见层是支持int8量化的,不然对性能影响很大。
TensorRT支持多种类型的层,其功能不断扩展; 但是,可能存在支持的层不满足模型的特定需求。 在这种情况下,用户可以通过使用C ++ API实现自定义层来扩展TensorRT功能。 自定义层(通常称为插件)由应用程序实现和实例化,它们必须跨越TensorRT引擎的生命周期。
通过扩展IPluginExt类来实现自定义层。 尽管用户在4.0.1.6版本之前的TensorRT中使用IPlugin类,但现在建议用户扩展IPluginExt类,它包括版本控制(以便在未来的TensorRT版本中保持插件的可移植性),并支持除NCHW和单精度之外支持其他数据格式的自定义层。 本节的其余部分是关于IPluginExt类型插件的使用,它除了支持多数据格式外,其他与IPlugin类型插件基本相同。
注:IPlugin类型插件只支持单精度NCHW张量。
插件层使用addPluginExt成员函数(请参考TensorRT API)添加到TensorRT网络中,该方法创建并向网络添加层,然后将层绑定到给定插件。 该方法还返回一个指向图层(IPluginLayerExt类型)的指针,该指针可用于访问层或插件本身(通过getPluginExt)。
要将插件层正确连接到相邻图层,并设置输入和输出数据结构,构建器会调用插件成员函数来检查输出的数量及其维度:
getNbOutputs
用于指定输出张量的数量。
getOutputDimensions
用于指定输出维度作为相邻下一层的输入维度的函数。
此外,在构建阶段中,网络被构造和分析去生成引擎,并且该插件检查支持格式:
supportsFormat
用于检查插件是否支持给定的数据格式。
插件层可以支持四种数据格式和布局,分别是单精度NCHW、半精度NCHW、半精度NC/2HW2和半精度NHWC8张量。这些格式由PluginFormatType枚举。
除了输入和输出张量之外,插件层不进行原位计算(in-place),这需要通过getWorkspaceSize成员函数来指定额外内存空间的要求,以便构建器调用该成员函数来确定和预分配临时空间。
在构建和推理期间,插件层可能被多次配置和执行。在构建时,为了发现最佳配置,该插件层被配置,初始化,执行和终止。一旦插件层选择了最佳格式,它将再次配置,然后被初始化一次,在推理生命周期内执行多次,并最终在引擎被销毁时终止。这些步骤由构建器和引擎使用插件成员函数控制:
configureWithFormat
传递输入和输出数量,维度,数据类型,格式和最大批大小。 此时,插件设置其内部状态,并为给定配置选择最合适的算法和数据结构。
initialize
已知配置以后,将创建推理引擎,因此插件可以设置内部数据结构并为执行作准备。
enqueue
调用封装算法和核函数的插件,并提供运行时批大小,指向输入,输出和暂存空间的指针,以及用于核函数执行的CUDA流。
terminate
销毁引擎上下文并释放插件保存的所有资源。
2.5节Serializing A Model In C++中介绍了引擎的序列化和反序列化,实现存储和部署引擎(例如,避免在部署中重复构建阶段)。为了支持这种功能,插件还必须支持序列化和反序列化;这是通过实现getSerializationSize来返回存储插件的状态所需的大小(包括相关的配置细节)和实现serialize将状态存储到给定的缓冲区中。当引擎被序列化时,它将首先检查插件的序列化大小,然后序列化提供所需大小的缓冲区的插件。
反序列化需要额外的IPluginFactory来识别插件层并实例化相应的插件对象。在运行时,请参考Serializing A Model In C++中序列化模型,反序列化引擎,它使用插件工厂createPlugin成员函数为给定的层名称和序列化图像创建插件对象。
C ++ API也可用于创建在Python中使用的自定义层。 C ++是实现自定义层的首选语言(例如,轻松访问CUDA和cuDNN等库)。可以使用Python setuptools中的SWIG插件打包在C ++中创建的自定义层,然后可以将插件加载到Python应用程序中(请参考使用Python API和TensorRT Python绑定创建网络)。相同的自定义层实现可用于C ++和Python。
自定义层也可以被集成到模型解析器,用于导入模式时使用。扩展解析,需要用户指定特定的解析器工厂,请参考2.2节Creating A Network Definition In C++和2.9节Creating A Network Definition In Python。
命名空间nvcaffeparser1和nvuffparser,分别用于Caffe和UFF,包括一个IPluginFactoryExt类(和IPluginFactory),它补充了命名空间nvinfer1中定义的IPluginFactoryExt。 要在解析器中使用,插件工厂必须扩展通用类和特定的解析器工厂类。
解析器的setPluginFactoryExt方法在解析器中设置工厂来启用自定义层。 在解析模型描述时,对于每个层,解析器调用isPluginExt来检查工厂是否对应于自定义层; 如果是,解析器调用createPlugin来实例化对应层名称的插件层,权重数组和权重数(和UFF的FieldCollection)作为参数。 如果单个工厂与不同的层名称关联,则单个工厂可以支持的插件数量没有限制。
示例samplePlugin详细说明了自定义层的使用以及如何扩展Caffe解析器。
混合精度是在计算中不同数值精度的组合使用。 TensorRT可以存储权重和激活,并以32位浮点,16位浮点或量化的8位整数执行网络。
使用低于FP32的精度可减少内存使用,允许部署更大的网络。 数据传输花费的时间更少,计算性能也会提高,尤其是在Tensor Core支持该精度的GPU上。
默认情况下,TensorRT使用FP32推理,但它也支持FP16和INT8。 在运行FP16推理时,它会自动将FP32权重转换为FP16权重。
注:一般直接指定应用程序所使用的网络的最小可接受精度。 如果对于某些特定的核函数参数集更快,或者如果不存在低精度核函数,则可以选择更高精度的核函数。
构建器设置Fp16Mode标志,表明16位精度可被接受。
builder->setFp16Mode(true);
这个标志表示允许,但是不保证在构建引擎时一定能使用16位核函数。
指定权重FP16或FP32精度,它们将自动转换为适当的计算精度。有关运行FP16推断的示例,请参考示例sampleGoogleNet。
在Python中设置fp16mode标志如下:
builder.set_fp16_mode(True)
更多信息,请参考示例sample_onnx、sampleMNIST、pytorch_to_trt。
当使用8位量化表示时,TensorRT需要理解每个激活张量的动态范围,以便它可以选择适当的量化缩放比例(scale)。
确定这些缩放比例因子的过程称为校准,并要求应用程序为网络传递批量代表性输入(通常来自训练集的批次)。实验表明,大约500张图像足以校准ImageNet分类网络。
提供校准数据给TensorRT,请实现IInt8Calibrator接口。构建器调用校准器如下:
‣首先,它调用getBatchSize()来确定所期望的输入批大小
‣然后,它反复调用getBatch()来获取批数据输入。批数据是getBatchSize()的批大小。当没有批数据时,getBatch()应该返回false。
校准可能很慢,因此,IInt8Calibrator接口提供了缓存中间数据的方法。有效地使用这些方法需要更详细地了解校准。
构建INT8引擎时,构建器执行以下步骤:
1. 构建一个32位引擎,在校准集上运行它,并记录激活值分布的每个张量的直方图。
2. 从直方图构建校准表。
3. 根据校准表和网络定义构建INT8引擎。
校准表可以缓存。在多次构建同一网络时(例如,在多个平台上),缓存非常有用。它捕获从网络和校准集派生的数据。参数记录在表中。如果网络或校准集发生更改,则应用程序负责使缓存无效。
缓存使用如下:
‣如果找到校准表,则跳过校准,否则:
‣校准表由直方图和参数构成
‣然后INT8网络由网络定义和校准表构建。
缓存数据使用指针和长度来传递。
在实现了校准器后,你可以配置构建器去使用它:
builder->setInt8Mode(true);
builder->setInt8Calibrator(calibrator);
使用writeCalibrationCache()和readCalibrationCache()成员函数来缓存和读取校准缓存文件。 构建器在执行校准之前检查缓存,如果找到缓存文件,则跳过校准。
有关配置INT8 Calibrator对象的更多信息,请参考示例sampleINT8。
示例说明如何使用Python API创建INT8 Calibrator对象。默认情况下,TensorRT支持INT8校准。
1.导入TensorRT,就像导入任何其他包一样:
import tensorrt as trt
2.类似的,测试/验证文件作为校准数据集来使用。确保校准数据代表整体推断数据。要让TensorRT使用校准数据,我们需要创建批处理流对象。 Batchstream对象将用于配置校准器。
NUM_IMAGES_PER_BATCH = 5
batchstream = ImageBatchStream(NUM_IMAGES_PER_BATCH,calibration_files)
3.使用输入节点名称和批处理流创建Int8_calibrator对象:
Int8_calibrator = trt.infer.EntropyCalibrator([“input_node_name”],batchstream)
4.设置INT8模式和INT8校准器:
trt_builder = trt.infer.create_infer_builder(G_LOGGER)
trt_builder.set_int8_mode(True)
trt_builder.set_int8_calibrator(Int8_calibrator)
引擎创建和推理的其余逻辑类似于2.9.4节Importing From ONNX Using Python。
在创建包含优化推理模型的计划文件后,可以将该文件部署到生产环境中。 如何创建和部署计划文件取决于您的环境。 例如,您可以为模型提供专用推理可执行文件,用于加载计划文件,然后使用TensorRT API将输入数据传递给模型,执行模型完成推理,最后从模型中读取输出。
本节讨论如何在一些常见的部署环境中部署TensorRT。
用于推理的一种常见云部署策略是通过为模型实现HTTP REST或gRPC端点的服务器去公开模型。 然后,远程客户端可以通过向该端点发送格式正确的请求来执行推理。 该请求将选择模型,提供模型所需的输入张量值,并指出应该计算哪些模型作为输出。
要在此部署策略中利用TensorRT优化模型,不需要进行任何根本性更改。 必须更新推理服务器以接受由TensorRT计划文件表示的模型,并且必须使用TensorRT API来加载和执行这些计划文件。 可以在NVIDIA Inference Server Container和Inference Server User Guide中找到为推理提供REST终结点的推理服务器示例。
TensorRT也可用于将经过训练好的网络部署到嵌入式系统,如NVIDIA Drive PX。在上下文中,部署意味着获取网络并在嵌入式设备上运行和使用软件应用程序,例如对象检测或地图服务。将训练好的网络部署到嵌入式系统涉及以下步骤:
1.将训练好的网络导出为UFF或ONNX等可导入TensorRT的格式(有关详细信息,请参考第三章Working With Deep Learning Frameworks)。
2.编写一个程序,使用TensorRT C ++ API将训练好的网络导入,优化和序列化为计划文件(请参考3.Working With Deep Learning Frameworks、2.15.Working With Mixed Precision和2.6.Performing Inference In C++)。出于讨论的目的,我们将此程序称为make_plan(生成计划文件)
。
a)(可选)执行INT8校准并导出校准缓存(请参考2.15.Working With Mixed Precision)。
3.在部署到目标系统之前,在主机系统上构建并运行make_plan验证训练好的模型。
4.将训练好的网络
和INT8校准缓存
复制到目标系统。在目标系统上重新
构建并重新运行make_plan程序以生成计划文件。
注:make_plan程序必须在目标系统上运行才能为该系统正确优化TensorRT引擎。 但是,如果在主机上生成INT8校准高速缓存,则在生成引擎时,构建器可以在目标上重新使用高速缓存(换句话说,不需要在目标系统本身上进行INT8校准)。
计划文件被生成后,嵌入式应用可以使用TensorRT C++ API利用计划文件创建引擎执行推理。更多信息,参考2.6Performing Inference In C++。
典型用例,参考:
‣Deploying INT8 Inference For Autonomous Vehicles for DRIVE PX
‣GitHub for Jetson and Jetpack