ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RK3588 INT8量化实战:YOLOv5s精度掉点分析与混合量化优化

RK3588 INT8量化实战:YOLOv5s精度掉点分析与混合量化优化 1. 为什么我要死磕 INT8 这个数字RK3588 这块板子做边缘推理绕不开一个核心矛盾算力看着挺唬人6 TOPS 的 NPU 标称值摆在那里但真把 YOLOv5s 跑起来FP16 精度下帧率也就那样。我最初在 RK3588 上部署 YOLOv5s 的时候FP16 模型跑 1080p 输入单核 NPU 大概能到 25 到 30 FPS 左右三核齐开能摸到 60 FPS 上下。这个成绩放在视频结构化场景里勉强够用但一旦你要做多路视频流并行推理或者模型换成 YOLOv8s 这种参数量更大的结构算力立刻捉襟见肘。这时候所有人都会告诉你同一个答案上 INT8 量化。INT8 的理论收益很诱人计算吞吐量直接翻倍甚至更多内存带宽占用砍半功耗也跟着降。但做工程的都知道量化从来不是免费的午餐。INT8 到底掉多少点 mAP这个问题没有标准答案它取决于你的模型结构、校准数据集的质量、量化策略的选择甚至跟你用的工具链版本都有关系。我这篇东西就是要把这个“掉多少点”从玄学变成可复现的工程数据。我会完整走一遍从 FP16 模型到 INT8 模型的转换流程用同一套验证集做对比测试把每一层的量化误差都摊开来看。如果你正在 RK3588 上做模型部署或者正在纠结要不要上 INT8这篇内容应该能帮你省下不少试错时间。2. 量化前的准备工作别急着转模型2.1 确认你的工具链版本RK3588 的 NPU 工具链迭代挺快的不同版本的 RKNN-Toolkit2 在量化策略上有明显差异。我踩过的坑是用 1.4.0 版本转换出来的 INT8 模型mAP 掉了将近 8 个点换成 1.6.0 之后同样的流程只掉了 3 个点出头。所以第一步不是急着写转换脚本而是先把环境版本确认清楚。我目前稳定使用的组合是RKNN-Toolkit2: 1.6.0RKNPU2 驱动: 0.9.6Python: 3.8ONNX: 1.14.0PyTorch: 1.13.1导出 ONNX 用注意RKNN-Toolkit2 的版本和板端 NPU 驱动版本必须匹配否则模型加载会直接报错。我见过有人用 1.6.0 的 toolkit 转模型板子上跑的是 0.8.2 的驱动结果推理输出全是乱码。2.2 模型导出从 PyTorch 到 ONNX 的关键细节YOLOv5s 的官方仓库直接提供了 export.py 脚本但默认导出参数有几个地方需要改。我用的命令是这样的python export.py --weights yolov5s.pt --include onnx --opset 12 --img-size 640 640 --batch-size 1这里有几个点值得展开说。opset 版本选 12 而不是更高的 13 或 14是因为 RKNN-Toolkit2 对 opset 12 的支持最稳定高版本 opset 里的一些算子转换容易出问题。img-size 设成 640x640 是 YOLOv5s 的标准输入尺寸如果你实际部署时用 1080p 输入建议在导出时就固定好尺寸不要依赖动态 shapeRK3588 的 NPU 对动态 shape 支持有限。导出完成后我习惯用 onnxsim 做一次简化python -m onnxsim yolov5s.onnx yolov5s-sim.onnx简化前后的模型在精度上完全一致但计算图会更干净RKNN 转换时出错的概率明显降低。这一步不是必须的但强烈建议做。2.3 校准数据集的准备决定量化精度的命门INT8 量化的核心原理是把 FP32 的权重和激活值映射到 8 位整数区间这个映射过程需要统计激活值的动态范围。校准数据集的作用就是给工具链提供一批“典型输入”让它统计出合理的缩放因子和零点。我见过太多人随便拿几十张图就去做校准结果量化后模型直接崩掉。校准数据集的选择有几个硬性要求数量至少 200 到 500 张我一般用 500 张。太少会导致统计不充分太多则转换时间线性增长。分布必须覆盖你实际部署场景中的所有类别和光照条件。比如你做安防监控白天、夜晚、逆光、雨雾天气的图都要有。标注校准阶段不需要标注文件但你需要确保这些图里包含模型要检测的目标。如果校准集里全是背景图量化后的模型对目标的响应会严重退化。我通常从训练集里随机抽 500 张再额外补充 100 张验证集里的困难样本。校准数据用 numpy 数组保存格式是 NHWC 的 uint8尺寸和模型输入一致。import cv2 import numpy as np import os calib_dir calib_images calib_data [] for img_name in os.listdir(calib_dir)[:500]: img cv2.imread(os.path.join(calib_dir, img_name)) img cv2.resize(img, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) calib_data.append(img) calib_data np.array(calib_data, dtypenp.uint8) np.save(calib_data.npy, calib_data)提示校准数据的预处理方式必须和推理时完全一致。如果你推理时做了归一化除以 255校准数据也要做同样的处理。但 RKNN 工具链通常接受 uint8 输入内部会自动处理归一化具体看你的模型定义。3. INT8 量化转换全流程拆解3.1 RKNN 转换脚本的核心参数RKNN-Toolkit2 的转换脚本看起来简单但每个参数背后都有讲究。我先把完整脚本贴出来然后逐项解释from rknn.api import RKNN rknn RKNN(verboseTrue) # 配置模型输入 rknn.config( mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588, quantized_dtypeasymmetric_quantized-8, quantized_algorithmnormal, optimization_level3 ) # 加载 ONNX 模型 ret rknn.load_onnx(modelyolov5s-sim.onnx) if ret ! 0: print(Load model failed) exit(ret) # 构建模型指定校准数据集 ret rknn.build(do_quantizationTrue, datasetcalib_data.npy) if ret ! 0: print(Build model failed) exit(ret) # 导出 RKNN 模型 ret rknn.export_rknn(yolov5s_int8.rknn) if ret ! 0: print(Export model failed) exit(ret)quantized_dtype我选的是asymmetric_quantized-8也就是非对称量化。对称量化dynamic_fixed_point-8在某些层上精度损失更小但 RK3588 的 NPU 对非对称量化的硬件支持更好实际推理速度更快。这是一个精度和速度的权衡后面我会给出两种方案的对比数据。quantized_algorithm有两个选项normal和mmse。mmse算法会尝试最小化量化误差的均方值理论上精度更高但转换时间会长很多。我实测下来mmse在 YOLOv5s 上大概能多挽回 0.5 到 1 个点的 mAP但转换时间从 3 分钟涨到 15 分钟。如果你的模型对精度极其敏感可以开mmse。optimization_level设成 3 是最高优化级别工具链会做一些算子融合和内存布局优化。这个参数一般不用改。3.2 混合量化哪些层该保留 FP16纯 INT8 量化对 YOLOv5s 来说有点激进尤其是检测头部分的卷积层量化误差会直接反映到框的回归精度上。RKNN-Toolkit2 支持混合量化也就是你可以指定某些层保持 FP16其余层用 INT8。我通常会把以下几类层设为 FP16检测头的最后几层卷积负责输出框坐标和类别分数第一层卷积输入层对量化误差最敏感任何包含 sigmoid 或 softmax 激活的层设置方法是在config里加一个hybrid_quantization参数或者在 build 之后用rknn.hybrid_quantization_step1和rknn.hybrid_quantization_step2做精细调整。我一般用 step1 生成量化误差报告然后根据报告手动指定哪些层不量化。# 混合量化第一步生成误差分析 rknn.hybrid_quantization_step1(datasetcalib_data.npy) # 查看生成的 quantization_error_analysis.txt # 手动编辑 config 文件指定不量化的层 # 混合量化第二步应用配置 rknn.hybrid_quantization_step2( model_inputyolov5s-sim.onnx, data_inputyolov5s-sim.quantization.cfg, model_quantizedyolov5s_mixed.rknn )注意混合量化不是银弹。保留 FP16 的层越多推理速度越慢。我的经验是控制在总层数的 10% 到 15% 之间能在精度和速度之间取得比较好的平衡。3.3 量化误差的逐层分析方法RKNN-Toolkit2 在 build 阶段会生成一个量化误差分析文件里面列出了每一层的余弦相似度。余弦相似度越接近 1说明量化后的输出和 FP32 输出越接近。我一般会重点关注相似度低于 0.98 的层。# 误差分析文件通常长这样 # Layer Name Cosine Similarity # Conv_0 0.9992 # Conv_1 0.9987 # ... # Conv_75 0.9721 - 这层有问题 # Conv_76 0.9688 - 这层也有问题对于相似度低的层我有三个处理策略一是把它加入混合量化的 FP16 列表二是检查这层的输入是否经过了不合适的归一化三是考虑在训练阶段加入 QAT量化感知训练来提升这层的鲁棒性。4. 精度对比测试数据说话4.1 测试环境与评估指标我的测试环境是这样的硬件RK3588 开发板16GB 内存NPU 三核全开系统Ubuntu 20.04内核 5.10推理框架RKNPU2 Runtime测试数据集COCO val2017 的子集共 5000 张图评估指标mAP0.5 和 mAP0.5:0.95评估脚本我用的是 YOLOv5 官方仓库里的 val.py把 RKNN 推理结果转换成 YOLO 格式的 txt 文件然后调用官方的评估逻辑。这样能保证评估标准和训练时一致。4.2 FP16 vs INT8 vs 混合量化三组数据对比我跑了三组模型每组都在同一台板子上用同样的推理脚本测试。结果如下模型类型mAP0.5mAP0.5:0.95推理延迟单帧NPU 占用FP1656.8%37.4%18.2ms单核 65%INT8 纯量化52.1%33.8%9.7ms单核 45%INT8 混合量化55.6%36.5%12.4ms单核 52%这组数据很能说明问题。纯 INT8 量化掉了 4.7 个点的 mAP0.5mAP0.5:0.95 掉了 3.6 个点。这个损失在安防场景里是致命的很多小目标直接检不出来了。混合量化把损失压到了 1.2 个点推理速度虽然比纯 INT8 慢了 28%但比 FP16 还是快了 32%。4.3 掉点最多的类别分析我把 COCO 的 80 个类别拆开看发现掉点分布很不均匀。掉点最严重的几个类别是小目标类别如 bird、traffic lightmAP 掉了 8 到 12 个点密集场景类别如 person 在人群中的检测掉了 6 到 9 个点低对比度类别如 snowboard 在雪地背景中掉了 7 到 10 个点相反大目标类别如 bus、truck的掉点基本在 2 个点以内。这个规律很好理解小目标的特征在量化后更容易被噪声淹没因为它们的激活值本身就小量化误差的相对占比更大。提示如果你的应用场景以小目标检测为主纯 INT8 基本不可行必须上混合量化或者 QAT。5. 常见问题与排查实录5.1 量化后模型输出全零或全乱这是最常见的问题通常有三个原因。一是校准数据集格式不对RKNN 要求输入是 NHWC 的 uint8 数组如果你传了 float32 或者 NCHW 格式量化统计会完全错乱。二是 mean 和 std 配置和训练时不一致比如训练时用了 ImageNet 的均值和方差转换时却设成了 0 和 255。三是模型里有 RKNN 不支持的算子转换时被静默跳过了。排查方法很简单先用rknn.inference在 PC 上跑一遍量化后的模型看输出是否正常。如果 PC 上正常但板子上不正常那就是驱动版本或者 runtime 的问题。5.2 量化后推理速度反而变慢这种情况通常发生在混合量化配置不当的时候。如果你把太多层设成了 FP16NPU 需要在 INT8 和 FP16 之间频繁切换反而比纯 FP16 还慢。我的经验是混合量化的 FP16 层比例不要超过 15%而且尽量让这些层集中在网络的尾部减少切换次数。另一个可能的原因是optimization_level设得太低。我建议至少设成 2设成 3 最好。5.3 校准数据集不够导致量化偏差如果你发现量化后的模型在某些场景下表现特别差但在其他场景下还行大概率是校准数据集覆盖不够。解决办法是针对性补充困难样本。比如你的模型在夜间场景掉点严重那就往校准集里加 100 张夜间图重新转换。我一般会做两轮校准第一轮用随机抽样的 500 张跑一遍评估第二轮根据评估结果把掉点最严重的场景的图补充进去再跑一遍。两轮下来量化精度通常能再提升 1 到 2 个点。5.4 常见问题速查表问题现象可能原因排查方法解决方案输出全零校准数据格式错误检查 dtype 和 shape确保 NHWC uint8输出乱码驱动版本不匹配查看板端 dmesg统一 toolkit 和驱动版本精度暴跌校准集覆盖不足分析各类别掉点补充困难样本速度变慢混合量化比例过高统计 FP16 层占比控制在 15% 以内加载失败模型文件损坏检查文件大小重新导出6. 我的实操心得与避坑建议6.1 量化不是一次性的工作很多人把量化当成一个“转换”步骤跑完脚本就完事了。实际上量化是一个迭代优化的过程。我的习惯是至少做三轮第一轮纯 INT8 摸底看掉点情况第二轮加混合量化把掉点严重的层捞出来第三轮针对性补充校准数据做精细调优。三轮下来通常能把 INT8 的精度损失控制在 1.5 个点以内。6.2 别忽视预处理和后处理的一致性量化模型的输入输出和 FP16 模型可能有细微差异尤其是输出层的反量化过程。如果你在后处理里硬编码了置信度阈值量化后可能需要微调。我一般会在量化后重新跑一遍阈值扫描找到最优的置信度和 NMS 阈值组合。6.3 板端推理的实测数据比 PC 模拟更重要RKNN-Toolkit2 提供了 PC 端的模拟推理功能但模拟结果和板端实测经常有偏差。我遇到过 PC 上模拟 mAP 只掉 2 个点板子上实测掉了 5 个点的情况。所以最终评估一定要在板子上跑PC 模拟只能作为参考。6.4 关于 INT8 和 FP16 的选择建议如果你的应用对精度极其敏感比如医疗影像或者工业质检我建议直接用 FP16别折腾 INT8。如果你的场景是安防监控或者自动驾驶感知INT8 混合量化是性价比最高的选择。如果只是做 demo 或者对精度要求不高纯 INT8 也能凑合用。最后分享一个我常用的技巧在转换 INT8 模型之前先用 FP16 模型在板子上跑一遍完整的验证集把每个类别的 mAP 记下来。量化后再跑一遍对比每个类别的变化。这样你能精确知道哪些类别受了影响而不是只看一个总数。这个习惯帮我省了很多盲目调参的时间。
返回列表