目录
- 前言
- 0. 简述
- 1. 案例运行
- 2. 补充说明
- 3. 量化分析
- 4. 探讨
- 总结
- 下载链接
- 参考
前言
自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考
本次课程我们来学习课程第七章—部署YOLOv8检测器,一起来学习分析 YOLOv8 量化掉点严重的问题
课程大纲可以看下面的思维导图
0. 简述
本小节目标:学习分析 YOLOv8 量化掉点严重的原因
这节我们主要解决上节课中提到的 YOLOv8 量化后掉点严重的的问题
下面我们开始本次课程的学习🤗
1. 案例运行
在正式开始课程之前,博主先带大家跑通 7.4-quantization-analysis 这个小节的案例
源代码获取地址:https://github.com/kalfazed/tensorrt_starter
首先大家需要把 tensorrt_starter 这个项目给 clone 下来,指令如下:
git clone https://github.com/kalfazed/tensorrt_starter.git
也可手动点击下载,点击右上角的 Code
按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2024/7/14 日,若有改动请参考最新)
由于 INT8 量化需要 calibration dataset 校准数据集,所以我们还需要下载 COCO 数据集,大家可以点击 here 下载,另外一般来说校准图片选个 1000 张左右就足够了,博主这里从 coco2017 训练集中随机选取了 1000 张,大家如果只是单纯的测试效果可以直接点击 here 下载博主准备好的 1000 张校准图片数据
和 6.3 小节案例一样,校准数据集准备好后我们还要准备一个 .txt 文档,该文档里面保存着每张校准图片的完整路径,因为本小节案例代码中是通过解析 calibration/calibration_list_coco.txt 去读取每张校准图片进行量化的,因此我们需要把我们自己校准数据集的路径覆盖写入到 calibration_list_coco.txt 中
在 7.4-quantization-analysis 主目录下新建一个 python 脚本文件,内容如下:(from ChatGPT)
import os
import re# 设置图像文件夹的路径
image_folder = '/home/jarvis/Learn/tensorrt_starter/chapter7-deploy-yolo-detection/7.4-quantization-analysis/images'
# 设置输出文件的路径
output_file = 'calibration/calibration_list_coco.txt'# 辅助函数,用于从文件名中提取数字
def extract_number(filename):# 从文件名中提取数字,确保正则表达式匹配 "任何数字" 部分match = re.search(r'\d+', filename)# 如果找到数字,转换为整数;否则返回一个很大的数字,以便将这些文件放在列表末尾return int(match.group()) if match else float('inf')# 初始化一个列表用来存储图像路径
image_paths = []# 遍历指定文件夹
for filename in os.listdir(image_folder):# 检查文件是否为 JPEG 图像if filename.endswith('.JPEG') or filename.endswith('.jpg'):# 构建完整的文件路径file_path = os.path.join(image_folder, filename)# 添加到列表image_paths.append(file_path)# 对图像路径列表按文件名中的数字排序
image_paths.sort(key=lambda path: extract_number(os.path.basename(path)))# 打开文件准备写入
with open(output_file, 'w') as f:for path in image_paths:# 将路径写入文件f.write(path + '\n')print(f'Image paths have been saved to {output_file}, number = {len(image_paths)}')
注意将 image_folder
的路径修改为你自己的校准数据集路径,执行上述脚本输出如下:
我们在 calibration/calibration_list_coco.txt 中可以看到路径的修改,如下图所示:
至此,我们完成了校准图片的准备工作,下面我们就来执行这个小节的案例
整个项目后续需要使用的软件主要有 CUDA、cuDNN、TensorRT、OpenCV,大家可以参考 Ubuntu20.04软件安装大全 进行相应软件的安装,博主这里不再赘述
假设你的项目、环境准备完成,下面我们一起来运行下 7.4-quantization-analysis 小节案例代码
开始之前我们需要创建几个文件夹,在 tensorrt_starter/chapter7-deploy-yolo-detection/7.4-quantization-analysis 小节中创建一个 models 文件夹,接着在 models 文件夹下创建一个 onnx 和 engine 文件夹,总共三个文件夹需要创建
创建完后 7.4 小节整个目录结构如下:
接着我们要导出 YOLOv8 的 ONNX 模型,具体流程可以参考:七. 部署YOLOv8检测器-deploy-yolov8-basic,博主这里就不再赘述了,大家也可以点击 here 下载博主准备好的 ONNX
大家需要把刚导出好的 ONNX 模型放在 tensorrt_starter/chapter7-deploy-yolo-detection/7.4-quantization-analysis/models/onnx 文件夹下
ONNX 模型准备好后,我们把韩君老师生成的推理结果给删除,方便后续查看我们自己推理的结果,指令如下:
cd 7.4-quantization-analysis
rm -rf *
然后我们需要利用 ONNX 生成对应的 engine 完成推理,在此之前我们需要修改下整体的 Makefile.config,指定一些库的路径:
# tensorrt_starter/config/Makefile.config
# CUDA_VER := 11
CUDA_VER := 11.6# opencv和TensorRT的安装目录
OPENCV_INSTALL_DIR := /usr/local/include/opencv4
# TENSORRT_INSTALL_DIR := /mnt/packages/TensorRT-8.4.1.5
TENSORRT_INSTALL_DIR := /home/jarvis/lean/TensorRT-8.6.1.6
Note:大家查看自己的 CUDA 是多少版本,修改为对应版本即可,另外 OpenCV 和 TensorRT 修改为你自己安装的路径即可
接着我们就可以来执行编译,指令如下:
make -j64
输出如下:
接着执行:
./bin/trt-infer
输出如下:
我们这里可以看到每张图片的一个推理结果,并且推理后的图片保存在 data/result 文件夹下,大家可以查看,如下图所示:
如果大家能够看到上述输出结果,那就说明本小节案例已经跑通,下面我们就来看看具体的代码实现
2. 补充说明
在分析代码之前我们先来看下韩君老师在这小节中写的 README 文档
这个小节主要解决 7.3-deploy-yolo-basic
中出现的 int8 量化掉点严重的问题,当我们发现 int8 量化掉点严重的时候,我们可以有以下几个参考的点:
- 是否在 input/output 附近做了 int8 量化
- 如果是 multi-task 的话,是否所有的 task 都掉点严重
- calibration 的数据集是不是选的不是很好
- calibration batch size 是不是选择的不是很好
- calibrator 是不是没有选择好
- 某些计算是否不应该做量化
- 使用 polygraphy 分析
其实,恢复精度有很大程度上需要依靠经验,但是比较好的出发点是从量化的基本原理去寻找答案,结合 yolov8 的模型架构,我们就能顺理成章的猜到 yolov8 掉点严重的原因是因为 IInt8EntropyCalibrator2
。这个小节带着大家引导一下思路,以及今后遇到类似的问题的时候应该如何去考虑。
3. 量化分析
我们首先利用 trt-engine-explore 来查看 tensorRT 优化后的 engine 结构,来看下每一个 layer 的精度是否符合我们的预期
关于 trt-engine-explore 的使用大家可以参考:六. 部署分类器-trt-engine-explorer,博主这里就不再赘述了
上面是 yolov8n-int8.engine 的部分结构图,从中我们可以发现几个现象:
- Reformat 节点大量存在,可能会影响模型推理速度
- 靠近输入部分是 INT8 精度,可能会影响模型精度
- 靠近输出部分是 FP32 精度
那掉点严重是不是因为输入部分 INT8 导致的呢?那其实我们上节课有提到目标框的回归比较好,而分类比较差,如果是输入部分 INT8 量化导致的问题,那应该二者都有损失,所以我们猜测应该不是输入 INT8 量化导致的
那是不是数据集的原因呢?是不是 calibration dataset 没有选好呢?是不是选多了或者选少了呢?那博主这里没有测试,大家感兴趣的可以自行测试下,不过一般来说应该不是 calibration dataset 导致的,因为首先 calibration dataset 来自于 coco2017 的训练数据,来源没问题,其次它是博主随机挑选的,分布没问题,最后数量是 1000 张也没有啥问题,因此 calibration 的数据集应该不是导致掉点的主因
那是不是 calibration batch size 的原因呢?我们在 四. TensorRT模型部署优化-quantization(calibration) 有提到 calibration 时的 batch size 会影响模型量化后的精度,更准确来说会影响 histogram 的分布,这个其实跟 TensorRT 在构建浮点数的 histogram 的算法有关。那是不是 batch size 选择的原因呢?那其实博主认为 calibration 的 batch size 大小其实对最终量化后的精度没有啥影响,当然这是博主个人的一些想法,大家感兴趣的可以自行测试下看看 batch size 的大小能否影响精度
那下一个可能的原因是不是 calibrator 没有选择好呢?在 trt_calibrator.hpp 文件中我们本来默认使用的是 IInt8EntropyCalibrator2
但是在 yolov8 的 int8 量化中韩君老师修改成了 IInt8MinMaxCalibrator
,如下所示:
// class Int8EntropyCalibrator: public nvinfer1::IInt8EntropyCalibrator2 {
class Int8EntropyCalibrator: public nvinfer1::IInt8MinMaxCalibrator {
// class Int8EntropyCalibrator: public nvinfer1::IInt8LegacyCalibrator {
// class Int8EntropyCalibrator: public nvinfer1::IInt8MinMaxCalibrator {
// class Int8EntropyCalibrator: public nvinfer1::IInt8Calibrator {public:Int8EntropyCalibrator(const int& batchSize,const std::string& calibrationSetPath,const std::string& calibrationTablePath,const int& inputSize,const int& inputH,const int& inputW);...
}
那我们来对比看下两个不同的 calibrator 的推理效果图,如下所示:
似乎也没有太大的区别,不过 MinMax calibrator 量化出来的模型检测率比较高,其回归和分类效果较 Entropy calibrator 要好点🤔
韩君老师在代码中有记录测试结果,在 TensorRT-8.6 下:
IInt8EntropyCalibrator2
:int8 精度下降严重,classness 掉点严重IInt8MinMaxCalibrator
:int8 推理精度可以恢复, classness 的 max 会凸显出来
那为什么会这样呢?韩君老师给出的解释是因为 MinMax 会把 FP32 中的最大最小值也会留下来,而 yolov8 的 fp32 的最大最小值非常重要,因为 C2F 架构中会 DWConv,depthwise 的存在会潜在性的让 output tensor 的 FP32 在每一个 layer 都会有很大不同的分布,如果用 entropy 的话,很有可能会让某些关键信息流失掉
4. 探讨
那首先上节案例中 yolov8 量化后掉点严重的原因博主认为依旧是因为预处理方式的实现将图片缩放到 224x224 导致的,而并不是 calibrator 导致的,而这节案例代码中韩君老师将输入修改回 640x640,所以从推理结果来看两个 calibrator 其实并没有太大的区别,掉点也是正常 PTQ 量化的掉点
那至于哪个 calibrator 比较好,需要大家具体拿数据集测试下 mAP 等评价指标,单纯看一两张推理结果图看不出什么东西来,不过像 YOLO 系列检测器而言就博主的经验一般来说用 Entropy calibrator 就行,但是也要看具体的数据集和模型,大家如果在自己的数据集发现掉点比较严重(一般而言 1~2 个掉点较正常)可以考虑换一个 calibrator,如果还是一样那就需要做敏感层分析或者上 QAT 了
其次关于 YOLOv8 的结构博主印象中 C2F 结构中是没有 DWConv 的,都是正常的 Conv,博主之前简单绘制过 YOLOv8 网络结构图如下:
我们可以使用 onnx 库来查找 onnx 模型中是否存在 DWConv,代码如下所示:(from ChatGPT)
import onnx
import numpy as npdef is_dwconv(weight_tensor):# 获取卷积权重的维度weight_shape = weight_tensor.dims# 深度卷积的特点是输出通道数等于输入通道数if len(weight_shape) == 4 and weight_shape[1] == 1 and weight_shape[0] == weight_shape[1] * weight_shape[0]:return Truereturn Falsedef check_dwconv_by_weights(model_path):# 加载ONNX模型model = onnx.load(model_path)graph = model.graph# 遍历模型的所有节点for node in graph.node:if node.op_type == 'Conv': # 只检查Conv节点# 获取该节点的输入权重for input_name in node.input:# 查找权重的初始值for initializer in graph.initializer:if initializer.name == input_name:weight_tensor = initializer# 检查是否是深度卷积if is_dwconv(weight_tensor):print(f"Found DWConv node: {node.name}")return Trueprint("No DWConv node found in the ONNX model.")return False# 使用模型路径进行检查
model_path = 'yolov8n.onnx' # 替换为你的ONNX模型路径
check_dwconv_by_weights(model_path)
因此,总的来说韩君老师在代码中提到的 DWConv 影响 yolov8 calibrator 的选择博主认为并没有这个说法,不过单纯推理的图片来看 MinMax calibrator 量化出来的效果确实要较 Entropy calibrator 要好,另外上节案例代码量化掉点的主因是预处理函数输入分辨率的问题,而 yolov8 的 calibrator 选择可以根据具体的数据集、模型以及 mAP 评价测试指标来综合考虑
总结
本次课程我们学习分析了 YOLOv8 量化掉点严重的问题,虽然没有定位到真正的问题所在但是我们还是掌握了当模型量化掉点严重时该怎么做。首先利用 trt-engine-explore 工具查看 INT8 模型的结构,观察是否在 inpput/output 附近做了 INT8 量化,如果是的可以考虑做敏感层分析输入输出量化对精度影响,让它们保持在 FP16 精度。另外可以考虑 calibration 数据集是不是没有选好,换一些校准图片测试下,还有 calibrator 的选择也很重要,如果这些都没有办法解决掉点问题,那么需要使用 polygraphy 工具进行分析
OK,以上就是 7.4 小节案例的全部内容了,下节我们进入第八章节—CUDA-BEVFusion部署分析的学习,敬请期待😄
下载链接
- tensorrt_starter源码
- 7.4-quantization-analysis案例文件
参考
- Ubuntu20.04软件安装大全
- https://github.com/kalfazed/tensorrt_starter
- 四. TensorRT模型部署优化-quantization(calibration)
- 六. 部署分类器-trt-engine-explorer
- 七. 部署YOLOv8检测器-deploy-yolov8-basic