地平线 J6 部署踩坑记:一次由 stride 引发的推理不一致问题
在把深度学习模型部署到地平线 J6 芯片时,我遇到了一个非常隐蔽、但又极其重要的问题——量化模型的板端推理结果与编译工具链(bc 推理)的结果不一致。
经过一整天的排查,罪魁祸首竟然是输入张量的 stride(内存对齐)。
经过一整天的排查,罪魁祸首竟然是输入张量的 stride(内存对齐)。
这篇文章将完整记录我的排查思路、对地平线工具链推理理论的理解,以及最终的解决方案,希望能帮助遇到类似问题的你少踩点坑。
🧩 1. 背景:从 ONNX 到板端推理
整个部署链路如下:
ONNX 模型
- 使用地平线工具链量化编译 → HBM 模型
- 中间产物 quant.bc 模型
上板加载 HBM + C++ 推理代码进行实际推理
理论上,bc 推理与板端推理结果应该完全一致,因为 HBM 是从 bc 转换而来。
但我遇到的问题是:
⚠️ bc 推理和板端推理完全对不上!
⚠️ bc 推理和板端推理完全对不上!
🔍 2. 初步定位:问题出在输入?
模型的输入来自一个 .npy 文件,它本质上已经是完成预处理的 feature map,输入 shape 为:
我一开始的拷贝 npy 数据非常直接:
看起来没毛病,但推理结果离谱地错误。
⚡ 3. 真正的问题:stride 导致的非紧凑布局
地平线模型的输入不是紧凑存储的,而是带有 stride(内存对齐 + padding)。通过 hrt_model_exec 命令可以查看模型的输入输出信息。

例如我模型的输入 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. 经验教训
- 不要默认所有内存都是紧凑的:部署到异构硬件时,工具链常为了性能改变数据布局(padding、pack、stride)。
- 三件事先确认:dtype(字节大小)、layout(NCHW/NHWC/pack)与 stride(字节或元素),三者任一不一致都会导致数值差异。
- 从输入开始调试:当输出异常时,先核验输入的字节流是否完全符合模型期望(shape、layout、stride、量化参数)。
- 用小样本复现:可控的小样本更容易定位问题,便于验证修复。
- 记录并复现:把复现步骤写清楚,方便后续回溯与分享。
✨ 结束语
内存布局与对齐问题在嵌入式与异构硬件部署中非常常见,但往往“看不见”因此容易被忽略。这个案例说明:即便模型、算子、量化参数都正确,**数据在内存上的「摆放方式」**也能把推理搞砸。通过确认 stride 单位、按目标布局拷贝数据并做严格越界检查,问题得以彻底解决。