TensorRT笔记(6)使以混合精度工作

5.以混合精度工作

混合精度是计算方法中不同数值精度的组合使用。 NVIDIA®TensorRT™可以32位浮点,16位浮点或量化的8位整数存储权重和激活,并执行图层。
使用低于FP32的精度会减少内存使用量,从而可以部署更大的网络。 数据传输花费的时间更少,并且计算性能得到提高,尤其是在具有Tensor Core支持该精度的GPU上。

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

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

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

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

如果平台支持,您还可以选择设置INT8和FP16模式。 同时使用INT8和FP16模式将使TensorRT可以从FP32,FP16和INT8内核中进行选择,从而从推理中获得最佳的引擎。

5.1 使用C ++ API的混合精度

5.1.1 使用C ++设置层精度

如果要以特定的精度运行某些图层,则可以使用以下API设置每层的精度:

layer->setPrecision(nvinfer1::DataType::kINT8)

这样可以为图层提供输入和输出的首选类型(例如,DataType :: kINT8)。 您可以使用以下方法为图层的输出选择其他首选类型:这将提供图层的输入并输出首选类型(例如,DataType :: kINT8)。 您可以使用以下方法为图层的输出选择其他首选类型:

layer->setOutputType(out_tensor_index, nvinfer1::DataType::kFLOAT)

注意:此方法不能用于设置TopK层的第二个输出张量的数据类型。 TopK层的第二个输出张量的数据类型始终为INT32。有关更多信息,请参见ITopKLayer
TensorRT几乎没有以异构精度运行的实现:在TensorRT 5.x.x中,仅有INT8实现用于卷积层,解卷积层和FullyConnected层,可生成FP32输出。

设置精度后,要求TensorRT使用其输入和输出与首选类型匹配的层实现,并在必要时插入重新格式化操作。默认情况下,TensorRT仅在产生更高性能的网络时才会选择这种实现。如果具有其他任何受构建器支持的精度的实施速度更快,TensorRT将使用它并发出警告。

您可以通过使类型约束严格来覆盖此行为。

IBuilderConfig *config = builder-> createBuilderConfig(); config-> setFlag(BuilderFlag :: kSTRICT_TYPES)

如果约束很严格,除非没有采用首选精度约束的实现,否则TensorRT将服从它们,在这种情况下,它将发出警告并使用最快的实现。

如果未明确设置精度,TensorRT将基于性能考虑因素和为构建器指定的标志来选择计算精度。
注意在GPU上运行INT8精度时,层张量的尺寸应大于或等于3。TensorRT 6.0.1不支持小于3的层张量尺寸。
有关使用这些API运行混合精度推理的示例,请参见GitHub存储库中的在INT8 Precision中执行推理(sampleINT8API)

5.1.2 使用C ++启用TF32推理

TensorRT默认情况下允许使用TF32 Tensor Core。在计算内积时,例如在卷积或矩阵乘法期间,TF32执行以下操作:

  • 将FP32被乘数四舍五入到FP16精度,但保持FP32动态范围。
  • 计算舍入后的被乘数的精确乘积。
  • 以FP32总和累积产品。

TF32 Tensor Core可以使用FP32加速网络,通常不会损失准确性。对于需要较大动态范围的重量或激活的模型,它比FP16更坚固。

无法保证TF32 Tensor Core实际使用,也无法强制使用。 TensorRT可以随时回退到FP32,如果平台不支持TF32,则始终回退。您可以通过查询IBuilder来检查平台支持,如下所示:

bool hasTf32 = builder-> platformHasTf32()

要禁用TF32,请在IBuilderConfig中清除标志BuilderFlag :: kTF32,如下所示:

config-> clearFlag(BuilderFlag :: kTF32);

如果TF32为您工作,我们仍然可以使用C ++启用FP16推理,因为它可能比TF32更快地执行。

尽管设置了BuilderFlag :: kTF32,但在构建引擎时设置环境变量NVIDIA_TF32_OVERRIDE = 0会禁用TF32。设置为0时,此环境变量将覆盖NVIDIA库的任何默认设置或程序配置,因此它们永远不会使用TF32 Tensor Core加速FP32计算。这只是一个调试工具,NVIDIA库之外的任何代码都不得基于此环境变量更改行为。除0以外的任何其他设置都保留供将来使用。
警告:当引擎运行时,将环境变量NVIDIA_TF32_OVERRIDE设置为其他值可能会导致不可预测的精度/性能影响。最好在发动机运转时保持不动。

5.1.3 使用C ++启用FP16推理

设置构建器的Fp16Mode标志表示可接受16位精度。

config-> setFlag(BuilderFlag :: kFP16);

该标志允许但不保证在构建引擎时将使用16位内核。 您可以通过设置以下构建器标志来选择强制16位精度:

config-> setFlag(BuilderFlag :: kSTRICT_TYPES)

可以在FP16或FP32中指定权重,权重将自动转换为适当的精度以进行计算。

请参阅在GitHub存储库中用于运行FP16推理的TensorRT(sampleMNIST)中的在TensorRT(sampleGoogleNet)中构建运行GoogleNet和“ Hello World”。

5.1.4 使用C ++启用INT8推理

为了执行INT8推断,需要对FP32激活张量和权重进行量化。为了表示32位浮点值和INT 8位量化值,TensorRT需要了解每个激活张量的动态范围。动态范围用于确定适当的量化比例。
设置构建器标志可启用INT8精度推断。

config-> setFlag(BuilderFlag :: kINT8);

TensorRT支持使用绝对最大动态范围值计算出的量化比例的对称量化。

TensorRT需要网络中每个张量的动态范围。可以通过两种方式向网络提供动态范围:

  • 使用setDynamicRange API手动设置每个网络张量的动态范围
    或者
  • 使用INT8校准使用校准数据集生成每个张量动态范围。

动态范围API也可以与INT8校准一起使用,以便手动设置范围将优先于校准生成的动态范围。如果INT8校准不能为某些张量生成令人满意的动态范围,则这种情况是可能的。

有关更多信息,请参见位于GitHub存储库中的在INT8 Precision中执行推理(sampleINT8API)。

5.1.4.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);

实现此目的的一种方法是遍历网络层和张量,并按张量设置动态范围。 TensorRT当前仅支持对称范围,因此,仅使用abs(min_float)和abs(max_float)进行量化。有关更多信息,请参见位于GitHub存储库中的在INT8 Precision中执行推理(sampleINT8API)

5.1.4.2 使用C ++进行INT8校准

INT8校准提供了生成每个张量动态范围的替代方法。校准器计算每个张量的比例。可以将此方法归类为后训练技术,以生成适当的量化标度。确定这些比例因子的过程称为校准,要求应用程序为网络传递批次的代表性输入(来自训练集的典型批次)。实验表明,约有500张图像足以校准ImageNet分类网络。
关于此任务
要将校准数据提供给TensorRT,请实现IInt8Calibrator接口。 TensorRT提供了IInt8Calibrator的多个变体:

IEntropyCalibratorV2

  • 这是推荐的校准器,对于DLA是必需的。默认情况下,校准发生在图层融合之前。建议将其用于基于CNN的网络。

IMinMaxCalibrator

  • 该校准器似乎更适合NLP任务。默认情况下,校准发生在图层融合之前。建议将其用于NVIDIA BERT(Google官方实施的优化版本)之类的网络。

IEntropyCalibrator
这是传统的熵校准器,它比传统的校准器简单,并产生更好的结果。默认情况下,校准在图层融合之后进行。有关在融合之前启用校准的信息,请参见kCALIBRATION_BEFORE_FUSION

ILegacyCalibrator

  • 该校准器与TensorRT 2.0 EA兼容。该校准器需要用户参数化,如果其他校准器产生差的结果,则作为备用选项提供。默认情况下,校准在图层融合之后进行。请参阅kCALIBRATION_BEFORE_FUSION以在融合之前启用校准。用户可以自定义此校准器以实现最大百分位数,例如99.99%的最大百分数被证明对NVIDIA
    BERT具有最佳准确性。有关更多信息,请参阅深度学习推理的整数量化:原理和经验评估论文。

构建器按如下方式调用校准器:

  • 首先,它调用**getBatchSize()**来确定期望的输入批处理的大小。
  • 然后,它反复调用getBatch()以获得批量输入。批处理应与getBatchSize()的批处理大小完全相同。如果没有更多批处理,则getBatch()应该返回false

校准可能很慢,因此,IInt8Calibrator接口提供了用于缓存中间数据的方法。有效地使用这些方法需要对校准有更详细的了解。

