神经网络量化-基础算法介绍
基本公式
首先,遵循如下基本公式进行量化
q为量化后的数据,例如如果量化为8bit,那么q是一个8bit的整数,S(scale)和Z(zero-point)为量化参数,为常量,r是量化前的真实值,为实数。S为正实数,Z和q数据类型相同,也是量化的,不同的矩阵和激活计算,我们使用不同的量化参数。那么,容易得到,对于给定的实数,其量化公式为:
矩阵乘法
我们来看下,如何基于上面的公式,完全通过量化整数的计算来实现矩阵乘法。考虑矩阵乘法,设矩阵的元素为
,那么量化公式变为
根据矩阵乘法定义我们有
(1)
进一步可以得到
(2)
我们可以看到唯一的非整数是M,根据经验M总是在(0,1)范围内,那么可以表示成规范化的形式:
n是非负的整数,在[0.5,1)范围内,此时我们可以增加
的位数来将浮点乘法转换为定点乘法(int16或者int32),例如,首先扩大
倍,运算玩再缩小
倍,而缩小的运算可以直接通过移位或者截断来非常高效的实现,下面通过一个实例来说明:
#include <iostream>
#include <stdint.h>
#include <math.h>
int main() {
float Mf = 0.239; // 浮点值M
uint32_t Q = 123; // M要相乘的整数
std::cout << "Real result is " << Mf * Q << std::endl;
uint32_t shiftScale = pow(2,16); // 扩大2^16倍
uint32_t M0 = shiftScale * Mf; //扩大后的M0
std::cout << " M0 is " << M0 << std::endl;
uint32_t result = M0 * Q;
std::cout << "Quantize result is " << (result >> 16) << std::endl;
std::cout << "Transform to real result is " << result / pow(2.0,16) << std::endl;
return 0;
}
执行结果
Real result is 29.397
M0 is 15663
Quantize result is 29
Transform to real result is 29.3968
可以看到通过这种方式,我们可以得到小数点位之前整数位计算的正确性,而且低16位其实保存了有效的小数位结果(15~0,依次存:0.5,0.25, 0.125.....),如果我们能够高效的转换成浮点那么可以进一步提高精度,整数部分如果考虑四舍五入(否则值会统一像低位偏),量化结果可以表示为
uint32_t result = M0 * Q + pow(2,15);
零点的高效处理
公式(1)可以进一步简化为:
(3)
其中,
可以看到,基于变换后的公式,主要计算量在,零点相关只需要通过两个累加来实现。
层融合
基于公式(3),我们可以进一步将偏置加和激活函数层也加入到公式(3)进一步提升效率。的输入是uint8输出位int32:
int32 += uint8 * uint8;
这样可以避免多次累加溢出的问题,如果想将偏置加加入到这个累加器,那么偏置向量需要取为int32类型量化数据类型,并且0为量化零点,最后其量化scale
应与累加器一致,即
。这样拿到累加器的结果之后,还有3件事情要做:
缩小比例(scale down)到8-bit输出激活函数需要的scale;
截断到uint8;
执行激活函数生成8-bit输出激活。
参考文献:
Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference