TensorRT教程

TensorRT教程

参考链接:https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#unique_1139626899

3.1. Importing TensorRT Into Python

Procedure

  1. Import TensorRT:

    import tensorrt as trt
    
  2. 实现一个日志接口,TensorRT通过该接口报告错误、警告和信息。下面的代码展示了如何实现日志接口。在本例中,我们抑制了informational messages,只报告警告和错误。TensorRT的Python绑定中包含了一个简单的logger。

3.2. Creating A Network Definition In Python

使用TensorRT进行推理的第一步是从您的模型创建一个TensorRT网络。

最简单的方法是使用TensorRT解析器库导入模型,(参见使用Python中的解析器导入模型、使用Python从Caffe导入、使用Python从TensorFlow导入和使用Python从ONNX导入),它支持以下格式的序列化模型。

  • Caffe (both BVLC and NVCaffe)
  • Supports ONNX releases up to ONNX 1.6, and ONNX opsets 7 to 11, and
  • UFF (used for TensorFlow)

另一种方法是直接使用 TensorRT 网络 API 来定义模型,(参见使用 Python API 从头开始创建网络定义)。这需要你进行少量的API调用来定义网络图中的每一层,并为模型的训练参数实现自己的导入机制。

注意:TensorRT Python API并非适用于所有平台。更多信息,请参见 TensorRT Support Matrix。

3.2.1. Creating A Network Definition From Scratch Using The Python API

创建网络时,必须首先定义engine ,并创建一个用于推理的builder对象。Python API用于从网络API中创建网络和engine 。网络定义引用用于向网络添加各种层。

下面的代码说明了如何创建一个具有Input、Convolution、Pooling、FullyConnected、Activation和SoftMax层的简单网络。

# Create the builder and network
with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network:
	# Configure the network layers based on the weights provided. In this case, the weights are imported from a pytorch model. 
	# Add an input layer. The name is a string, dtype is a TensorRT dtype, and the shape can be provided as either a list or tuple.
	input_tensor = network.add_input(name=INPUT_NAME, dtype=trt.float32, shape=INPUT_SHAPE)

	# Add a convolution layer
	conv1_w = weights['conv1.weight'].numpy()
	conv1_b = weights['conv1.bias'].numpy()
	conv1 = network.add_convolution(input=input_tensor, num_output_maps=20, kernel_shape=(5, 5), kernel=conv1_w, bias=conv1_b)
	conv1.stride = (1, 1)

	pool1 = network.add_pooling(input=conv1.get_output(0), type=trt.PoolingType.MAX, window_size=(2, 2))
	pool1.stride = (2, 2)
	conv2_w = weights['conv2.weight'].numpy()
	conv2_b = weights['conv2.bias'].numpy()
	conv2 = network.add_convolution(pool1.get_output(0), 50, (5, 5), conv2_w, conv2_b)
	conv2.stride = (1, 1)

	pool2 = network.add_pooling(conv2.get_output(0), trt.PoolingType.MAX, (2, 2))
	pool2.stride = (2, 2)

	fc1_w = weights['fc1.weight'].numpy()
	fc1_b = weights['fc1.bias'].numpy()
	fc1 = network.add_fully_connected(input=pool2.get_output(0), num_outputs=500, kernel=fc1_w, bias=fc1_b)

	relu1 = network.add_activation(fc1.get_output(0), trt.ActivationType.RELU)

	fc2_w = weights['fc2.weight'].numpy()
	fc2_b = weights['fc2.bias'].numpy()
	fc2 = network.add_fully_connected(relu1.get_output(0), OUTPUT_SIZE, fc2_w, fc2_b)

	fc2.get_output(0).name =OUTPUT_NAME
	network.mark_output(fc2.get_output(0))

3.2.2. Importing A Model Using A Parser In Python

要使用parse导入模型,您需要执行以下high-level步骤。

  1. Create the TensorRTbuilder and network.
  2. 为特定格式创建TensorRT parse。
  3. 使用parse解析导入的模型并填充网络。

builder 必须在网络之前创建,因为它是网络的工厂。不同的builder有不同的机制来标记网络输出。更多信息,请参见 UFF Parser API, Caffe Parser API, and ONNX Parser API。

3.2.3. Importing From Caffe Using Python

下面的步骤说明了如何使用CaffeParser和Python API直接导入Caffe模型。

Procedure

  1. Import TensorRT.

    import tensorrt as trt
    
  2. Define the data type. In this example, we will use float32.

    datatype = trt.float32
    
  3. Additionally, define some paths. Change the following paths to reflect where you placed the model included with the samples:

    deploy_file = 'data/mnist/mnist.prototxt'
    model_file = 'data/mnist/mnist.caffemodel'
    
  4. Create the builder, network, and parser:

    with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.CaffeParser() as parser:
    model_tensors = parser.parse(deploy=deploy_file, model=model_file, network=network, dtype=datatype)
    

parse返回model_tensors,这是一个包含从张量名称到ITensor对象的映射表。

3.2.4. Importing From TensorFlow Using Python

Procedure

  1. Import TensorRT:

    import tensorrt as trt
    
  2. Create a frozen TensorFlow model for the tensorflow model. The instructions on freezing a TensorFlow model into a stream can be found in Freezing A TensorFlow Graph.

  3. Use the UFF converter to convert a frozen tensorflow model to a UFF file. Typically, this is as simple as:

    convert-to-uff frozen_inference_graph.pb
    

    根据你安装 TensorRT 的方式,convert-to-uff 工具可能没有安装在你的系统路径中。在这种情况下,直接调用底层的Python脚本。它应该位于UFF模块的bin目录下; 例如, ~/.local/lib/python3.6/site-packages/uff/bin/convert_to_uff.py.

    要找到UFF模块的位置,运行python -c "import uff; print(uff.path) "命令。

    另外,你也可以使用 UFF Parser API,直接转换TensorFlow GraphDef。

  4. Define some paths. Change the following paths to reflect where you placed the model that is included with the samples:

    model_file = '/data/mnist/mnist.uff'
    
  5. Create the builder, network, and parser:

    with builder = trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.UffParser() as parser:
        	parser.register_input("Placeholder", (1, 28, 28))
        	parser.register_output("fc2/Relu")
    parser.parse(model_file, network)
    

3.2.5. Importing From ONNX Using Python

以下步骤说明了如何使用 Onnx Parser 和 Python API 直接导入 ONNX 模型。

一般来说,较新版本的OnnxParser是为了向后兼容而设计的,因此,遇到较早版本的ONNX导出器制作的模型文件应该不会有问题。可能会有一些例外情况,当变化不向后兼容时。在这种情况下,请将早期的ONNX模型文件转换为后期支持的版本。有关此问题的更多信息,请参见 ONNX Model Opset Version Converter。

用户模型也有可能是由支持比TensorRT附带的ONNX解析器更晚的opset的导出工具生成的。在这种情况下,请检查发布到 GitHub 的最新版本 TensorRT onnx-tensorrt 是否支持所需版本。

支持的版本由 onnx_trt_backend.cpp 中的 BACKEND_OPSET_VERSION 变量定义。从GitHub下载并构建最新版本的ONNXTensorRT Parse。

在TensorRT 7.0中,ONNX解析器只支持full-dimensions mode,这意味着必须使用 explicitBatch flag set创建网络定义。有关详细信息,请参见 Working With Dynamic Shapes。

Procedure

  1. Import TensorRT:

    import tensorrt as trt
    
  2. Create the builder, network, and parser:

    EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    with trt.Builder(TRT_LOGGER) as builder, builder.create_network(EXPLICIT_BATCH) as     network, trt.OnnxParser(network, TRT_LOGGER) as parser:
    with open(model_path, 'rb') as model:
    if not parser.parse(model.read()):
        for error in range(parser.num_errors):
            print(parser.get_error(error))
    

3.2.6. Importing From PyTorch And Other Frameworks

使用 TensorRT 与 PyTorch (或任何其他具有 NumPy 兼容权重的框架) 涉及使用 TensorRT API复制网络架构, (请参阅 Creating A Network Definition From Scratch Using The Python API),然后从 PyTorch 复制权重。如需了解更多信息,请参阅 Working With PyTorch And Other Frameworks。

要执行推理,请按照Performing Inference In Python中概述的说明进行。

