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

0 阅读8分钟

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

1. 问题背景

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

标准 YOLOv8 ONNX 的输出通常为:

output0: [1, 84, 8400]

其中:

84 = 4 个边框坐标 + 80 个类别分数

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

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

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


2. 初始实验现象

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

模型阶段mAP50-95mAP50
Optimized Float0.5370.696
INT8 Calibrated0.1070.216
INT8 PTQ约 0.116约 0.230
INT16 Calibrated0.5320.689
INT16 PTQ0.5320.686

可以看到:

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. 为什么可以排除输入和校准数据问题

校准数据采用:

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

YAML 中运行时输入配置为:

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 通常也不会恢复到:

mAP50-95 = 0.532

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


5. 根本原因分析

问题的核心在于:

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

5.1 DFL 对 INT8 量化误差敏感

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

left   → 16 个 logits
top    → 16 个 logits
right  → 16 个 logits
bottom → 16 个 logits

随后执行:

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

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

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

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

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

P3 stride = 8
P4 stride = 16
P5 stride = 32

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

0.2 × 32 = 6.4 像素

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

5.3 mAP50-95 比 mAP50 下降更多

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

mAP50-95 下降明显
mAP50 下降相对较少

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

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

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


6. Raw6 解决方案

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

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

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]

其中:

64 = 4 × reg_max
reg_max = 16
num_classes = 80

新的部署边界为:

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-95mAP50
Raw6 Original Float0.5370.696
Raw6 Optimized Float0.5370.696
Raw6 INT8 Calibrated0.5290.685
Raw6 INT8 PTQ0.5290.685

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

模型形式阶段mAP50-95mAP50
完整输出 [1,84,8400]Float0.5370.696
完整输出 [1,84,8400]INT8 Calibrated0.1070.216
Raw6 六路输出Float0.5370.696
Raw6 六路输出INT8 Calibrated0.5290.685

8. 实验结果说明

8.1 Raw6 导出和 CPU 后处理正确

Raw6 Original Float 的结果为:

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 完全一致:

Original Float  = 0.537 / 0.696
Optimized Float = 0.537 / 0.696

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

8.3 Raw6 INT8 量化损失很小

Raw6 从浮点到 INT8:

mAP50-95:0.537 → 0.529,下降 0.008
mAP50:   0.696 → 0.685,下降 0.011

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

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

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

Backbone
Neck
P3/P4/P5 box 分支卷积
P3/P4/P5 cls 分支卷积

但最终精度只下降 0.008。

因此可以判断:

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

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

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

8.5 Raw6 INT8 接近完整输出 INT16

对比:

完整输出 INT16:
mAP50-95 = 0.532
mAP50    = 0.689

Raw6 INT8:
mAP50-95 = 0.529
mAP50    = 0.685

二者仅相差:

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 后:

完整输出 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 保留:

Backbone
Neck
Detect Head box/cls 卷积

建议从 ONNX 中移出:

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

推荐输出形式:

P3 box + P3 cls
P4 box + P4 cls
P5 box + P5 cls

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

[1, 84, 8400]

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

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

该结论仅针对:

Horizon J6
Open Explorer 3.8.1
YOLOv8
全 INT8 PTQ

这一具体部署环境。