前言
深度学习模型部署过程中,我们希望可以快速地对模型进行压缩和推理加速,离线量化是一种常用的压缩加速方法。
一、量化概述
量化是指将连续的信号取值,离散化为有限个取值的过程。
深度学习模型量化是使用低比特定点数表征模型浮点参数的压缩方法。
我们以线性量化的 conv 计算为例,详细说明量化计算的流程。线性量化的数学表达如下:
对于模型 weight 而言,可以离线标定其数值范围,而模型 activation 数值范围,则需要少量标定集进行统计。
基于上述公式,可以将模型浮点参数量化成为整型参数。线性量化 Conv 的计算流程如下:
int8 输入与 int8 权重卷积运算后,累加至 int32,乘以 fp32 的 s_w 和 s_x 进行解量化,再与 fp32 bias 相加,最后进行输出量化;
若部署平台不支持 fp32 的 scale 进行解量化,则会将 (s_w * s_x / sy) 近似为整数乘法和位移代替(或者将 scale 近似为 power of two 的形式),
并累加量化的 bias,最后将 int32 中间结果直接 requantize 至 int8 输出。
量化推理过程中,为避免不必要的数据转换开销,应量化将尽可能多的层。
二、量化工具框架
PPQ Paser 模块可读取 onnx 或 caffe 模型,并解析成内部格式。解析完成后,Scheduler 模块对模型进行切分与调度,粗颗粒度地划分量化与非量化算子。
Quantizer 模块是 PPQ 量化执行的中枢,为模型算子分配特定的部署平台,并初始化量化设置,调用各种优化 Pass,完成量化联合定点、图融合及量化优化。
Executor 模块依据模型拓扑关系,调用底层算子实现,执行前向推理。模型量化完成后,调用 Exporter 模块,导出模型和量化参数。
三、量化工具图调度
执行量化前,我们应首先划分模型的可量化区和不可量化区。
单一的根据算子类型来判断是否量化是不合理的,因为训练框架的模型转换成 onnx 模型时,大量程序逻辑被转换成细碎的 onnx 算子实现。
比如分类模型和检测模型的前处理部分,经常出现 Shape 相关算子,用于获取动态输入的相关信息;
而检测模型的后处理部分则存在大量争议算子。如下图所示。
PPQ 图调度
PPQ 使用 graph dispatcher 将图中所有算子划分为三类:
不可量化区:这区域的算子与 shape或者 index 有关,一旦量化将导致图的计算发生错误,因此不可量化,同时默认被调度到 Host 端以浮点精度执行。
可量化区:这区域的算子被认为是可以量化的,它们是 input, conv, gemm 的延伸算子,PPQ 使用数值追踪技术标记这些算子,这些算子处理的运算一定是 input, conv, gemm 的计算结果。它们被调度到设备端以 int8 精度执行。
争议区:这区域的算子同时接收来自不可量化区以及可量化区的输入,所有争议区的算子延伸也是争议算子,量化这些算子是有风险的,PPQ 不能保证量化产生的影响。该区算子被调度到设备端以浮点精度执行。
为了找出这些区域,PPQ 使用图搜索引擎进行区域划分,其基本思想是通过枚举所有算子的计算情况,确定输入的来源是否与 shape 或 index 相关。你可以通过 ppq.scheduler 中的代码看到它们的具体实现。
在 PPQ 中,我们实现了三种不同的调度逻辑,不同的调度逻辑将产生不同的区域划分:
激进式调度:该调度方法将所有争议区算子视作可量化的。
保守式调度:该调度方法将所有争议区算子视作不可量化的(它们依然将被调度到设备端)。
pplnn:该调度方法只量化卷积层与其相关算子。
对于 mask_rcnn 这样的复杂网络结构来说,我们推荐你尝试使用保守式调度或 pplnn 调度方式。对于复杂检测网络而言,量化其后处理算子是风险的,可能导致网络精度大幅下降。
四、量化联合定点与图融合
量化部署过程中,时常遇到量化模拟器与硬件精度不对齐的问题,这也是量化部署非常令人「头疼」的问题。
硬件精度未对齐的主要原因在于 —— 推理库后端会对模型做大量的联合定点和图融合优化,我们写入的量化参数已被后端融合或修改,量化模拟与后端推理并不一致,导致优化算法大打折扣。
常见的融合操作包括 Conv-Relu 融合、Conv-Add 融合与 Conv-Clip 等;而对于一些非计算算子,如 Concat, Reshape, Slice, MaxPool 等,则需要输入输出联合定点,以避免不必要的 requantize 运算。
PPQ 可模拟常见的联合定点与图融合,如下图所示:
图中灰色模块表示量化被融合或覆盖,未生效
PPQ 使用 Tensor Quantization Config 类来描述算子数值量化的细节,其绑定在算子之上。
Executor 模块执行每一个算子时,并不会在模型中插入量化节点,而是通过一种类似于 hook 的形式,直接将量化操作添加到算子的执行逻辑中。
模型算子输入/输出变量是否量化,由算子输入/输出的 Tensor Quantization Config 的 state 属性决定。
参考链接:
https://zhuanlan.zhihu.com/p/478898529