Skip to content

Latest commit

 

History

History

week2

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Week2

1. 论文阅读


2. 对称量化和非对称量化

参考Quantizing deep convolutional networks for efficient inference: A whitepaper

2.1 非对称量化

如上图右边所示。

公式1,对真实值进行尺度缩放并round后,再加上一个int8的偏移量z,得到一个与真实值对应的整数。

其中z称为零点,即量化后的z值对应真实值的0。

公式2,对公式1计算得到的整数进行裁切,最终得到真实值对应的量化值。

2.2 对称量化

如上图左边所示。

对称量化与非对称量化的不同在于其零点固定为0,也就是说量化后的0也对应真实值的0。

2.3 对称量化VS非对称量化

参考:https://intellabs.github.io/distiller/algo_quantization.html

1.当使用非对称量化时,量化范围得到了充分的利用。这是因为我们将浮点数范围的最小/最大值精确地映射到量化范围的最小/最大值。而使用对称量化,如果浮点数的分布偏向一侧,可能会导致量化范围中大量的范围被用于量化本没有出现过的值。这方面最极端的例子是ReLU之后,整个张量都是正的,使用对称量化意味着我们实际上没有利用符号位,浪费了1位量化空间。

2.另一方面,在卷积和全连接层的实现上,由于没有零点这一个量化参数,对称量化实际上实现要简单得多。如果使用非对称量化,零点需要在硬件/算法上增加逻辑。例如,在对量化后的输入特征图进行zero_padding时,如果使用的是非对称量化,那么padding的值应当是z。


3. ncnn的量化方案

参考:知乎:MegFlow和ncnn int8简介trt量化ppt

ncnn所采用的是一种后训练的对称量化方案,具体实现见ncnn2table

3.1量化机制

$q = clamp(round(r*S))$

其中$q$是量化后的参数,为int8类型;$r$为权重或激活值的真实值,为fp类型;$S$为量化参数-缩放系数,每个卷积核或每层的激活值都具有一个与之对应的量化参数。

3.2确定量化参数

以“kl”量化方法为例,介绍量化参数是如何确定的。

1.确定模型权重的量化参数

$S = 127 / absmax(W)$

其中$absmax(W)$是卷积核权重$W$中的最大绝对值。

2.确定各层激活值(特征图)的量化参数

上图描述了确定激活值的量化参数的过程。

  1. 准备矫正集,将其送入模型,用于获得各层激活值的分布。
  2. 统计每一层激活值中的最大绝对值m及其分布。(统计激活值分布时设置了2048个等间隔的bin, 间隔大小为st0=m/2048)
  3. 设置一个阈值$t \in [128,2048)$。
    • 计算裁切后的分布clip_distribution: 设置t个bins,将2048个bins中索引大于t的bin中的元素,全部统计至第t个bin中。如上图中部所示。
    • 计算量化后的分布quantize_distribution:设置128个bins,每个bins对应的间隔st1=st0*(t/128)。也就是将2048个bins中的前t个bins的数据重新划分至128个bins中。
    • 对量化后的分布进行扩张expand_distribution:将128个bins的数据扩张至t个bins中。
    • 度量clip_distributionexpand_distribution两个分布之间的KL散度。
  4. 重复第三步的操作,找到一个合适的阈值t,使得KL散度最小,即clip_distributionexpand_distribution最接近。
  5. 对阈值t做一个映射,将其从bin的索引映射至真实值。真实的阈值为$t'=(t+0.5)/2048*m$
  6. 确定缩放尺度$S=127/t'$

上述过程的伪代码如下:

关于如何求取quantize_distributionexpand_distribution,可见如下例子:

经过此过程,即可生成一个量化参数表。例如week1量化squeezenet任务中所生成的squeezenet_v1.1.table

上述过程的Python实现和C++实现是同一个大佬写的。

3.3 将模型转换至int8

ncnn2int8实现了以下功能:

  • 将模型权重量化成int8。
  • 根据量化参数表,将量化参数绑定至各层中。
  • 将反量化操作和重量化操作融合成一个操作。如下图所示。

经过上述过程,会生成量化后的模型权重。例如week1量化squeezenet任务中所生成的squeezenet_v1.1-int8.binsqueezenet_v1.1-int8.param


4. ncnn-int8 的卷积实现

具体实现见源码

对于一个卷积层来说,其对应的量化参数如下所示:

// Convolution class
#if NCNN_INT8
    Mat weight_data_int8_scales;
    Mat bottom_blob_int8_scales;
    Mat top_blob_int8_scales;
#endif

其中weight_data_int8_scales是模型权重对应的缩放因子。

其中bottom_blob_int8_scales是当前卷积层输入特征图对应的缩放因子。

top_blob_int8_scales则当前卷积层的下一个卷积(全连接)层输入特征图对应的缩放因子。

forward_int8的流程:

  1. 基于input_scale,对输入的特征图进行量化。
  2. 对于一个卷积核作一次卷积输出的的int32类型的结果sum,进行反量化,即 sumfp32 = sum / (weight_scale * input_scale)。
  3. 在sumfp32的基础上加上bias,通过激活函数。
  4. 对输出的激活值重量化:out = sumfp32 * scale_out。这一步基于模型的结构,也有可能不做out=sumfp32。如3.3图所示。
  5. 获得输出特征图中的一个元素的值out。重复上述的2,3,4步直到完成整个卷积操作。

其中:

input_scale=bottom_blob_int8_scales中与该层激活值对应的值

weight_scale=weight_data_int8_scales中与该层卷积中的某一个卷积核对应的值

scale_out=top_blob_int8_scales中与该层激活值对应的值