12.8 QAT 量化实践

前言

上一小节介绍了PTQ量化,本节介绍另外一种重要量化方法——QAT(quantization aware training,感知量化训练)。

本节将从QDQ节点出发,深入了解QAT的量化过程,然后基于QAT进行ResNet50的量化及性能、效率评估。

QAT基础概念

再回顾上上小结QAT与PTQ的区别。

  • PTQ(Post-training quantization,训练后量化),是不需要训练数据(带标签的数据),通常采用小部分数据(不需要标签)进行模型激活值校准,统计激活值的分布,然后计算动态范围,再计算量化scale和Z值;对于weight和bias直接根据模型权重数据进行量化。

  • QAT(Quantization-aware training,量化感知训练) ,是需要训练数据,并需要在训练阶段插入伪量化层,训练得到带QDQ节点的模型,然后在量化时采用QDQ节点进行量化,通常可以得到比PTQ更高的精度。

    • QDQ(quantize节点、dequantize节点)节点是QAT的灵魂,是一个量化节点和反量化节点,可在训练时进行迭代优化,思想是在训练的时候获得一个好的Q节点,它的scale和Z值是比较好的scale和Z值,为什么说它得到的值比较好呢?因为这些值可通过DQ节点恢复出更接近真实值的数据,因此认为训练阶段获得的Q节点中的scale和Z值是较好的选择。
    • ptq-vs-qat-2

伪量化节点

刚接触量化时,伪量化是较难理解的,伪量化的思想是在训练时模拟量化带来的误差,通过反向传播来对抗误差带来的精度下降,从而得到一个较好的scale和Z值。

具体可参考Nvidia的PPT《toward-int8-inference-deploying-quantization-aware-trained-networks-using-tensorrt》

下图是一个常规fp32的运算过程,接下来将逐步讲解QAT过程。

qdq-diagram2

第一步,插入QDQ(fake quant ops)节点。

插入QDQ的地方即需要量化的地方,回顾上上小结,需要量化的通常是weight、bias和activation,因此有以下4个节点需要量化:

X:可看成第一个activation,因此是需要量化的

Wx:conv1的权重

Relu:之所以在Relu而不是conv1之后,是因为Relu是线性的,可以合并它两,在Relu后做FQ即可。

Wy:conv2的权重

qdq-diagram3

qdq-diagram4

第二步,训练导出ONNX,最终得到scale和Z值。

qdq-onnx

第三步,在TensorRT中对计算图进行优化

  • 常量的折叠:如权重的Q节点可与权重合并,无需在真实推理中由fp32的权重经过scale和Z变为int8的权重

qdq-diagram5

  • op融合:将DQ信息融合到算子(如图中conv)中,通过op融合,模型计算将变为真实的int8输入、int8输出

    qdq-constant-fuse

qdq-constant-fuse2

于TensorRT是如何做op融合的,暂时找不到相关资料,暂且略过。

思想大概能理解,QConvRelu是吸收了DQ节点的信息,改变weight的数值,这样能更好的计算int8与int8的卷积。

通过以上案例可知conv2输出的是fp32,因为它没有插入QDQ,而conv1输出的是int8,因为插入了QDQ。

这点可以让我们在量化时根据需要,自定义插入QDQ,使得需要高精度输出的地方保持fp32。大意如下图所示

qat-inference-diagram

总结一下,QAT的过程:

  1. 插入QDQ
  2. 训练,得到scale和Z值,导出onnx模型
  3. TensorRT图优化、算子融合,导出engine,算子融合中将DQ信息注入op中,使得op接收int8,输出int8,从而提升了运算速度。

ResNet50 QAT 实践

接下来进行QAT实践,初识量化时,一直认为QAT比PTQ繁琐,因为QAT需要训练,而PTQ直接校准就好了。但是,在代码实现上,QAT反而比PTQ简单许多。

流程分析

首先,分析QAT的实现流程步骤:

  • 第一步,初始化pytorch_quantization,将pytorch的模块替换掉,这一步pq库已经封装好了,只需要一行代码quant_modules.initialize()
  • 第二步,创建模型、加载训练权重
  • 第三步,常规训练步骤
  • 第四步,保存成ONNX模型和pth模型

由此可知,只是在常规训练流程的代码中,插入quant_modules.initialize() 即可。

当然,在QAT训练和常规训练不一样,学习率通常要小100倍,同时可以用余弦下降法,逐步下降学习率。

TensorRT文档中给了一些QAT的建议:

Usually, it doesn’t need to fine tune very long. We usually use around **10% of the original training schedule**, starting at **1% of the initial training learning rate**, and a cosine annealing learning rate schedule that follows the decreasing half of a cosine period, down to 1% of the initial fine tuning learning rate (0.01% of the initial training learning rate).

代码说明

同样的,代码位于chapter-8/01_classification中,代码整体与训练代码train_main.py保持一致,其中删减了一些不必要的日志记录代码段。

同时修改了学习率调整方法,以及增加ONNX模型导出。

这里采用4epoch,lr=0.001*0.01进行训练,代码直接运行即可:python resnet50_qat.py

日志如下:

