专栏算法工具链地平线 J6M YOLOv8s 部署

地平线 J6M YOLOv8s 部署

O2026-07-20
81
0
本文记录 YOLOv8s 在地平线 J6M 上的最终可复现部署流程。正式方案统一采用 Raw6 六路输出、PTQ INT8、NV12 Runtime 输入、CPU 后处理。文中命令需要根据实际 SDK 安装目录、服务器地址和开发板目录调整。

1. 环境搭建

1.1 创建 OpenExplorer 容器

目的:使用固定版本的 OpenExplorer 工具链,隔离模型转换和编译环境。

本文使用的镜像为:

先确认镜像存在:

准备宿主机目录:

创建容器:

参数说明:

参数

作用

--name xy_j6_381

指定容器名称

--gpus all

允许容器使用宿主机 GPU

--shm-size=16g

增大共享内存

-v ~/projects:/workspace/projects

挂载项目目录

-v ~/docker-data/xy_j6_381:/workspace/data

挂载持久化数据目录

1.2 验证工具链和挂载目录

进入容器后检查基础环境:

检查挂载目录:

建议把本次模型转换文件集中放到一个目录中:

1.3 常用容器命令

退出容器:

启动并重新进入容器:

检查容器状态:


2. 最终部署方案

2.1 整体流程

正式部署主线如下:

2.2 为什么导出 Raw6

Ultralytics 默认导出的检测模型通常把 DFL、坐标解码和输出拼接放在图中,最终形成单路检测输出。该形式便于通用推理,但解码算子和大张量拼接可能增加量化误差或工具链适配难度。

Raw6 方案只保留三个尺度的回归分支和分类分支,把以下操作移到 CPU:

这样可以逐层对齐 PyTorch、ONNX、量化模型和板端结果,也便于定位精度问题。

2.3 最终模型输入输出约定

模型训练侧和校准侧输入:

板端 Runtime 输入:

Raw6 输出约定:

其中,64 = 4 × reg_max,YOLOv8 默认 reg_max=16;80 是 COCO 类别数。若使用自定义类别数,分类分支的通道数应以实际模型为准。
输出名称、顺序、布局和数据类型必须以 ONNX 检查结果、编译报告及 hrt_model_exec model_info 的实际输出为准,不要仅依赖代码中的固定下标。

3. 导出 Raw6 ONNX

3.1 安装依赖

目的:准备与 yolov8s.pt 兼容的 Ultralytics、PyTorch、ONNX 和 ONNX Runtime 环境。

记录实际版本,便于复现:

不同 Ultralytics 版本的 Detect 实现可能变化。修改前应先确认当前版本源码,不要直接套用其他版本的补丁。

3.2 修改 Detect Head 并导出

目的:让检测头在导出模式下直接返回 P3/P4/P5 的回归和分类特征,不在 ONNX 图中执行 DFL 和坐标解码。

核心逻辑示意:

需要根据当前 Ultralytics 版本把该逻辑接入 Detect.forward 或使用导出包装模块。正式导出时固定输入尺寸和输出名称:
上述代码是调用框架示意。导出前必须确认 model(dummy) 已返回六个 Tensor;若仍返回单路结果,说明 Detect Head 修改尚未生效。

3.3 检查 ONNX 输入输出

先执行 ONNX 结构检查:

再打印输入输出信息:

检查重点:

  • 输入名是否为 images。
  • 输入 Shape 是否为 [1,3,640,640]。
  • 输出数量是否为 6。

  • 输出名称和 Shape 是否与第 2.3 节一致。

  • ONNX 输出顺序是否与板端后处理的绑定顺序一致。

3.4 验证浮点 ONNX 精度

目的:先证明 Raw6 导出和 CPU 后处理正确,再进入量化。若 ONNX 已经偏离 PyTorch,后续 PTQ 结果没有参考价值。

对同一验证集使用相同的:

  • LetterBox 参数和填充值。

  • RGB 转换与 /255 归一化。
  • 置信度阈值和 NMS IoU 阈值。

  • DFL、Anchor Point 和 stride 约定。

  • 标签映射与 COCO mAP 评测脚本。

至少记录:

阶段

mAP50-95

mAP50

Recall

结论

PyTorch yolov8s.pt

待单独评测

待单独评测

待填写

原始 PyTorch 基线

Raw6 yolov8s_raw6.onnx

0.537

0.696

未输出

Raw6 导出正确

