TNN行業首發Arm 32位 FP16指令加速,理論性能翻倍

1.1 什麼是FP16數據?FP16指令有什麼好處?

FP16是半精度浮點格式,相比常用的FP32單精度浮點,數據寬度降低了一半。2016年Arm更新了Armv8.2-A Extension擴展指令集,其中包含FP16半精度浮點運算。Arm NEON向量指令長度為128位,一條FP32向量可完成4個單精度浮點數運算,一條FP16向量可完成8個半精度浮點數運算,使理論峰值性能翻倍。如果該指令用於加速網路推理,相比於FP32預期能達到2倍加速。

1.2 為什麼要支援Arm32位FP16指令加速?

智慧手機分為Arm32和Arm64位兩種架構,其中Arm64占絕大比例,蘋果從2013年9月發布iPhone5s後,所有機型全都是Arm64架構。在Arm64架構手機上,App編譯為64位可以獲得最大的性能,但Arm64架構也支援按照32位編譯的APP運行,而Arm32架構無法支援按照64位編譯的APP運行。因此出於機型覆蓋率以及軟體包大小的考慮,當前眾多App只會發布32位編譯的版本。

經調研,行業開源推理框架如ncnn、MNN等僅支援Arm64位FP16指令加速,這樣32位App無法享受FP16指令加速效果。針對這個行業缺失,TNN在架構兼容、模型兼容、程式碼結構設計等方面率先進行探索,對Arm64位和Arm32架構均實現了FP16指令優化,讓64位和32位App都能發揮硬體FP16向量加速的能力。

2.1 架構兼容性設計

由於深度學習網路的運算元種類繁多,並且隨著新模型不斷被開發,運算元類型也會隨之增加,因此,難以一次性為所有層提供FP16加速。我們採用TNN中廣泛使用的註冊機制,以實現FP16加速的增量開發。實現如下:

①在ArmDevice下維護一個全局的layer_precision_map,將運算元類型映射到其支援的數據類型;

②每實現一個FP16加速運算元,使用REGISTER_ARM_PRECISION_FP16+LayerType,更新layer_precision_map;

③在運行模型的初始化過程,TNN根據layer_precision_map的資訊,為模型中的每一層分發計算精度。僅當運算元已支援FP16加速,並且運行平台具備FP16加速硬體時,該層才會使用FP16精度計算。當用戶設置的網路精度為PRECISION_HIGH時,可以強制禁用FP16加速。

2.2 模型兼容性設計

部署時模型的運算元各種各種,為了獲得最大的性能,TNN支援模型按照FP32和FP16混合運行加速。對模型中已實現FP16加速的運算元,TNN默認自動按照FP16加速,而對模型中未實現FP16加速的運算元,TNN在靜態圖中自動插入Reformat層轉為FP32加速。如下圖所示:

在TNN的圖優化過程中,當發現Pad層不支援FP16加速時(如圖a所示),會在其輸入和輸出分別插入Reformat層。Reformat層負責將FP16和FP32數據格式以及數據排布做相互轉換,以支援Pad層單獨採用FP32計算,其餘層仍採用FP16計算。

如果模型中存在多個相連的層不支援FP16(如圖b所示),TNN的圖優化機制會避免在這些層之間插入成對的Reformat層,以提高運行效率。

2.3 程式碼結構設計

為了在32位和64位庫中都支援FP16,整體程式碼結構如下圖所示。其主要分為五個部分,通過CMake中不同的配置項,可編譯出不同的target。各個部分的主要區別體現在針對不同Arm指令集實現了特定優化。

由上圖可知,aarch32和aarch64 FP16指令程式碼獨立於其他部分。因為編譯FP16指令需要添加特定的編譯選項,如果對TNN程式碼全局添加該選項,會導致編譯器將選項應用到所有程式碼中,然後基於Armv8.2-A架構生成目標文件。

例如在Arm64 Target中,在編譯Armv8指令程式碼時添加該選項,會生成一些Armv8.1或Armv8.2指令集中獨有的指令。這些指令若在不支援v8.1和v8.2的Armv8 CPU上運行,會直接導致程式崩潰。因此,為了最大限度地提高兼容性,Armv8.2-A FP16指令程式碼被單獨剝離,單獨使用編譯選項,避免影響其他部分。

綜上所述,TNN庫會同時包含兩種架構的指令:

  • 64位庫:包含Armv8指令和aarch64 Armv8.2-A FP16指令。

  • 32位庫:包含Armv7指令和aarch32 Armv8.2-A FP16指令。

2.4 運行時兼容性設計

由上可知,TNN庫中包含兩種架構的指令。當運行64位或32位庫時,若在不支援Armv8.2-A的CPU上執行Armv8.2-A指令,會直接導致程式崩潰,在運行時造成兼容性問題。

針對上述問題,在執行推理之前,TNN會判斷當前運行的CPU是否在白名單中、是否支援Armv8.2-A。如果支援,則會運行FP16運算元,否則仍然運行FP32運算元,避免執行Armv8.2-A FP16指令。具體判斷方式如下:

①在IOS和OSX下,通過系統調用sysctlbyname(“hw.cpufamily”),獲取CPU型號,然後與維護的白名單比較,判斷CPU是否支援FP16加速。目前的白名單包括IOS的A11-A14,和OSX的M1。

②在Android和Linux下,通過系統調用getauxval(AT_HWCAP),獲取hwcap flag,然後與HWCAP_FPHP和HWCAP_ASIMDHP掩碼比較,當FPHP位和ASIMDHP位都為1時,CPU可支援FP16加速。

由於Android的C庫在API級別18及更高版本中才支援 getauxval,在低版本的32位Android系統中,該系統調用可能會失敗。為了支援aarch32 FP16加速,採用直接解析/proc/cpuinfo的方式,判斷CPU是否支援FP16加速。通過/proc/cpuinfo獲取處理器的MIDR(Main ID Register),根據該標誌可判斷處理器廠商和型號,例如Arm的Cortex-A55,海思的Cortex-A76 (HiSilicon)等。然後與維護的白名單比較,最終判斷硬體是否支援FP16加速。

TNN已對部分運算元實現了FP16優化,在64位和32位庫中均取得了不錯的加速效果,相比於一些開源框架也具備一定的性能優勢,如下圖中的對比數據所示。

在A76大核上,TNN的FP32和FP16性能均能保持前列。在A55小核上,Bolt框架針對A55單獨做了特殊優化,在性能測試時效果會更好。但實際應用中,由於進程可能會在小核和大核上動態調度,A55特殊優化版本在A76大核上運行的性能較差,所以實際應用的表現不一定會好。TNN採取的是相對摺中的實現,在大核和小核上都能取得不錯的性能表現。

TNN已經對大量的實現細節做了封裝,只需要對少量參數進行配置,就可以輕鬆獲得FP16的加速。

鏈接://github.com/Tencent/TNN

點擊「閱讀原文」可直接跳轉鏈接👇

1)編譯

TNN工程的根目錄下提供了各個平台的編譯腳本,只需要在這些腳本的基礎上打開TNN_ARM82_ENABLE,就可以將Armv8.2的優化程式碼編譯到TNN的lib中,當前默認是OFF。

2)運行

在初始化時,將precision參數設置成PRECISON_AUTO,TNN內部就會根據CPU的型號以及layer的實現情況自動去調用FP16的程式碼,當前默認是PRECISION_AUTO,所以不用做任何修改。

TNN當前正在與PCG光影團隊合作,後續還會支援更多運算元的Armv8.2-A FP16優化,同時也會嘗試去實現Armv8.2-A的Dot擴展指令,優化在最新機型上的int8模型性能。

Tags: