隨着深度學習的快速發展和端側設備算力的不斷提高,本來在雲端執行的推理預測工做正在部分遷移到端側。在 GMTC 全球大前端技術大會上,淘寶無線開發專家陳以鎏發表了《MNN - 端側推理引擎面臨的挑戰與應對》的演講,與你們分享了 MNN (Mobile Neural Network) 開發、開源中的思考與總結。本文整理內容以下。前端
開源與背景
人工智能從 2006 年開始,迎來了第三次浪潮。隨着 AlphaGo 在 2016 年、2017 年前後打敗李世石和柯潔,人工智能完全進入了公衆的視野。人工智能大熱的背後,是大數據的積累,是深度學習的發展,也是設備算力的提高。與此同時,深度學習的框架也在不斷演進 —— 從 Torch 、 Caffe 到 TensorFlow 、PyTorch ,再到更面向移動端的 CoreML 、 NNAPI 、 NCNN 、 MACE 等。淘寶的深度學習推理引擎 MNN 也於 2019 年 5 月宣佈開源。android
MNN 項目從 2017 年開始啓動,在經歷一年多的開發迭代並經過了淘寶雙十一的考驗後,於 2018 年末啓動開源計劃,在歷時小半年的開源改造後,今年 5 月份正式在 Github 開源。git
開源首先仍是由於經歷過雙十一以後,咱們以爲本身作好了準備,開源有助於咱們鞭策本身,把 MNN 作的更好;另外一方面,業界的開源方案,不管是 TensorFlow Lite、NCNN 仍是 Mace,都給了咱們很好的輸入和借鑑,咱們也但願藉着開源,將咱們的思考和創新回饋社區。github
下文就主要圍繞着 MNN,來介紹淘寶在移動 AI 上的一些實踐經驗。算法
挑戰與應對
端側推理引擎面臨的挑戰中,碎片化是最爲顯著的,這種碎片化是多層次、多維度的 ——後端
-
訓練框架上,Caffe、TensorFlow、PyTorch、MXNet 在訓練模型時都很經常使用;緩存
-
計算設備上,CPU、GPU 已經是主流,NPU、TPU 漸漸成爲標配,DSP、FPGA 在 IoT 上也很常見;網絡
-
算子層面上,衆多參數會造成不一樣的組合,從而對應出不一樣的優化方式,輕量化和通用化須要取捨;app
一款優秀的端側推理引擎,就須要在這樣碎片化的環境下,利用設備有限的資源,儘量發揮出設備的性能。爲此,也須要在轉換、調度、執行上加入相應的優化策略。下文,會就其中的部分展開說明。
轉換工具
模型優化
在模型優化中,MNN 引入了前端的概念來統一訓練框架。不一樣的前端負責加載不一樣訓練框架的模型,統一轉換爲 MNN 的模型格式。對於最經常使用的訓練框架 TensorFlow 和 Caffe,咱們提供了獨立的前端;其餘訓練框架,好比 MXNet,則須要先將模型轉換爲 ONNX,再經過 ONNX 前端加載。這裏,因爲 TensorFlow 的算子顆粒度相比 Caffe 和 ONNX 要更小,咱們引入了圖優化的模塊來對齊算子之間的顆粒度。模型轉換以後,會通過優化器優化,包含算子融合、算子替換、佈局調整等等。以後,能夠選擇對浮點模型執行量化壓縮。目前模型壓縮的模塊尚未開源,咱們會在完善以後,將相關代碼開源。這些步驟都完成以後,會使用 flatbuffer 來保存部署模型。
圖優化
這裏以 RNN-GRU cell 爲例,說明一下圖優化。
左圖是 RNN-GRU cell 在 TensorBoard 中的可視化描述。它足足包含了 3584 個節點,而每個節點都表明了必定的數據讀寫或運算,累積起來的總量很是大。然而,全部這些節點能夠打包使用一個大顆粒的算子來替代。這不只大幅下降了部署模型的大小,還能夠在大顆粒算子的實現中聚合大量的計算,避免沒必要要的數據讀寫。
右圖展現的是一個實際業務模型在圖優化先後的性能對比。在華爲 P十、紅米 3x、小米 6 上都有 1 倍左右的性能提高。而若是是雙向 GRU,效果還會更明顯。
算子融合
再以 Convolution、Batchnorm、Scale、ReLU 爲例說明優化器中的算子融合。
首先融合 Convolution 和 Batchnorm,Convolution 的 weight 等於 weight 乘 alpha,而 bias 等於 bias 乘 alpha 再加 beta;
然後融合 Convolution 和 Scale,融合過程和 Batchnorm 相似;
最後融合 Convolution 和 ReLU,在輸出結果前,計算激活函數 ReLU 便可。
這樣,四個算子就能夠合併成一個算子。融合的過程避免了三次 tensor 讀寫、兩次 tensor 乘加。優化效果見右圖,MobileNet V1 在小米 5 和華爲 P10 上有 20 ~ 40% 的性能提高,效果仍是比較明顯的。
智能調度
總體設計
在調度上,MNN 將每一類計算設備抽象爲一個後端,將算子在特定後端上的實現抽象爲執行器。後端負責特定設備上的資源分配和計算調度,執行器負責具體的實現。後端和算子的添加都經過註冊表來實現,這是一個雙層註冊表結構,拓展起來就相對靈活。
調度時,能夠爲子圖選擇相應的後端,再由後端建立出相應的執行器,組成管線;也能夠爲子圖選擇後端組,實現混合調度。好比,在 GPU 上不宜實現排序算子時,能夠回退到 CPU 來執行。
目前,MNN 在 CPU 上實現了 76 個算子,Metal 上有 55 個,OpenGL 覆蓋了基礎的 CNN 網絡,OpenCL 和 Vulkan 分別有 29 和 31 個。
緩存管理
在建立完執行器以後,子圖和管線已經就緒。下來,須要計算出全部 tensor 的形狀,在相應的後端上完成內存的分配。然後,在準備執行器時,再爲全部的執行器預先在後端上申請好必要的 buffer。運行結束後,返回 tensor 便可。
因爲推理所需的全部內存在準備期就已經申請完畢,在後續推理時,若是輸入的形狀不變,就能夠複用 tensor 和 buffer,從而避免頻繁地申請、釋放內存;只有輸入形狀改變的時候,才須要從形狀計算開始,調整一次內存分配。同時,因爲使用後端統一管理緩存,後端內的執行器之間,緩存就能夠充分複用的,這就大大減小了內存的需求量。此外,MNN 分配內存時,默認按照 32 位對齊,內存對齊有利於數據讀寫。
執行優化
數據佈局與滑窗卷積
數據佈局對性能影響巨大。
先來看一看在 NCHW 的佈局下,怎麼利用 SIMD 加速 3x3 的 depth-wise 卷積:
首先,讀取數據時,須要一次性讀取四個 float 做爲第一行的數據,後兩行的讀取也是類似的;
這時,讀取出的三行數據已經足夠計算兩列輸出,即,能夠複用部分數據;
然後,爲了提升數據複用,會再讀取出第四行數據,一次計算兩行兩列,即,能夠引入循環展開;
然而,殘留的 5~25 和 21~25 亮度眼邊界沒法利用 SIMD 計算,只能逐一循環讀寫完成計算;
按照這樣的方式,就能夠相應完成後幾個通道的計算。
可是,NCHW 佈局下,沒法充分利用 SIMD 進行加速,同時,實現優化分支越多,佔用包大小也就越多。
再來看一看 NC/4HW4 佈局下,利用 SIMD 加速的狀況又是怎樣的。
這裏的 "C/4" 指的是按照 4 個通道對齊的方式重排數據。重排全部輸入和權重數據後,每次 SIMD 讀寫都自然是 4 個通道的輸入數據和 4 個通道的權重數據。這樣,不論 kernel、stride、dilation 怎麼變化,咱們均可以簡單地使用 for 循環和 SIMD 的一套通用優化完成卷積計算。既不會有邊緣數據沒法加速的問題,也不會對包大小形成影響。
Winograd
對於對於 KxK 卷積,可使用 Winograd 算法進一步加速。MNN 中支持 2x2 到 7x7 的 Winograd 實現。Winograd 計算時,須要把輸出拆分紅 NxN 的小塊,把輸入拆分紅 (N+K-1)x(N+K-1) 的小塊。這樣,問題就能夠簡化爲兩個小矩陣的卷積。
再套用 Winograd 的公式,將矩陣間的卷積運算轉換爲矩陣點乘運算。在這個過程當中,除了矩陣點乘外,還引入三個矩陣轉換,分別是輸入矩陣 d、權重矩陣 g 和結果矩陣 Y’的轉換。其中,權重轉換時,G 矩陣能夠利用中國剩餘數定理計算,GgGT 就能夠在準備執行器時提早計算;輸入轉換和輸出轉換時用到的 A 和 B 矩陣須要根據 N 和 K 計算,咱們在代碼中內置了幾種優化後的組合,因此實際計算時,這兩個轉換並不須要通過複雜的矩陣乘法。
這樣,原來矩陣卷積所須要的 9x4 次乘法計算就能夠用矩陣點乘的 4x4 次乘法計算代替。只考慮乘法耗時的話,加速了 2.25 倍。示例中,K=3,N=2,但實際使用時,能夠選擇更大的 N 值,獲取高的加速倍數,但也要相應消耗更多的內存。
Strassen
MNN 多是端側推理引擎中,第一個應用 Strassen 算法優化矩陣乘法的。
Strassen 在計算矩陣乘法時,首先須要將矩陣平均拆分紅四個小矩陣。這裏使用 a11 ~ a2二、b11 ~ b2二、c11 ~ c22 表明四個小矩陣,計算過程一共須要 8 次小矩陣乘法運算。
這裏能夠引入中間小矩陣,s1 ~ s四、t1 ~ t四、m1 ~ m七、u1 ~ u7。其中,只有 m1 ~ m7 包含小矩陣乘法,一共 7 次小矩陣乘法運算。而其餘的,只包含小矩陣的加減法。也就是說,經過 4 + 4 + 7 次小矩陣加減法,替代了一次小矩陣乘法。
與原來的矩陣乘法相比,Strassen 的時間複雜度從 n 的 3 次方,下降到 n 的 2.81 次方。在矩陣較大時,矩陣乘法遠遠慢於矩陣加減法,收益就更明顯。
在 MNN 中,咱們會遞歸使用 Strassen。也就是說,遞歸拆分矩陣。在矩陣足夠大時,繼續拆分;在矩陣不夠大時,使用普通的矩陣算法。這裏使用減免的矩陣乘法開銷做爲收益,使用小矩陣 s、小矩陣 t、小矩陣 u 矩陣的加減法開銷之和做爲代價,收益大於代價時,就能夠考慮使用 Strassen 算法。
鏈路優化
鏈路優化能夠舉一個 19 年春節淘寶掃年貨的例子。在得到手機相機輸入後,每一幀的圖像首先須要通過一次預處理,將圖片縮放到年貨檢測模型的輸入大小上,然而再通過推理,斷定圖像有沒有年貨,若是有,就發放相關權益。這個過程當中,圖片預處理的耗時也不容忽視。下降這個耗時,就能夠幫助咱們提高幀率,從而改進用戶體驗。爲此,咱們引入了一個輕量級的 2D 圖片處理庫,能夠高效地完成色值變化、色彩空間的轉換或者仿射變換等。這樣,MNN 的用戶就再也不須要爲圖片處理引入 libyuv 或者 opencv 了。
性能比較
通過種種優化後,這是咱們在性能上交出的答卷。
MobileNet V2,在 OPPO r17 和 iPhone 7Plus 上作了一系列的性能對比。
如圖,MNN 的性能在 CPU 和 GPU 上都有必定的優點。
小結
總的來講,MNN 吸納了前人的經驗,也結合本身對端側推理引擎的認知,作了許多創新。綜合考慮性能、模型和後端的拓展性、緩存、CPU 和 GPU 的算子實現,以及 CV 庫等方面的表現,在端側推理引擎中,MNN 是值得移動 AI 用戶嘗試的選擇。
後續規劃
在後續計劃上,轉換部分,咱們計劃追加更多算子和更多圖優化匹配模板,也計劃將模型量化工具開源;調度部分,咱們計劃分步實現端側訓練和邊緣學習,計算設備自動選擇也在籌劃中;執行部分,仍是會持續優化現有各端算子的實現,也計劃優化量化卷積、矩陣乘算法,計劃在 CV 庫上直接支持 GPU,咱們也考慮將現有 NC/4HW4 實現的算法,整理爲獨立的高性能計算庫,算法自動選擇一樣在籌劃中;其餘部分,咱們會持續建設項目的可用性,持續加入更多的文檔和示例。
也歡迎你們參與到 MNN 中來。歡迎你們給咱們提 issue,提 pull request。
嘉賓介紹
陳以鎏,花名離青,2015 年加入淘寶,從事無線開發至今,目前擔任無線開發專家一職。是阿里巴巴開源的首款端側推理引擎 MNN (Mobile Neural Network) 的核心開發。