3.3. Building An Engine In Python

构建器的功能之一是在其CUDA内核目录中搜索最快的实现,因此有必要使用相同的GPU进行构建,就像优化引擎将在其上运行一样。

IBuilderConfig有很多属性,你可以设置这些属性来控制网络运行的精度,以及自动调优参数,比如TensorRT在确定哪个内核最快时,应该给每个内核计时多少次(迭代次数越多,运行时间越长,但对噪声的敏感度越低),你还可以查询builder,找出硬件本机支持的混合精度类型。

一个特别重要的属性是最大工作空间大小(maximum workspace size)。

  • Layer algorithms通常需要临时工作空间。这个参数限制了网络中任何层可以使用的最大尺寸。如果提供的scratch 不足,TensorRT有可能无法找到给定层的实现。

For more information about building an engine in Python, see the Introduction To Importing Caffe, TensorFlow And ONNX Models Into TensorRT Using Python

Procedure

  1. Build the engine using the builder object:

    with trt.Builder(TRT_LOGGER) as builder, builder.create_builder_config() as config:
        config.max_workspace_size = 1 << 20 # This determines the amount of memory 		# available to the builder when building an optimized engine and should 		# generally be set as high as possible.
        with builder.build_engine(network, config) as engine:
    
    # Do inference here.
    

    当engine建立后,TensorRT会对权重进行复制。

  2. 要执行推理,请遵循在Performing Inference In Python中概述的说明。

3.4. Serializing A Model In Python

从这里开始,您可以序列化engine,也可以直接使用engine进行推理。在使用模型进行推理之前,序列化和反序列化(Serializing and deserializing)模型是可选的步骤—如果需要,可以直接使用engine对象进行推理。

序列化时,您正在将engine转换为一种格式,以便在以后进行推理时存储和使用。要用于推断,只需反序列化engine(deserialize the engine)。序列化和反序列化是可选的。由于从网络定义创建engine可能非常耗时,因此可以避免每次应用程序重新运行时都重新生成engine,方法是将其序列化一次,并在推断时反序列化。因此,在构建engine之后,用户通常希望将其序列化,以便以后使用。

注意:序列化engine不能跨平台或TensorRT版本移植。除了平台和TensorRT版本之外,engine是特定于它们所建立的GPU模型的。

  1. 将模型序列化为一个modelstream:

    serialized_engine = engine.serialize()
    
  2. 反序列化modelstream来执行推理。反序列化需要创建一个运行时对象:

    with trt.Runtime(TRT_LOGGER) as runtime:    
    	engine = runtime.deserialize_cuda_engine(serialized_engine)
    

也可以将序列化的engine保存到文件中,并从文件中读回。

  1. Serialize the engine and write to a file:

    with open(“sample.engine”, “wb”) as f:
    		f.write(engine.serialize())
    
  2. Read the engine from the file and deserialize:

    with open(“sample.engine”, “rb”) as f, trt.Runtime(TRT_LOGGER) as runtime:
    		engine = runtime.deserialize_cuda_engine(f.read())
    

3.5. Performing Inference In Python

下面的步骤说明了如何在Python中执行推理,现在你已经有了一个engine。

  1. 为输入和输出分配一些主机和设备buffer。这个例子假设context.all_binding_dimensions == True,并且engine在binding_index=0处有一个输入,在binding_index=1处有一个输出。

    # Determine dimensions and create page-locked memory buffers (i.e. won't be swapped #to disk) to hold host inputs/outputs.
    		h_input = cuda.pagelocked_empty(trt.volume(context.get_binding_shape(0)), 				dtype=np.float32)
    		h_output = cuda.pagelocked_empty(trt.volume(context.get_binding_shape(1)), 				 dtype=np.float32)
    		# Allocate device memory for inputs and outputs.
    		d_input = cuda.mem_alloc(h_input.nbytes)
    		d_output = cuda.mem_alloc(h_output.nbytes)
    		# Create a stream in which to copy inputs/outputs and run inference.
    		stream = cuda.Stream()
    
  2. 创建一些空间来存储中间激活值。由于engine 持有网络定义和训练参数,因此需要额外的空间。这些都是在execution context中保存的。

    with engine.create_execution_context() as context:
    		# Transfer input data to the GPU.
    		cuda.memcpy_htod_async(d_input, h_input, stream)
    		# Run inference.
    		context.execute_async_v2(bindings=[int(d_input), int(d_output)], stream_handle=stream.handle)
    		# Transfer predictions back from the GPU.
    		cuda.memcpy_dtoh_async(h_output, d_output, stream)
    		# Synchronize the stream
    		stream.synchronize()
    		# Return the host output. 
    return h_output
    

    一个engine可以有多个execution contexts,允许将一组权重用于多个重叠的推理任务。例如,您可以使用一个engine和one context per stream在并行的CUDA流中处理图像。每个context 将在与engine相同的GPU上创建。

