基于int8量化技术的模型加速方案总结

1.简介

1.1目的

在过去的一段时间里,对基于INT8量化技术的模型加速方案的算法进行了一系列的实现和实验,特别有引入的INT8矩阵相乘的方法替代原本caffe中MKL运算库FLOAT32矩阵相乘的方法,两者相比,INT8量化技术几乎不能实现模型加速的需求,但是在gpu上,由于gpu硬件对INT8矩阵乘法的支持,引入INT8技术后,在gpu上模型前向的时间是可以得到3倍左右的提升的。所以本文档针对上述的实验结果和现象进行分析和总结。

1.2范围

本文档描述的代码修改以及实验方法都是基于caffe框架进行的,主要的加速策略是模型INT8量化,并引入的INT8*INT8=INT32的矩阵操作,所有的实验都是基于Mnist数据集的Lenet网络。

1.3定义、首字母缩写词和缩略语

序号

术语或缩略语

说明性定义

1

2

3

4

5

6

7

1.4 参考资料

《基于int8量化技术方案的模型加速实验设计.pdf》

int8量化代码评审.ppt

2.实验的方法

        本文档中的实验是基于caffe框架进行的,修改了其中的源码,使得这个框架可以按照制定的模型加速测绿,并修改对应参数层中前向和后向算法将对应可训练的参数的基于MKL的FLOAT32矩阵乘法替换为基于INT8矩阵乘法。

2.1整体流程

GOOGLEINT8量化用于语音识别,是因为它迫切需要降低运算和储存/传输的压力。如果把经典深度神经网络(如CNN)看做一张网,那么用于语音识别的RNN就是一堆网叠加在一块,彼此互联。因为语音是一个时序数据,每一个时间点的信号都需要一整张网来模拟。可想而知数据流之大。论文中也说明了可以显著的减少储存和运算的需求,我们这里是将INT8量化技术用到CNN网络中。

如果把神经网络分解到最后,绝大部分都会对应FMA操作。其中FMA操作如公式(1)所示。

y=WX+B

(1)

INT8量化的操作也是在FMA操作的过程中进行的,如图1所示。

图1 量化和恢复过程

其中W表示的已经量化过的矩阵,对XQ(.)操作是在Mult(.)之前迅速的完成的。INT8进行Mult(.)操作之后会进行R(.)操作恢复到float32,并保持float32的表示完成Add(.)操作以及F(.)激活操作。

2.2量化策略

2.2.1论文中给出理论上的量化策略

  • Quantizing

给定一组FLOAT型的数据V=Vx,一个量化的尺度S (例如INT8,则S=256),通过计算一个因子Q,得到量化后的结果V'=0Vx'≤S。具体的过程如下:给定R=Vminmax,其中Vmax是给定的一组float型数据中最大的数,Vmin为最小的数。论文中因子Q的计算如下:Q=SR,最终的量化表达式为:Vx'=Q*Vx-Vmin()

  • Recovery

同样的,量化后的值可以恢复到量化前高精度值得近似值,具体的恢复表达式为:Vx=Vx'*Q-1+Vmin,其中Q-1=RS

  • Quantization error and bias

整个的量化过程是两种loss损失的过程,第一种就是输入的值和quantization-recovery操作后的值之间的loss损失,即精度损失。第二种是在quantization-recovery操作计算数值的过程中引入偏差带来的损失,即偏置损失。第一种错误不可避免但是平均上看,带来的影响很小。而第二种偏置错误,如果更注意的去量化是可以避免的,所以花费特别的精力去消除第二种错误是非常必要的。

  • Eliminating bias error

FMA的操作为就是去执行一系列的如下操作

Vc=Va*Vb

(2)

       根据之前量化和恢复的方法可以令Vx''=Vx'+QVmin,那么就有:Vx=Vx''Q。那么量化恢复后的(2)式可以改写成:

Vc=Va''*Vb''Qa*Qb

