TensorRT使用INT8 原理总结

网友投稿 1808 2022-05-30

目标: 在没有明显准确度丢失的情况下将FP32的CNNs网络转换为INT8

理由: INT8类型的存储方式有很高的通量和较低的内存需求

挑战: 相对于FP32, INT8有明显较低的精度和动态范围

解决方式: 在将权值以及计算时最小化有效信息损失.

结果: 上述转换可以通过TensorRT来进行实现,同时该方法不需要额外的大量调整和重新训练

对于INT8 推断(Inference),需要生成一个校准表来量化模型。接下来主要关注INT8推断(Inference)的几个方面,即:如何生成校准表,如何使用校准表,和INT8推断(Inference)实例。

面临的挑战

相对于FP32,INT8的精度和动态范围要小很多:

从FP32到INT8需要不止一次的类型转换

1) 如何生成校准表?

校准表的生成需要输入有代表性的数据集, 对于分类任务TensorRT建议输入五百张到一千张有代表性的图片,最好每个类都要包括。生成校准表分为两步:第一步是将输入的数据集转换成batch文件;第二步是将转换好的batch文件喂到TensorRT中来生成基于数据集的校准表,可以去统计每一层的情况。

2) 如何使用校准表?

校准这个过程如果要跑一千次是很昂贵的,所以TensorRT支持将其存入文档,后期使用可以从文档加载,其中存储和加载的功能通过两个方法来支持,即writeCalibrationCache和readCalibrationCache。最简单的实现是从write()和read()返回值,这样就必须每次执行都做一次校准。如果想要存储校准时间,需要实现用户自定义的write/read方法,具体的实现可以参考TensorRT中的simpleINT8实例。

INT8相较于FP32的计算量变小了,同样也需要适合小精度的计算单元来执行,否则同样在FP32计算单元上面执行,则只在模型大小上面有一定的优势,而并不能带来真正性能的提升,这时候就要谈到GPU为INT8计算的提供的硬件支持。

对于sm_61+如Tesla P4/P40 GPU,我们提供了新的INT8点乘运算的指令支持---DP4A,其将FP32单元“拆开“分成4个INT8单元,从而通过两个FP32单元实现4个INT8数的点乘操作,最后累加成INT32的结果,计算过程如下图所示:

从而对于Tesla P4来说,其拥有5.5T的FP32计算性能,通过DP4A指令为其赋予了INT8的计算能力,并达到了FP32的4倍也就是22T的计算性能。

线性量化

对于每一个FP32的Tensor(权值和激活值),我们无法直接用INT8来表示,因此最直接的表达方法为:

Tensor Values = FP32 scale factor * int8 array + FP32 bias

这时候我们需要考虑一个问题,我们是否真的需要FP32的bias?

TensorRT使用INT8 原理总结

对于以下两个矩阵:

A = scale_A * QA + bias_A

B = scale_B * QB + bias_B

推理过程中绝大部分为矩阵乘法,因此这两个矩阵相乘的计算可以表示为:

A * B = scale_A * scale_B * QA * QB + scale_A * QA * bias_B + scale_B * QB * bias_A + bias_A * bias_B

如果我们直接将去掉bias,则两者相乘为:

A * B = scale_A * scale_B * QA * QB

通过去掉bias我们能极大的简化计算内容,降低对GPU中寄存器等资源的消耗,而我们的实验也发现去掉bias不会对性能产生很大的影响。故而TensorRT在这里采用的优化的对称线性量化方法:

Tensor Values = FP32 scale factor * int8 array

现在问题就变成如何寻找一个最优的scale factor?

量化有以下两种方法:

图左-非saturation:对weights和activations使用线性量化,即找到其中绝对值最大的值,然后将这个范围映射回INT8

图右-saturation:选择一个阈值T,将范围T的FP32值映射至INT8,对于范围外的使用-127或128

根据实验证明,图左的方法转化后会带来很大的准确度损失。而对于图右的方法:

weights:无法提升准确度

activations:能有效提升准确度

因此对于weights和activations分别采用了不同的量化方法,前者使用了简单的非saturation的方法,而后者采用的是较为复杂的saturation方法。

量化整体流程

以卷积kernel为例:

输入为:INT8_INPUT,I8_weights

输出为:INT8_OUTPUT

所需参数:FP32 bias (来自于FP32模型中),FP32 scaling factors: input_scale, output_scale, weights_scale[K]

利用DP4A指令计算 INT8_INPUT与I8_weights的乘积获得I32_gemm_out

利用input_scale以及weights_scale将I32_gemm_out转化成为FP32的F32_gemm_out

利用input_scale, output_scale和weights_scale      将FP32的F32_gemm_out映射至输出的activation分布,获得rescaled_F32_gemm_out

给rescaled_F32_gemm_out加上FP32的bias获得rescaled_F32_gemm_out _with_bias

对rescaled_F32_gemm_out _with_bias执行relu从而获得F32_result

最后根据前文的阈值T将F32_result转成I8_output

根据所选的量化方法以及量化的整体流程,对于量化最关键的是如何实现saturation方法中的阈值T的选择,这个选择流程被称之为校准。

校准

上图分别是vgg19-conv3_4, resnet152-res4b8_branch2a, googlenet:incetion_3a/pool的activations的分布直方图,其中横轴为activation值,纵轴是正则化后的值的数量级。

对于将activations从FP32映射至INT8可以看成是信息的重编码过程,需要做的是在INT8中最大的保留FP32的信息,这里通过引入KL divergence来作为INT8的信息损失率评价指标。

校准的整体流程

选取validation数据集中一部分具有代表的数据作为校准数据集

对于校准数据进行FP32的推理,对于每一层

收集activation的分布直方图

使用不同的threshold来生成一定数量的量化好的分布

计算量化好的分布与FP32分布的KL divergence,并选取使KL最小的threshold作为saturation的阈值

整个流程将花费几分钟至几十分钟来完成

以下是几个常见网络的部分activation分布及校准结果:

GOOGLENET:Inception_5a/5x5

ALEXNET:Pool2

RESNET:Res4b30

如上三图分别是GOOGLENET/ALEXNET/RESNET中某一层的activation的分布,其中白线的左边的activation数据将映射至INT8中,而右边的将被截断。

可以明显的看到前两个网络的FP32 activation分布在INT8中得以几乎完整的保留。

第三个图左的白线截断了其右侧activation的分布,第三个图右为截断后的分布图。图中的绿点即为截断部分映射至INT8的极大值所占比例,可以看到绿点在整体分布的所占比例并不大,因此损失的信息仍然是可以接受的,校准也极大的保留的FP32的activation的分布信息。

人工智能 AI

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【Flutter】Flutter 布局组件 ( Opacity 组件 | ClipRRect 组件 | Padding 组件 )
下一篇:HarmonyOS之AI能力·通用文字识别技术
相关文章