针对深度学习算法模型参数大,部署后会占用内存、计算量增加及能耗巨大。不利于后期的一个维护,是因为每一个任务单独进行。多任务学习可以有效的缓解以上问题。但多任务学习可能会带来一些问题,相似任务之间出现混乱。难训练,可以从另外一个角度出发,对模型进行量化:其主要是通过减少原始模型参数的数量或比特数来实现对内存和计算需求的降低,从而进一步降低能耗。目前性能最稳定的就是INT8的模型量化技术,相对于原始模型的FP32计算相比,INT8量化可将模型大小减少 4 倍,并将内存带宽要求减少 4 倍,对 INT8 计算的硬件支持通常快 2 到 4 倍。 值得注意的是量化主要是一种加速前向推理的技术,并且绝大部分的量化算子仅支持前向传递。
weight的8 bit量化 :data_type = qint8,数据范围为[-128, 127]
activation的8 bit量化:data_type = quint8,数据范围为[0, 255]
bias一般是不进行量化操作的,仍然保持float32的数据类型,还有一个需要提前说明的,weight在浮点模型训练收敛之后一般就已经固定住了,所以根据原始数据就可以直接量化,然而activation会因为每次输入数据的不同,导致数据范围每次都是不同的,所以针对这个问题,在量化过程中专门会有一个校准过程,即提前准备一个小的校准数据集,在测试这个校准数据集的时候会记录每一次的activation的数据范围,然后根据记录值确定一个固定的范围。
具有 AVX2 支持或更高版本的 x86 CPU:fbgemm
ARM CPU:qnnpack
ONNXRuntime 中的量化是指 ONNX 模型的 8 bit 线性量化,在量化过程中,浮点实数值映射到 8 bit 量化空间,其形式为:VAL_fp32 = Scale * (VAL_quantized - Zero_point),Scale 是一个正实数,用于将浮点数映射到量化空间,计算方法如下:对于非对称量化:scale = (data_range_max - data_range_min) / (quantization_range_max - quantization_range_min),对于对称量化:scale = abs(data_range_max, data_range_min) * 2 / (quantization_range_max - quantization_range_min),Zero_point 表示量化空间中的零:重要的是,浮点零值在量化空间中可以精确地表示。这是因为许多 CNN 都使用零填充。如果在量化后无法唯一地表示 0,则会导致精度误差。
对于动态量化,缩放因子(Scale)和零点(Zero Point)是在推理时计算的,并且特定用于每次激活。
因此它们更准确,但引入了额外的计算开销
对于静态量化,它们使用校准数据集离线计算
所有激活都具有相同的缩放因子(Scale)和零点(Zero Point)
通常,建议对 RNN 和基于 Transformer 的模型使用动态量化,对 CNN 模型使用静态量化
Int8 (QuantType.QInt8): 有符号 8 bit 整型
UInt8 (QuantType.QUInt8): 无符号 8 bit 整型
结合激活和权重,数据格式可以是(activation:uint8,weight:uint8),(activation:uint8,weight:int8)等。
这里使用 U8U8 作为 (activation:uint8, weight:uint8) 的简写,U8S8 作为 (activation:uint8, weight:int8) 和 S8U8, S8S8 作为其他两种格式的简写。CPU 上的 OnnxRuntime Quantization 可以运行 U8U8,U8S8 和 S8S8。
具有 QDQ 格式的 S8S8 是性能和准确性的默认设置,它应该是第一选择。只有在精度下降很多的情况下,才能尝试U8U8。
请注意,具有 QOperator 格式的 S8S8 在 x86-64 CPU 上会很慢,通常应避免使用。GPU 上的 OnnxRuntime Quantization 仅支持 S8S8 格式。在具有 AVX2 和 AVX512 扩展的 x86-64 计算机上,OnnxRuntime 使用 U8S8 的 VPMADDUBSW 指令来提高性能,但此指令会遇到饱和问题。一般来说,对于最终结果来说,这不是一个大问题。如果某些模型的精度大幅下降,则可能是由饱和度引起的。在这种情况下,您可以尝试 reduce_range 或 U8U8 格式,没有饱和度问题。在其他 CPU 架构(使用 VNNI 和 ARM 的 x64)上没有这样的问题。
该格式使用 DQ (Q (tensor)) 来模拟量化和去量化过程,并且 QuantizeLinear 和DeQuantizeLinear 算子也携带量化参数
所有量化运算符都有自己的 ONNX 定义,如QLinearConv、MatMulInteger 等。
# fp_32 to int8
import onnx
from onnxruntime.quantization import quantize_dynamic, QuantType
#需要量化的onnx模型
model_fp32 = 'input.onnx'
#量化后的模型
model_quant = 'output1.onnx'
quantized_model = quantize_dynamic(model_fp32, model_quant, weight_type=QuantType.QUInt8)
# QAT quantization QAT量化
import onnx
from onnxruntime.quantization import quantize_qat, QuantType
#需要量化的onnx模型
model_fp32 = 'input.onnx'
#量化后的模型
model_quant = 'output2.onnx'
quantized_model = quantize_qat(model_fp32, model_quant)
#fp_32 to fp_16
import onnx
from onnxconverter_common import float16
model=onnx.load('your.onnx')
model_fp16=float16.convert_float_to_float16(model)
onnx.save(model_fp16,'your_result.onnx')
如果量化后模型表现不佳,可考虑部分量化,也即是mixed precision。
import onnx
from onnxconverter_common import float16
model=onnx.load('your.onnx')
model_fp16=auto_convert_mixed_precision(model,test_data,rtol=0.01,atol=0.001,keep_io_types=Ture)
onnx.save(model_fp16,'your_result.onnx')