(3)

       可以看出每个Vx''已经是一个整数类型,Vx'也已经是一个整型类型,而QVmin却是一个FLOAT型将要近似为整型,这样就引入一个误差:

E=floatQVmin()QVmin()

(4)

       那么为了避免这种误差,就要求在量化过程中执行的方式和这个公式是一致的,所以就引入一个近似的操作round(.),即有:

Vx'=roundQVx-roundQVmin()

(5)

       因为这些错误在量化和乘积操作中是一致的,并且可以相互抵消。同样的在量化过程中有:

Vx=Vx'+roundQVmin()Q

(6)

  • Efficient implementation

上述将FLOAT32量化成INT8,通过引入SIMD,降低了数据的内存带宽,这样就可以增加cpu对数据的吞吐量。以avx指令集为例子,其指令长度为256bit,如果存储FLOAT32,可以存储有8个,而存储INT8可以存储32个,对于数据的吞吐量是前者的四倍。

2.2.2实际在工程实践上使用的量化策略

       为了方便在工程上的实验,这里对上述的量化策略做了一些无损的修改,(语音识别组宋亚楠和刘迪源提供的思路)。

  • Quantizing

将映射空间从0-255变成-127-128,即上述的float2uchar变成float2char的方法,这里就有新的量化方法如公式(7)所示:

Q=S>>1maxabsVmax,absVmin

(7)

最终的量化方法如公式(8)所示:

Vx'=Q*Vx

(8)

  • Recovery

相应的量化恢复的方法也发生了变化公式(9)和公式(10)所示。

Q-1=maxabsVmax ,absVmin S>>1

(9)

Vx=Vx'*Q-1

(10)

3.实验结果及其分析

针对上述方法修改完毕之后的caffe框架,做了两组对比实验。第一组实验主要是观察不同的卷积层进行INT8量化对模型的性能的影响。第二组实验主要是观察不同卷积层进行INT8量化对模型前向耗时影响。所有的实验都是基于Mnist数据集在LeNet网络上进行的,具体的实验结果以及实验分析如下所示。

3.1 性能效果

第一层卷积是否(int8)量化

第二层卷积是否(int8)量化

accuracy

loss

0.9901

0.0294976

0.9902

0.29693

0.9891

0.0322802

0.9892

0.0321049

表1 不同层量化的模型压缩效果

不同的卷积层进行INT8量化后的性能效果如表1所示。其中第一条记录表示的是LeNet网络中两层卷积层都没有量化的基线结果。第二条记录可以看出仅仅对LeNet的第二层卷积层进行INT8量化,对模型的输出准确度几乎没有影响。第三条记录可以看出当对LeNet两个卷积层都进行量化后,模型的的输出准确度出现了下降的趋势,可能的原因是直接对模型的输入数据进行INT8量化会对模型的准确度是有一定的影响的。第四条记录可以看出仅仅对LeNet中的第一个卷积层进行量,出现了模型输出准确度的下降,从而证明了上述假设。

         总体上,就LeNet网络而言,对其做INT8量化之后对模型的准确度效果影响不大。

3.2 模型前向加速的效果

第一层卷积是否(int8)量化

第二层卷积是否(int8)量化

Mean_forward_cpu_time(us)

(10000次取平均值)

493.146

654.282

722.201

565.444

表2 不同层量化的前向算法耗时

不同的卷积层进行INT8量化后的前向算法耗时情况如表2所示,所有的实验都是设置openmp线程数为1的,即OMP_NUM_THREADS=1的,INT8量化的矩阵相乘的方法是基于AVX2指令集加速的。表中的第一条记录是LeNet网络两个卷积层使用MKL库FLOAT32矩阵乘法的基线前向耗时。第二条记录表示的是仅仅对LeNet网络中第二个卷积层进行INT8量化的前向耗时情况,可以看出前向的耗时要比基线要多,速度变慢,说明引入INT8量化之后整体的耗时是增加的。第三条记录可以看出,当对LeNet网络中的两个卷积层同时进行INT8量化之后,网络的前向耗时变得更大了,说明INT8量化之后网络相比原本的MKL,变慢了,前向耗时增加了。第四条记录也是一样的结论。

