作為我司頭發儲量前三的程式員
始終仗著頭發多奮斗在加班的第一線
時時靈魂拷問自己
年輕人,你憑什么不加班?
雖然我沒有女朋友
但是,我有代碼呀

但我不明白的是,隔壁工位那個,到崗比我遲,下班比我早,天天準點兒下班接女朋友,作業還完成的不錯的樣子,當然,頭發也還不錯,除了長得比我顯老,難道他有什么制勝法寶嗎?趁著午休,以一禮拜咖啡為代價,我偷師了他的制勝法寶,GET了秘訣,或許我也可以事業愛情雙豐收了,


直接集成NCNN的缺點
直接集成NCNN熬老少男顏哇,想當年我一邊淚流滿面地集成,一邊想用女友的SK2給自己的臉補補(不,你沒有,both SK2和女友),咋回事兒呢,為SqueezeNet接入NCNN,把相關的模型檔案,NCNN的頭檔案和庫,JNI呼叫,前處理和后處理相關業務邏輯等,把這些內容都放在SqueezeNet Sample工程里,這樣簡單直接的集成方法,問題也很明顯,和業務耦合比較多,不具有通用性,前處理后處理都和SqueezeNcnn這個Sample有關,不能很方便地提供給其他業務組件使用,深入思考一下,如果我們把AI業務,作為一個一個單獨的AI組件提供給業務的同學使用,會發生這樣的情況:

每個組件都要依賴和包含NCNN的庫,而且每個組件的開發同學,都要去熟悉NCNN的介面,寫C的呼叫代碼,寫JNI,所以我們很自然地會想到要提取一個NCNN的組件出來,提取以后呢長得順眼了很多,大概是這個樣子,

AOE SDK里的NCNN組件
有了AOE SDK,我也可以一頓操作猛如虎了!在AOE開源SDK里,我們提供了NCNN組件,下面我們從4個方面來講一講NCNN組件:
●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 - - - - - - - - - - - - - - - - - - - - - - - - - - -

原創不易,歡迎打賞
https://github.com/didi/AoE←據說點了這里的程式員們都準點下班了/

歡迎添加小助手微信進入AOE開源交流群!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/37759.html
標籤:架構設計

