Horizon J6m 部署 YOLOv8s INT8 精度下降问题排查记录

Horizon J6m 部署 YOLOv8s INT8 精度下降问题排查记录

1. 问题背景

在 Horizon J6 Open Explorer 工具链中部署 YOLOv8s 时,最初采用 Ultralytics 标准导出的 ONNX 模型。

标准 YOLOv8 ONNX 的输出通常为:

plaintext 复制代码
output0: [1, 84, 8400]

其中:

plaintext 复制代码
84 = 4 个边框坐标 + 80 个类别分数

该输出已经在 ONNX 图内完成了:

plaintext 复制代码
Detect Head 卷积
→ DFL reshape
→ DFL Softmax
→ 16 个离散 bin 加权求期望
→ anchor point 解码
→ stride 缩放
→ 分类 Sigmoid
→ 输出拼接

使用 Horizon 工具链对该完整模型进行 INT8 量化后,检测精度出现严重下降。


2. 初始实验现象

在同一批 COCO val2017 前 50 张图片、相同预处理和相同评测逻辑下,原完整输出 YOLOv8s 的结果为:

模型阶段 mAP50-95 mAP50
Optimized Float 0.537 0.696
INT8 Calibrated 0.107 0.216
INT8 PTQ 约 0.116 约 0.230
INT16 Calibrated 0.532 0.689
INT16 PTQ 0.532 0.686

可以看到:

plaintext 复制代码
Float → INT8:
mAP50-95 下降约 0.430
​
Float → INT16:
mAP50-95 仅下降约 0.005

该现象说明模型并不是不能量化,而是对 INT8 位宽非常敏感。


3. 初期排查方向

最开始主要怀疑以下问题:

  1. RGB 和 BGR 顺序不一致;

  2. 输入是否重复除以 255;

  3. 校准数据范围是否错误;

  4. NCHW 和 NHWC 布局不一致;

  5. LetterBox 预处理不一致;

  6. COCO 类别编号或坐标恢复错误;

  7. ONNX 导出本身存在问题;

  8. Horizon 图优化阶段改变模型计算结果;

  9. 校准图片数量不足;

  10. INT8 对 YOLOv8 检测头不友好。

经过多轮对照后,前面的大部分原因都被排除。


4. 为什么可以排除输入和校准数据问题

校准数据采用:

plaintext 复制代码
shape: [3, 640, 640]
dtype: float32
layout: NCHW
颜色顺序: RGB
数据范围: 0~1

YAML 中运行时输入配置为:

plaintext 复制代码
input_type_train: rgb
input_type_rt: nv12
input_layout_train: NCHW
scale_value: '0.003921568627451'

这里并不是重复除以 255:

  • 校准数据已经是 ONNX 输入域的 float32,范围为 0~1;

  • 板端 NV12 输入仍然是 uint8,范围为 0~255;

  • YAML 中的 scale_value 用于将板端输入转换到模型输入域。

更关键的是,同一套校准数据下,INT16 模型可以恢复到接近浮点精度。

如果 RGB/BGR、输入范围或校准数据存在严重错误,INT16 通常也不会恢复到:

plaintext 复制代码
mAP50-95 = 0.532

因此,主要问题不在输入预处理和校准数据。


5. 根本原因分析

问题的核心在于:

Ultralytics 标准导出的 YOLOv8 ONNX 将 DFL、Softmax、Sigmoid 和边框解码全部包含在模型图中,Horizon 对整个检测图执行 INT8 量化时,这些数值敏感运算也进入了量化范围。

5.1 DFL 对 INT8 量化误差敏感

YOLOv8 的边框回归不是直接预测四个边框坐标,而是为每个方向预测 16 个离散值:

plaintext 复制代码
left   → 16 个 logits
top    → 16 个 logits
right  → 16 个 logits
bottom → 16 个 logits

随后执行:

plaintext 复制代码
logits
→ Softmax
→ 与 0~15 加权求和
→ 得到 left、top、right、bottom 距离

INT8 量化会对 logits 进行缩放、截断和取整。

即使单个 logits 的误差较小,也可能改变 16 个 bin 之间的相对关系。误差经过 Softmax 和加权求期望后,会转化为边框距离误差。

5.2 stride 会进一步放大边框误差

DFL 得到的距离还会乘以不同特征层的 stride:

plaintext 复制代码
P3 stride = 8
P4 stride = 16
P5 stride = 32

