简单理解nvidia tensorRT模型量化原理

参考资料:
某人的量化原理笔记
https://blog.csdn.net/sinat_31425585/article/details/101607785
某人对int8比较详细的介绍
https://zhuanlan.zhihu.com/p/58182172
某人对ncnn的量化原理和源码理解(ncnn量化是基于tensorRT改进的)
https://zhuanlan.zhihu.com/p/72375164
 

一、两张图粗糙理解量化思路

简单理解nvidia tensorRT模型量化原理

一句话把原始值等比例映射到-127~127, 以便用int8表示.
原始思想是, 把原始值的绝对值的最大值作为阈值, 但是如果原始值分布不均匀, 部分值域被空余浪费, 导致精度损失很大.(比如原始值范围-1270.0~+1270.0, 但是大多数都在0~+10, 那么量化后全部都变成了int8的+1).
所以很多情况下是截取其中一部分(也就是选用小的阈值)来建立映射关系. 为什么这么做(为什么右边的饱和截取就ok呢?见https://zhuanlan.zhihu.com/p/58182172)

简单理解nvidia tensorRT模型量化原理

整个量化的问题就变成怎样找到最优的阈值使得精度损失最小?
 

二、int8量化后的模型推理

首先以简单的1个网络层中的乘法为例, 看看使用int8量化后的forward推理公式:

缩放系数: s = 127 / T, 这个T就是在原始值上要找的阈值.
输入向量:
    input_int8 = s_input * input_fp,
input_int8是input量化后的值, s_input是input的缩放系数, input_fp是input的原始值, fp表示float类型
模型权重:
    weight_int8 = s_weght * weight_fp,
weight_int8是weght量化后的值, s_weight是weight的缩放系数, weight_fp是weight的原始值
forward时计算二者乘积: 
    input_fp * weight_fp = (input_int8 * weight_int8) / (s_input * s_weight)
令ss_fp = 1 / (s_input * s_weight), 那么这个计算就可以分两步:
第一是int8计算:
    inner_int8 = (input_int8 * weight_int8)
第二是乘上float类型的系数变回原来的float类型
    output_fp = inner_int8 * ss_fp

总结一下: 在每层计算时是需要先将feature map量化到INT8,然后将weights量化到INT8,最后卷积计算得到INT32的输出值,输出值乘以scale(float)值反量化到float,然后加上浮点格式的bias作为下一层的输入。(这段话是https://zhuanlan.zhihu.com/p/58182172里面的原话, 讲的是ncnn的底层量化).
 

三、如何寻找最优阈值T?

有了阈值T就有了缩放系数, 问题就差不多解决了.
怎么样寻找最优阈值T? 就是通过计算量化前后两个数据分布的差异性, 分布差异最小的阈值T就是最优的阈值.
原理中使用KL散度(相对熵)来计算两个分布的差异性:

简单理解nvidia tensorRT模型量化原理

p(i)表示原始分布P中某个元素的值, q(i)表示量化后分布Q中某个元素的值.
但是模型推理过程中数值都是连续的, 如何得到P和Q分布? 就是用直方图统计来表示.

原始值的直方图统计(也就是分布P)计算过程:
 

1. 计算原始fp类型数据的最大绝对值(即最大值和最小值绝对值的最大值): 
    mav = max(abs(min_v), abs(max_v))
2. 将原始数据划分成2048个bins, 每个bin的宽度:
    interval = mav / 2048
3. 统计原始值的直方图, p(i)就是第i个bin中原始值数据点的个数了.


接下来是寻找最优阈值T的过程, 也是整个量化算法的中心(记住, T是<=mav的, 使用时是通过-T和+T来截断数据的):

for num_bins from 127 to 2048:
    a. T_cur = interval * num_bins, (ncnn是使用(interval+0.5)*num_bins, 取的是某个bin的中心点),
       使用T_cur阶段数据, 得到截断后的分布P'(截断区外面的值被加到截断区最后一个值上面, 比如num_bins是200, 也就是最后一个bin的序号是200,  那么201~2048bin上的频数(频数还是频率?)都被加到了第200个bin上)
    b. 把P'分布映射成Q, Q的bins是128, Q就是量化后的数据分布
       (注意, 这里是直接对直方图P'映射成128个bins的直方图Q, 而不是对原始数据做映射到int8再统计直方图)
       (看https://zhuanlan.zhihu.com/p/7237516知道 ncnn里大概的思路就是把原始的num_bins的直方图重新均匀分成128个bins)
    c. 将Q扩展到和P'一样的长度, 得到Q_expand(要计算KL散度前提是两个分布长度一样)
    d. 计算P'和Q_expand的KL散度, 并判断KL散度是否为最小

具体过程可以看https://zhuanlan.zhihu.com/p/72375164中对NCNN量化源码的理解, 挺详细的, 值得一看, 可以理解得更深入.

上面步骤c是将Q扩展到和P'一样长度, 举例过程如下(从https://zhuanlan.zhihu.com/p/72375164复制来的):

P=[1 2 2 3 5 3 1 7]     // fp32的统计直方图,T=8
// 假设只量化到两个bins,即量化后的值只有-1/0/+1三种
Q=[1+2+2+3, 5+3+1+7] = [8, 16]
// P 和 Q现在没法做KL散度,所以要将Q扩展到和P一样的长度
Q_expand = [8/4, 8/4, 8/4, 8/4, 16/4, 16/4, 16/4, 16/4] = [2 2 2 2 4 4 4 4]
D = KL(P||Q_expand)  // 这样就可以做KL散度计算了

这个扩展的操作,就像图像的上采样一样,将低精度的统计直方图(Q),上采样的高精度的统计直方图上去(Q_expand)。由于Q中一个bin对应P中的4个bin,因此在Q上采样的Q_expand的过程中,所有的数据要除以4.

done.


 

上一篇:模型量化-对称量化和非对称量化


下一篇:3.Go内建类型