Morgan Woods

真真夜夜的炼金工坊

AI編譯原理1

感謝 up 主 ZOMI 醬:https://space.bilibili.com/517221395

編譯器基礎概念#

  1. 什麼是編譯器?
  2. 為什麼 AI 框架需要引入編譯器?
  3. 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 構建了這些過程所需要的分析結果。
image

在編譯器 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 blockextract-blocks Pass

IR(Intermediate Representation)中間表示,是編譯器中很重要的一種數據結構。編譯器在完成前端工作以後,首先生成其自定義的 IR,並在此基礎上執行各種優化算法,最後再生成目標代碼。


如圖所示,在編譯原理中,通常將編譯器分為前端和後端。其中,前端會對所輸入的程序進行詞法分析、語法分析、語義分析,然後生成中間表達形式 IR。後端會對 IR 進行優化,然後生成目標代碼
2

例如: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。
3

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 在整個編譯器中則起着重要的承上啟下作用。從便於開發者編寫程序代碼的理解到便於硬體機器的理解。
4

總結#

  1. 解釋器是一種計算機程序,將每個高級程序語句轉換成機器代碼
  2. 編譯器把高級語言程序轉換成機器碼,即將人可讀的代碼轉換成計算機可讀的代碼
  3. Pass 主要是對源程序語言的一次完整掃描或處理
  4. 中間表示 IR 是編譯器中的一種數據結構,負責串聯起編譯器內各層級和模塊

02 傳統編譯器發展#

編譯器與編程語言幾乎是同步發展起來的,發展過程可以分為幾個階段:

  • 第一階段:20 世紀 50 年代,出現了第一個編譯程序,將算術公式翻譯成機器代碼,為高級語言的發展奠定了基礎。
  • 第二階段:20 世紀 60 年代,出現多種高級語言和相應的編譯器,如 Fortran、COBOL、LISP、ALGOL 等,編譯技術也逐漸成熟和規範化
  • 第三階段:20 世紀 70 年代,出現了結構化程序設計方法和模塊化編程思想,以及面向對象的語言和編譯器,如 Pascal、C、Simula 等,編譯技術也開始注重工程代碼的可讀性和可維護性。
  • 第四階段是 20 世紀 80 年代,出現了並行計算機和分佈式系統,以及支持並行和分佈式的語言和編譯器,如 Ada、Prolog、ML 等,編譯技術也開始考慮程序的並行和分佈能力。
  • 第五階段:20 世紀 90 年代,出現了互聯網和移動設備等新興平台,以及支持跨平台和動態特性的語言和編譯器,如 Java、C#、Python 等,編譯技術也開始關注程序的安全性和效率。
  • 第六階段:21 世紀第一個 10 年,出現了以 Lua 為首的 Torch 框架,用於解決爆炸式湧現的 AI 應用和 AI 算法研究,之後又推出 TensorFLow、PyTorch、MindSpore、Paddle 等 AI 框架,隨著 AI 框架和 AI 產業的發展,出現了如 AKG、MLIR 等 AI 編譯器。

傳統編譯器之爭#

5

目前主流如 LLVM 和 GCC 等經典的開源編譯器,通常分為三個部分,前端 (frontEnd),優化器 (Optimizer) 和後端 (backEnd)。

  1. Front-End:主要負責詞法和語法分析,將源代碼轉化為抽象語法樹,即將程序劃分為基本的組成部分,檢查代碼的語法、語義和語法,然後生成中間代碼
  2. Optimizer:優化器則是在前端的基礎上,對得到的中間代碼進行優化(如去掉冗餘代碼、子表達式消除等工作),使代碼更加高效
  3. Back-end:後端則是將已經優化的中間代碼,針對具體的硬體生成目標代碼,轉換成為包括代碼優化器和代碼生成器
類型GCCClang/LLVM
許可證GNU GPLApache 2.0
代碼模塊化一體化架構模塊化
支持平台Unix, Windows, MACUnix, MAC
代碼生成高效,有很多編譯器選項可以使用高效,LLVM 後端使用了 SSA 表單
語言獨立類型系統沒有
構建工具Make BaseCMake
解析器最早採用 Bison LR,現在改為遞歸下解析器手寫的遞歸下降解析器
鏈接器LDlld
調試器GDBLLDB

總結#

  • 編譯技術是計算機科學皇冠上的一顆明珠,作為基礎軟件中的核心技術
  • 編譯器能夠識別高級語言程序代碼中的詞彙、句子以及各種特定的格式和數據結構
  • 編譯過程,是將源代碼程序轉換成機器能夠識別的二進制碼
  • 傳統編譯器通常分為三個部分,前端 (frontEnd),優化器 (Optimizer) 和後端 (backEnd)
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。