4. Extending TensorRT With Custom Layers

NVIDIA® TensorRT™支持多种类型的层,其功能也在不断扩展;但是,在某些情况下,所支持的层可能无法满足模型的特定需求。

在这种情况下,用户可以通过使用C++和Python API的IPluginV2Ext类实现自定义层来扩展TensorRT功能。自定义层,通常被称为plugins,由应用程序实现和实例化,它们的生存期必须在TensorRT engine中使用( and their lifetime must span their use within a TensorRT engine)。

TensorRT层(不包括TopK)预计将以zero workspace size工作,然而,如果没有使用zero workspaces的实现,则可能忽略所要求的精度。在后一种情况下,即使精度设置为其他,该层也会在FP32上运行。

4.2. Adding Custom Layers Using The Python API

虽然C++ API是实现自定义层的首选语言;但由于可以轻松访问CUDA和cuDNN等库,您可以在Python应用程序中使用自定义层。

您可以使用 C++ API 创建一个自定义层,使用pythd11在python中打包该层,然后将插件加载到 Python 应用程序中。更多信息,请参见Creating A Network Definition In Python。

4.2.1. Example: Adding A Custom Layer to a TensorRT Network Using Python

可以使用插件节点(plugin nodes)将自定义层添加到Python中的任何TensorRT网络中。

Python API 有一个叫做 add_plugin_v2的函数,它可以让你向网络中添加一个插件节点。下面的例子说明了这一点。它创建了一个简单的TensorRT网络,并通过查找TensorRT Plugin Registry添加了一个Leaky ReLU插件节点。

import numpy as np

TRT_LOGGER = trt.Logger()

trt.init_libnvinfer_plugins(TRT_LOGGER, '')
PLUGIN_CREATORS = trt.get_plugin_registry().plugin_creator_list

def get_trt_plugin(plugin_name):
        plugin = None
        for plugin_creator in PLUGIN_CREATORS:
            if plugin_creator.name == plugin_name:
                lrelu_slope_field = trt.PluginField("neg_slope", np.array([0.1], \							dtype=np.float32), trt.PluginFieldType.FLOAT32)
                field_collection = trt.PluginFieldCollection([lrelu_slope_field])
                plugin = plugin_creator.create_plugin(name=plugin_name, 									field_collection=field_collection)
        return plugin

def main():
    with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network:
        builder.max_workspace_size = 2**20
        input_layer = network.add_input(name="input_layer", dtype=trt.float32, shape=						(1, 1))
        lrelu = network.add_plugin_v2(inputs=[input_layer], 						   						   plugin=get_trt_plugin("LReLU_TRT"))
        lrelu.get_output(0).name = "outputs"
        network.mark_output(lrelu.get_output(0))

4.2.2. Example: Adding A Custom Layer That Is Not Supported In UFF Using Python

TensorFlow网络可以转换为UFF格式,并使用Python接口与TensorRT一起运行。

为了做到这一点,我们利用了GraphSurgeon API。如果你正在编写自己的插件,你需要用C++实现它,实现IPluginExt和IPluginCreator类,如: Example: Adding A Custom Layer Using C++.

下面的步骤说明了如何使用在TensorRT Plugin Registry注册的插件节点,使用UFF parser运行自定义图层。

