量化&反量化
量化操作可以将浮点数转换为低比特位数据表示,比如int8和 uint8.
Q(x_fp32, scale, zero_point) = round(x_fp32/scale) + zero_point,
量化后的数据可以经过反量化操作来获取浮点数
x_fp32 = (Q - zero_point)* scale
pytorch中 quantize_per_tensor的解释
pytorch可以使用quantize_per_tensor
函数来对一个浮点tensor做8bit量化.
ts_quant = torch.quantize_per_tensor(ts, scale = 0.1, zero_point = 10, dtype = torch.quint8)
print(f'fp32 ts:{ts}, quant ts:{ts_quant}, int_repr:{ts_quant.int_repr()}')
# 截断后的浮点数.
naive_quant = np.array([24.5, 1.0, 2.0]) / 0.1 + 10
print(f'naive_quant:{naive_quant}')
打印结果:
fp32 ts:tensor([100., 1., 2.]), quant ts:tensor([24.5000, 1.0000, 2.0000], size=(3,), dtype=torch.quint8,
quantization_scheme=torch.per_tensor_affine, scale=0.1, zero_point=10), int_repr:tensor([255, 20, 30], dtype=torch.uint8)
naive_quant:[255. 20. 30.]
有几点值得说明
- ts_quant直接打印出来的值并不是quint8数据,依然是个浮点,它表示的是映射到8bit后被截断剩下有效的浮点范围. scale=0.1的前提下,uint8只能表示出[0, 25.5]范围的浮点数,加上zero_point=10, 那么输入的浮点必须要在[0, 24.5]范围内才能被表示,超出部分会被截断,所以打印出来的是
24.5, 1, 2
. - 浮点表示的[24.5, 1.0, 2.0]可以通过int_repr方法打印出定点表示.
- int_repr等效与naive_quant的实现
量化流程分:
后量化(ptq), 量化参数基于calib数据获取
QAT:量化参数来自训练数据。
从量化作用位置上来分:
只量化权重
动态量化(权重静态量化,激活动态量化)
静态量化(权重和激活静态量化)
对于输入r,Q(r) = round(r/S + Z)
, 其中S,Z分别是quant scale和bias。
通过公式r^ = (Q(r) -Z)* S
反量化操作可以获取浮点数r^,量化误差:quant_error = r - r^
量化的目的就是找到一个合适的S,对于torch来说,可以通过 MinMaxObserver, MovingAverageMinMaxObserver, HistogramObserver
这些方法来统计quant scale和bias。
- 非对称量化(Affine or asymmetric quantization)
affine策略对沿激活值偏向一边时更合适(比如范围在[-0.5,1]之间),它直接统计了最大最小值,不适合做weight的量化。对应方法:torch.per_tensor_affine - 对称量化
不需要计算zero point, 对于skew分布的输入量化效果会很差。weight更适合使用per-channel 对称量化。对应函数:torch.per_tensor_symmetric
一般来说,对于weight使用对称 MinMax量化会更好,对于激活使用affine-per-tensor + MovingAverageMinMax 会更好。
示例代码:
Affine or asymmetric quantization
def get_symmetric_range(x):
beta = torch.max(x.max(), x.min().abs())
return -beta.item(), beta.item()
def get_affine_range(x):
return x.min().item(), x.max().item()
torch 可选的量化策略有:
per_channel_affine: qscheme = ...
per_tensor_symmetric: qscheme = ...
per_channel_symmetric: qscheme = ...
per_channel_affine_float_qparams: qscheme = ...
qconfig与obser的关系:
torch.quantization.QConfig
activation
MovingAverageMinMaxObserver
qscheme:torch.per_tensor_affine or torch.per_tensor_symmetric
min_val
max_val
caculate_qparams()
weight
MovingAveragePerChannelMinMaxObserver
...
PTQ静态量化步骤
pre-trained model -> fuse modules -> insert stubs & observers -> calib -> quantization -> ptq model
注意,quantstub&dequantstub只是一个占位符, 在calib时会被替换成observer。
参考代码:https://pytorch.org/blog/quantization-in-practice/#post-training-static-quantization-ptq
参考链接:
https://blog.csdn.net/qq_34218078/article/details/127521819
https://pytorch.org/blog/quantization-in-practice/#post-training-static-quantization-ptq