傳統編譯器與神經網絡編譯器
傳統編譯器
以LLVM(low level virtual machine)為例,輸入是高級編程語言源碼,輸出是機器碼,由一系列模塊化的編譯器組件和工具鏈組成。
LLVM通過模塊分為前端,中端(優化)和后端三部分。每當出現新的編程語言,只需要開發相應的前端,將編程語言轉換成LLVM的中間表示;類似地,出現新的硬件架構,只需要開發相應的后端,對接上LLVM的中間表示。
模塊化的划分,避免了因編程語言和CPU架構的翻新,而引發的編譯器適配性問題,大大簡化了編譯器的開發工作。
神經網絡編譯器
輸入是深度學習訓練框架,訓練出來的模型定義文件,輸出是能夠在不同硬件高效執行的代碼。
從上至下由四個層級組成:
1. 最上層對接各個深度學習訓練框架,訓練出來的算法模型(Tensorflow, Caffe, Pytorch, Mxnet等)。
2. 圖層級(High-level IR):神經網絡的結構可以表示成計算圖,圖層級的操作,對計算圖進行一些和具體硬件和框架無關的操作,比如算子融合,內存分配優化,數據類型和數據維度的推導等。
可通過算子融合的方式,避免中間數據頻繁的在寄存器和內存直接來回讀寫,從而提升整體推理性能。
Nividia通過把conv, bn, relu這三個算子融合成一個算子 fuse- CBR,實現了三倍的推理性能提升。
3. 算子層級(operator level/kernel level)算子層級,主要是張量計算。為了實現這些計算在硬件上高效實現,發揮芯片的性能,通常硬件芯片配有專門優化的算子計算庫,如Intel的MKL, Nvidia的CuDNN, TensorRT。這個層級需要支持每個硬件后端的算子實現。
4. 各硬件后端:GPU, ARM CPU, X86 CPU, NPU等。
自深度學習編譯器的概念提出以來,各類編譯器變層出不窮的出現。
TVM的前世今生
在編譯器快速發展的浪潮中,較為突出的便是TVM(Tensor Virtual Machine)。
TVM最早提出是2017年,深度學習系統的編譯器堆棧。
第一代TVM的設計,借鑒了借鑒傳統編譯器框架LLVM的設計思路,設計抽象出中間表示層,不同的模型,只需要開發相應的前端接口,不同的后端,只需要開發相應的后端接口。
TVM全稱為Tensor Virtual Machine,屬於算子層級,主要用於張量計算,提供獨立於硬件的底層計算中間表示,采用各種方式(循環分塊,緩存優化等)對相應的計算進行優化。第一代的圖層級表示叫NNVM(Neural Network Virtual Machine)。NNVM的設計目標是:將來自不通深度學習框架的計算圖,轉換為統一的計算圖中間表示(IR),對之進行優化。
第一代的靜態圖存在一定的缺陷:
1. 不能較好支持控制流,如分支跳轉,循環等。
2. 不能支持計算圖輸入形狀,取決於輸入tensor大小的模型,比如word2vec等。
雖然Tensorflow有如tf.cond.Tf.while_loop的控制接口,在某種程度上解決第一個問題,tf.fold來解決第二個問題,但是這些方式對剛剛接觸深度學習框架的人來說門檻過高。
后面出現的動態圖,摒棄了傳統的計算圖先定義,后執行的方式,采用了計算圖在運行時定義的模式,這種計算圖就稱為動態圖。
第二代TVM的圖計算層,變為Relay VM,Relay和第一代的圖計算表示NNVM的最主要區別,Relay IR除了支持dataflow(靜態圖), 能夠更好地解決control flow(動態圖)。不僅是一種計算圖的中間表示,也支持自動微分。
總結一下,目前TVM的架構是:
1. 最高層級支持主流的深度學習前端框架,包括TensorFlow,MXNet,Pytorch等。
2. Relay IR支持可微分,該層級進行圖融合,數據重排等圖優化操作。
3. 基於tensor張量化計算圖,並根據后端進行硬件原語級優化,autoTVM根據優化目標探索搜索空間,找到最優解。
4. 后端支持ARM、CUDA/Metal/OpenCL、加速器VTA(Versatile Tensor Accelerator)。
Halide
Algorithm部分主要是算法描述和計算的數學表達式。
Halide於2012年提出,主要用於自動優化。其嵌入到C++中,MIT研究人員專門為圖像處理設計的一種程序語言。Halide語言易於編寫,語法簡單,數據結構清晰,自動對代碼進行優化,使得程序獲得更好的執行效率。
設計的核心思想是把算法和調度分離。這樣做的好處,在給定算法的情況下,只需要去調整Schedule 調度選擇,不用重寫算法實現不同的Schedule。當調整Schedule、探索設計空間時,也不會擔心因為重寫算法,而導致計算的正確性會發生變化。
Algorithm部分主要是算法描述和計算的數學表達式。
Schedule部分,告訴機器什么時候分配內存,如何計算(分塊計算還是順序計算)——目前已經提供了一些調度策略。
不同調度策略,考慮重復冗余計算和局部性(locality)的權衡。
AutoKernel
深度學習模型能否成功在終端落地應用,滿足產品需求,一個關鍵的指標就是神經網絡模型的推理性能。
目前的高性能算子計算庫,主要是由高性能計算優化工程師,進行手工開發。然而新的算法/硬件的不斷涌現,導致了算子層級的優化開發工作量巨大。同時優化代碼的工作,並不是一件簡單的事,要求工程師,既要精通計算機體系架構,又要熟悉算子的計算流程。
人才少,需求多,技術門檻高,算子優化自動化是未來的大趨勢。而提出AutoKernel的初衷,便是希望能把這個過程自動化,從小處入手,在算子層級的優化,實現優化代碼的自動生成。
AutoKernel的輸入是算子的計算描述(如Conv、Poll、Fc),輸出是經過優化的加速源碼。
這一工具的開發,旨在降低優化工作的門檻,不需要有底層匯編的知識門檻,不用手寫優化匯編。可通過直接調用開發的工具包,可生成匯編代碼。同時還提供了包含CPU、GPU的docker環境,無需部署開發環境,只需使用docker便可。還可通過提供的插件——plugin,可以把自動生成的算子,一鍵集成到推理框架中——Tengine。
對應地,算子層級的AutoKernel,主要分為三個模塊,
1. Op Generator:算子生成器,采用了開源的Hallide。
2. AutoSearch:目前還在開發中,目標是通過機器學習、強化學習常用算法自動搜索出優化策略。
3. AutoKernel Plugin:把生成的自動算子以插件的形式,插入到Tengine中,和人工定制互為補充。
Tengine對象層對接了不同的神經網絡模型,圖層級的NNIR包含了模型解析、圖層優化,算子層級包括高性能計算庫(HCL lib)。
AutoKernel Plugin主要分為生成和部署兩部分,生成部分用Hallid填寫算法描述和調度策略,執行時指定后端類型(基本覆蓋目前的主流后端)。
部署部分則封裝為Tengine的庫,直接調用。
參考鏈接:
https://www.163.com/dy/article/G583KIQD0511DPVD.html