例如某个方向的 DFL 距离只偏差 0.2 个网格,在 P5 上就可能产生:

plaintext 复制代码
0.2 × 32 = 6.4 像素

四个方向都可能存在偏差,最终导致边框 IoU 明显下降。

5.3 mAP50-95 比 mAP50 下降更多

在 YOLOv8x 的对照实验中,完整输出 INT8 的结果表现为:

plaintext 复制代码
mAP50-95 下降明显
mAP50 下降相对较少

这说明模型仍然能够找到目标,但边框定位变得不够准确。

目标在 IoU=0.5 时可能仍然匹配,但在 IoU=0.75、0.85 或 0.95 等高阈值下无法匹配,因此 mAP50-95 下降更明显。

这与 DFL 和坐标解码误差的表现高度一致。


6. Raw6 解决方案

为避免将敏感后处理纳入 INT8 量化图,重新导出了 YOLOv8s Raw6 ONNX。

模型固定输出六路原始特征:

plaintext 复制代码
p3_box: [1, 64, 80, 80]
p3_cls: [1, 80, 80, 80]

p4_box: [1, 64, 40, 40]
p4_cls: [1, 80, 40, 40]

p5_box: [1, 64, 20, 20]
p5_cls: [1, 80, 20, 20]

其中:

plaintext 复制代码
64 = 4 × reg_max
reg_max = 16
num_classes = 80

新的部署边界为:

plaintext 复制代码
BPU / INT8 模型:
Backbone
→ Neck
→ Detect Head box/cls 卷积
→ 输出六路原始特征

CPU / Float32:
DFL Softmax
→ 距离加权
→ anchor point 解码
→ stride 缩放
→ 分类 Sigmoid
→ 置信度筛选
→ NMS
→ LetterBox 坐标恢复

CPU 后处理实现包括:

  • 六路输出顺序检查;

  • NCHW/NHWC 输出适配;

  • DFL Softmax;

  • 16 个 bin 的距离期望;

  • 三尺度 anchor point 解码;

  • 分类 Sigmoid;

  • class-aware NMS;

  • 原图坐标恢复;

  • COCO 评测结果格式转换。


7. Raw6 实验结果

在相同的 COCO val2017 前 50 张图片上,Raw6 得到以下结果:

模型阶段 mAP50-95 mAP50
Raw6 Original Float 0.537 0.696
Raw6 Optimized Float 0.537 0.696
Raw6 INT8 Calibrated 0.529 0.685
Raw6 INT8 PTQ 0.529 0.685

完整输出模型与 Raw6 模型的对比如下:

模型形式 阶段 mAP50-95 mAP50
完整输出 1,84,8400 Float 0.537 0.696
完整输出 1,84,8400 INT8 Calibrated 0.107 0.216
Raw6 六路输出 Float 0.537 0.696
Raw6 六路输出 INT8 Calibrated 0.529 0.685

8. 实验结果说明

8.1 Raw6 导出和 CPU 后处理正确

Raw6 Original Float 的结果为:

plaintext 复制代码
mAP50-95 = 0.537
mAP50    = 0.696

与原完整输出浮点模型完全一致。

这证明以下实现均正确:

  • 六路输出顺序;

  • DFL channel reshape;

  • DFL Softmax;

  • bin 加权求期望;

  • stride 配置;

  • anchor center 使用 x+0.5、y+0.5

  • 分类 Sigmoid;

  • class-aware NMS;

  • LetterBox 坐标恢复;

  • COCO 类别编号映射;

  • 评测代码接口。

8.2 Horizon 图优化没有影响精度

Raw6 Original Float 与 Optimized Float 完全一致:

plaintext 复制代码
Original Float  = 0.537 / 0.696
Optimized Float = 0.537 / 0.696

因此 Horizon 的普通 ONNX 图优化阶段没有破坏模型计算结果。

8.3 Raw6 INT8 量化损失很小

Raw6 从浮点到 INT8:

plaintext 复制代码
mAP50-95:0.537 → 0.529,下降 0.008
mAP50:   0.696 → 0.685,下降 0.011

该下降幅度较小,属于正常的 INT8 PTQ 精度损失。

8.4 检测头卷积本身不是主要问题

Raw6 模型中仍然被量化的部分包括:

plaintext 复制代码
Backbone
Neck
P3/P4/P5 box 分支卷积
P3/P4/P5 cls 分支卷积