总体上可以看出,通过引入INT8矩阵乘法之后,相比原先caffe中的MKL库的FLOAT32的矩阵相乘方法,模型的前向耗时并没有得到加速的效果,下面会对具体的矩阵乘法的耗时情况进行对比。

3.3 矩阵乘法耗时效果对比

因为效果不佳,这里对LeNet网络中的两个卷积层矩阵乘法的耗时情况进行单独测试,结果如表3所示。

Release(-02)

OMP_NUM_THREADS=1

Conv1

Conv2

Avx2int8

MKLfloat32

Avx2int8

MKLfloat32

100000mean(us)

43.7489

20.2298

70.3936

76.4201

表3 MKL库FLOAT32矩阵乘法和基于AVX2指令集INT8矩阵乘法的耗时对比

        

从表中发现,虽然LeNet网络中第一个卷积层中MKL库的FLOAT32矩阵乘法较快,但是在第二层卷积层中INT8矩阵乘法的耗时是比MKL库中FLOAT32矩阵乘法要小的。其中第一层卷积层中矩阵乘法的尺寸是(576*32,32*20),第二个卷积层中的卷积乘法的尺寸是(64*512,512*50),矩阵尺寸的形式是(N*K,K*M),这说明矩阵的尺寸大小可能对矩阵运算速度上有影响。于是下面对固定N和M对不同的K的取值做了一系列的实验。特别的解释int8avx2表示的是基于avx2指令集的INT8矩阵乘法耗时情况,mkl表示的基于MKL库的FLOAT32矩阵乘法耗时情况,eigen表示的是基于Eigen3库的FLOAT32矩阵乘法耗时情况,floatavx2表示的是基于avx2指令集的FLOAT32矩阵乘法的耗时情况。

图2 100_K_100尺寸的四种矩阵乘法耗时随K变化的趋势情况对比

图2中表示的是N=100 ,M=100 ,K=32,64,96,...10240 。可以看出,随着K的增大int8avx2方法是最快的,其次是mkl,eigen,最后是floatavx2的方法。并且有mkl的耗时和int8avx2的方法很接近。

图3 1000_K_1000尺寸的四种矩阵乘法耗时随K变化的趋势情况对比

图3中表示的是N=1000 ,M=1000 ,K=32,64,96,...10240 。可以看出,随着K的增大mkl方法是最快的,其次是int8avx2,eigen,最后是floatavx2的方法。可以看出int8avx2的方法的优势已经没有了。

从图2和图3中可以得到结论是当N和M的取值相比K的取值较小的时候,随之K的增大,int8avx2相比mkl是有一定优势,但是随着M和N的值和K为同一个量级或者差距很小的时候,这种优势就没有了。图4和图5分别对比了int8avx2,floatavx2,mkl在不同的M和N取值上随着K变化的耗时比。

图4 100_K_100尺寸的int8avx2,floatavx2以及mkl矩阵乘法随K变化的耗时比

理论上INT8矩阵乘法要比FLOAT32矩阵乘法要快4倍。在我们的实验中在图4和图5中可以看出,随着K的增大,int8avx2的速度会比floatavx2快3倍还多,这个结论和ocr组实验得到的结论是一致的,这样就验证了在实际应用中同样使用avx2指令集INT8要比FLOAT32要快3倍多。然而int8avx2和mklfloat的速度几乎没有什么差距。图4中的黑线基本上维持在1附近,图5中的黑线几乎都是低于1的,这说明了在实际的应用中经过avx2优化后的INT8矩阵方法相比于mkl的FLOAT32矩阵乘法基本上没有什么加速效果。

图5 1000_K_1000尺寸的int8avx2,floatavx2以及mkl矩阵乘法随K变化的耗时比

