同一个 YOLOv5s 模型,同样十张图片,正确输入时得到 51 个检测框。只把输入数据复制到内存的方式改错,十次运行依然全部返回成功,检测框却一个都没有了。
这次实验没有改模型,也没有访问非法地址。问题出在文件每行有 672 字节,当前 SDK 默认分配的输入缓冲每行却占 704 字节。直接把整个文件连续复制进去,文件长度检查能通过,模型读取到的图片却已经错行。
在 Codex 协助下,我先用官方工具建立真实 J6E 检测基线,再分别改变 U/V 顺序和行组织方式,比较接口返回、原始输出和最终检测框。这个过程让我更明确地看到:输入文件能读完、模型任务能执行,并不足以确认送进去的是那张图片。
一、先把正确图片送进模型
本轮使用官方 OE 3.9.1 优化模型包中的 YOLOv5s,输入大小 672×672,编译为 nash-e 单核 HBM。实际开发板为 J6E MatrixB,运行库为 UCP 3.15.8、HBRT 4.11.11。
SDK 的 50 张 COCO 示例图片按文件名提前固定拆分:前 40 张用于校准,后 10 张用于这次测试,图片内容哈希去重。模型采用 KL 校准、O2 编译;后处理阈值 0.4、NMS 阈值 0.45。
图片处理沿用 SDK 示例:保持宽高比缩放,在 672×672 画布中央补边,填充值 127,然后从 RGB 转成 NV12。NV12 把亮度放在 Y 平面,把色度 U、V 交错放在另一个平面。
这份 HBM 对外提供两个输入:
输入 | 类型与有效 shape | 文件中的有效字节 |
|---|---|---|
Y | UINT8,[1,672,672,1] | 451584 |
UV | UINT8,[1,336,336,2] | 225792 |
完整 JPEG 入口随后也在板上执行了十张图片。生成的 NV12 与事先保存的 SDK 参考一致,三个原始输出与既有板端基线一致。这样,后面的故障对照就有了一条明确的正确路径。
二、第一个错误:U 和 V 放反了
我保留 Y 平面,只交换 UV 文件中的相邻字节;模型、内存组织和后处理全部保持不变。
十张错误输入全部成功执行,但三个检测头的 raw 输出全部变化。原本 51 个检测框变为 45 个;以正确输入结果为参照,类别相同且 IoU≥0.5 的一对一匹配只剩 39 个。
猫图是一个很直观的例子。正确输入得到一个猫框;U/V 交换后,不只是分数发生变化,还多出了另一个猫框。成功状态没有告诉我颜色顺序错了。
三、第二个错误:文件是连续的,缓冲区却有行间距
这里的 stride 可以理解成“走到下一行,要跨过多少字节”。它与图片一行的有效内容长度有关,但二者不一定相等。
输入 | 使用的 stride | 分配字节 |
|---|---|---|
Y | (473088,704,1,1) | 473088 |
UV | (236544,704,2,1) | 236544 |
704 是这次采用官方默认补全规则得到的行间距,并非所有 NV12 模型都固定使用这个数。
如果直接这样写:
分配的缓冲比文件大,复制并不会越界。但模型按 704 字节跨到下一行,读到的是原文件中偏移了 32 字节的位置。越往后,内容错位越明显。
我将这种错误复制放进独立故障版本,其余代码与正确执行器相同;内存仍合法分配并清零。十张运行全部退出 0,三个检测头全部变化,最终十张图都没有得到超过阈值的检测框。
四、两种错误放在一起看
输入方式 | 成功退出 | raw 输出变化 | 检测框总数 | 与正确输入匹配的框 |
|---|---|---|---|---|
正确输入 | 10/10 | 基线 | 51 | 51 |
U/V 交换 | 10/10 | 30/30 个检测头 | 45 | 39 |
紧密文件直接复制到 704 stride 缓冲 | 10/10 | 30/30 个检测头 | 0 | 0 |
匹配采用同一条贪心规则:先按参考框分数从高到低处理,再从尚未匹配的同类别预测框里选 IoU 最大者,IoU≥0.5 才计入;每个预测框只用一次。这张表检查的是输入错误如何改变同一模型的结果,没有使用真实目标标注来计算 mAP。

图1|三个版本使用同一原图和同一 HBM。错误行复制版本没有超过阈值的框,图片仍然显示正常,因为可视化背景使用的是原 JPEG;模型实际读取的输入缓冲已经错行。
故障后,我重新使用正确执行器,补跑三张正常图片,每张重复两次。三个检测头的原始输出都恢复为基线。这一步用于确认差异随输入组织方式出现和消失。
五、我现在会先检查哪些东西?
这次排错后,我把输入核对放到最终效果图之前。
随后核对文件代表什么:RGB 还是 BGR,NV12 还是其他排列,Y 和 UV 是否拆分。文件长度正确只能说明数量相符,不能证明颜色和布局含义相符。
再检查文件到 SDK 缓冲区的复制。紧密存储文件需要按实际 stride 写入,内存分配大小和有效数据大小分别记录。接入阶段可以导出实际输入,与官方路径逐字节比较。
最后看 raw 输出和统一后处理后的检测结果。先保持模型、阈值、NMS 和坐标恢复不变,才能把变化定位到输入这一层。
这些检查已经放进本轮固定模型执行器和 JPEG 工具。单图入口可以直接接收新 JPEG,输出原始数据、JSON 和标注图;故障代码则单独保留,用于复现实验,不混进正常入口。
当前运行库的 allocator ERROR 日志仍然存在,原因未确认;本文保留日志,并用官方输出对照和故障后的恢复结果检查执行一致性。
这轮最有价值的现象,我认为,是两个错误都绕过了“任务是否成功”的检查。一个改错颜色,一个改错行组织,程序照样结束。把输入的含义和内存布局核对清楚,才知道模型到底看到了什么。
参考资料
- OE 3.9.1:samples/onnx_ptq/examples/detection/yolov5x/preprocess.py 和 postprocess.py。
- OE 3.9.1:hrt_model_exec/src/util/tensor_util.cpp 中动态 stride 补全和内存分配逻辑。
本轮 J6E 原始输入、张量属性、运行日志和检测框 JSON。