Procedure

  1. 通过调用trt.init_libnvinfer_plugins(TRT_LOGGER, ‘’)来注册TensorRT插件(或者加载你已经注册了自己的插件的.so文件)。

  2. 准备网络并检查TensorFlow的输出:

    tf_sess = tf.InteractiveSession()
    tf_input = tf.placeholder(tf.float32, name="placeholder")
    tf_lrelu = tf.nn.leaky_relu(tf_input, alpha=lrelu_alpha, name="tf_lrelu")
    tf_result = tf_sess.run(tf_lrelu, feed_dict={tf_input: lrelu_args})
    tf_sess.close()
    
  3. 准备namespace mappings。op name LReLU\TRT对应于TensorRT附带的Leaky ReLU plugin插件。

    trt_lrelu = gs.create_plugin_node(name="trt_lrelu", op="LReLU_TRT", negSlope=lrelu_alpha)
    namespace_plugin_map = {
                "tf_lrelu": trt_lrelu
     }
    
  4. 使用GraphSurgeon转换TensorFlow图并保存到UFF:

    dynamic_graph = gs.DynamicGraph(tf_lrelu.graph)
    dynamic_graph.collapse_namespaces(namespace_plugin_map)
    
  5. 运行UFF parse并将结果与TensorFlow进行比较:

    uff_model = uff.from_tensorflow(dynamic_graph.as_graph_def(), ["trt_lrelu"], output_filename=model_path, text=True)
    parser = trt.UffParser()
    parser.register_input("placeholder", [lrelu_args.size])
    parser.register_output("trt_lrelu")
    parser.parse(model_path, trt_network)
    

    For more information, see the Adding A Custom Layer To Your TensorFlow Network In TensorRT In Python (uff_custom_plugin) sample.

4.3. Using Custom Layers When Importing A Model From A Framework

TensorRT parse使用layer operation字段来标识网络中的特定层是否是支持TensorFlow的操作。

TensorFlow
与之前发布的TensorRT相比,TensorFlow中的自定义图层如何使用TensorRT UFF解析器运行有一些变化。对于TensorFlow模型,使用UFF转换器将你的图转换为UFF文件。在这个过程中,如果网络中包含插件层,还需要将这些层的操作字段映射到TensorRT中对应注册的插件名称。这些插件可以是TensorRT附带的插件,也可以是你编写的自定义插件。网络中的插件字段名也应该与插件所期望的字段相匹配。这可以使用GraphSurgeon来完成,正如在Preprocessing A TensorFlow Graph Using the Graph Surgeon API中所解释的那样,以及在位于GitHub仓库中的Object Detection With A TensorFlow SSD Network (sampleUffSSD)中所演示的那样,通过使用带有UFF转换器的配置文件来完成。

与TensorRT的早期版本相比,TensorFlow中的自定义层如何使用TensorRT UFF parser运行有一些变化。对于TensorFlow模型,使用UFF转换器将你的图转换为UFF文件。在此过程中,如果网络包含插件层,则还需要将这些层的operation字段映射到TensorRT中对应注册的插件名称。这些插件可以是TensorRT附带的插件,也可以是您编写的自定义插件。网络中的插件字段名称也应该与插件所期望的字段匹配。这可以使用GraphSurgeon来完成,如Preprocessing A TensorFlow Graph Using the Graph Surgeon API中所述,以及在位于GitHub仓库中的 Object Detection With A TensorFlow SSD Network (sampleUffSSD)中所演示的那样,通过使用带有UFF转换器的配置文件来完成。

UFF parser将为每个不支持的operation查找插件注册表。如果发现与任何已注册的插件名称匹配,parser将解析输入网络中的插件字段参数,并使用它们创建一个插件对象。然后将此对象添加到网络中。在以前版本的TensorRT中,您必须实现nvuffparser::IPluginFactoryExt并手动将插件参数传递给createPlugin(…)函数。尽管这个流仍然可以使用,但是对于插件API的新添加,它不再是必需的。有关详细信息,请参阅:

  • IPluginV2Ext and IPluginCreator in the C++ API
  • IPluginV2Ext and IPluginCreator in the Python API

ONNX

对于ONNX模型,ONNX parse将自动尝试将无法识别的ops 作为插件导入。如果在注册表中找到与该节点具有相同 op_type的插件,解析器将从ONNX模型中解析插件字段参数,并使用相应的creator 创建一个plugin实例。默认情况下,它将尝试加载plugin version 1。可以通过在相应的ONNX节点中设置plugin_version字符串参数来重写这种行为。

在某些情况下,您可能希望在将ONNX graph导入TensorRT之前对其进行修改;例如,添加上面提到的plugin_version属性,或者用plugin节点替换一组ops。要实现这一点,您可以使用 ONNX GraphSurgeon 实用程序。

For the Python usage of custom layers with TensorRT, refer to:

  • Adding A Custom Layer To Your TensorFlow Network In TensorRT In Python (uff_custom_plugin) and Object Detection With SSD In Python (uff_ssd) samples for UFF networks
  • TensorRT Inference Of ONNX Models With Custom Layers (onnx_packnet) sample for ONNX

4.3.1. Example: Adding A Custom Layer To A TensorFlow Model

为了使用TensorRT运行TensorFlow网络,必须首先将其转换为UFF格式。在转换过程中,可以使用graphsurgeon工具将自定义层标记为plugin 节点。

然后,UFF转换器将处理后的图转换为UFF格式,然后由UFF parser运行。然后由UFF parser将plugin 节点添加到TensorRT网络中。

For details using the Python API, see Example 2: Adding A Custom Layer That Is Not Supported In UFF Using Python. Additionally, the Object Detection With SSD In Python (uff_ssd) sample demonstrates an end-to-end workflow in Python for running TensorFlow object detection networks using TensorRT.

4.4. Plugin API Description

虽然IPluginV2和IPluginV2Ext接口仍然支持向后兼容TensorRT 5.1和6.0.x,但我们建议您编写新plugin 或重构现有plugin ,以IPluginV2DynamicExt或IPluginV2IOExt接口为目标,如第4.1节所述。
为了使用最新的Plugin层特性,您的自定义Plugin应该实现IPluginV2DynamicExt或IPluginV2IOExt接口。

The new features in

IPluginV2DynamicExt

are as follows:

virtual DimsExprs getOutputDimensions(int outputIndex, const DimsExprs* inputs, int nbInputs, IExprBuilder& exprBuilder) = 0;

virtual bool supportsFormatCombination(int pos, const PluginTensorDesc* inOut, int nbInputs, int nbOutputs) = 0;

virtual void configurePlugin(const DynamicPluginTensorDesc* in, int nbInputs, const DynamicPluginTensorDesc* out, int nbOutputs) = 0;

virtual size_t getWorkspaceSize(const PluginTensorDesc* inputs, int nbInputs, const PluginTensorDesc* outputs, int nbOutputs) const = 0;

virtual int enqueue(const PluginTensorDesc* inputDesc, const PluginTensorDesc* outputDesc, const void* const* inputs, void* const* outputs, void* workspace, cudaStream_t stream) = 0;

The new features in IPluginV2IOExt are as follows:

virtual void configurePlugin(const PluginTensorDesc* in, int nbInput, const PluginTensorDesc* out, int nbOutput) = 0;

virtual bool supportsFormatCombination(int pos, const PluginTensorDesc* inOut, int nbInputs, int nbOutputs) const = 0;

迁移到IPluginV2DynamicExt或IPluginV2IOExt的指南。

  • getOutputDimensions实现给定输入的输出张量维度的表达式。
  • supportsFormatCombination检查plugin是否支持指定输入/输出的格式和数据类型。
  • configurePlugin模仿IPluginV2Ext中等效configurePlugin的行为,但接受张量描述符(tensor descriptors)。
  • getWorkspaceSize和enqueue模仿IPluginV2Ext中等价API的行为,但接受张量描述符(tensor descriptors)。

See the API description in IPluginV2 API Description for more details about the API.

4.5. Best Practices For Custom Layers Plugin

Converting User-Defined Layers

要创建一个自定义层实现作为TensorRT plugin,你需要为你的plugin实现IPluginV2Ext类和IPluginCreator类。

For more information about both API classes, see Plugin API Description.

Using The UFF Plugin API

For an example of how to use plugins with UFF in both C++ and Python, see Example: Adding A Custom Layer Using C++ and Example: Adding A Custom Layer That Is Not Supported In UFF Using Python.

Debugging Custom Layer Issues

