pytorch模型压缩方法总结
剪枝
structed vs unstructed
一个是在channel粒度上做剪枝,另一个是在神经元Unit维度上做剪枝
random vs l1 vs ln vs global
- 一个是随机做剪枝
- 一个是根据权重的l1范数大小排序做剪枝
- 一个是根据权重的ln范数大小排序做剪枝
- 前面三个都是对units/channels in a tensor(layer)
- 最后一个是对全局做剪枝 units/channels in a global tensor
量化
动态量化 vs 静态量化
区别
1.torch.quantize_per_tensor()函数的scale和zero_point需要自己设定。
所谓动态是指这个函数torch.quantization.quantize_dynamic能自动选择最合适的scale和zero_point。
- 网络在前向推理的时候动态的量化float32类型的输入。
- 权重部分的量化是“静态”的,是提前就转换完毕的,而之所以叫做“动态”量化,就在于前向推理的时候动态的把input的float tensor转换为量化tensor。
- 动态量化的本质就藏身于此:基于运行时对数据范围的观察,来动态确定对输入进行量化时的scale值。这就确保 input tensor的scale因子能够基于输入数据进行优化,从而获得颗粒度更细的信息。模型的参数则是提前就转换为了INT8的格式(在使用quantize_dynamic API的时候)。这样,当输入也被量化后,网络中的运算就使用向量化的INT8指令来完成。 而在当前layer输出的时候,我们还需要把结果再重新转换为float32——re-quantization的scale值是依据input、 weight和output scale来确定的
- 动态量化中可是只量化了op的权重哦,输入的量化所需的scale的值是在推理过程中动态计算出来的。而静态量化中,统统都是提前就计算好的。
QuantStub使用的是HistogramObserver,根据输入从[-3,3]的分布,HistogramObserver计算得到min_val、max_val分别是-3、2.9971,而qmin和qmax又分别是0、127,其schema为per_tensor_affine,因此套用上面的per_tensor_affine逻辑可得:
- 静态量化的float输入必经QuantStub变为int,此后到输出之前都是int;
- 动态量化的float输入是经动态计算的scale和zp量化为int,op输出时转换回float。
动态量化
Post Training Dynamic Quantization,简称为Dynamic Quantization,也就是动态量化,或者叫作Weight-only的量化,是提前把模型中某些op的参数量化为INT8,然后在运行的时候动态的把输入量化为INT8,然后在当前op输出的时候再把结果requantization回到float32类型。动态量化默认只适用于Linear以及RNN的变种。
当对整个模型进行转换时,默认只对以下的op进行转换:
Linear
LSTM
LSTMCell
RNNCell
GRUCell
为啥呢?因为dynamic quantization只是把权重参数进行量化,而这些layer一般参数数量很大,在整个模型中参数量占比极高,因此边际效益高。对其它layer进行dynamic quantization几乎没有实际的意义。
静态量化
与其介绍post training static quantization是什么,我们不如先来说明下它和dynamic quantization的相同点和区别是什么。相同点就是,都是把网络的权重参数转从float32转换为int8;不同点是,需要把训练集或者和训练集分布类似的数据喂给模型(注意没有反向传播),然后通过每个op输入的分布特点来计算activation的量化参数(scale和zp)——称之为Calibrate(定标)。是的,静态量化包含有activation了,也就是post process,也就是op forward之后的后处理。为什么静态量化需要activation呢?因为静态量化的前向推理过程自(始+1)至(终-1)都是INT计算,activation需要确保一个op的输入符合下一个op的输入。
PyTorch会使用五部曲来完成模型的静态量化:
- fuse_model
- 设置qconfig
- prepare
prepare用来给每个子module插入Observer,用来收集和定标数据。以activation的observer为例,就是期望其观察输入数据得到四元组中的min_val和max_val,至少观察个几百个迭代的数据吧,然后由这四元组得到scale和zp这两个参数的值。 - 喂数据
这一步不是训练。是为了获取数据的分布特点,来更好的计算activation的scale和zp。至少要喂上几百个迭代的数据,
- 转换模型
第四步完成后,各个op权重的四元组(min_val,max_val,qmin, qmax)中的min_val,max_val已经有了,各个op activation的四元组(min_val,max_val,qmin, qmax)中的min_val,max_val也已经观察出来了。
这个过程和dynamic量化类似,本质就是检索模型中op的type,如果某个op的type属于字典DEFAULT_STATIC_QUANT_MODULE_MAPPINGS的key(注意字典和动态量化的不一样了),那么,这个op将被替换为key对应的value
per-channel vs per-tensor
per tensor 和 per channel。Per tensor 是说一个tensor里的所有value按照同一种方式去scale和offset; per channel是对于tensor的某一个维度(通常是channel的维度)上的值按照一种方式去scale和offset,也就是一个tensor里有多种不同的scale和offset的方式(组成一个vector),如此以来,在量化的时候相比per tensor的方式会引入更少的错误。PyTorch目前支持conv2d()、conv3d()、linear()的per channel量化。