如果差异明显,优先排查输出顺序、DFL 维度、stride、Anchor Point、LetterBox 和 NMS,而不是继续编译。


4. PTQ 校准与 HBM 编译

4.1 准备校准图片

目的:用具有代表性的样本估计激活分布。校准图片应覆盖实际部署中的目标尺寸、光照、背景和拍摄角度。

建议从训练集或验证集中抽取有代表性的图片;样本数量应结合数据分布和工具链建议确定,不应只追求固定数量。

4.2 校准预处理流程

校准链路必须与 ONNX 输入约定一致:

核心代码:

保存前抽查:

预期约定:

具体文件格式由当前版本 OpenExplorer 配置要求决定。若配置要求二进制文件,应使用连续内存数据写入;若接受 .npy,则保留 Shape 和 dtype 信息。

4.3 生成 YAML 模板

目的:先由当前工具链生成完整模板,再修改关键字段,避免遗漏版本相关配置。

J6M 使用的 march 应结合当前 OpenExplorer 版本和芯片资料确认;本文按原环境采用 nash-e。

4.4 编写正式 YAML

正式配置文件统一命名为 yolov8s_raw6_config.yaml,核心配置如下:

关键约定:

input_name 必须与 ONNX 实际输入名一致。scale_value 与校准数据是否已经归一化存在工具链版本和配置语义差异时,应依据当前 SDK 文档和编译日志确认,避免重复归一化。

4.5 编译生成 HBM

执行正式 PTQ 编译:

目标输出文件为:

编译成功只说明模型能够生成目标产物,不代表量化精度正确。

4.6 查看编译报告

检查 model_output_raw6_int8 中实际生成的日志、HTML、JSON、BC 和 HBM 文件:

重点检查:

  • 是否存在不支持算子或 CPU fallback。

  • 六路输出是否保留。

  • 输入转换是否为 NV12 Runtime 到 RGB Train 输入。

  • 各节点量化信息和异常饱和情况。

  • 静态性能估计、BPU core 分配和内存占用。

  • 最终 HBM 文件名是否为 yolov8s_raw6_640x640_nv12.hbm。

5. 量化精度评测

5.1 为什么必须先测精度

模型成功编译不等于精度正确。输入颜色、归一化、输出顺序、量化尺度或后处理中的任一处不一致,都可能在程序正常运行的情况下产生错误结果。

必须先确认 HBM 精度,再进行板端延迟和 FPS 测试。

5.2 评测阶段

按照以下顺序逐级评测:

每一级均使用同一验证集、同一前处理、同一 Raw6 后处理、同一阈值和同一指标实现。若某一级首次出现明显下降,排查范围即可收敛到该转换阶段。

建议同时进行两类对齐:

  1. 数据集级指标对齐:比较 mAP50-95、mAP50、Recall。
  2. 样本级张量对齐:保存相同图片在六路输出上的 min/max、均值、余弦相似度或误差分布,并比较解码后的候选框。

5.3 指标记录表

阶段

模型文件

输入形式

mAP50-95

mAP50

Recall

说明

PyTorch

yolov8s.pt

RGB FP32

待单独评测

待单独评测

待填写

原始 PyTorch 基线

Raw6 ONNX

yolov8s_raw6.onnx 或 *_original_float_model.onnx
NCHW RGB FP32,[0,1]

0.537

0.696

未输出

Raw6 导出正确

Optimized ONNX

*_optimized_float_model.onnx

NCHW RGB FP32,[0,1]

0.537

0.696

未输出

图优化无精度影响

Calibrated ONNX

*_calibrated_model.onnx

NCHW RGB FP32,量化仿真

0.529

0.685

未输出

INT8 校准后仅小幅下降

PTQ ONNX

*_ptq_model.onnx

NCHW RGB FP32,量化仿真

0.529

0.685

未输出

PTQ 转换未引入额外损失

Quantized BC

*_quantized_model.bc

NHWC NV12 uint8

0.537

0.687

待填写

需确认六路输出反量化

HBM

yolov8s_raw6_640x640_nv12.hbm

NHWC NV12 uint8

待评测

待评测

待填写

最终板端模型

当前已测阶段的精度变化:

目前可以确认图优化没有引入可见精度损失,校准阶段出现小幅下降,PTQ 转换未继续扩大损失。由于 PyTorch、Quantized BC 和 HBM 指标尚未完成,暂时不能得出完整的端到端精度结论。

5.4 精度下降判断

精度是否可接受应以项目验收阈值为准,不应直接套用固定百分比。建议按以下顺序定位:

只有 HBM 指标达到项目要求后,才进入性能验收。


6. 板端部署与性能测试

6.1 拷贝 HBM 到开发板

先在主机确认文件:

示例拷贝命令:

服务器地址和板端目录按实际环境修改。

6.2 查看 HBM 输入输出

板端必须先执行:

本次实测环境:

项目

实际值

模型名称

yolov8s_raw6_640x640_nv12

Runtime

UCP/DNN 3.12.1,HBRT 4.4.6

BPULib

2.2.6

Builder

3.5.11

HBDK

4.9.7

HMCT

2.7.3

March

nash-e

HBM 加载到 DDR

547.77 ms,仅为本次 model_info 记录

NV12 Runtime 在 HBM 中表现为两个输入 Tensor,分别承载 Y 平面和 UV 平面:

输入

名称

Valid Shape

类型

量化

Stride

0

images_y

(1,640,640,1)

HB_DNN_TENSOR_TYPE_U8

NONE

(-1,-1,1,1)

1

images_uv

(1,320,320,2)

HB_DNN_TENSOR_TYPE_U8

NONE

(-1,-1,2,1)

六路输出均为 FP32,未附带量化参数:

输出

名称

Valid Shape

类型

Aligned Byte Size

Stride

0

p3_box

(1,64,80,80)

HB_DNN_TENSOR_TYPE_F32

1966080

(1966080,30720,384,4)

1

p3_cls

(1,80,80,80)

HB_DNN_TENSOR_TYPE_F32

2457600

(2457600,30720,384,4)

2

p4_box

(1,64,40,40)

HB_DNN_TENSOR_TYPE_F32

655360

(655360,10240,256,4)

3

p4_cls

(1,80,40,40)

HB_DNN_TENSOR_TYPE_F32

819200

(819200,10240,256,4)

4

p5_box

(1,64,20,20)

HB_DNN_TENSOR_TYPE_F32

163840

(163840,2560,128,4)

5

p5_cls

(1,80,20,20)

HB_DNN_TENSOR_TYPE_F32

204800

(204800,2560,128,4)

实测结果确认:

输入 stride 显示为动态值。应用程序不能把 -1 当作实际步长,也不能固定假设只有一个输入 Tensor。应按 SDK 的 Tensor 分配结果或明确设置的实际 stride 写入 Y、UV 两个平面。

6.3 测试单线程延迟

具体子命令参数可能随 SDK 版本变化,先查看帮助:

本次使用 1 线程、2000 帧进行较长时间测试:

记录:

指标

结果

工具线程平均延迟

1.778251 ms

工具统计平均延迟

1.778 ms

最小延迟

1.729 ms

最大延迟

2.177 ms

帧数

2000

程序运行时间

3675.454 ms

帧累计延迟

3556.503 ms

单线程吞吐

544.194 FPS

HBM 加载到 DDR

530.64 ms,不计入逐帧平均延迟
本次命令没有通过 --input_file 提供真实 NV12 数据。工具为动态 stride 自动采用:
因此,1.778 ms 和 544.194 FPS 可用于记录当前 HBM 的单线程工具级 BPU 性能,但不能作为真实图片端到端结果。该数据不包含图片读取、LetterBox、NV12 转换、Raw6 CPU 后处理、NMS 和可视化耗时;若模型中存在对输入范围敏感的算子,还应使用有效的 --input_file 重新测试。

6.4 测试多线程 FPS

本次对 1、2、4、8 线程分别执行 2000 帧测试:

线程数

Batch

测试时长

FPS

平均延迟

1

1

3675.454 ms / 2000 帧

544.194

1.778 ms

2

1

2945.075 ms / 2000 帧

679.157

2.890 ms

4

1

2945.172 ms / 2000 帧

679.136

5.798 ms

8

1

2946.025 ms / 2000 帧

678.933

11.672 ms

从本次工具测试可以看出:

  • 2 线程相对 1 线程,吞吐从 544.194 FPS 提升到 679.157 FPS,约提升 24.8%
  • 从 2 线程增加到 4 或 8 线程后,吞吐基本不再增长,稳定在约 679 FPS。
  • 并发继续增加时,单帧平均延迟近似按线程数增长:2.890 → 5.798 → 11.672 ms。
  • 如果目标是该测试条件下的最高吞吐且兼顾延迟,2 线程是更合理的配置;4、8 线程没有带来有效吞吐收益。