插件中分配的内存必须被释放,以确保不发生内存泄漏。如果资源是在initialize()函数中获取的,则需要在 terminate()函数中释放。所有其他的内存分配最好在插件类的destructor或destroy()方法中释放。Adding Custom Layers Using The C++ API详细概述了这一点,还提供了一些使用插件时的最佳实践注意事项。

5. Working With Mixed Precision

混合精度是指在一种计算方法中综合使用不同的数值精度。NVIDIA® TensorRT™可以以32位浮点、16位浮点或量化的8位整数来存储权重和激活以及execute层。

使用比FP32更低的精度可以减少内存的使用,从而可以部署更大的网络。数据传输所需时间更短,计算性能也会提高,特别是在该精度的Tensor Core支持的GPU上。

默认情况下,TensorRT使用FP32推理,但它也支持FP16和INT8。在运行FP16推理时,它会自动将FP32权重转换为FP16权重。

您可以使用以下API检查平台上支持的精度。

if (builder->platformHasFastFp16()) { … }; 
if (builder->platformHasFastInt8()) { … };

指定网络的精度可定义应用程序的最小可接受精度。如果对于某些特定的内核参数集,或者如果不存在较低精度的内核,则可以选择精度较高的内核。您可以设置生成器配置标志BuilderFlag::kSTRICT\u TYPES来强制网络或层精度,这可能没有最佳性能。仅建议出于调试目的使用此标志。

如果平台支持,你也可以选择同时设置INT8和FP16模式。同时使用INT8和FP16模式将允许TensorRT从FP32、FP16和INT8内核中选择,从而从推理中得到最优化的引擎。

Mixed Precision Using The Python API

5.2.1. Setting The Layer Precision Using Python

In Python, you can specify the layer precision using the precision flag:

layer.precision = trt.int8

你可以设置输出张量数据类型,以符合层的实现。

layer.set_output_type(out_tensor_index, trt.int8)

Ensure that the builder understands to force the precision:

builder.strict_type_constraints = true

For more information, see the INT8 Calibration In Python (int8_caffe_mnist) sample.

5.2.2. Enabling FP16 Inference Using Python

In Python, set the fp16_mode flag as follows:

builder.fp16_mode = True

Force 16-bit precision by setting the builder flag:

builder.strict_type_constraints = True

5.2.3. Enabling INT8 Inference Using Python

Enable INT8 mode by setting the builder flag:

builder.int8_mode = True

与C++ API类似,您可以使用动态范围或使用ITE8校准来选择每激活张量的动态范围。

INT8校准可以与动态范围API一起使用。手动设置动态范围将覆盖INT8校准生成的动态范围。

5.2.3.1. Setting Per-Tensor Dynamic Range Using Python

为了执行INT8推理,您必须为每个网络张量设置动态范围。您可以使用不同的方法来推导动态范围值,包括量化感知训练或简单地记录每个张量在上一个训练时段的最小值和最大值。要设置动态范围,请使用:

layer = network[layer_index]
tensor = layer.get_output(output_index)
tensor.dynamic_range = (min_float, max_float)

您还需要设置网络输入的动态范围。

input_tensor = network.get_input(input_index)
input_tensor.dynamic_range = (min_float, max_float)

5.2.3.2. INT8 Calibration Using Python

INT8校准提供了一种替代方法来生成每个激活张量的动态范围。这种方法可以被归类为后训练技术,以生成适当的量化尺度。下面的步骤说明了如何使用Python API创建一个INT8 calibrator 对象。默认情况下,TensorRT支持INT8校准。

Procedure
  1. Import TensorRT:

    import tensorrt as trt
    
  2. 与test/validation文件类似,使用一组输入文件作为校准文件数据集。确保校准文件代表整个推断数据文件。为了让TensorRT使用校准文件,我们需要创建一个batchstream对象。batchstream对象将用于configure the calibrator。

    NUM_IMAGES_PER_BATCH = 5
    batchstream = ImageBatchStream(NUM_IMAGES_PER_BATCH, calibration_files)
    
  3. Create an Int8_calibrator object with input nodes names and batch stream:

    Int8_calibrator = EntropyCalibrator(["input_node_name"], batchstream)
    
  4. Set INT8 mode and INT8 calibrator:

    config.set_flag(trt.BuilderFlag.INT8)
    config.int8_calibrator = Int8_calibrator
    

    其余的engine创建和推理逻辑类似于 Importing From ONNX Using Python.

