NNAPI 2.0+新特性:放宽计算精度、批量执行、优先级队列

各位同学,今天我们来聊聊 NNAPI 2.0 之后引入的几个重要特性。说实话,这些特性刚出来的时候,我第一反应是「终于来了」。为什么这么说?因为在实际项目中,我们经常遇到一些很尴尬的情况——模型跑得慢、精度要求没那么高却被迫用高精度、多个请求排队乱糟糟。NNAPI 2.0+ 的这几个新特性,说白了就是专门来解决这些痛点的。

一、放宽计算精度(RELAXED)

先讲放宽计算精度。这个特性,我个人觉得是 NNAPI 2.0 里最实用的一个。

什么叫放宽计算精度?简单说,就是允许驱动用低精度(比如 FP16)来代替高精度(FP32)进行计算。你可能会问:「精度低了,模型结果不就不准了吗?」嗯,这里要注意——不是所有场景都需要那么高的精度。

我在项目中遇到过这样一个案例:一个图像分类模型,用 FP32 跑,准确率 92.3%;换成 FP16 跑,准确率 92.1%。差了 0.2%,但推理速度提升了将近一倍。你说值不值?

核心机制:

  • 应用层通过 ANeuralNetworksCompilation_setPreference 设置 ANEURALNETWORKS_PREFER_LOW_POWERANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER 来暗示驱动可以放宽精度
  • 驱动层根据暗示,自主决定是否使用 FP16 或 INT8 等低精度计算
  • 结果可能略有偏差,但通常不影响最终输出

怎么用呢?看代码:

// 创建编译时设置精度偏好
ANeuralNetworksCompilation* compilation;
ANeuralNetworksCompilation_create(model, &compilation);

// 告诉驱动:可以放宽精度,我优先要速度
ANeuralNetworksCompilation_setPreference(
    compilation, 
    ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER
);

// 完成编译
ANeuralNetworksCompilation_finish(compilation);

避坑指南:我曾经在一个语音识别项目里用了放宽精度,结果识别率掉了 5%。后来发现是模型对数值精度特别敏感。所以我的建议是:先跑一遍对比测试,看看精度损失在不在可接受范围内。别一上来就开。

二、批量执行(Burst Execution)

接下来讲批量执行。这个特性,说白了就是让你一次提交多个输入,让驱动一次性处理完。

为什么要这么做?你想想看,每次调用 ANeuralNetworksExecution_compute 都要经历:提交输入 → 驱动调度 → 硬件执行 → 返回结果。如果一次只处理一张图片,那调度开销占比就很大。批量执行可以把多次调度的开销摊薄到多个输入上。

我记得第一次用这个特性是在一个视频帧处理项目里。每秒钟要处理 30 帧,单帧推理根本扛不住。改成批量执行后,一次提交 8 帧,吞吐量直接翻了三倍。

使用方式如下:

// 创建 burst 对象
ANeuralNetworksBurst* burst;
ANeuralNetworksBurst_create(compilation, &burst);

// 创建多个 execution,每个对应一个输入
ANeuralNetworksExecution* executions[4];
for (int i = 0; i < 4; i++) {
    ANeuralNetworksExecution_create(compilation, &executions[i]);
    // 设置各自的输入输出...
}

// 批量执行
ANeuralNetworksBurst_execute(burst, executions, 4);

// 释放资源
for (int i = 0; i < 4; i++) {
    ANeuralNetworksExecution_free(executions[i]);
}
ANeuralNetworksBurst_free(burst);

注意事项:批量执行不是万能的。如果每个输入的处理时间差异很大,那快的输入要等慢的输入一起返回。另外,驱动对批量大小有限制,一般建议 4-8 个一批。我曾经试过一次提交 32 个,结果驱动直接报错。

三、优先级队列(Priority Queue)

最后讲优先级队列。这个特性,我觉得是 NNAPI 2.0+ 里最被低估的一个。

为什么这么说?因为在真实场景中,不同任务的优先级是不一样的。比如一个实时视频通话的模型推理,肯定比后台相册分类的推理要紧急。如果没有优先级机制,所有请求都排队,那高优先级的任务就可能被低优先级的堵住。

优先级队列的机制是这样的:

  • 应用可以为每个 execution 设置优先级(高、中、低)
  • 驱动内部维护多个队列,优先处理高优先级任务
  • 低优先级任务可能会被抢占或延迟

设置优先级的方法:

// 创建 execution
ANeuralNetworksExecution* execution;
ANeuralNetworksExecution_create(compilation, &execution);

// 设置优先级为高
ANeuralNetworksExecution_setPriority(
    execution,
    ANEURALNETWORKS_PRIORITY_HIGH
);

// 正常执行
ANeuralNetworksExecution_compute(execution);

优先级枚举值:

常量 说明 适用场景
ANEURALNETWORKS_PRIORITY_LOW 低优先级 后台任务、批量处理
ANEURALNETWORKS_PRIORITY_MEDIUM 中优先级 普通用户交互
ANEURALNETWORKS_PRIORITY_HIGH 高优先级 实时任务、关键路径

个人经验:我曾经在一个多任务相机应用里,把预览帧的推理设为高优先级,把美颜效果的推理设为中优先级,把后台场景识别的推理设为低优先级。效果非常好,预览帧的延迟从 80ms 降到了 30ms。但要注意,别把所有任务都设成高优先级,那样优先级就没意义了。

知识体系总览

这三个特性其实是一个整体:放宽精度解决「算得快不快」的问题,批量执行解决「吞吐量够不够」的问题,优先级队列解决「调度合不合理」的问题。把它们组合起来用,效果最好。

下面这张图展示了它们之间的关系:

NNAPI 2.0+ 三大新特性关系图 NNAPI 2.0+ 推理引擎核心 放宽计算精度 FP16 / INT8 低精度 提升单次推理速度 批量执行 一次提交多个输入 提升整体吞吐量 优先级队列 高/中/低三级 优化任务调度 三者协同:放宽精度提升单次速度,批量执行提升整体吞吐,优先级队列优化调度顺序

最后说一句,这三个特性在 Android 10 及以上版本才完整支持。如果你的目标设备是 Android 9 或更早,那可能就用不上了。嗯,这就是为什么我一直建议项目最低 API 级别设到 29 以上。

好了,关于 NNAPI 2.0+ 的新特性就讲到这里。实际用的时候,记得先做性能测试,别盲目开所有特性。每个项目的情况不一样,找到最适合自己的组合才是关键。


公众号:蓝海资料掘金营,微信deep3321