做爲我司頭髮儲量前三的程序員html
始終仗着頭髮多奮鬥在加班的第一線java
時時靈魂拷問本身git
年輕人,你憑什麼不加班?程序員
雖然我沒有女友
可是,我有代碼呀github
但我不明白的是,隔壁工位那個,到崗比我遲,下班比我早,每天準點兒下班接女友,工做還完成的不錯的樣子,固然,頭髮也還不錯。除了長得比我顯老,難道他有什麼制勝法寶嗎?趁着午休,以一禮拜咖啡爲代價,我偷師了他的制勝法寶。GET了祕訣,或許我也能夠事業愛情雙豐收了。web
直接集成NCNN的缺點api
直接集成NCNN熬老小男顏哇,想當年我一邊淚流滿面地集成,一邊想用女朋友的SK2給本身的臉補補(不,你沒有,both SK2和女朋友),咋回事兒呢,爲SqueezeNet接入NCNN,把相關的模型文件,NCNN的頭文件和庫,JNI調用,前處理和後處理相關業務邏輯等。把這些內容都放在SqueezeNet Sample工程裏。這樣簡單直接的集成方法,問題也很明顯,和業務耦合比較多,不具備通用性,前處理後處理都和SqueezeNcnn這個Sample有關,不能很方便地提供給其餘業務組件使用。深刻思考一下,若是咱們把AI業務,做爲一個一個單獨的AI組件提供給業務的同窗使用,會發生這樣的狀況:數組
每一個組件都要依賴和包含NCNN的庫,並且每一個組件的開發同窗,都要去熟悉NCNN的接口,寫C的調用代碼,寫JNI。因此咱們很天然地會想到要提取一個NCNN的組件出來,提取之後呢長得順眼了不少,大概是這個樣子。緩存
有了AOE SDK,我也能夠一頓操做猛如虎了!在AOE開源SDK裏,咱們提供了NCNN組件,下面咱們從4個方面來說一講NCNN組件:app
●NCNN組件的設計
●對SqueezeNet Sample的改造
●應用如何接入NCNN組件
●對NCNN組件的一些思考
★ NCNN組件的設計
不懂NCNN的組件設計,即便一頓操做猛如虎,你可能最後也只有兩塊五。那它的組件是什麼嘞?NCNN組件的設計理念是組件裏不包含具體的業務邏輯,只包含對NCNN接口的封裝和調用。具體的業務邏輯,由業務方在外部實現。在接口定義和設計上,咱們參考了TF Lite的源碼和接口設計。目前提供的對外調用接口,長這個樣子:
// 加載模型和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和多維數組)。
光說不練假把式,AOE NCNN的實現過程,且聽我細細道來。
★ 如何支持多輸入多輸出
爲了支持多輸入和多輸出,咱們在Native層建立了一個Tensor對象的列表,每一個Tensor對象裏保存了相關的輸入和輸出數據。Native層的Tensor對象,經過tensor_jni提供給java層調用,java層維護這個指向native層tensor的「指針」地址。這樣在有多輸入和多輸出的時候,只要拿到這個列表裏的對應的Tensor,就能夠就行數據的操做了。
★ ByteBuffer的使用
ByteBuffer,字節緩存區處理子節的,比傳統的數組的效率要高。
DirectByteBuffer,使用的是堆外內存,省去了數據到內核的拷貝,所以效率比用ByteBuffer要高。
固然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 │ ├── squeezenet_v1.1.id.h │ ├── squeezenet_v1.1.param.bin │ └── synset_words.txt └── java └── com └── didi └── aoe └── features └── squeeze └── SqueezeInterpreter.java
↑ 本Sample也適用於其餘的AI業務組件對NCNN組件的調用。
(牛逼就完事兒)
★ 應用如何接入NCNN組件
對NCNN組件的接入,有兩種方式
●直接接入
●經過AOE SDK接入
▲兩種接入方式比較:
不BATTLE了,我單方面宣佈,AOESDK完勝!
★ 對NCNN組件的總結和思考
經過對NCNN組件的封裝,如今業務集成NCNN更加快捷方便了。以前咱們一個新的業務集成NCNN,可能須要半天到一天的時間。使用AOE NCNN組件之後,可能只須要1-2小時的時間。固然NCNN組件目前還存在不少不完善的地方,咱們對NCNN還須要去加深學習和理解。後面會經過不斷的學習,持續的對NCNN組件進行改造和優化。
- - - - - - - - - - - - - - - - - - - - - - - - - - - A o E - - - - - - - - - - - - - - - - - - - - - - - - - - -
原創不易,歡迎打賞