在 i.MX 8M Plus EVK 上,空间内核维度(H 或 W)为>= 16 的 Conv2D 操作在 VX 委托 (libvx_delegate.so) 下会产生不正确的输出。模型加载完毕,Conv2D 通过 vsi_nn_kernel_selector 绑定到 OPENVX 内核,error_during_init/prepare/invoke 均为 0,推理延迟正常,但在不同输入的每个位置,int8 输出都饱和为单一值(int8 = 127)。
沿任一空间轴,K=15 和 K=16 之间的边界都很明显。这与记录的跨度> 15 限制的阈值相符(https://community.nxp.com/t5/i-MX-Processors/Conv2D-not-working-with-stride-16-for-NPU-Kernel-crash...。
两个问题
内核-dim ≥ 16 悬崖是否是一个已知限制,如果是,能否将其添加到《i.MX 机器学习用户指南》(UG10166)的 Conv2D 约束部分?
在图形编译时,vsi_nn_op_conv2d::op_check(或 VX 委托的分区逻辑)是否应该拒绝这种情况,以便操作返回 CPU?当前"accept, run, return constant int8=127, no diagnostic" 行为是不安全的--已部署的具有大型内核的模型似乎可以正常加载和运行。
元器件价值
| 电路板 | i.MX 8M Plus EVK |
| BSP | 恩智浦 i.MX 发行版 6.18-whinlatter(version_id=6.18-whinLatter) |
| 内核 | Linux 6.18.2-1.0.0-gf49f45233f7bSMP PREEMPT (aarch64) |
| libvx_delegate.so md5 | 2f88ec0871d18298bfa357ddaaea4d6d |
| libGAL.so md5 | af4806f617b23363b3be69c4dad2dc05 |
| libOpenVX.so{,.1,.1.3.0}md5 | 92f85c32746d4d0b38800e21503d13d0 (三处相同) |
| imx-gpu-viv 软件包 | 1:6.4.11.p4.4-aarch64-r0 |
| tim-vx 软件包 | 1.2.2-r0 |
| OVxlib(运行时报告) | OVXLIB_VERSION==1.2.14 |
| TFLite 运行时 | TFLite 2.19.0 (/usr/bin/tensorflow-lite-2.19.0/examples/benchmark_model) |
随附的 conv_1x17_broken.tflite(2,136 字节)是单 conv2D 神器:内核(1、17)、输入 1×1×1000×5 int8、输出 1×1×1000×8 int8、每通道 int8 权重量化、padding=Same、stride= (1,1)、无融合激活。
# On the i.MX 8M Plus board: python3 reproduce.py conv_1x17_broken.tflite # exits 1: BROKEN, saturated to int8=127 python3 reproduce.py conv_1x15_torch_control.tflite # exits 0: OK (K<16, same pipeline)
reproduce.py(附后,仅依赖于 tflite_runtime 和 numpy)通过普通解释器和 VX 委托解释器输入 6 个种子随机 int8 输入值,并报告平均值|Δ|、最大值|Δ|,以及来自 VX 路径的唯一 int8 输出值的计数。
INT8 PTQ 单例二维单元测试模型。在相同的 int8 输入字节上均值 VX 与板 CPU 的对比;uniq_VX = 6 个输入(总共 256 个)上的 int8 输出值是唯一的 int8 输出值。
核 (H, W) 平均值 |Δ| uniq_VX 结果
| (1, 11) | 0.03 | 256 | OK |
| (1, 15) | 0.02 | 256 | OK |
| (1, 16) | 134.9 | 1 | 破损(常量 int8=127) |
| (1, 17) | 136.9 | 1 | 破损(常量 int8=127) |
| (1, 23) | 130.5 | 1 | 破损(常量 int8=127) |
| (15, 1) | 0.11 | 256 | OK |
| (17, 1) | 136.9 | 1 | 破损(常量 int8=127) |
| (23, 1) | 129.3 | 1 | 破损(常量 int8=127) |
| (3, 3), (5, 5), (7, 7) | < 0.2 | 256 | OK |
| (15,15)(区域 225) | 0.41 | 256 | OK |
| (3, 23) | 123.4 | 1 | 破损(常量 int8=127) |
| (5, 15) | 0.24 | 256 | OK |
阈值独立适用于 H 和 W。在每种破碎情况下,VX 输出在每个位置都饱和到 int8 = 127 - 饱和到 int8 的正极端值(而不是 output_zp 或 0)表明,要么是乘法器移位在最终钳位前溢出,要么是一个固定值被写入输出磁贴以代替 MAC 结果。这个问题似乎与内核面积无关(例如,15x15 的内核可以正常工作)
阈值是必要的,但还不够 — 某些 int8 权重值模式在 K ≥ 16 时会触发错误,而另一些则不会。我们已经进行了字节级隔离实验(在损坏和未损坏的伪影之间移植单个 TfLite 张量场),将值模式触发范围缩小到特定的张量场。如果有用,我很乐意分享隔离结果和工具。
INFO: Vx delegate: error_during_init set to 0. INFO: Vx delegate: error_during_prepare set to 0. INFO: Vx delegate: error_during_invoke set to 0. I [vsi_nn_CreateGraph:1327] OVXLIB_VERSION==1.2.14 D [setup_node:535] Setup node id[3] uid[30000] op[DATACONVERT] D [setup_node:535] Setup node id[0] uid[1] op[PERMUTE] D [setup_node:535] Setup node id[1] uid[2] op[CONV2D] D [setup_node:535] Setup node id[2] uid[3] op[PERMUTE] D [setup_node:535] Setup node id[4] uid[30001] op[DATACONVERT] D [vsi_nn_kernel_selector:1286] Instance OPENVX node with kernel "conv2d"
完整日志作为 vsi_nn_log_level_5_conv_1x17.txt 附后。
捆绑为 bug_report_artifacts.tar.gz
其他工件可根据要求提供:字节级隔离工具、用于增量分析的结构相同的工作工件(1、17)、完全符合条件的移植结果。
你好@themis_stewart
感谢您提供的信息,我正在与内部团队核实您提出的两个问题。
致敬,
Zhiming
您好@themis_stewart
请尝试使用以下基于 L6.18.2 的补丁,以便在内核大小为> 16 时,将操作回退到 CPU
,谢谢
Zhiming
非常感谢你们如此迅速地处理此事——我们已经应用并测试了该保护补丁,并确认它解决了我们这边的问题。
我们使用您的 op_map.cc 更改,从 lf-6.18.2_1.0.0 委托源代码构建了打过补丁的 libvx_delegate.so,并在我们的 i.MX 8M Plus EVK 上针对原始报告中的最小复现步骤进行了验证。安装补丁后:
在实践中,CPU 回退路径对我们来说表现良好,尽管回退速度自然比完全在 NPU 上运行的模型要慢。如果详细的前后对比基准测试结果对您的回归测试有所帮助,我们很乐意私下分享。
再次感谢,非常感激。