Chapter 5. WORKING WITH MIXED PRECISION
混合精度是在计算方法中组合使用不同的数值精度。 TensorRT可以存储权重和激活值,并以32位浮点、16位浮点或量化的8位整数执行层。
使用低于FP32的精度可以减少内存使用,允许部署更大的网络。数据传输花费的时间更少,计算性能也会提高,尤其是在Tensor Core支持该精度的GPU上。
默认情况下,TensorRT使用FP32推理,但它也支持FP16和INT8。在运行FP16推理时,它会自动将FP32权重转换为FP16权重。
您可以使用以下API检查平台上支持的精度:
if (builder->platformHasFastFp16()) { … };
if (builder->platformHasFastInt8()) { … };
指定网络的精度定义了应用程序的最低可接受精度。如果对于某些特定的内核参数集更快,或者如果不存在低精度内核,则可以选择更高精度的内核。您可以设置构建器标志setStrictTypeConstraints以强制网络或层精度,这可能没有最佳性能。仅建议在调试时使用此标志。
如果平台支持,您也可以选择设置INT8和FP16模式。TensorRT将选择性能最佳的内核来执行推理。
5.1 使用C ++ API进行混合精度
5.1.1 使用C ++设置层精度
如果要以特定精度运行某些图层,可以使用以下API设置每层的精度:
layer->setPrecision(nvinfer1::DataType::kINT8)
这为图层的输入和输出提供了首选类型。您可以使用以下方法为图层输出选择不同的首选类型:
layer->setOutputType(out_tensor_index, nvinfer1::DataType::kINT8)
TensorRT几乎没有以异构精度运行的实现:在TensorRT 5.0中,唯一的是用于产生FP32输出的卷积、反卷积和全连接层的INT8实现。
设置精度,需要TensorRT使用输入和输出与首选类型相匹配的层,必要时插入重新格式化操作。默认情况下,TensorRT只有在产生更高性能的网络时才会选择此类实现。如果更高精度的实现更快,TensorRT将使用它,并发出警告。因此,您可以检测使用较低的精度是否会导致意外的性能损失。
您可以通过严格限制类型约束来覆盖此行为。
builder->setStrictTypeConstraints(true);
如果约束是严格的,TensorRT将遵守它们,除非没有优先精度约束的实现,在这种情况下它将发出警告并使用最快的可用实现。
如果未明确设置精度,TensorRT将根据性能注意事项和为构建器指定的标志选择计算精度。
有关使用这些API运行混合精度推断的示例,请参见sampleINT8API。
5.1.2 使用C ++启用FP16推理
设置builder的Fp16Mode标志表示可以接受16位精度。
builder->setFp16Mode(true);
此标志允许(但不保证)在构建引擎时将使用16位内核。您可以通过设置以下builder标志来选择强制16位精度:
builder->setStrictTypeConstraints(true);
可以在FP16或FP32中指定权重,它们将自动转换为适当的计算精度。
有关运行FP16推断的示例,请参阅sampleGoogleNet和sampleMNIST。
5.1.3 使用C ++启用INT8推理
设置builder标志可启用INT8精度推断。
builder->setInt8Mode(true);
为了执行INT8推断,需要量化FP32激活张量和权重。为了表示32位浮点值和INT 8位量化值,TensorRT需要了解每个激活张量的动态范围。动态范围用于确定适当的量化比例。
TensorRT支持使用绝对最大动态范围值计算的量化比例的对称量化。
TensorRT需要网络中每个张量的动态范围。有两种方法可以为网络提供动态范围:
- 使用setDynamicRange API手动设置每个网络张量的动态范围;
- 使用INT8校准:使用校准数据集生成每个张量的动态范围。
动态范围API也可以与INT8校准一起使用,这样手动设置范围将优先于校准生成的动态范围。如果INT8校准不能为某些张量产生令人满意的动态范围,则可能出现这种情况。
有关更多信息,请参阅sampleINT8API。
5.1.3.1 使用C ++设置每个张量的动态范围
您可以使用各种技术生成每个张量的动态范围。基本技术包括在最后一个训练时期记录每张量的最小值和最大值,或者使用量化感知训练。TensorRT希望您设置每个网络张量的动态范围以执行INT8推理。获得动态范围信息后,可以按如下方式设置动态范围:
ITensor* tensor = network->getLayer(layer_index)->getOutput(output_index);
tensor->setDynamicRange(min_float, max_float);
您还需要设置网络输入的动态范围:
ITensor* input_tensor = network->getInput(input_index);
input_tensor->setDynamicRange(min_float, max_float);
实现此目的的一种方法是迭代网络层和张量,设置每个张量的动态范围。有关更多信息,请参阅sampleINT8API。
5.1.3.2 使用C ++进行INT8校准
INT8校准提供了生成每个激活张量动态范围的替代方案。可以将该方法归类为后训练技术以生成适当的量化比例。确定这些比例因子的过程称为校准,并要求应用程序为网络传递批量的代表性输入(通常来自训练集的批次)。实验表明,大约500张图像足以校准ImageNet分类网络。
要向TensorRT提供校准数据,请实施IInt8Calibrator接口。
builder调用校准器的过程如下:
- 首先,它调用getBatchSize()来确定期望的输入批处理的大小;
- 然后,它重复调用getBatch()以获取批量输入。批次应该是getBatchSize()的批量大小。当没有批次时,getBatch()应该返回false。
校准可能很慢,因此,IInt8Calibrator接口提供了缓存中间数据的方法。有效地使用这些方法需要更详细地了解校准。
构建INT8引擎时,builder执行以下步骤:
- 构建一个32位引擎,在校准集上运行它,并记录每个张量激活值分布的的直方图;
- 根据直方图构建校准表;
- 从校准表和网络定义构建INT8引擎。
校准表可以缓存。在多次构建同一网络时(例如,在多个平台上),缓存非常有用。它捕获从网络和校准集中获得的数据。参数记录在表中。如果网络或校准集发生更改,则应用程序负责使缓存无效。
缓存使用如下:
- 如果找到校准表,则跳过校准,否则由直方图和参数构建校准表;
- 然后INT8网络由网络定义和校准表构建。
缓存数据作为指针和长度传递。
实施校准器后,您可以配置builder以使用它:
builder->setInt8Calibrator(calibrator);
可以使用writeCalibrationCache()和readCalibrationCache()方法缓存校准输出。builder在执行校准之前检查缓存,如果找到数据,则跳过校准。
有关配置INT8 Calibrator对象的更多信息,请参阅sampleINT8。
5.2 使用混合精度
混合精度是在计算方法中组合使用不同的数值精度。TensorRT可以存储权重和激活,并以32位浮点、16位浮点或量化的8位整数执行层运算。
使用低于FP32的精度可以减少内存使用,允许部署更大的网络。数据传输花费的时间更少,计算性能也会提高,尤其是在Tensor Core支持该精度的GPU上。
默认情况下,TensorRT使用FP32进行推理,但它也支持FP16和INT8。在运行FP16推理时,它会自动将FP32权重转换为FP16权重。
您可以使用以下API检查平台上支持的精度:
if (builder->platformHasFastFp16()) { … };
if (builder->platformHasFastInt8()) { … };
指定网络的精度定义了应用程序的最低可接受精度。如果对于某些特定的内核参数集更快,或者如果不存在低精度内核,则可以选择更高精度的内核。您可以设置builder标志setStrictTypeConstraints以强制网络或层的精度,这可能没有达到最佳性能。仅建议在调试时使用此标志。
如果平台支持,您也可以选择设置INT8和FP16模式。TensorRT将选择性能最佳的内核来执行推理。
5.2.1 使用Python设置层精度
在Python中,您可以使用precision标志指定层精度:
layer.precision = trt.int8
您可以设置输出张量数据类型以符合层实现:
layer.set_output_type(out_tensor_index, trt.int8)
确保builder理解强制精度:
builder.strict_type_constraints = true
5.2.2 使用Python启用FP16推理
在Python中,设置fp16_mode标志如下:
builder.fp16_mode = True
通过设置builder标志强制使用16位精度:
builder.strict_type_constraints = True
5.2.3 使用Python启用INT8推理
通过设置builder标志启用INT8模式:
trt_builder.int8_mode = True
与C ++ API类似,您可以使用set_dynamic_range或使用INT8校准选择每个激活张量的动态范围。
INT8校准可与动态范围API一起使用。手动设置动态范围将覆盖INT8校准生成的动态范围。
5.2.3.1 使用Python设置每个Tensor的动态范围
要执行INT8推理,必须为每个网络张量设置动态范围。您可以使用各种方法导出动态范围值,包括量化感知训练或简单记录每个张量在最后一个训练时期内的最小值和最大值。要设置动态范围,请使用:
layer = network[layer_index]
tensor = layer.get_output(output_index)
tensor.set_dynamic_range(min_float, max_float)
您还需要设置网络输入的动态范围:
input_tensor = network.get_input(input_index)
input_tensor.set_dynamic_range(min_float, max_float)
5.2.3.2 使用Python进行INT8校准
INT8校准提供了另一种生成每个激活张量动态范围的方法。该方法可以归类为后训练技术以生成适当的量化比例。
以下步骤说明了如何使用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 = EntropyCalibrator(["input_node_name"], batchstream)
4.设置INT8模式和INT8校准器:
trt_builder.int8_calibrator = Int8_calibrator
引擎创建和推理的其余逻辑类似于使用Python导入ONNX模型。