Epoch: [000/005]  Train Loss avg: 0.1086  Valid Loss avg: 0.1770  Train Acc@1 avg:  96.1009   Valid Acc@1 avg: 93.4295
Epoch: [001/005]  Train Loss avg: 0.0786  Valid Loss avg: 0.1819  Train Acc@1 avg:  97.2477   Valid Acc@1 avg: 93.4295
Epoch: [002/005]  Train Loss avg: 0.0773  Valid Loss avg: 0.1742  Train Acc@1 avg:  97.3624   Valid Acc@1 avg: 92.9487
Epoch: [003/005]  Train Loss avg: 0.0735  Valid Loss avg: 0.1771  Train Acc@1 avg:  97.0183   Valid Acc@1 avg: 93.5897
Epoch: [004/005]  Train Loss avg: 0.0633  Valid Loss avg: 0.1704  Train Acc@1 avg:  97.9740   Valid Acc@1 avg: 93.4295

可以看到,Accuracy基本稳定在93.5上下,最优为93.58,相较于原始的94.3,掉了不到1个百分点,比PTQ稳定以及性能更优。

效率对比

得到onnx模型,下面比较fp32、PTQ量化和QAT量化,三者的时延和吞吐量。

可以看到PTQ和QAT在效率上是一样的。

时延(中位数) ms:

fp32 PTQ int8 QAT int8
bs=1 1.84 1.01(↓46%) 1.07(42%)
bs=32 26.79 15.4(↓43%) 15.4(↓43%)

吞吐量(FPS)

fp32 PTQ int8 QAT int8
524 860(↑64%) 817
37.1*32=1187 64.5*32=2064(↑73.9%) 64.4*32 = 2060(↑73.8%)

FP32\ptq\qat 三种模型的对比指令如下:

trtexec --onnx=resnet_50_fp32_bs1.onnx 
trtexec --onnx=resnet_50_fp32_bs32.onnx
trtexec --onnx=resnet_50_ptq_bs1_data-num512_percentile_91.03%.onnx --int8
trtexec --onnx=resnet_50_ptq_bs32_data-num512_percentile_91.03%.onnx --int8 --saveEngine=resnet_ptq_int8.engine
trtexec --onnx=resnet_50_qat_bs1_93.43%.onnx --int8
trtexec --onnx=resnet_50_qat_bs32_93.43%.onnx --int8

注1:在使用trtexec时,看到了这样的日志,随后尝试了加上 --fp16,整体吞吐量又提升了~40%(相较于fp32提高140%),这或许是将fp32变为fp16带来的效率提升?

[09/29/2023-16:20:37] [I] FP32 and INT8 precisions have been specified - more performance might be enabled by additionally specifying --fp16 or --best

trtexec --onnx=resnet_50_qat_bs32_93.43%.onnx --int8 --best
trtexec --onnx=resnet_50_qat_bs32_93.43%.onnx --int8 --fp16

注2:在新电脑里又看到了这样的warning,尝试使用--useCudaGraph ,可以提高吞吐。尝试之后,发现吞吐从1200 提升到了1600,看来trtexec里的提示信息非常有帮助! [04/04/2024-20:55:04] [W] Throughput may be bound by Enqueue Time rather than GPU Compute and the GPU may be under-utilized. [04/04/2024-20:55:04] [W] If not already in use, --useCudaGraph (utilize CUDA graphs where possible) may increase the throughput.*

小结

本节回顾了QAT和PTQ的区别,同时介绍了QDQ(伪量化算子:量化和反量化)在训练、推理时的变化,最后通过代码实现QAT,并对比了性能和效率变化。

到这里,量化的基础就结束了,模型量化的内容远不止这些,模型量化还有更高级的内容,例如逐层精度损失分析、逐层插入/取消量化节点、自定义量化算法。

想要深入研究模型加速/量化的朋友,可以进一步学习其它资料,这里推荐一些学习量化用到的资料:

  1. TRT int8量化文档:https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#working-with-int8
  2. TRT范例代码:https://github.com/NVIDIA/TensorRT/blob/main/quickstart/quantization_tutorial/qat-ptq-workflow.ipynb
  3. pytorch-quantization:https://github.com/NVIDIA/TensorRT/tree/main/tools/pytorch-quantization
  4. nvidia int8 QAT量化介绍:https://developer.nvidia.com/blog/achieving-fp32-accuracy-for-int8-inference-using-quantization-aware-training-with-tensorrt/
  5. yolov6 量化工程范例:https://tech.meituan.com/2022/09/22/yolov6-quantization-in-meituan.html
  6. 老潘的TRTv8量化入门博客:https://zhuanlan.zhihu.com/p/479101029
  7. B站量化系列教程-ZOMI酱:https://www.bilibili.com/video/BV1VD4y1n7AR/?spm_id_from=333.788&vd_source=19b783e279d4d4ceff5b927f27ea6aa3
  8. B站量化系列教程-手写AI:https://www.bilibili.com/video/BV18L41197Uz/?spm_id_from=333.337.search-card.all.click&vd_source=19b783e279d4d4ceff5b927f27ea6aa3
  9. MIT的TinyML-第六节:MIT-TinyML-Lec06-Quantization-II

下一小节,将利用本章学习的TensorRT知识,进行YOLOv8的量化实践,学习优秀开源代码的工程化过程。

Copyright © TingsongYu 2021 all right reserved,powered by Gitbook文件修订时间: 2024年04月26日21:48:10

results matching ""

    No results matching ""