AOE工程实践-NCNN组件

NCNN是腾讯开源的一个为手机端极致优化的高性能神经网络前向计算框架。在AOE开源工程里,咱们提供了NCNN组件,下面咱们以SqueezeNet物体识别这个Sample为例,来说一讲NCNN组件的设计和用法。java

直接集成NCNN缺点

为SqueezeNet接入NCNN,把相关的模型文件,NCNN的头文件和库,JNI调用,前处理和后处理相关业务逻辑等。把这些内容都放在SqueezeNet Sample工程里。这样简单直接的集成方法,问题也很明显,和业务耦合比较多,不具备通用性,前处理后处理都和SqueezeNcnn这个Sample有关,不能很方便地提供给其余业务组件使用。深刻思考一下,若是咱们把AI业务,做为一个一个单独的AI组件提供给业务的同窗使用,会发生这样的状况:git

每一个组件都要依赖和包含NCNN的库,并且每一个组件的开发同窗,都要去熟悉NCNN的接口,写C的调用代码,写JNI。因此咱们很天然地会想到要提取一个NCNN的组件出来,例如这样:

AOE SDK里的NCNN组件

在AOE开源SDK里,咱们提供了NCNN组件,下面咱们从4个方面来说一讲NCNN组件:github

  • NCNN组件的设计
  • 对SqueezeNet Sample的改造
  • 应用如何接入NCNN组件
  • 对NCNN组件的一些思考

NCNN组件的设计

NCNN组件的设计理念是组件里不包含具体的业务逻辑,只包含对NCNN接口的封装和调用。具体的业务逻辑,由业务方在外部实现。在接口定义和设计上,咱们参考了TF Lite的源码和接口设计。目前提供的对外调用接口,主要有如下几个:api

// 加载模型和param
void loadModelAndParam(...)
// 初始化是否成功
boolean isLoadModelSuccess()
// 输入rgba数据
void inputRgba(...)
// 进行推理
void run(...)
// 多输入多输出推理
void runForMultipleInputsOutputs(...)
// 获得推理结果
Tensor getOutputTensor(...)
// 关闭和清理内存
void close()
复制代码

新的代码结构以下:数组

├── AndroidManifest.xml
├── cpp
│   └── ncnn
│       ├── c_api_internal.h
│       ├── include
│       ├── interpreter.cpp
│       ├── Interpreter.h
│       ├── jni_util.cpp
│       ├── jni_utils.h
│       ├── nativeinterpreterwrapper_jni.cpp
│       ├── nativeinterpreterwrapper_jni.h
│       ├── tensor_jni.cpp
│       └── tensor_jni.h
├── java
│   └── com
│       └── didi
│           └── aoe
│               └── runtime
│                   └── ncnn
│                       ├── Interpreter.java
│                       ├── NativeInterpreterWrapper.java
│                       └── Tensor.java
└── jniLibs
    ├── arm64-v8a
    │   └── libncnn.a
    └── armeabi-v7a
        └── libncnn.a
复制代码
  • Interpreter,提供给外部调用,提供模型加载,推理这些方法。
  • NativeInterpreterWrapper是具体的实现类,里面对native进行调用。
  • Tensor,主要是一些数据和native层的交互。

AOE NCNN组件有如下几个特色:缓存

  • 支持多输入多输出。
  • 使用ByteBuffer来提高效率。
  • 使用Object做为输入和输出(实际支持了ByteBuffer和多维数组)。

下面咱们来讲说具体是如何作的。bash

如何支持多输入多输出
为了支持多输入和多输出,咱们在Native层建立了一个Tensor对象的列表,每一个Tensor对象里保存了相关的输入和输出数据。Native层的Tensor对象,经过tensor_jni提供给java层调用,java层维护这个指向native层tensor的“指针”地址。这样在有多输入和多输出的时候,只要拿到这个列表里的对应的Tensor,就能够就行数据的操做了。网络

ByteBuffer的使用
ByteBuffer,字节缓存区处理子节的,比传统的数组的效率要高。
DirectByteBuffer,使用的是堆外内存,省去了数据到内核的拷贝,所以效率比用ByteBuffer要高。app

固然ByteBuffer的使用方法不是咱们要说的重点,咱们说说使用了ByteBuffer之后,给咱们带来的好处:
1,接口里的字节操做更加便捷,例如里面的putInt,getInt,putFloat,getFloat,flip等一系列接口,能够很方便的对数据进行操做。
2,和native层作交互,使用DirectByteBuffer,提高了效率。咱们能够简单理解为java层和native层能够直接对一块“共享”内存进行操做,减小了中间的字节的拷贝过程。框架

如何使用Object做为输入和输出
目前咱们只支持了ByteBuffer和MultiDimensionalArray。在实际的操做过程当中,若是是ByteBuffer,咱们会判断是不是direct buffer,来进行不一样的读写操做。若是是MultiDimensionalArray,咱们会根据不一样的数据类型(例如int, float等),维度等,来对数据进行读写操做。

对SqueezeNet Sample的改造

集成AOE NCNN组件之后,让SqueezeNet依赖NCNN Module,SqueezeNet Sample里面只包含了模型文件,前处理和后处理相关的业务逻辑,前处理和后处理能够用java,也能够用c来实现,由具体的业务实现来决定。新的代码结构变得很是简洁,目录以下:

├── AndroidManifest.xml
├── assets
│   └── squeeze
│       ├── model.config
│       ├── squeezenet_v1.1.bin
│       ├── squeezenet_v1.1.id.h
│       ├── squeezenet_v1.1.param.bin
│       └── synset_words.txt
└── java
    └── com
        └── didi
            └── aoe
                └── features
                    └── squeeze
                        └── SqueezeInterpreter.java
复制代码

其余的AI业务组件对NCNN组件的调用,均可以参考SqueezeNet这个Sample。

应用如何接入NCNN组件

对NCNN组件的接入,有两种方式

  • 直接接入

  • 经过AOE SDK接入

两种接入方式比较:

功能特性 直接接入 经过AOE SDK接入
易用性 容易 容易
稳定性 不能肯定,依赖实现方的实现 高,安卓有独立进程机制,更加稳定,推理过程不影响主进程
模型配置
模型下载和动态升级
模型配置
模型准确率,性能等数据分析
提供图像处理工具包 AOE SDK提供了Vision组件
支持模型加密 要看具体的模型和框架 AOE SDK提供了模型加密解密组件

经过比较,咱们更建议是经过AOE SDK来对咱们的NCNN组件进行接入。

对NCNN组件的总结和思考

经过对NCNN组件的封装,如今业务集成NCNN更加快捷方便了。以前咱们一个新的业务集成NCNN,可能须要半天到一天的时间。使用AOE NCNN组件之后,可能只须要1-2小时的时间。
固然NCNN组件目前还存在不少不完善的地方,咱们对NCNN还须要去加深学习和理解。后面会经过不断的学习,持续的对NCNN组件进行改造和优化。

欢迎你们来使用和提建议

AoE (AI on Edge,终端智能,边缘计算) 是一个终端侧AI集成运行时环境 (IRE),帮助开发者提高效率。 github.com/didi/aoe

Github地址:

欢迎star~

相关文章
相关标签/搜索