提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
1 介绍
为了进一步弄清楚通用模型推理框架对低精度量化的支持情况,下面对TensorRT、MIGraphX、OnnxRuntime三种推理框架进行调研,总结出哪个框架更容易接入实现,符合架构设计逻辑。
目前,业内主流的量化模式有两种:训练后量化(PTQ)和量化感知训练(QAT)
- 训练后量化:神经网络模型以浮点型精度进行训练,通过推理框架计算量化参数,进行INT8量化。其中,最重要的过程就是校准,让模型在校准数据上执行,统计数据分布,然后使用该数据分布计算量化参数。
- 量化感知训练:神经网络模型在训练过程中插入QDQ模块计算缩放因子,而不需要推理框架再进行校准,可以直接以INT8精度进行推理。这种在训练过程中学习量化参数的方法,可以更准确地统计张量的数据分布,减少量化和反量化的误差。
2 推理框架
下面分别介绍三种主流推理框架的低精度量化的实现方法:TensorRT\MIGraphX\ORT
2.1 TensorRT推理框架
TensorRT同时支持训练后量化和量化感知训练,而且量化感知训练有专门插件(torch2trt/TensorFlow-TensorRT),针对tensorrt量化特性对模型对应位置插入QDQ算子,进行量化。
针对训练后量化,在校准过程中提供了多种量化校准器:
- IInt8EntropyCalibrator2:通过熵校准选择张量的比例因子来量化张量的信息论内容,通常会抑制分布中的异常值,这是当前推荐的熵校准器。默认情况下,校准发生在图层融合之前。推荐用于基于 CNN 的网络。
- IInt8MinMaxCalibrator:最小最大值校准器使用激活分布的整个范围来确定比例因子。默认情况下,校准发生在图层融合之前。推荐用于 NLP 任务中。
- IInt8EntropyCalibrator:原始的熵校准器。它的使用没有LegacyCalibrator 复杂,通常会产生更好的结果。
- IInt8LegacyCalibrator:此校准器需要用户参数化,并且在其他校准器产生不良结果时作为备用选项提供。
图层融合:TensoRT在量化过程中做了很多图层融合操作,如下图所示。
以Resnet50为例,下图是一个onnx已经量化后的onnx模型:
TensorRT在量化的过程中,通过匹配在对应节点插入QDQ算子,后续进行图层融合,通过移除DequantizeLinear和QuantizeLinear算子,融合quantizeLinear+Conv+BN+Relu+MaxPool算子执行int8精度计算。如下图红框圈中的地方进行整体融合。
我们通过打印TRT的详细的日志中,也可以看到是这样融合的:
另外,Tensorrt也可以通过调整QDQ插入位置进行次优融合优化:
2.2 MIGraphX推理框架
MIGraphX也支持训练后量化和量化感知训练,但是针对量化感知训练没有专用的量化插件支持,往往利用tensorrt插件导出的QAT模型,总会有不支持的问题。
针对训练后量化,在校准过程中仅提供一种量化校准器,使用激活分布的整个范围来确定比例因子:
首先,计算缩放因子S,其中x_min和x_max表示为浮点型张量的最小值和最大值:
其次,计算出缩放因子S后,将量化/反量化操作表示为:
migraphx量化流程图:
以Resnet50为例,miagrpahx与TensorRT最大的区别在于量化方式不同,不支持conv算子融合。因此,QDQ的插入位置也会有所不同。
如下图所示,首先,通过匹配卷积/GEMM算子,插入QDQ算子。其次,通过移动DequantizeLinear算子在卷积/GEMM算子后,这样就保证了卷积/GEMM算子按照int8精度计算。后续,将量化和反量化算子拆分成pointwise类算子做算子融合。
2.3 OnnxRuntime推理框架
目前,ONNXruntime量化方案还不完善,目前仅支持伪量化方案。Onnxruntime量化分为两步:模型转换、模型推理。
2.3.1 模型转换
如下代码所示,将标准的FP32模型导出为INT8模型。
import onnx
import numpy as np
import onnxruntime as ort
from onnxruntime.quantization import quantize_static, CalibrationDataReader, QuantType
# 加载 ONNX 模型
model_fp32 = '/mode_path/resnet50.onnx'
model_quant = '/mode_path/resnet50_int8.onnx'
# 定义校准数据读取器
class DataReader(CalibrationDataReader):
def __init__(self, calibration_data):
self.data = calibration_data
self.iterator = iter(calibration_data)
def get_next(self):
return next(self.iterator, None)
def rewind(self):
self.iterator = iter(self.data)
input_data = np.random.rand(32, 3, 224, 224).astype(np.float32)
calibration_data = [{'input': input_data}]
# 使用量化工具进行静态量化
quantize_static(
model_fp32,
model_quant,
DataReader(calibration_data),
quant_format=QuantType.QInt8, # INT8 量化
optimize_model=True # 算子融合
)
print(f"量化后的模型保存在 {model_quant}")
其中,通过设置optimize_model为true,导出的模型可以自动融合卷积+BN+Relu算子
设置optimize_model为false时,则不做卷积融合:
2.3.2 模型推理
其实用onnxruntime进行模型推理时,按照计算图顺序执行,先经过量化操作为int8精度,再反量化为fp32精度,所以实际卷积计算精度还是按照fp32精度进行计算。因此,这种方式实际还是fp32推理,并非低精度推理。
3 总结
通过上述分析,onnxruntime的int8量化还是达不到可用的地步,而且migraphx相比Tensorrt性能明显差异大。最大区别在于Tensorrt做了很多卷积融合,极大的减少访存操作。而MIGrpahX插入的量化和反量化算子,没有办法融合到卷积中增加了很多计算量,造成这部分耗时增大,降低性能。