构建INT8引擎时,构建器将执行以下步骤:

  1. 构建一个32位引擎,在校准集上运行它,并记录激活值分布的每个张量的直方图。
  2. 根据直方图构建校准表。
  3. 根据校准表和网络定义构建INT8引擎。

校准表可以被缓存。当多次(例如在多个平台上)构建同一网络时,缓存非常有用。它捕获从网络和校准集获得的数据。参数记录在表中。如果网络或校准集发生更改,则应用程序有责任使缓存无效。

缓存的用法如下:

  • 如果找到校准表,则跳过校准,否则:
    校准表是根据直方图和参数构建的
  • 然后根据网络定义和校准表构建INT8网络。

缓存的数据作为指针和长度传递。
实施校准器后,可以配置构建器以使用它:

config-> setInt8Calibrator(calibrator.get());

可以使用**writeCalibrationCache()readCalibrationCache()**方法来缓存校准输出。构建器在执行校准之前检查缓存,如果找到数据,则跳过校准。

有关配置INT8校准器对象的更多信息,请参阅位于GitHub存储库中的使用自定义校准在INT8中执行推理(sampleINT8)。

5.1.5 使用C ++以显式精度工作

TensorRT支持显式精度网络,其中可以推断网络中所有层和张量的精度。此功能可将具有明确量化和反量化比例层(QDQ比例层)的量化模型导入TensorRT。网络中QDQ标度层的存在决定了精度转换(即从FP32到INT8,反之亦然)。无需设置其他精度。

要创建一个明确的精度网络,必须使用createNetworkV2创建INetworkDefinition,如下所示:

builder-> createNetworkV2(1U << static_cast <uint32_t>(NetworkDefinitionCreationFlag :: kEXPLICIT_PRECISION)

可以将量化比例图层指定为输出精度类型为INT8的比例图层。类似地,可以将反量化比例图层指定为具有FP32输出精度类型的比例图层。有关设置精度的更多信息,请参见使用C ++设置层精度。

在显式精度网络中,权重和偏差应为FP32精度。预期要量化的图层需要在输入处插入QDQ缩放图层(包括权重(如果存在))。

使用来自QDQ标度层的标度参数对激活张量和权重进行量化/去量化。通常使用了解量化的训练来计算比例参数,或者使用实验(例如校准)来确定比例参数。用户不得在校准精度模式下使用校准器或提供动态范围。取而代之的是,使用QDQ标度层来表达量化和反量化标度。

TensorRT 7.1仅在显式精度模式下支持激活和权重的按张量量化比例。 QDQ标度层不允许移位权重,因为仅支持对称量化。

使用Python API的混合精度

5.2.1 用Python设置图层精度

在Python中,您可以使用precision标志指定图层精度:

layer.precision = trt.int8

您可以设置输出张量数据类型以符合图层实现:

layer.set_output_type(out_tensor_index,trt.int8)

确保构建者理解强制精度:

builder.strict_type_constraints = true

有关更多信息,请参见Python中的INT8校准(int8_caffe_mnist)示例。

5.2.2 使用Python启用FP16推理

在Python中,按如下所示设置fp16_mode标志:

builder.fp16_mode = True

通过设置构建器标志强制16位精度:

builder.strict_type_constraints = True

5.2.3 使用Python启用INT8推理

通过设置构建器标志来启用INT8模式:

builder.int8_mode = True

与C ++ API相似,您可以使用dynamic_range或INT8校准为每个激活张量选择动态范围。

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

5.2.3.1 使用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 使用Python进行INT8校准

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

  1. 导入TensorRT:
import tensorrt as trt
  1. 与测试/验证文件相似,使用一组输入文件作为校准文件数据集。确保校准文件代表总体推断数据文件。为了使TensorRT使用校准文件,我们需要创建一个batchstream对象。
    Batchstream对象将用于配置校准器。
NUM_IMAGES_PER_BATCH = 5
batchstream = ImageBatchStream(NUM_IMAGES_PER_BATCH,Calibration_files)
  1. 使用输入节点名称和批处理流创建一个Int8_calibrator对象:
Int8_calibrator = EntropyCalibrator([“” input_node_name“],batchstream)
  1. 设置INT8模式和INT8校准器:
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = Int8_calibrator

引擎创建和推理的其余逻辑类似于使用Python从ONNX导入

5.2.4 使用Python处理显式精度

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

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

有关设置精度的更多信息,请参见使用Python设置层精度

猜你喜欢

转载自blog.csdn.net/qq_33287871/article/details/113735124