感謝up主 ZOMI醬:https://space.bilibili.com/517221395
《AI編譯器開發》沒點基礎還真看不懂
01 編譯器基礎概念
- 什麼是編譯器?
- 為什麼 AI 框架需要引入編譯器?
- AI 框架和 AI 編譯器之間什麼關係?
編譯器與直譯器
編譯器(Compiler)和直譯器(Interpreter)是兩種不同的工具,都可以將程式語言和指令碼語言轉換為機器語言。雖然兩者都是將高階語言轉換成機器碼,但是其最大的區別在於:直譯器在程式執行時將程式碼轉換成機器碼,編譯器在程式執行之前將程式碼轉換成機器碼。
JIT和AOT編譯方式
目前,程式主要有兩種執行方式:靜態編譯和動態解釋。
- 靜態編譯的程式碼程式在執行前全部被翻譯為機器碼,通常將這種型別稱為 AOT(Ahead of time),即“提前編譯”;
- 動態解釋的程式則是對程式碼程式邊翻譯邊執行,通常將這種型別稱為 JIT(Just in time),即“即時編譯”。
AOT 程式的典型代表是用 C/C++ 開發的應用,其必須在執行前編譯成機器碼,然後再交給作業系統具體執行;而 JIT 的代表非常多,如 JavaScript、Python 等動態解釋的程式。
特點 | JIT (即時編譯) | AOT (提前編譯) |
---|---|---|
優點 | 1. 可以根據當前硬體情況實時編譯生成最優機器指令 2. 可以根據當前程式的執行情況生成最優的機器指令序列 3. 當程式需要支援動態連結時,只能使用JIT的編譯方式 4. 可以根據程序中記憶體的實際情況調整程式碼,使記憶體能夠更充分的利用 |
1. 在程式執行前編譯,可以避免在執行時的編譯效能消耗和記憶體消耗 2. 可以在程式執行初期就達到最高效能 3. 可以顯著加快程式的執行效率 |
缺點 | 1. 編譯需要佔用執行時Runtime的資源,會導致程序執行時候卡頓 2. 編譯佔用執行時間,對某些程式碼編譯最佳化不能完全支援,需在流暢和時間權衡 3. 在編譯準備和識別頻繁使用的方法需要佔用時間,初始編譯不能達到最高效能 |
1. 在程式執行前編譯會使程式安裝的時間增加 2. 將提前編譯的內容儲存起來,會佔用更多的記憶體 3. 犧牲高階語言的一致性問題 |
在AI框架中區別
目前主流的 AI 框架,都會帶有前端的表達層,再加上 AI 編譯器對硬體使能,因此 AI 框架跟 AI 編譯器之間關係非常緊密,部分如 MindSpore、TensorFlow 等 AI 框架中預設包含了自己的 AI 編譯器。目前 PyTorch2.X 版本升級後,也預設自帶 Inductor 功能特性,可以對接多個不同的 AI 編譯器。
編譯方式 | 描述 | 典型代表 |
---|---|---|
AOT (提前編譯) | 靜態編譯的程式碼程式在執行前全部被翻譯為機器碼,適合移動、嵌入式深度學習應用。 | 1. 推理引擎:訓練後的AI模型提前固化,用於推理部署。 2. 靜態圖生成:神經網路模型表示為統一的IR描述,執行時執行編譯後的內容。 |
JIT (即時編譯) | 動態解釋的程式邊翻譯邊執行,適合需要實時最佳化的場景。 | 1. PyTorch JIT:將Python程式碼實時編譯成本地機器程式碼,最佳化和加速深度學習模型。 2. Jittor:基於動態編譯JIT,使用元運算元和統一計算圖的深度學習框架,實現高效操作和自動最佳化。 |
Pass 和中間表示 IR
Pass 主要是對源程式語言的一次完整掃描或處理。在編譯器中,Pass 指所採用的一種結構化技術,用於完成編譯物件(IR)的分析、最佳化或轉換等功能。Pass的執行就是編譯器對編譯單元進行分析和最佳化的過程,Pass 構建了這些過程所需要的分析結果。
在編譯器 LLVM 中提供的 Pass 分為三類:Analysis pass、Transform pass 和 Utility pass。
Pass 型別 | 描述 | 功能 | 常見例子 |
---|---|---|---|
Analysis Pass | 計算相關IR單元的高層資訊,但不對其進行修改。這些資訊可以被其他Pass使用,或用於除錯和程式視覺化。 | 1. 從IR單元中挖掘並儲存資訊 2. 提供查詢介面供其他Pass訪問 3. 提供invalidate介面以處理資訊失效 |
Basic Alias Analysis、Scalar Evolution Analysis |
Transform Pass | 使用Analysis Pass的分析結果,以某種方式改變和最佳化IR。 | 1. 改變IR中的指令和控制流 2. 可能會減少函式呼叫,暴露更多最佳化機會 |
Inline Pass |
Utility Pass | 功能性的實用程式,不屬於Analysis Pass或Transform Pass。 | 1. 執行特定任務,如提取basic block | extract-blocks Pass |
IR(Intermediate Representation)中間表示,是編譯器中很重要的一種資料結構。編譯器在完成前端工作以後,首先生成其自定義的 IR,並在此基礎上執行各種最佳化演算法,最後再生成目的碼。
如圖所示,在編譯原理中,通常將編譯器分為前端和後端。其中,前端會對所輸入的程式進行詞法分析、語法分析、語義分析,然後生成中間表達形式 IR。後端會對 IR 進行最佳化,然後生成目的碼。
例如:LLVM 把前端和後端給拆分出來,在中間層明確定義一種抽象的語言,這個語言就叫做 IR。定義了 IR 以後,前端的任務就是負責最終生成 IR,最佳化器則是負責最佳化生成的IR,而後端的任務就是把 IR 給轉化成目標平臺的語言。LLVM 的 IR 使用 LLVM assembly language 或稱為 LLVM language 來實現 LLVM IR的型別系統,就指的是 LLVM assembly language 中的型別系統。
因此,編譯器的前端,最佳化器,後端之間,唯一交換的資料結構型別就是 IR,透過 IR 來實現不同模組的解耦。有些IR還會為其專門起一個名字,比如:Open64的IR通常叫做WHIRL IR,方舟編譯器的IR叫做MAPLE IR,LLVM則通常就稱為LLVM IR。
IR 在通常情況下有兩種用途,1)一種是用來做分析和變換,2)一種是直接用於解釋執行。
編譯器中,基於 IR 的分析和處理工作,前期階段可以基於一些抽象層次比較高的語義,此時所需的 IR 更接近原始碼。而在編譯器後期階段,則會使用低層次的、更加接近目的碼的語義。基於上述從高到低的層次抽象,IR 可以歸結為三層:高層 HIR、中間層 MIR 和 底層 LIR。
IR 型別 | 描述 | 用途 | 特點 |
---|---|---|---|
HIR (High IR) | 基於源程式語言執行程式碼的分析和變換。 | 用於IDE、程式碼翻譯工具、程式碼生成工具等,執行高層次程式碼最佳化(如常數摺疊、內聯關聯)。 | 1. 準確表達源程式語言的語義 2. 可以使用AST和符號表 |
MIR (Middle IR) | 獨立於源程式語言和硬體架構執行程式碼分析和最佳化。 | 用於編譯最佳化演算法,執行通用最佳化(如算術最佳化、常量和變數傳播、死程式碼刪除)。 | 1. 與源程式程式碼和目標程式程式碼無關 2. 通常基於三地址程式碼(TAC) |
LIR (Low IR) | 依賴於底層具體硬體架構做最佳化和程式碼生成。 | 用於執行與具體硬體架構相關的最佳化,生成機器指令或彙編程式碼。 | 1. 指令通常與機器指令一一對應 2. 體現了具體硬體架構的底層特徵 |
三地址程式碼 TAC 的特點:最多有三個地址(也就是變數),其中賦值符號的左邊是用來寫入,右邊最多可以有兩個地址和一個運算子,用於讀取資料並計算。
多層 IR 和單層 IR 比較起來,具有較為明顯的優點:
- 可以提供更多的源程式語言的資訊
- IR表達上更加地靈活,更加方便最佳化
- 使得最佳化演算法和最佳化Pass執行更加高效
如在 LLVM 編譯器裡,會根據抽象層次從高到低,採用了前後端分離的三段結構,這樣在為編譯器新增新的語言支援或者新的目標平臺支援的時候,就十分方便,大大減小了工程開銷。而 LLVM IR 在這種前後端分離的三段結構之中,主要分開了三層 IR,IR 在整個編譯器中則起著重要的承上啟下作用。從便於開發者編寫程式程式碼的理解到便於硬體機器的理解。
總結
- 直譯器是一種計算機程式,將每個高階程式語句轉換成機器程式碼
- 編譯器把高階語言程式轉換成機器碼,即將人可讀的程式碼轉換成計算機可讀的程式碼
- Pass 主要是對源程式語言的一次完整掃描或處理
- 中間表示 IR 是編譯器中的一種資料結構,負責串聯起編譯器內各層級和模組