特别的我们还做了基于Eigen库的float矩阵乘法和char矩阵乘法的耗时对比。在实验之前我们推测,Eigen的char的矩阵乘法应该比Eigen的float型的矩阵乘法要快上四倍左右,但是实际上,在测试过程中前者比后要慢很多,耗时对比情况如图6所示。

图6 100_K_100尺寸的Eigenchar和Eigenfloat矩阵乘法随K变化的耗时比

基于Eigen的char矩阵乘法竟然会比基于Eigen的float矩阵乘法的速度慢上七倍还多,这说明寄希望于Eigen库的INT8矩阵乘法加速的方法也是不可行的。

3.4 gpu上矩阵乘法耗时效果对比

    前面做的都是cpu上FLOAT32矩阵乘法和INT8矩阵乘法上的时间对比情况,同样,我们也验证了在gpu上两个矩阵乘法的时间对比情况。英伟达公司在最新的GPU卡上已经支持了INT8矩阵乘法,需要调用的函数为cublasGemmEx ,本次的实验是P4机器以及人脸组集群机器P30G24进行的,我们分别对cublasGemm(普通的cublas支持的FLOAT32矩阵乘法)、cublasGemmEx(CUDA_R_32,新的接口支持的FLOAT32矩阵乘法)以及cublasGemmEx(CUDA_R_8I,新的接口支持INT8矩阵乘法)。所有的矩阵都是方阵,实验的结果如下:

Matrix

size

P4(time ms)

P30G24(time ms)

Sgemm

(float32)

sgemmEx

(float32)

sgemmEx

(int8)

Sgemm

(float32)

sgemmEx

(float32)

sgemmEx

(int8)

512

0.094034

0.087107

0.029559

0.071754

0.066665

0.021427

1024

0.545898

0.550913

0.159189

0.269500

0.267248

0.095028

2048

3.822290

3.917560

1.141507

1.634034

1.63391

0.521268

表4 P4卡和P30G24卡FLOAT32矩阵乘法和INT8矩阵乘法的耗时对比

    从表4可以看出,在P4和P40的卡上,INT8矩阵乘法的速度比FLOAT32矩阵乘法的速度要快上3倍多,所以如果对FLOAT32的数据进行INT8量化后再进行矩阵相乘,确实可以得到加速效果。特别的,这里使用gpu测试时间的函数是cudaEventRecord相关的函数,普通的计时函数好像测试GPU的时间不准。

4.总结

         OCR模型之所以可以给出CPU上使用INT8矩阵乘法要比FLOAT32矩阵乘法要快3倍多的效果的原因是,他们组的FLOAT32矩阵乘法baseline的速度就比较慢,对比的对像是用SSE指令集自己实现的FLOAT32和INT8矩阵乘法的对比,对于我们要替换caffe中的MKL矩阵乘法库的需求来说没有参考意义,在我们的实验中也确实可以复现OCR组的实验结果,但是在和MKL库FLOAT32矩阵乘法对比的实验中INT8本身优势已经荡然无存了。综上所述,MKL库在矩阵乘法的优势非常的明显,之后基于INT8量化的思路方向有两个:一是等MKL2018的版本,据官方论坛说,会在这个版本中添加支持INT8计算的MKL矩阵乘法;二是学习一下tensorflow中对Eigen库矩阵乘法优化的方法,据吕亚飞他们说,谷歌工程师在tensorflow中的Eigen库的矩阵运算方面做了很大优化工作,这个是他们组研究计划,可以经常和他们保持沟通学习。

在GPU上已经有高性能的卡片(如P4和P40卡)可以支持INT8矩阵乘法的运算,并且对比FLOAT32可以得到3倍多的速度提升,所以在GPU上INT8量化进行模型加速的方案还是可行的,只是目前来看,这种GPU都比较昂贵,实际大规模投入使用更多成本比较高,以后也会关注gpu方面的加速方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chauvin912

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值