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. 初期排查方向
最开始主要怀疑以下问题:
-
RGB 和 BGR 顺序不一致;
-
输入是否重复除以 255;
-
校准数据范围是否错误;
-
NCHW 和 NHWC 布局不一致;
-
LetterBox 预处理不一致;
-
COCO 类别编号或坐标恢复错误;
-
ONNX 导出本身存在问题;
-
Horizon 图优化阶段改变模型计算结果;
-
校准图片数量不足;
-
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
这一具体部署环境。