上述多线程测试同样未提供 --input_file,结论仅适用于当前 HBM 的工具级调度和 BPU 吞吐,不代表真实业务程序的端到端并发性能。

7. 板端推理程序

7.1 程序整体流程

7.2 NV12 预处理

正式方案的板端输入是 NV12,不使用 RGB Runtime 的 pixel - 128 写入逻辑。

预处理必须统一以下规则:

  • LetterBox 缩放和填充规则与校准、评测一致。

  • 输出尺寸为 640×640;NV12 宽高必须满足相应对齐要求。
  • BGR/RGB 到 YUV 的色彩矩阵和 UV 排列与模型 Runtime 输入一致。

  • 区分 NV12(UV 交错)和 NV21(VU 交错)。

  • 按 Tensor 的 validShape、alignedShape 和 stride 写入,不能默认数据紧密排列。

建议保留的接口:

NV12 数据大小通常为:

实际写入大小和分平面方式必须以 HBM 输入 Tensor 属性及 SDK API 约定为准。

7.3 模型加载与 Tensor 内存

模型加载和属性查询顺序:

不要只为第 0 个输出申请内存。应按实际 output_count 遍历:
TensorMemSize 应根据实际数据类型、对齐 Shape 和 SDK 的 Tensor 属性计算。

7.4 BPU 推理调度

关键调用顺序:

正式代码需要检查每个 API 的返回值,并在失败路径释放已经申请的资源。

7.5 Raw6 CPU 后处理

统一后处理流程:

三个尺度的 stride 通常为:

核心接口建议:

输出绑定不应只依赖数组位置。建议同时校验名称、Shape 和通道数,建立 p3_box 到 p5_cls 的明确映射。

坐标还原:

还原后需要裁剪到原图边界。

7.6 可视化和资源释放

可视化只保留必要调用:

资源释放顺序:

完整实现建议拆分为:

7.7 交叉编译与运行

原环境使用的交叉编译器:

建议使用单层构建目录:

拷贝程序、模型和测试数据:

板端运行示例:

动态库目录应根据开发板 BSP 和实际部署目录配置,不要照搬其他项目的 LD_LIBRARY_PATH。

8. 踩坑记录

本节记录早期默认单输出 RGB Runtime 模型的排查过程。当前正式方案已经改为 Raw6 六路输出和 NV12 Runtime,因此本节中的输入和后处理实现不能直接用于最终模型。

8.1 默认 ONNX 包含 DFL 和坐标解码

现象

默认导出的 ONNX 输出为 [1,84,8400],图中包含 DFL、坐标解码、拼接等操作;量化后精度下降明显或难以逐层定位。

排查过程

对比 PyTorch、默认 ONNX 和量化输出,确认差异集中在检测头末端及单路输出。

根因

默认导出形式把对量化较敏感的解码和拼接留在模型图中,同时失去了逐尺度、逐分支对齐能力。

修改方式

修改 Detect Head,导出 P3/P4/P5 的 box 和 cls 六路原始输出;DFL、Anchor 解码和 NMS 移到 CPU。

修改前后结果

对当前方案的影响

当前 Raw6 后处理不得再按 [1,84,8400] 解析。

8.2 单输出 INT8 模型精度下降

现象

早期 HBM 可以正常运行,但类别、置信度、框数量和 ONNX 明显不一致。

排查过程

依次检查 LetterBox、坐标映射、校准数据范围、YAML 归一化和 Runtime Tensor 类型。单图结果显示,问题并非只由 NMS 阈值造成。

根因

早期方案同时混用了默认单输出模型、RGB Runtime 和板端 INT8 Tensor 写入约定,导致输入分布或模型末端量化误差难以区分。

修改方式

最终改用 Raw6 + NV12:用 Raw6 缩小量化排查范围,用 NV12 Runtime 统一板端图像输入。

修改前后结果

旧方案中曾通过修正 RGB INT8 写入改善单图对齐,但这只能证明旧模型的输入编码问题得到缓解,不能代替当前 HBM 的数据集级 mAP 评测。

对当前方案的影响

旧方案结果不得作为 Raw6 + NV12 的精度结论。

8.3 RGB Runtime 的 pixel-128 问题

现象

早期 RGB Runtime HBM 查询结果表现为 NHWC INT8 输入。直接把 uint8 像素强制转换为 int8 后,板端结果与 ONNX 不一致。

排查过程

确认 LetterBox 参数一致后,比较原始写入和 pixel - 128 写入。旧模型修正后,单图类别和置信度更接近 ONNX。

