4. 基础的量化函数
4.1 计算尺度因子和零点
首先我们需要把量化的基本公式实现。他们分别求出缩放尺度(scale)和 零点(zero point)。
其代码如下所示:
""" 计算量化尺度和零点 :min_val: 最小值 :scale: 最大值 :num_bits: 量化后的bit位 :returns: 量化尺度; 零点 """ def calcScaleZeroPoint(min_val, max_val, num_bits=8): qmin = 0. qmax = 2. ** num_bits - 1. scale = float((max_val - min_val) / (qmax - qmin)) zero_point = qmax - max_val / scale if zero_point < qmin: zero_point = qmin elif zero_point > qmax: zero_point = qmax zero_point = int(zero_point) return scale, zero_point
4.2 量化与反量化
计算出缩放尺度和零点值后我们需要构建量化和反量化函数,主要是依据以下两个公式进行,这里的r rr表示浮点实数,q qq表示量化后的定点整数:
""" 这是对tensor进行量化 :x: 输入的需量化的tensor :scale: 量化的尺度 :zero_point: 零点 :num_bits: 量化后的bit位 :signed: 量化是有无符号 :returns: 量化之后的张量 """ def quantize_tensor(x, scale, zero_point, num_bits=8, signed=False): if signed: qmin = - 2. ** (num_bits - 1) qmax = 2. ** (num_bits - 1) - 1 else: qmin = 0. qmax = 2.**num_bits - 1. q_x = zero_point + x / scale #求量化之后的值 q_x.clamp_(qmin, qmax).round_() #round代表了四舍五入 return q_x #量化之后的值 """ 这是对tensor进行反量化 :x: 量化后的值 :scale: 量化的尺度 :zero_point: 零点 :returns: 反量化后输出的张量 """ def dequantize_tensor(q_x, scale, zero_point): return scale * (q_x - zero_point)
4.3 求解最大与最小值进行量化与反量化操作
从以上公式可以知道,倘若我们要想计算尺度因子和零点进而实现数值的量化,我们需要知道数值的最大值和最小值,以及量化的比特位。所以在后训练量化过程中,需要先统计样本以及中间层的 min, max,同时也频繁涉及到一些量化,反量化操作,因此我们可以把这些功能都封装成一个 QParam类,代码如下所示:
""" QParam 在后训练量化过程中,需要先统计样本以及中间层的 min, max,同时也频繁涉及 到一些量化,反量化操作,因此我们可以把这些功能都封装成一个 QParam类 :num_bits: 比特 :scale: 量化的尺度 :min: 最小值 :max: 最大值 """ class QParam: def __init__(self, num_bits=8): self.num_bits = num_bits self.scale = None self.zero_point = None self.min = None #手工定义的最大值 self.max = None #手工定义最小值 """ update 函数就是用来统计 min、max """ def update(self, tensor): if self.max is None or self.max < tensor.max(): self.max = tensor.max() self.max = 0 if self.max < 0 else self.max if self.min is None or self.min > tensor.min(): self.min = tensor.min() self.min = 0 if self.min > 0 else self.min #计算参数的量化尺度和零点 self.scale, self.zero_point = calcScaleZeroPoint(self.min, self.max, self.num_bits) def quantize_tensor(self, tensor): return quantize_tensor(tensor, self.scale, self.zero_point, num_bits=self.num_bits) def dequantize_tensor(self, q_x): return dequantize_tensor(q_x, self.scale, self.zero_point)
定义的这个类就可以实现对数据的量化操作,其主要操作步骤是,首先使用update()函数去计算输入张量的最大值和最小值,并调用函数calcScaleZeroPoint()计算出尺度因子和零点(这就是公式1的具体实现过程)。接着就是对数值进行具体的量化操作,采用的函数是quantize_tensor(),这就是公式2的具体实现过程。其中QParam这个类还定义了一种用于反量化的方法,dequantize_tensor()。
要注意的是,除了第一个 conv 需要统计输入的 min、max 外,其他层都只需要统计中间输出 feature 的 min、max 即可。另外,对于 relu、maxpooling 这类激活函数来说,它们会沿用上一层输出的 min、max,不需要额外统计,即上图中会共享相同的 min、max。