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~

相關文章
相關標籤/搜索