根因

该旧模型使用 RGB INT8 Runtime 输入约定,板端应把 0~255 映射到 -128~127,不能直接发生溢出式转换。

修改方式

旧 RGB Runtime 模型使用:

修改前后结果

原笔记中的单图样例在修改后基本对齐,但没有提供完整数据集指标。

对当前方案的影响

该问题来自早期的默认单输出 RGB Runtime 模型,不适用于当前 Raw6 + NV12 最终方案。

正式 NV12 输入流程中不要加入 pixel - 128。

8.4 Raw6 输出顺序和 Shape 对齐

现象

六路 Tensor 数量正确,但解码结果异常,常见表现为框尺度错误、类别置信度异常或完全无框。

排查过程

分别打印六路输出的名称、Shape、数据类型和数值范围,并与 ONNX Runtime 输出逐路比较。

根因

可能原因包括:

  • 把 cls 分支当作 box 分支。

  • P3/P4/P5 顺序错误。

  • NCHW/NHWC 解释错误。

  • 使用 validShape 访问对齐后的 Tensor 时忽略 stride。
  • 忽略输出量化类型或反量化参数。

修改方式

按名称、Shape 和通道数建立显式映射;以 hrt_model_exec model_info 和 Tensor 属性查询结果为准。

修改前后结果

对当前方案的影响

Raw6 后处理必须在启动阶段验证全部六路输出,验证失败应立即终止推理。

8.5 校准前处理与 YAML 不一致

现象

编译可以完成,但 calibrated、quantized 或 HBM 阶段精度突然下降。

排查过程

检查校准文件的颜色、Shape、dtype、数值范围,并逐项核对 input_type_train、input_layout_train、mean_value、scale_value 和 std_value。

根因

常见问题包括 BGR/RGB 颠倒、HWC/CHW 颠倒、重复 /255、没有 /255、LetterBox 规则不同或校准样本不具代表性。

修改方式

统一校准输入为:

并用同一预处理函数驱动 ONNX 精度评测和校准数据生成。

修改前后结果

数据集指标必须重新评测,结果填写到第 5.3 节。

对当前方案的影响

校准链路和板端链路的数据格式不同,但它们在模型内部转换后的有效输入分布必须一致。


9. 常用 API 速查

9.1 OpenCV API

API

作用

cv::imread

读取 BGR 图片

cv::resize

等比例缩放

cv::copyMakeBorder

LetterBox 填充

cv::cvtColor

颜色空间转换

cv::dnn::NMSBoxes

候选框 NMS;按类别调用

cv::rectangle

绘制检测框

cv::putText

绘制类别和置信度

cv::imwrite

保存结果图

9.2 Horizon DNN API

API

作用

hbDNNInitializeFromFiles

加载 HBM 模型包

hbDNNGetModelNameList

获取模型名称列表

hbDNNGetModelHandle

获取具体模型句柄

hbDNNGetInputCount

查询输入数量

hbDNNGetOutputCount

查询输出数量

hbDNNGetInputTensorProperties

查询输入 Tensor 属性

hbDNNGetOutputTensorProperties

查询输出 Tensor 属性

hbDNNInferV2

创建推理任务

hbDNNRelease

释放模型包

9.3 Horizon UCP API

API

作用

hbUCPMallocCached

申请 BPU/CPU 可访问的缓存内存

hbUCPMemFlush(...CLEAN)

把 CPU 写入同步给 BPU

HB_UCP_INITIALIZE_SCHED_PARAM

初始化调度参数

hbUCPSubmitTask

提交推理任务

hbUCPWaitTaskDone

等待任务完成

hbUCPMemFlush(...INVALIDATE)

让 CPU 读取 BPU 更新后的输出

hbUCPReleaseTask

释放任务

hbUCPFree

释放 Tensor 内存

9.4 API 调用顺序

职责划分:


10. 最终总结

本次部署的固定主线是:

部署验收必须同时满足:

  • Raw6 ONNX 与 PyTorch 精度对齐。

  • optimized、calibrated、quantized 各阶段精度变化可解释。

  • HBM 使用实际 NV12 链路评测且满足项目精度阈值。

  • 板端程序根据实际 Tensor 属性处理 Y、UV 两个输入 Tensor 和六路 FP32 输出。

  • 延迟、FPS、CPU/BPU 占用和内存满足项目要求。

  • 所有评测均记录数据集、阈值、工具版本和运行条件。

算法工具链
社区征文征程6
评论0
0/600