专栏算法工具链J6 部署踩坑记:一次由 stride 引发的推理不一致问题

J6 部署踩坑记:一次由 stride 引发的推理不一致问题

YCJ2025-11-27
87
0

地平线 J6 部署踩坑记:一次由 stride 引发的推理不一致问题

在把深度学习模型部署到地平线 J6 芯片时,我遇到了一个非常隐蔽、但又极其重要的问题——量化模型的板端推理结果与编译工具链(bc 推理)的结果不一致
经过一整天的排查,罪魁祸首竟然是输入张量的 stride(内存对齐)

这篇文章将完整记录我的排查思路、对地平线工具链推理理论的理解,以及最终的解决方案,希望能帮助遇到类似问题的你少踩点坑。


🧩 1. 背景:从 ONNX 到板端推理

整个部署链路如下:

  1. ONNX 模型

  2. 使用地平线工具链量化编译 → HBM 模型
  3. 中间产物 quant.bc 模型
  4. 上板加载 HBM + C++ 推理代码进行实际推理

理论上,bc 推理与板端推理结果应该完全一致,因为 HBM 是从 bc 转换而来。
但我遇到的问题是:
⚠️ bc 推理和板端推理完全对不上!

🔍 2. 初步定位:问题出在输入?

模型的输入来自一个 .npy 文件,它本质上已经是完成预处理的 feature map,输入 shape 为:

我一开始的拷贝 npy 数据非常直接:

看起来没毛病,但推理结果离谱地错误。


⚡ 3. 真正的问题:stride 导致的非紧凑布局

地平线模型的输入不是紧凑存储的,而是带有 stride(内存对齐 + padding)。通过 hrt_model_exec 命令可以查看模型的输入输出信息。
Description
例如我模型的输入 stride 为:
而 numpy 的 .npy 文件 —— 永远都是紧凑的连续内存

这意味着:

  • npy 数据: 每个维度紧密排列,无空洞
  • 模型输入: 每个维度后都插入 padding 用于对齐

➡️ 所以直接 memcpy 会把紧凑数据强行塞入带 padding 的结构里,严重破坏内存布局,推理当然完全错误。


📦 4. 什么是 stride?

可以把张量想像成一本 3D 书:

  • dim2:每一行文字

  • dim1:每一页

  • dim0:整本书

硬件要求每一行、每一页必须按固定字节对齐:

  • 行末补空白 → 对应 stride[2]
  • 页末补空白页 → 对应 stride[1]
  • 章节补空白页 → 对应 stride[0]

这就形成了:

📘 npy:紧凑版书
没有空白页,内容连续。
📕 模型 tensor:对齐版书
为了对齐,每行/每页后面都塞很多空白。
如果你把 紧凑版书内容直接复制到对齐书结构里,所有页码立刻错乱,模型当然读不出正确输入。

🛠️ 5. 解决方案:按 stride 逐层复制

最终,我用以下方式解决了问题:按目标 stride 逐层逐行复制最内层连续的数据块,而不是一次性 memcpy 整块数据。

下面是我在项目中读取并拷贝 npy 数据的 C++ 实现:

在实际使用中,我把 properties.stride(工具链/SDK 提供)除以 tensor_elem_size 得到目标的元素级 stride,然后按上面的函数把 .npy 紧凑数据逐层填到目标 buffer。修复后,板端推理结果与 quant.bc 的推理一致了。

✅ 6. 经验教训

  1. 不要默认所有内存都是紧凑的:部署到异构硬件时,工具链常为了性能改变数据布局(padding、pack、stride)。
  2. 三件事先确认:dtype(字节大小)、layout(NCHW/NHWC/pack)与 stride(字节或元素),三者任一不一致都会导致数值差异。
  3. 从输入开始调试:当输出异常时,先核验输入的字节流是否完全符合模型期望(shape、layout、stride、量化参数)。
  4. 用小样本复现:可控的小样本更容易定位问题,便于验证修复。
  5. 记录并复现:把复现步骤写清楚,方便后续回溯与分享。

✨ 结束语

内存布局与对齐问题在嵌入式与异构硬件部署中非常常见,但往往“看不见”因此容易被忽略。这个案例说明:即便模型、算子、量化参数都正确,**数据在内存上的「摆放方式」**也能把推理搞砸。通过确认 stride 单位、按目标布局拷贝数据并做严格越界检查,问题得以彻底解决。

算法工具链
技术深度解析杂谈
评论0
0/1000