21、错误处理与调试:常见错误码、日志分析、使用ADB调试NNAPI

说实话,NNAPI 开发最让人头疼的,不是写不出代码,而是跑起来之后一脸懵——模型加载失败、推理结果不对、甚至直接崩溃。我早期做 NNAPI 适配的时候,光是一个 ANEURALNETWORKS_BAD_DATA 就折腾了我整整两天。后来我总结了一套调试方法,今天全部分享给你。

21.1 常见错误码:读懂 NNAPI 的“黑话”

NNAPI 的 API 函数基本都会返回一个状态码。你想想看,如果连返回值都不检查,那跟闭着眼睛开车有什么区别?

下面这张表,是我个人认为最常遇到的几个错误码,建议你收藏一下。

错误码 含义 常见原因
ANEURALNETWORKS_NO_ERROR 0 一切正常 ——
ANEURALNETWORKS_BAD_DATA 1 输入数据格式不对 张量维度、类型或缓冲区大小不匹配
ANEURALNETWORKS_BAD_STATE 4 状态机异常 在编译完成前就执行推理,或者重复执行了某个操作
ANEURALNETWORKS_INCOMPLETE 5 操作未完成 模型构建过程中缺少必要操作,比如没设置输入张量
ANEURALNETWORKS_OP_FAILED 6 算子执行失败 硬件不支持该算子,或者算子参数非法
ANEURALNETWORKS_OUT_OF_INSUFFICIENT_MEMORY 7 内存不足 模型太大,或者设备驱动分配内存失败
ANEURALNETWORKS_UNEXPECTED_NULL 8 传入了空指针 你忘了初始化某个句柄,或者传了 nullptr

核心原则:每次调用 NNAPI 函数后,务必检查返回值。不要偷懒,否则你会在调试上花掉 10 倍的时间。

21.2 日志分析:从 logcat 里“挖”线索

我习惯在调试 NNAPI 时,同时开三个 logcat 过滤器。为什么?因为 NNAPI 的日志分散在好几个 TAG 里,只看一个很容易漏掉关键信息。

21.2.1 关键 TAG

  • NNAPI:框架层日志,能看到模型编译、执行的生命周期。
  • NeuralNetworks:驱动层日志,能看到具体硬件(比如 DSP、NPU)的调用情况。
  • System:系统级错误,比如内存分配失败、权限问题。

举个例子,你可能会看到这样的日志:

// 模型编译失败
E NNAPI: [ANEURALNETWORKS_BAD_DATA] Model compilation failed: operand type mismatch at index 3

// 驱动层报错
E NeuralNetworks: [nnapi] nnapi_implementation::execute: OP_FAILED, op_code=29

// 内存不足
E System: mmap failed: Cannot allocate memory

嗯,这里要注意:op_code=29 对应的是哪个算子?你可以去 NNAPI 头文件里查 ANEURALNETWORKS_ADDANEURALNETWORKS_CONV_2D 这些枚举值。我一般会写个小脚本,把枚举值和数字对应起来,省得每次翻头文件。

21.2.2 日志分析三步法

  1. 先看 NNAPI TAG:确认是框架层报错,还是驱动层报错。
  2. 再看错误码:如果是 BAD_DATA,优先检查张量维度;如果是 OP_FAILED,优先检查算子兼容性。
  3. 最后看上下文:日志里通常会附带操作序号、张量索引,顺着这些索引去检查你的模型构建代码。

小技巧:adb logcat -s NNAPI NeuralNetworks 只显示这两个 TAG,避免被其他日志刷屏。

21.3 使用 ADB 调试 NNAPI:实战三板斧

ADB 不只是用来装 APK 的。我个人觉得,ADB 是调试 NNAPI 最趁手的工具,没有之一。

21.3.1 第一板斧:查看设备支持的 NNAPI 能力

你写的模型,目标设备到底能不能跑?别猜,直接查:

adb shell dumpsys nnaapi capabilities

这条命令会输出当前设备支持的所有算子、性能数据、内存限制。我在项目中遇到过,某款手机号称支持 NNAPI,结果跑 ANEURALNETWORKS_QUANTIZED_16BIT_LSTM 直接返回 OP_FAILED。一查 capabilities,果然不支持。

21.3.2 第二板斧:强制指定设备

NNAPI 默认会选一个“最佳”设备。但有时候它选的不对。你可以用 ADB 强制指定:

// 强制使用 CPU
adb shell setprop debug.nnapi.partition 1

// 强制使用 GPU
adb shell setprop debug.nnapi.partition 2

// 强制使用 DSP/NPU
adb shell setprop debug.nnapi.partition 3

为什么要这么做?我曾经遇到一个 bug,模型在 CPU 上跑得好好的,一上 GPU 就崩溃。通过强制指定设备,我很快定位到是 GPU 驱动对某个算子的实现有 bug。

注意:这些属性是调试用的,不要在生产环境里设置。另外,不同厂商的设备编号可能不一样,建议先查一下文档。

21.3.3 第三板斧:抓取详细执行日志

如果上面的方法还不够,那就开启 NNAPI 的详细日志:

adb shell setprop debug.nnapi.trace 1
adb logcat -b all | grep NNAPI

开启后,你会看到每个算子的输入输出张量、执行时间、内存地址。信息量很大,但排查疑难杂症时非常有用。我一般只在问题复现时才开,平时关掉,否则日志刷得太快。

21.4 知识体系总览

下面这张图,是我对 NNAPI 错误处理与调试的总结。你可以把它当成一张“作战地图”。

NNAPI 错误处理与调试知识体系 常见错误码 • BAD_DATA (1) • BAD_STATE (4) • OP_FAILED (6) • OUT_OF_MEMORY (7) 日志分析 • NNAPI TAG • NeuralNetworks TAG • System TAG • 三步分析法 ADB 调试三板斧 • 查看 capabilities • 强制指定设备 • 开启 trace 日志 调试流程:检查错误码 → 分析日志 → 使用 ADB 定位 → 修复代码 ⚠ 避坑指南 不要跳过返回值检查 | 不要在生产环境开 trace | 不同厂商设备编号可能不同

21.5 避坑指南:我踩过的那些坑

最后,分享几个我亲身经历过的坑,希望能帮你省点时间。

  • 坑一:返回值检查不彻底。 我曾经只检查了 ANeuralNetworksCompilation_create 的返回值,却没检查 ANeuralNetworksExecution_setInput。结果模型编译成功,执行时却因为输入维度不对崩溃了。嗯,从那以后我养成了“每调用必检查”的习惯。
  • 坑二:日志里看到 OP_FAILED 就以为是驱动 bug。 其实很多时候,是你传的参数不对。比如 ANEURALNETWORKS_CONV_2D 的 padding 参数,我填错过一次,驱动直接报错。先检查自己的代码,再怀疑驱动。
  • 坑三:ADB 强制指定设备后忘了恢复。 调试完记得把 debug.nnapi.partition 清掉,否则后续测试都会跑在同一个设备上,测不出问题。

我的习惯:写一个 checkNnapiStatus 工具函数,把错误码转成可读的字符串,再带上文件名和行号。这样日志一打出来,我立刻知道是哪行代码出了问题。

好了,关于 NNAPI 的错误处理与调试,今天就聊到这儿。记住,调试不是浪费时间,而是帮你更深入理解系统。下次遇到 ANEURALNETWORKS_BAD_DATA,别慌,按今天的方法一步步来,肯定能搞定。