但最终精度只下降 0.008。

因此可以判断:

YOLOv8 的 Detect Head 卷积本身并不是导致精度崩溃的主要原因。

真正的严重掉点发生在原始 box/cls logits 之后,即:

plaintext 复制代码
DFL Softmax
距离期望
anchor point 解码
stride 缩放
分类 Sigmoid
输出拼接

8.5 Raw6 INT8 接近完整输出 INT16

对比:

plaintext 复制代码
完整输出 INT16:
mAP50-95 = 0.532
mAP50    = 0.689

Raw6 INT8:
mAP50-95 = 0.529
mAP50    = 0.685

二者仅相差:

plaintext 复制代码
mAP50-95:0.003
mAP50:   0.004

说明通过重新划分模型部署边界,Raw6 INT8 基本达到了完整模型 INT16 的精度,同时保留了大部分卷积算子的 INT8 执行效率。


9. 最终结论

本次精度问题不是由以下因素导致:

  • ONNX 模型导出错误;

  • Horizon 图优化错误;

  • RGB/BGR 配置错误;

  • 输入重复除以 255;

  • 校准数据格式错误;

  • COCO 标注或类别编号错误;

  • LetterBox 和 NMS 整体实现错误;

  • YOLOv8 Detect Head 卷积完全不支持 INT8。

真正的原因是:

标准 YOLOv8 ONNX 将 DFL、Softmax、Sigmoid、anchor point 解码和坐标计算放在模型图内。Horizon J6 对完整检测图执行 INT8 PTQ 时,这些数值敏感操作受到量化误差影响,微小的 logits 误差经过 DFL、距离期望和 stride 解码后被放大,导致边框定位偏移和 IoU 下降,最终造成 mAP50-95 严重下降。

将模型改为 Raw6 六路原始输出,并在 CPU 使用 float32 完成 DFL、Sigmoid、解码和 NMS 后:

plaintext 复制代码
完整输出 INT8 mAP50-95:0.107
Raw6 INT8 mAP50-95:    0.529
浮点基准 mAP50-95:     0.537

因此,Raw6 方案已经验证为当前 Horizon J6 工具链上部署 YOLOv8 INT8 的有效方案。


10. 后续模型导出建议

对于 Horizon J6 上的 YOLOv8 INT8 部署,建议 ONNX 保留:

plaintext 复制代码
Backbone
Neck
Detect Head box/cls 卷积

建议从 ONNX 中移出:

plaintext 复制代码
DFL Softmax
距离加权
anchor 解码
stride 缩放
分类 Sigmoid
NMS

推荐输出形式:

plaintext 复制代码
P3 box + P3 cls
P4 box + P4 cls
P5 box + P5 cls

不建议直接使用标准 Ultralytics 最终输出:

plaintext 复制代码
[1, 84, 8400]

但这不是所有硬件平台的统一限制。

在 GPU、TensorRT、FP16、混合精度或对相关算子量化支持较好的平台上,完整输出模型仍然可能正常工作。

该结论仅针对:

plaintext 复制代码
Horizon J6
Open Explorer 3.8.1
YOLOv8
全 INT8 PTQ

这一具体部署环境。

相关推荐
橘子汽水1681 小时前
Leetcode 200,994岛屿数量,腐烂的橘子
数据结构·算法·leetcode
hansang_IR3 小时前
【题解】P5364 [SNOI2017] 礼物
c++·线性代数·算法
Tisfy3 小时前
LeetCode 3871.统计范围内的逗号 II:每次算一个逗号
数学·算法·leetcode·题解
小O的算法实验室3 小时前
IEEE TEVC,基于K-means与DQN辅助元启发式算法的多目标两栖无人艇调度模型
算法·kmeans·启发式算法
mmmmath_33 小时前
LeetCode.350.两个数组的交集II
算法
Ivanqhz3 小时前
图是“拓扑 + 类型 + 形状“的世界
算法·决策树·机器学习·php·集成学习
find1star3 小时前
LeetCode 54:螺旋矩阵——用四个边界模拟矩阵收缩
java·算法·leetcode·边缘计算·学习方法
z_mazin4 小时前
逆向一种私有二进制序列化格式:从零到字节级解析器
网络·python·网络协议·算法·rpc
luj_17684 小时前
罚球线右移破防新策略
c语言·开发语言·网络·经验分享·算法