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_POWER或ANEURALNETWORKS_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。但要注意,别把所有任务都设成高优先级,那样优先级就没意义了。
知识体系总览
这三个特性其实是一个整体:放宽精度解决「算得快不快」的问题,批量执行解决「吞吐量够不够」的问题,优先级队列解决「调度合不合理」的问题。把它们组合起来用,效果最好。
下面这张图展示了它们之间的关系:
最后说一句,这三个特性在 Android 10 及以上版本才完整支持。如果你的目标设备是 Android 9 或更早,那可能就用不上了。嗯,这就是为什么我一直建议项目最低 API 级别设到 29 以上。
好了,关于 NNAPI 2.0+ 的新特性就讲到这里。实际用的时候,记得先做性能测试,别盲目开所有特性。每个项目的情况不一样,找到最适合自己的组合才是关键。
公众号:蓝海资料掘金营,微信deep3321