5.2.4. Working With Explicit Precision Using Python

要使用Python API创建一个显式精度网络,请将EXPLICIT_PRECISION标志传递给构建器。

network_creation_flag = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_PRECISION)
self.network = self.builder.create_network(network_creation_flag)

See Setting The Layer Precision Using Python for more information on setting the precision.

6. Working With Reformat-Free Network I/O Tensors

Requirements from Automotive Safety Integrity Level ,要求从NvMedia DLA安全路径中删除对GPU地址空间的访问。为了实现这一目标,引入了免重新格式化的网络I/O tensors,让您可以在将数据传递给NVIDIA® TensorRT™之前,指定NvMedia tensor所支持的I/O格式。

另一方面,由于小于6.0.1的TensorRT假设网络I/O tensors是FP32,因此张量重格式化的潜在开销会导致性能问题。在多个TensorRT子网络嵌入到一个大型网络的情况下,(例如,TensorFlow),精度为INT8或FP16,不可避免的从FP32到FP32的I/O重新格式化可能会浪费大量的内存流量时间。同样的问题也可能发生在用户定义的插件上。现在您可以明确地将网络I/O tensors指定为INT8或FP16格式,以消除这些不必要的重新格式化。

6.1. Building An Engine With Reformat-Free Network I/O Tensors

您可以使用以下API来指定网络I/O tensors的格式。

Python API:

network.get_input(0).allowed_formats = formats
network.get_output(0).allowed_formats = formats

6.3. Calibration For A Network With INT8 I/O Tensors

INT8 I/O tensors支持INT8自动校准。在这种情况下,您需要为校准提供FP32数据,为推理提供INT8 I/O tensors。

使用INT8 I/O网络,TensorRT希望校准数据达到FP32精度,以生成校准缓存。在使用INT8 I/O张量进行推理期间,builder将在内部使用校准缓存数据。

INT8 I/O网络需要FP32校准数据的这种限制将在未来的版本中放宽。现在,您只需将INT8 I/O校准数据转换为FP32精度即可创建FP32校准数据。您还应确保FP32 cast校准数据应在[-128.0f,127.0f]范围内,并且可以转换为INT8数据而不会造成任何精度损失。

为具有INT8 I/O Tensors的网络设置校准器与具有FP32 I/O Tensors的网络完全相同。

7. Working With Dynamic Shapes

动态形状是指在运行前推迟指定部分或全部张量尺寸的能力。动态形状可以通过C++和Python接口来使用。
下面的章节提供了更多的细节,但是,这里是一个关于使用动态形状构建引擎的步骤的概述。

Dynamic shapes是指在运行前推迟指定部分或全部张量尺寸的能力。

以下各节提供了更详细的信息;但是,下面概述了使用Dynamic shapes构建engine的步骤:

  1. 网络定义不能有隐式batch维度。

    • C++

      创建INetworkDefinition的方法是调用

      IBuilder::createNetworkV2(1U <<
              static_cast(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH))
      

      Python

      创建tensorrt.INetworkDefinition的方法是调用

      create_network(1 <<
              int(tensorrt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
      

这些调用要求网络没有隐含的batch维度。

  1. 通过使用-1作为维度的占位符来指定输入张量的每个运行时维度。
  2. 在构建时指定一个或多个optimization profiles,这些optimization profiles指定了具有运行时维度的输入指定允许的维度范围,以及auto-tuner应该优化的维度。更多信息,请参见Optimization Profiles。
  3. To use the engine:
    a. 从engine创建execution context,和没有dynamic shapes一样。
    b. 从步骤3中指定包含输入维度的optimization profiles。
    c. 指定execution context的输入维度。设置输入维度后,可以得到TensorRT为给定输入维度计算的输出维度。
    d. 排队工作(Enqueue work).

要更改运行时维度,请重复步骤4b和4c,在输入维度更改之前不必重复这些步骤。

7.1. Specifying Runtime Dimensions

你可能感兴趣的:(TensorRT教程)