量化的目的:是为了减少计算时间和计算能耗 。在一些场景下对能耗和时间的要求,要高于模型的指标,所以在这种情况下量化是一个必然的选择。
模型量化方法: 训练后量化(PTQ)和量化感知训练(QAT)。PTQ方法,是将已经训练好的模型进行量化,同时只需要很少的数据或者不需要数据,少部分需要手动调整的超参数以及不需要端到端训练。这使得PTQ成为一种工程实现简单并且不需要大量计算成本的量化方法。QAT,它依赖神经网络在训练过程中进行模拟量化。虽然QAT需要进行重新训练以及调整超参数,但是在低bit时却可以比PTQ获得更接近全精度的效果。
量化方法:
对称量化
1. 量化卷积核权重:量化的目的是为了把原来的 float32 位的卷积操作,转换为 int8 的卷积操作,这样计算就变为原来的 1/4
,但是访存并没有变少哈,因为我们是在 kernel 里面才把 float32 变为 int8 进行计算的。
比如说 float32
位的变量 a=6.238561919405008
,可以通过 scale=23.242536
映射到 int8
空间上到整数 a*scale=145
。
如上所示,这个 scale 是根据最大的权重绝对值 thresh
决定的,然后计算 127
与它的比值,便得到了 scale
值。
可以看到我们是按照通道对卷机层进行切片的。
def quantize_weight(self):
"""
对该层的卷积核权重进行量化, 计算出 scale
"""
weights = self.layer.weight.cpu().detach().numpy() # 剥离每一层的卷积权重
group_weights = np.array_split(weights, self.channels) # 将卷积权重按通道划分
for i, group_weight in enumerate(group_weights): # 对每个通道的卷积权重进行遍历
max_val = np.max(group_weight)
min_val = np.min(group_weight)
thresh = max(abs(max_val), abs(min_val)) # 求出阈值 thresh 从而求出 scale
if thresh < 0.0001:
self.weight_scales[i] = 0.
else:
self.weight_scales[i] = 127 / thresh # int8: -127 ~ 127
由于卷积运算是卷积核(weights
)和数据流(blob
)之间乘加操作,因此光对卷积核量化是不够的,还需要对数据流进行量化!
int8推理过程:
整个 INT8 推理过程可以简述为:输入流 x 在喂入每层卷积之前,需要先乘以 blob_scale 映射为 int8 类型数据,然后得到 int8 类型的卷积结果 x。由于卷积层的偏置 bias 没有被量化,它仍然是 float32 类型,因此我们需要将卷积结果 x 再映射回 float32,然后再与偏置 bias 相加。