
應忍冬 劉佩林編著
本書介紹嵌入式系統中的機器學習算法優化原理、設計方法及其實現技術。內容涵蓋通用嵌入式優化技術,包括基於SIMD指令集的優化、內存訪問模式優化、參數量化等,並在此基礎上介紹了信號處理層面的優化、AI推理算法優化及基於神經網絡的AI算法訓練—推理聯合的優化理論與方法。其中信號處理層面優化介紹了基於線性代數的快速近似算法、基於多項式的快速卷積構造技術、基於數據二進制結構的快速乘法算法等;在AI推理層面,介紹了機器學習推理模型共性結構、運算圖中各個算子的計算優化途徑;對基於神經網絡的AI算法,闡述瞭如何將推理階段的運算量約束以及底層數據量化約束加入訓練代價函數,從算法訓練端減少運算量以提升AI嵌入式系統的運行效率。此外,本書還通過多個自動搜索優化參數並生成C代碼的例子介紹了通用的嵌入式環境下機器學習算法自動優化和部署工具開發的基本知識,通過應用示例和大量代碼說明了AI算法在通用嵌入式系統中的實現方法,力求讓讀者在理解算法的基礎上,通過實踐掌握高效的AI嵌入式系統開發的知識與技能。
第七章 AI芯片思考與展望 1. GPU架構與CUDA關係 CUDA(Compute Unified Device Architecture,統一計算設備架構),其具有通用並行計算架構(Parallel Computing Architecture)和編程模型(Programming Model),利用 GPU 的並行處理能力,將 GPU 用作通用並行計算設備,以加速各種計算任務,而不僅限於圖形處理 基於 LLVM 構建了 CUDA 編譯器,開發人員可使用 CUDA C/C++語言編寫並行程序,通過調用 CUDA API 將計算任務發送到 GPU 執行 CUDA 在軟件方面組成 1. 一個 CUDA 庫 2. 一個應用程序編程接口(API)及其運行庫(Runtime) 3. 兩個較高級別的通用數學庫(即 CUFFT 和 CUBLAS) CUDA 工具包(TOOLKIT)包括 1. 編譯和 C++核 2. CUDA DRIVER(驅動器)驅動 GPU 負責內存和圖像管理 3. CUDA-X LIBRARIES 主要提供機器學習、深度學習和高性能計算方面的加速庫 4. APPS & FRAMEWORKS 主要對接TensorFlow 和 PyTorch 等框架 [圖片] CUDA 最基本的執行單位是線程(Thread)、 1. 線程的執行,對應於硬件上的 CUDA Core,每個線程對應於 CUDA Core 2. 軟件方面線程數量是超配的,硬件上 CUDA Core 是固定數量的。 3. 線程塊只在一個 SM 上通過 Warp 進行調度 4. SM 可同時保存多個線程塊 5. 多個 SM 組成的 TPC 和 GPC 硬件實現 GPU 並行計算 [圖片] GPU 的算力峯值通常由以下因素決定 1. CUDA 核心數量 2. 核心頻率 3. 每個核心的計算能力 4. 並行度 [圖片] F 爲 GPU 時鐘週期內指令執行數 (FLOPS/Cycle); SMN 是 SM 數量; reqF 是 Tensor Core核心運行頻率(GHz) 2. 從GPU對AI芯片思考 DSA 之所以在硬件架構的指令和設計上比較激進,並非因爲軟件體系做得好,而是在剛開始並沒有太多考慮編程體系的問題 CUDA 的成功之處在於通過 SIMT 架構掩蓋了流水編排、實現了並行指令隱藏以及提升了 CUDA 的易用性 在指令流水編排方面,最重要的是從硬件設計上解決了 SIMD 數據路徑(Data Path)流水編排問題。 程序執行最大的瓶頸是訪存和控制流 SIMD 往往依賴於 CPU 自身亂序、投機、緩存和預取等能力來緩解上述問題 英偉達 GPU 則是依靠多線程交錯執行,來提升整體並行計算的性能,大量的線程通過不同的線程塊和不同的線程來讀取數據和執行計算指令。 DSA流水編排仍然需要開發者動手實現,想寫出開箱即用、性能較優的代碼同樣很難 3. AI芯片發展方向 谷歌在 2014 年開始研發基於特定領域架構(DSA)的張量處理單元(TPU),David Patterson 是計算機體系結構領域科學家,他提出: 1. 模型對內存和算力的需求日益變大 1. 推理模型需求增長速度快 1. AI 模型近幾年所需的內存空間和算力平均每年增長 50%,模型所需內存和算力的增長爲 10~20 倍。 2. 從芯片設計到實際應用需要一定的週期,其中芯片設計需要 1 年,部署需要 1年,實際應用並優化大約需要 3 年,因此共需要 5 年的時間 2. 訓練模型參數量的增長速度要比推理模型更快 1. 2016~2023 年,SOTA 訓練模型的算力需求年均增長 10 倍; 2. 2019~2020 年中期,GPT-2 模型的參數量從 15 億增長到 GPT-3 的 1750 億,提高了約 100 倍。 3. AI 芯片的內存容量增長相對比較緩慢 1. A100 的 HBM 最大內存是 80 GB,H100 最大內存是 188 GB 2. 谷歌 TPU v4 的內存是 32 GB 3. 特斯拉 DOJO 的內存是 16 GB 4. 華爲昇騰內存是 64 GB 5. 寒武紀 MLU 370 內存是 24 GB/48 GB 2. 模型結構快速演變 1. 深度神經網絡(DNN)是一個發展迅速的領域 1. 2016 年多層感知器(MLP) 2. 2026年長短期記憶網絡(LSTM) 3. 2020 年卷積神經網絡(CNN) 4. 2020年循環神經網絡(RNN) 5. 2020年雙向編碼器表示(BERT) 2. 大型語言模型(LLM)基於 Transformer 1. 參數規模穩步增長到如今的萬億參數 2. OpenAI 的 GPT-3.5 3. 微軟的 Phi-3 4. 谷歌的Gemma 5. Meta 的 Llama 3. 生產部署提供多租戶 1. 需要多模型部署在一個NPU上 1. 機器翻譯涉及語言對比,需要使用不同的模型; 2. 用一個主模型和配套多個模型進行實驗; 3. 對吞吐量和時延有不同要求,不同模型使用不同的 Batch Size 2. 需要多租戶技術(Multi-Tenancy)實現算力切分、顯存虛擬化、內存尋址、虛擬內存頁等 3. 目前常見的 GPU 虛擬化技術包括 NVIDIA vGPU(圖 7.3.1)、AMD MxGPU 以及 Intel GVT-g 等虛擬化方案 4. SRAM 與 DRAM 的權衡 1. 多模型時候,如果從 CPU 主機重新加載參數,上下文切換需要 10 s 2. 因此需要更快的 DRAM(片外存儲)用於交換多種模型的數據 3. 部分芯片的設計思路是期望利用 SRAM 解決所有任務,減少內存數據搬運時間,但這在多租戶場景下很難實現 4. AI 芯片不僅需要更大的 SRAM 片上存儲內存空間,更需要存儲速度更快的片外存儲 DRAM [圖片] 5. 內存優先於計算速度 1. 訪問片外 DRAM 需要的能耗是訪問片上 SRAM 的 100 倍,是算術運算能耗的 5000 ~ 10000 倍 2. 減少內存訪問是更有效的辦法,GPGPU 的功耗大多浪費在數據搬運上,而非核心計算,而優化數據流正是 AI 芯片的核心價值 6. DSA 既要專業也要靈活 1. DSA 難點在於既要對模型進行鍼對性的優化,同時還須保持一定的靈活性 7. 半導體供應鏈的選型 8. 編譯器優化和 AI 應用兼容 編譯器維度比較多,既有類似於 CUDA 提供編程體系,又有類似於 TVM(深度學習編譯器)和 XLA(加速線性代數)提供編譯優化,包括對計算圖進行圖優化、內存優化、並行化、向量化等操作,以提高模型的執行速度和減少資源消耗 1. 實現 4096 個芯片的多核並行; 2. 向量、矩陣、張量等功能單元的數據級並行; 3. 322~400 位超長指令字的指令級並行,一條指令同時包含多個操作,這些操作在同一時鐘週期內可並行執行; 4. 編譯優化取決於軟硬件能否進行緩存,編譯器需要管理內存傳輸; 5. 編譯器能夠兼容不同功能單元和內存中的數據佈局(如 Trans Data) 實際上可以通過算子融合(Operator Fusion)減少內存,從而優化性能。例如可以將矩陣乘法與激活函數融合,省略那些將中間結果寫入 HBM 後再讀取的步驟 [圖片] 4. 超異構計算 異構計算的主要優勢有: 1. 性能飛躍:異構架構將 CPU、GPU、FPGA 等計算單元有機結合,充分發揮各自優勢,實現 1+1>2 的效果,顯著提升計算性能。 2. 靈活定製:針對不同計算任務,靈活選擇合適的主張計算單元,實現資源的高效利用。 3. 降低成本:相比於昂貴的專用計算單元,異構架構用更低的成本實現更高的性能,帶來更佳的性價比。 4. 降低功耗:異構架構能夠根據任務需求動態調整資源分配,降低整體功耗,提升能源利用效率 [圖片] [圖片] CPU 把數據準備好,並保存在 CPU 內存中; 1. 將待處理的數據從 CPU 內存複製到 GPU 內存(Step1); 2. CPU 指示 GPU 工作,配置並啓動 GPU 內核(Step2); 3. 多個 GPU 內核並行執行,處理準備好的數據(Step3); 4. 處理完成後,將處理結果複製回 CPU 內存(Step4); 5. CPU 對 GPU 的結果進行後續處理 異構架構的優勢 1. 適用於處理高性能計算 2. 適用於處理數據中心產生的海量數據 3. 可以共享內存空間,消除冗餘內存副本 架構對比 1. GPU 具有較好的靈活性,但性能效率不夠極致; 2. DSA 性能優異,但靈活性較差,難以適應複雜計算場景對靈活性的要求; 3. FPGA 的功耗和成本較高,需要進行定製開發,落地案例相對較少; 4. ASIC的功能完全固定,難以適應靈活多變的複雜計算場景 超異構的出現是應運而生的 1. 需求驅動 2. 工藝和封裝支撐 3. 系統架構持續創新 [圖片] 超異構有以下基本特徵 1. 超大規模的計算集羣 2. 複雜計算系統 超異構的軟件層 1. 開放接口/架構及生態 2. 軟件兼容 3. 編程體系 4. 開放架構 [圖片] 1. 硬件定義軟件(HDW) 1. 強調通過硬件來定義軟件的功能和架構 2. 硬件加速器、專用指令集等技術 3. HDW 能夠充分利用硬件的並行計算能力,顯著提升軟件的性能 2. 軟件定義硬件(SDH) 1. 它強調通過軟件來定義硬件的功能和性能 2. 軟件抽象層(SAL)將軟件和硬件解耦 3. 爲軟件提供了統一的硬件接口,屏蔽了不同硬件平臺之間的差異 3. 軟硬件協同定義(Co-Design) 1. 軟件和硬件的設計團隊緊密合作,共同探討系統需求,並提出滿足需求的軟硬件解決方案 第八章 傳統編譯器 1. 引言 解釋器在程序運行時將代碼轉換成機器碼,編譯器在程序運行之前將代碼轉換成機器碼 編譯器特點 1. 編譯器讀取源程序代碼,輸出可執行機器碼 2. 將輸入源程序轉換爲機器語言或低級語言,並在執行前報告程序中出現的錯誤 3. 編譯的過程比較複雜,會消耗比較多的時間分析和處理開發者編寫的程序代碼。 4. 可執行結果,屬於某種形式的特定於機器的二進制碼 [圖片] 前主流如 LLVM (Low Level Virtual Machine,底層虛擬機)和 GCC (GNU CompilerCollection, GNU 編譯器套件) 等經典開源編譯器的類型分爲 1. 前端編譯器: 1. 編譯器的分析階段也稱爲前端編譯器,將程序劃分爲基本的組成部分,檢查代碼的詞法、語義和語法,然後生成中間代碼 2. 中間層編譯器 1. 中間層主要是對源程序代碼進行分析和優化 2. 分析階段包括詞法分析、語義分析和語法分析; 3. 優化主要是優化中間代碼,去掉冗餘代碼、子表達式消除等工作 3. 後端編譯器 1. 編譯器的合成階段也稱爲後端,針對具體的硬件生成目標代碼,合成階段包括代碼優化器和代碼生成器 編譯器和解釋器兩者最大的差別在於編譯器將一個程序作爲一個整體進行翻譯,而解釋器則一條一條地翻譯一個程序 程序主要有兩種運行方式: 1. 動態解釋是對代碼程序邊翻譯邊運行,通常將這種類型稱爲 JIT(Just in Time),即“即時編譯 2. 靜態編譯是代碼程序在執行前全部被翻譯爲機器碼,通常將這種類型稱爲 AOT(Ahead ofTime),即“提前編譯 IT 優點爲: 1. 可以根據當前硬件情況實時編譯生成最優機器指令。 2. 可以根據當前程序的運行情況生成最優的機器指令序列。 3. 當程序需要支持動態鏈接時,只能使用 JIT 的編譯方式。 4. 可以根據進程中內存的實際情況調整代碼,使內存能夠更充分地利用。 JIT 缺點也非常明顯: 1. 編譯需要佔用運行時(Runtime)的資源,會導致進程執行時卡頓,對某些代碼編譯優化不能完全支持,需在流暢和時間之間進行權衡。 2. 在編譯準備和識別頻繁使用的方法需要佔用時間,初始編譯不能達到最高性能。 JIT 的缺點也是 AOT 的優點所在: 1. 在程序運行前編譯,可以避免在運行時的編譯性能消耗和內存消耗。 2. 可以在程序運行初期就達到最高性能。 3. 可以顯著加快程序的執行效率。 AOT 除優點之外,也會帶來一些問題: 1. 在程序運行前編譯會使程序安裝的時間增加。 2. 將提前編譯的內容保存起來,會佔用更多的內存。 3. 犧牲高級語言的一致性問題 MindSpore、TensorFlow 等 AI 框架中默認包含了自己的 AI 編譯器。目前 PyTorch2.X 版本升級後,也默認自帶 Inductor 功能特性,可以對接多個不同的 AI 編譯器 在 AI 領域,目前 AOT 的典型代表有: 1. 推理引擎,在訓練之後 AI 編譯器把網絡模型提前固化下來,然後在推理場景直接使用提前編譯好的模型結構,進行推理部署; 2. 靜態圖生成,通過 AI 編譯器對神經網絡模型表示稱爲統一的 IR 描述,接着在真正運行時執行編譯後的內容 典型的代表有: 1. PyTorch框架中的 JIT 特性,可以將 Python 代碼實時編譯成本地機器代碼,實現對神經網絡模型的優化和加速。 2. 清華髮布的計圖(Jittor),完全基於動態編譯 JIT,內部使用創新的元算子和統一計算圖的 AI 框架,元算子和 Numpy 一樣易於使用,並且超越 Numpy 能夠實現更復雜更高效的操作。基於元算子開發的神經網絡模型,可以被計圖實時地自動優化並且運行在指定的硬件上 1. Pass 作爲編譯優化中間層的一個遍歷程序或者模塊 1. Pass 主要是對源程序語言的一次完整掃描或處理 2. Pass 指所採用的一種結構化技術,用於完成編譯對象的分析、優化或轉換等功能 3. Pass 的執行就是編譯器對編譯單元進行分析和優化的過程 4. Pipeline,則是由一個個 Pass 組成的,對於這些 Pass的管理,則是由 PassManager 完成的 5. LLVM 中提供的 Pass 分爲三類:Analysis Pass、Transform Pass 和 Utility Pass 1. Analysis Pass 計算相關 IR 單元的高層信息,但不對其進行修改。這些信息可以被其他Pass 使用,或用於調試和程序可視化 2. Transform Pass 可以使用 Analysis Pass 的分析結果,然後以某種方式改變和優化 IR 3. Utility Pass 是一些功能性的實用程序,既不屬於 Analysis Pass,也不屬於 Transform Pass 2. 中間表示 (Intermediate Representation,IR) 負責串聯起編譯器內各層級和模塊 1. 從廣義上看,編譯器的運行過程中中間節點的表示都可以統稱爲 IR。 2. 從狹義上講編譯器的 IR 是指該編譯器明確定義的一種具體的數據結構 3. 編譯器的前端、優化、後端之間,唯一交換的數據結構類型就是 IR,通過 IR 來實現不同模塊的解耦 4. IR 在通常情況下有兩種用途,一種是用來做分析和變換,另一種是直接用於解釋執行 5. HIR(High IR)主要負責基於源程序語言執行代碼的分析和變換 6. MIR(Middle IR),獨立於源程序語言和硬件架構執行代碼分析和具體優化。這些優化包括部分算術優化、常量和變量傳播、死代碼消除等,實現分析和優化功能。MIR 跟源程序代碼和目標程序代碼都無關,所以在編譯優化過程中,通常是基於 MIR,比如三地址碼(Three-Address Code,TAC) 7. LIR(Low IR),依賴於底層具體硬件架構做優化和代碼生成。其指令通常可以與機器指令一一對應,比較容易翻譯成機器指令或彙編代碼 1. Frontend:主要負責詞法和語法分析,將源代碼轉化爲抽象語法樹,即將程序劃分爲基本的組成部分,檢查代碼的詞法、語義和語法,然後生成中間代碼; 2. Optimizer:優化則是在前端的基礎上,對得到的中間代碼進行優化(如去掉冗餘代碼、子表達式消除等工作),使代碼更加高效; 3. Backend:後端則是將已經優化的中間代碼,針對具體的硬件生成目標代碼,轉換成爲機器可讀的二進制碼 2. 傳統編譯器介紹 [圖片] 3. GCC基本介紹與特性(略) 4. LLVM架構設計和原理 低級虛擬機(Low Level Virtual Machine)的首字母縮寫 LLVM LLVM 的優化通過多個優化 Pass 來提升中間表示的性能。每個 Pass 都對 IR 進行特定的優化操作,例如: 1. 常量摺疊(Constant Folding):將編譯時已知的常量表達式直接計算並替換。 2. 循環優化(Loop Optimizations):如循環展開、循環交換等,以提高循環執行效率。 3. 死代碼消除(Dead Code Elimination):移除不必要的代碼,提高執行效率 LLVM 的後端負責將優化後的中間表示轉換爲目標平臺的機器碼。這包含以下步驟: 1. 指令選擇(Instruction Selection):將 IR 轉換爲目標架構的彙編指令。 2. 寄存器分配(Register Allocation):爲指令分配合適的寄存器。 3. 指令調度(Instruction Scheduling):優化指令執行順序,以提高指令流水線的效率。 4. 代碼佈局(Code Layout):調整代碼的排列順序,以適應目標硬件的執行特性。 5. 代碼生成(Code Generation):生成目標平臺的彙編代碼和最終的機器碼 在中端優化完成之後會傳一個 DAG 圖的 IR 給後端,DAG 圖能夠非常有效地表示硬件的指定的順序 DAG(Directed Acyclic Graph,有向無環圖)是圖論中的一種數據結構,它是由頂點和有向邊組成的圖,其中頂點之間的邊是有方向的,並且圖中不存在任何環路 在計算機科學中,DAG 圖常常用於描述任務之間的依賴關係,例如在編譯器和數據流分析中。DAG 圖具有拓撲排序的特性,可以方便地對圖中的節點進行排序,以確保按照依賴關係正確地執行任務 5. LLVM IR基本概念 在 LLVM IR 中,所生成的 .ll 文件的基本語法爲: 1. 指令以分號;開頭表示註釋。 2. 全局表示以@開頭,局部變量以%開頭。 3. 使用 define 關鍵字定義函數,在本例中定義了兩個函數:@test 和 @main。 4. alloca 指令用於在堆棧上分配內存,類似於 C 語言中的變量聲明。 5. store 指令用於將值存儲到指定地址。 6. load 指令用於加載指定地址的值。 7. add 指令用於對兩個操作數進行加法運算。 8. i32 是 32 位 4 個字節的意思。 9. align 字節對齊。 10. ret 指令用於從函數返回 11. icmp 指令是根據比較規則,比較兩個操作數,將比較的結果以布爾值或者布爾值向量返回,且對於操作數的限定是操作數爲整數或整數值向量、指針或指針向量 12. br 指令有兩種形式,分別對應於條件分支和無條件分支 6. LLVM IR細節詳解 LLVM IR 具有類精簡指令集、使用三地址指令格式的特徵,使其在編譯器設計中非常強大和靈活 三地址碼是一種中間表示形式,每條指令最多包含三個操作數:兩個源操作數和一個目標操作數 三地址碼可以看作是一系列的四元組(4-Tuple),每個四元組表示一個簡單的操作,操作符,操作數1,操作數2,目標結果 優點: 1. 簡單性:三地址碼具有簡單的指令格式,極大地方便了編譯器進行語義分析和中間代碼生成操作。 2. 清晰性:每條指令執行一個簡單操作,便於理解和調試。 3. 優化潛力:由於指令簡單且結構固定,編譯器能夠輕鬆地實現多種優化技術,如常量摺疊、死代碼消除和寄存器分配。 4. 獨立性:三地址碼獨立於具體機器,可以在不同平臺之間移植 LLVM IR 是 LLVM 編譯器框架使用的一種中間表示,採用了類似三地址碼的設計理念。以下是 LLVM IR 指令集的一些特點: 1. 虛擬寄存器:LLVM IR 使用虛擬寄存器,而不是物理寄存器。這些寄存器以 % 字符開 頭命名。 2. 類型系統:LLVM IR 使用強類型系統,每個值都有一個明確的類型。 3. 指令格式:LLVM IR 指令也可以看作三地址指令 LLVM IR 是一種通用的、低級的虛擬指令集,用於編譯器和工具鏈開發 1. LLVM IR 設計爲模塊化的,代碼和數據分爲多個模塊,每個模塊包含多個函數、全局變量和其他定義 2. LLVM IR 是編譯過程中的中間表示,位於源代碼和機器碼之間。這種層次化設計使得不同語言和目標架構可以共享通用的優化和代碼生成技術 3. LLVM IR 採用 SSA (靜態單賦值)形式,每個變量在代碼中只被賦值一次。SSA 形式簡化了數據流分析和優化 4. LLVM IR 使用強類型系統,支持基本類型(如整數、浮點數)和複合類型(如數組、結構體) 靜態單賦值好處: 1. 每個值都由單一的賦值操作定義,這使得我們可以輕鬆地從值的使用點直接追溯到其定義的指令。 2. 由於 SSA 形式構建了一個簡單的使用——定義鏈,即一個值到達其使用點的定義列表,這極大地簡化了代碼優化過程。 一個 LLVM IR 文件的基本單位是 Module。它包含了所有模塊的元數據,例如文件名、目標平臺、數據佈局 在 Module 中,可以定義多個函數(Function),每個函數都有自己的類型簽名、參數列表、局部變量列表、基本塊列表和屬性列表等,Function 類包含有關函數定義和聲明的所有對象 每個函數可以有多個基本塊(BasicBlock),每個基本塊由若干條指令(Instruction)組成,最後以一個終結指令(Terminator Instruction)結束 Instruction 類表示 LLVM IR 的運算原子,即單個指令 7. LLVM前端和優化層 Clang 是一個強大的編譯器工具,作爲 LLVM 的前端承擔着將 C、C++ 和 Objective-C 語言代碼轉換爲 LLVM 中間表示(IR)的任務 通過 Clang 的三個關鍵步驟:詞法分析、語法分析和語義分析,源代碼被逐步轉化爲高效的中間表示形式 1. 詞法分析器負責將源代碼分解爲各種標記的流 2. 語法分析器負責根據編程語言的語法規則,將這些標記流組織成符合語言語法結構的語法樹。 3. 語義分析器確保語法樹的各部分之間的關係和含義是正確的 LLVM IR 是連接前端和後端的中樞,讓 LLVM 能夠解析多種源語言,爲多種目標生成代碼。前端產生 IR,而後端接收 IR LLVM 優化層在輸入的時候是一個 AST 語法樹,輸出的時候已經是一個 DAG 圖 優化層每一種優化的方式叫作 Pass,Pass 就是對程序做一次遍歷 1. 分析 Pass(Analysis Pass):分析 Pass 用於分析程序的特定屬性或行爲而不對程序進行修改 2. 轉換 Pass(Transformation Pass):轉換 Pass 用於修改程序代碼以進行優化或重構 1. 顯式依賴:轉換 Pass 需要一種分析,則 Pass 管理器自動地安排它所依賴的分析 Pass 在它之前運行 2. 隱式依賴:轉換或者分析 Pass 要求 IR 代碼運用特定表達式。需要手動地以正確的順序把這個 Pass 加到 Pass 隊列中,通過命令行工具(clang 或者 opt)或者 Pass 管理器來完成 8. LLVM後端代碼生成 LLVM 的後端是與特定硬件平臺緊密相關的部分,它負責將經過優化的 LLVM IR 轉換成目標代碼,這個過程也被稱爲代碼生成 整個後端流水線涉及四種不同層次的指令表示,包括: 1. 內存中的 LLVM IR:LLVM 中間表現形式,提供了高級抽象的表示,用於描述程序的指令和數據流。 2. SelectionDAG 節點:在編譯優化階段生成的一種抽象的數據結構,用以表示程序的計算過程,幫助優化進行高效的指令選擇和調度。 3. Machinelnstr:機器相關的指令格式,用於描述特定目標架構下的指令集和操作碼。 4. MCInst:機器指令,是具體的目標代碼表示,包含了特定架構下的二進制編碼指令。 [圖片] 5. 編譯器的每個模塊和 Pass 均可通過 Pass manager 進行管理,可以動態添加、刪除或調整 Pass 來優化編譯過程中的各個階段 PASS作用 1. 指令選擇的主要任務是將中間表示(例如 LLVM IR)轉換爲目標特定的 SelectionDAG 節點,生成目標機器碼的指令序列,實現從高級語言表示到底層機器指令的轉換 1. 將內存中的 LLVM IR 變換爲目標特定的 SelectionDAG 節點。 2. 每個 SelectionDAG 節點能夠表示單一基本塊的計算過程。 3. 在 DAG 圖中,節點表示具體執行的指令,而邊則編碼了指令之間的數據流依賴關係。 4. 目標是讓 LLVM 代碼生成程序庫能夠利用基於樹的模式匹配指令選擇算法,以實現高效的指令選擇過程 2. 指令調度是編譯器優化的一部分,旨在通過重新排序程序中的指令來提高計算機程序的性能。這個過程通常包括前寄存器分配(Pre-Register Allocation, Pre-RA)調度和後寄存器分配(Post-Register Allocation, Post-RA)調度兩個階段 1. 前寄存器分配調度 2. 寄存器分配 3. 後寄存器分配調度 3. 代碼輸出(Code Emission)是 LLVM 後端的重要階段,其目標是將中間表示(IR)轉化爲高效的目標機器碼 1. 指令融合(Instruction Fusion) 2. 啓發式優化(Heuristic Optimization) 3. 循環優化(Loop Optimization) 1. 循環展開(Loop Unrolling):通過展開循環體,減少循環控制開銷,提高指令流水線效率。 2. 循環交換(Loop Exchange):調整嵌套循環的順序,提高數據局部性。 3. 循環合併(Loop Fusion):將多個循環合併爲一個循環,減少循環開銷 4. 在 LLVM 中,代碼輸出由以下組件共同完成: 1. 指令選擇器: 2. 指令調度器 3. 寄存器分配器: 4. 彙編生成器 5. 機器代碼生成器 第九章 AI編譯器 1. 引言 首先,下一代編譯器需要提供硬件抽象,以跨越多樣化的加速器 其次,編譯器必須支持異構計算平臺 此外,隨着特定領域計算需求的增長,領域特定語言(DSL)和編程模型變得越來越重要 最後,下一代編譯器還需要確保基礎設施的質量、可靠性和可擴展性 第一個重要挑戰就是對於算子的優化,越來越多的 AI 算法誕生,帶來了更多的新算子 這些算子還需要放在特定的 AI 芯片上進行優化,充分發揮硬件性能 目前階段算子的開發和維護還是以人工手動實現、優化、測試爲主的,人工優化算子的工作量是非常大的 第二個重要挑戰是性能可移植性十分困難 AI編譯器和傳統編譯器的聯繫: 1. 實現思路類似:AI 編譯器與傳統編譯器都是通過自動化的方式進行程序優化和代碼生成,從而節省大量的人力對不同底層硬件的手動優化。 2. 優化方式類似:在編譯優化層,AI 編譯器與傳統編譯器都是通過統一 IR 執行不同的 Pass進行優化,從而提高程序執行時的性能。 3. 軟件棧結構類似:它們都採用前端、優化、後端三段式結構,通過 IR 解耦前端和後端實現模塊化表示。 4. AI 編譯器依賴傳統編譯器:AI 編譯器對 Graph IR 進行優化後,將優化後的 IR 轉化成傳統編譯器 IR,最後依賴傳統編譯器進行機器碼生成 1. 編譯目標的差異:傳統編譯器的起點是高級編程語言,終點則是硬件能夠執行的機器碼。相對地,AI 編譯器 的輸入是神經網絡模型的計算圖,而輸出同樣是機器碼 [圖片] 2. 軟件棧的差異 [圖片] 3. IR差異 1. AI 編譯器通常針對的是神經網絡模型,它們處理的是高度抽象的 IR,這種 IR 通常是爲了描述複雜的數學運算而設計的 2. 傳統編譯器處理的是更爲底層的 IR,這種 IR 更接近於機器指令,用於描述基本的計算操作 4. 優化策略的差異 1. 領域特定優化,AI 編譯器面向 AI 領域,優化時會引入更多領域特定知識 2. 計算精度,AI 編譯器可以降低計算精度,使用如 int8、fp16、bf16 等較低精度的數據類型 3. 硬件適配,AI 編譯器在優化時會考慮特定硬件的特性,如 GPU、TPU 等 2. AI編譯器歷史階段 AI 編譯器具有以下四個特徵 1. 以 Python 語言爲前端,AI 編譯器擁有以 Python 爲主的動態解釋器語言前端 2. 有多層 IR 設計,AI 編譯器擁有多層 IR 設計,包括圖編譯 IR、算子編譯 IR、代碼生成 IR 等 3. 面向神經網絡深度優化,這些優化可能包括內存訪問模式的優化、並行計算的調度,以及針對特定 AI 框架 的定製化支持 4. 針對 DSA 芯片架構,AI 編譯器需要能夠理解和利用這些 DSA 芯片的特性,生成能夠充分利用這些定製化硬件的代碼,從而實現性能的最優化 樸素AI編譯器: 1. 硬件無關的優化 1. 表達式化簡 2. 常量摺疊 3. 自動微分 2. 硬件相關的優化 1. 算子融合 2. 內存分配 專用 AI 編譯器 1. PyTorch 框架以其動態圖(也稱爲即時執行模式)而受到歡迎, 2. 編譯器需要將動態圖轉換爲靜態圖,以便於進行進一步的優化 1. 在某些場景下,需要根據運行時信息動態調整計算圖結構,而專用編譯器在這方面的支持不足 2. 當輸入數據的形狀(例如批量大小或輸入特徵維度)在運行時變化時,專用編譯器難以適應這種動態性。 3. 對於稀疏數據集,專用編譯器可能沒有優化計算路徑以減少不必要的計算和內存使用。 4. 在多設備或多節點上進行模型訓練和推理時,專用編譯器可能缺乏有效的並行化策略和優化 3. 專用 AI 編譯器在算子實現方面依然缺乏自動化的優化手段,如調度、分塊和代碼生成 AI編譯器基本架構如下圖 [圖片] 3. AI編譯器基本架構 推薦閱讀 AI 編譯器的一篇綜述—— The deep learning compiler: a comprehensive survey(Li et al., 2021) [圖片] 1. 編譯器前端 編譯器前端主要負責接收和處理來自不同 AI 框架的模型,並將其轉換爲通用的中間表示(IR),進行初步優化 這些模型通過符號表示的轉換(如 TVM、nGraph 等)生成計算圖;高級 IR/圖IR(設備無關)包含了 DAG(有向無環圖)表示形式以及張量計算;計算圖經過多種優化得到初步優化的計算圖;隨後通過模式匹配和圖重寫等方法進一步優化,最終生成優化後的計算圖 1. 輸入格式:支持多種 AI 框架,如 MindSpore、PyTorch、飛槳(PaddlePaddle)和 ONNX 2. 轉換:將來自不同框架的模型轉換爲統一的表示形式 3. 高級 IR/圖 IR:這些 IR 是設備無關的,主要用於表示計算圖 4. 計算圖優化策略:構建計算圖並進行優化,具體優化包括代數簡化、算子融合、CSE(公共子表達式消除)、DCE(死代碼消除)、靜態內存優化、佈局轉換 5. 優化方法:對計算圖進行進一步優化,使用各種方法如 Pattern Matcher(模式匹配)和Graph Rewriting(圖重寫)等 6. 調試工具 2. 編譯器後端 編譯器後端負責將優化後的計算圖轉換爲特定硬件平臺的低層次表示,並進行硬件特定優化和代碼生成 1. 硬件特定優化:針對特定硬件的優化技術,包括:Intrinsic Mapping(內在映射)、Memory Allocation(內存分配)、Memory Latency Hiding(內存時延隱藏)、Loop Oriented Optimization (面向循環的優化)以及 Parallelization(並行化)等。 2. 調度:包括 Auto Scheduling(自動調度),如 Polyhedral(多面體模型);也包括 Manual Scheduling(手動調度),如 Halide。 3. 自動調優:使用自動調優策略,方法包括 Parameterization(參數化模型)、Cost Model (成本模型)以及 Parameter Searching(參數搜索)等。 4. 使用內核庫:使用優化的內核庫提高執行效率,如:Intel DNNL、NVIDIA cuDNN/ TensorRT、AMD MIOpen 或者其他定製化庫。 5. 低級 IR/操作符 IR:生成設備特定的低層次 IR,包括:基於 Halide 的 IR、基於多面體模型的 IR 或者其他特有的 IR。 6. 編譯方案:支持 JIT 和 AOT 等編譯方案。 7. 代碼生成:生成適用於特定硬件平臺的代碼,如:LLVM、CUDA、OpenCL、OpenGL 等。 8. 目標平臺:最終生成的代碼可以在多種硬件平臺上運行。例如能在 x86、ARM、RISC-V 等架構的 CPU 上運行;或在英偉達、AMD 的 GPU 上運行;亦可在 ASIC(應用專用集成電路上運行,例如 TPU、Inferentia、NNP 等;也可在 DSP(數字信號處理器)上運行;同時也需要對越來越多新出現的 Accelerators(加速器)擁有一定的兼容性 3. 中間表示IR 1. 高級IR 1. 高級 IR 主要用於表示計算圖,它的設計目的是解決傳統編譯器在表達神經網絡模型等複雜運算時遇到的困難,同時爲實現更高效的優化提供支持 2. 高級抽象 3. 支持神經網絡模型 4. 優化支持 5. 硬件無關性 6. 跨平臺支持 2. 低級IR 1. 基於 Halide 的 IR 2. 基於多面體模型的 IR 3. 其他獨特的 IR 4. 前端優化 構建計算圖後,前端會進行圖級優化 前端優化通常由一系列 Pass 定義,通過遍歷計算圖節點並執行圖轉換來應用這些優化 除了預定義的 Pass 外,開發人員還可以在前端自定義 Pass,以滿足特定的優化需求或實現定製化的優化策略 1. 節點級優化 1. 節點級優化是指針對計算圖中的單個節點或操作進行的優化,旨在改進節點級別的計算效率和性能。 2. 常用的節點級優化方法包括 Zero-Dim-Tensor Elimination(零維張量消除)、Nop Elimination(空操作消除)等 2. 塊級優化 1. 塊級優化是指在計算圖中針對連續的一組節點或者局部區域進行的優化。這些優化更關注局部區域的性能改進,而不是單個節點。 2. 局部優化則更廣泛地指涉到對一組節點或者一個子圖進行的各種優化。常用的方法包括代數簡化、常量摺疊、算子融合等 3. 數據流級優化 1. 數據流級優化是針對整個計算圖或者數據流進行的優化,目標是最大化整體計算效率和性能。 2. 包括流水線調度、內存佈局優化、並行化、數據流重構等技術, 3. 常用的方法包括公共子表達式消除、死代碼消除等 5. 後端優化 通用 AI 編譯器的後端通常包含各種特定硬件優化、自動調整和優化內核庫 1. 特定硬件優化 1. 特定硬件優化,也稱爲目標相關優化,是爲了獲得針對特定硬件的高性能代碼而應用的 2. 實現後端優化的一種方法是將低級 IR 轉換爲 LLVM IR,利用 LLVM 基礎設施來生成優化的 CPU或 GPU 代碼。 3. 另一種方法是利用深度學習領域的專業知識設計定製化的優化,更有效地利用目標硬件 2. 自動調優 1. Halide/TVM 方法,Halide 和 TVM 可以自動搜索和評估不同的調度和計算表達組合,從而找到性能最優的配置方案 2. 應用多面體模型進行參數調整 3. 優化內核庫 1. 英特爾的 DNNL、英偉達的 cuDNN、AMD 的 MIOpen 6. AI 編譯器全棧產品 1. Hardware 2. Kernel Level 3. Graph Level 4. DL Models [圖片] 4. AI編譯器挑戰與思考 [圖片] [圖片] AI編譯器五大挑戰是 1. 動態 Shape 泛化,包含動態 Shape 輸入或者控制流語義的動態計算圖,AI 編譯器的支持確實有限 2. Python 編譯靜態化 3. 充分發揮硬件性能,AI 編譯器還將多種優化手段融合在一起,包括垂直融合優化(如 Buffer Fusion 等)和水平並行優化(如 Data Parallel 等),重新組合優化後的子圖的 Kernel 代碼也將自動生成,包括調度(Scheduling)、分塊(Tiling)、向量化(Vectorizing)等操作 4. 特殊算法優化方法,通過規模擴展(Scale Out)利用多維混合並行能力,如數據並行、張量並行和流水並行,通過性能提升(Scale Up)採用重計算、混合精度和異構並行等技術 5. 易用性與性能兼顧 1. 與 AI 框架的邊界和對接 2. 對用戶透明性問題 3. 編譯開銷 4. 性能問題 AI編譯器的未來 1. 編譯器形態 1. 未來的 AI 編譯器將分爲推理和訓練兩個階段,採用 AOT 和 JIT 兩種編譯方式 2. 在推理階段,編譯器將預先編譯模型以實現高效執行 3. 在訓練階段,編譯器則傾向於即時編譯以應對動態需求 2. IR 形態 1. 未來的 AI 編譯器需要一個類似於 MLIR 的統一中間表示(IR) 3. 自動並行 1. 未來的 AI 編譯器將具備自動並行的編譯優化能力,能夠跨機器、跨節點進行任務的並行處理 2. 這種功能的引入將爲大模型的訓練和推理提供更強大的支持 4. 自動微分 1. 未來的 AI 編譯器將提供先進的自動微分功能,能夠支持高階微分的計算方式 5. Kernel 自動生成 1. 未來的 AI 編譯器將實現自動化的 Kernel 生成功能,從而降低開發門檻,快速實現高效且泛化性強的算子 2. 這種自動生成 Kernel 的功能將大大簡化算法開發和優化的流程,減少了手工調優的時間和精力 第十章 前端優化 1. 引言 [圖片] AI 編譯器的前端優化中包含許多圖層優化的技術點,即優化 Pass,包括算子融合 Pass、內存分配 Pass、內存排布 Pass、常量摺疊 Pass 等,不同的 Pass 執行不同的優化邏輯,相互組合共同完成 AI 編譯器的前端優化 2. 圖算IR AI 框架前端將對 Python 代碼進行解析產生 Graph IR AI 編譯器前端優化流程中,AI 編譯器將對輸入的 Graph IR,依次執行包括但不限於常量摺疊、常量傳播、算子融合、表達式簡化、表達式替換、公共子表達式消除等各種前端優化 Pass,各個 Pass 的執行結果仍然爲 Graph IR 並輸入到下一個 Pass 中,直到前端優化結束並輸出最終優化後的 Graph IR 計算圖是一個有向無環圖(Directed Acyclic Graph,DAG),主要用來表示神經網絡模型在訓練與推理過程中的計算邏輯與狀態,它由基本數據結構張量(Tensor)和基本運算單元算子(Operator)構成 一般將多維數據稱爲張量 “算子”是 AI 框架的一個概念,在硬件底層實現具體算子執行部分,一般被稱爲 Kernel [圖片] 一般靜態圖的模型權重文件不僅包含權重數據,還包含了計算圖的信息,而動態圖一般用於訓練階段,在訓練完成後通常不會保存計算圖的信息 通常在推理階段會使用模型的計算圖的信息,因此在部署能力方面,靜態圖優於動態圖 動態圖轉換爲靜態圖 1. 基於追蹤轉換的方式 2. 基於源碼轉換的方式 計算圖對 AI 編譯器作用 1. 方便底層編譯優化 2. 分層優化便於擴展 1. 將 AI 編譯器的前端優化部分分爲三個解耦的優化層 2. 計算圖優化 3. 運行時調度優化 4. 算子/內核執行優化 3. 算子融合 算子融合主要爲了解決內存牆和並行牆問題 1. 內存牆,存在數據依賴的“生產者-消費者”算子進行融合,從而提升中間 Tensor 數據的訪存局部性,Buffer 融合一直是算子融合的主流技術. 2. 並行牆,主要是由芯片多核增加與單算子多核並行度不匹配引起 4. 佈局轉換原理 空間局部性指的是如果一個內存位置被引用了一次,那麼程序很可能在不遠的將來引用其附近的一個內存位置,它會影響到程序執行中的緩存及其他性能 數據在內存中存儲時相對於起始地址的偏移量是數據大小的整數倍,稱爲自然對齊,相對應的通過編譯器或語言的指令強制變量按照特定的對齊方式存儲,稱爲強制對齊 四個維度分別爲 N——批量(Batch)大小,一般指圖像數量;C——特徵圖通道數(Channels);H——特徵圖的高度(Height);W——特徵圖的寬度(Width) NHWC 排布方式適合多核 CPU 運算,而 NCHW排布方式適合 GPU 並行處理,且張量的連續性取決於其邏輯與物理存儲結構的相鄰性。 5. 內存分配算法 內存分配主要包括兩類情況——靜態內存和動態內存 靜態內存主要包括三個部分 1. 首先是模型的權重參數(Parameter),指的是神經網絡中的可訓練參數,這些權重參數在模型訓練時會被反覆調用和更新,但在 NPU 的內存中始終佔用固定大小的相同的內存區域 2. 其次是常量值節點(Constant Value Nodes),通常用於存儲訓練過程中的常量數據或需要持久化的中間結果,與權重參數類似,內存空間也是預先分配好的,並且內存佔用在計算過程中不會改變 3. 最後是模型的輸出(Output),指的是網絡在推理或訓練結束時產生的最終結果,這部分內存同樣是固定分配的,在訓練或推理過程中不會被重新分配或釋放 動態內存與靜態內存不同,其內存的分配和釋放是動態進行的 首先是輸出張量(Output Tensor),指的是在神經網絡前向傳播過程中每層計算所生成的中間結果 其次是工作區張量(Workspace Tensor),用於存儲中間計算結果或者其他臨時數據,僅在特定的計算操作中需要 常見的節省內存的算法分爲四種類型,包括空間換內存、計算換內存、模型壓縮、內存複用等 1. 空間換內存的算法:將 GPU 或 NPU 內存中的部分模塊卸載到 CPU 內存中(CPU Offload),該類算法更常見於對 MoE 的模型結構進行算法優化。 2. 計算換內存的算法:在部分場景下重新計算數據的效率比從存儲空間中讀取數據的效率高,那麼可以選擇不保存數據而在需要數據時進行重計算,例如使用梯度檢查點(Gradient Checkpointing)技術。 3. 模型壓縮的算法:在端側推理場景下應用較多,包括但不限於量化訓練 (Quantification)、模型剪枝、模型蒸餾等壓縮算法。 4. 內存複用:利用 AI 編譯器對計算圖中的數據流進行分析,以允許重用內存 在內存複用中存在若干常見操作,例如替換操作 Inplace Operation,即當一塊內存不再被需要時,且下一個操作是 element-wise 時,可以在原地覆蓋內存 6. 常量摺疊原理 常量摺疊(Constant Folding)是編譯器的一種優化技術,它通過在編譯期間對常量表達式進行計算,將其結果替換爲常量值,從而減少程序運行時的計算和開銷 傳統編譯器通常是對抽象語法樹進行常量摺疊優化,而 AI 編譯器是對計算圖進行常量摺疊優化 1. 當計算圖中某個節點的數據輸入邊的源節點均爲編譯期常量的節點,則可以提前計算出該節點的值來完全替換該節點 2. 以 AddN 爲例,對於兩個形狀大小爲(N,C,H,W)四維常量 Tensor,AddN 的計算結果在編譯期間是可以確定的,可以生成一個與該結果等值的數據節點替換掉 AddN 3. 輸入形狀確定的 Shape 類型的操作,比如 Size、Shape 等操作。這些操作都只與輸入的形狀有關,與輸入的具體值無關 4. 數據輸入節點不全爲編譯常量節點,但是這部分節點是可常量摺疊的節點,即經過一系列的常量摺疊後,該節點會被替換成編譯常量節點 7. 公共子表達式消除原理 公共子表達式消除(Common Subexpression Elimination,CSE)也稱爲冗餘表達式消除 在程序中,有時會出現多個地方使用相同的表達式進行計算,並且這些表達式的計算結果相同 公共子表達式消除的目標就是識別出這些重複的計算,並將其提取出來,只計算一次,然後將結果保存起來供後續使用 編譯器開發者將公共子表達式消除分成兩類。如果這種優化僅限於程序的基本塊內,便稱爲局部公共子表達式消除;如果這種優化範圍涵蓋了多個基本塊,那就稱爲全局公共子表達式消除 局部值編號(LVN)和緩式代碼移動(LCM)是兩種常見的公共子表達式消除方法 8. 死代碼消除 死代碼消除(Dead Code Elimination,DCE)是一種編譯器優化技術,旨在刪除程序中不會被執行的代碼,從而提高程序的執行效率和資源利用率 在傳統編譯器中,死代碼消除通常是通過分析控制流圖,從而找到並刪除無用和不可達操作對應的代碼 1. 不可達操作 2. 無用操作 9. 代數簡化 代數簡化(Algebraic Simplification)是一種從數學上來指導我們優化計算圖的方法。其目的是利用交換律、結合律等規律調整圖中算子的執行順序,或者刪除不必要的算子,以提高計算圖整體的計算效率 1. 先抽象出一套通用的子圖替換框架,再對各規則實例化。 2. 針對每一個具體的規則實現專門的優化邏輯 1. 算術簡化 結合律即不論我們怎樣結合數字(即先計算哪些數字),答案都是一樣的 交換律是說,把數的位置對換而結果不變 分配律,A*(B+C)=A*B+A*C 2. 運行簡化 1. 逆函數等於其自身函數的對合算子簡化 2. 冪等算子簡化,即作用在某一元素兩次與一次相同 3. 廣播簡化 當多個張量形狀 (Shape)不同情況下,需要進行廣播(Broadcast)將張量的形狀拓展爲相同的 Shape 再進行運算 第十一章 後端優化 1. 引言 後端優化的流程一般分爲三步 1. 生成低級 IR:將高級或計算圖級別 IR(Graph IR)轉換爲低級 IR(Tensor IR) 2. 後端優化:進行後端優化,並將 IR 轉換爲更低級的 IR 1. 數據形式不同:深度學習中數據形式主要爲張量(Tensor)。而傳統編譯器不擅長對張量計算優化,更擅長對標量進行計算。 2. 缺乏必要的支持:傳統編譯器主要針對通用編程語言,缺乏對領域特定語言(DSL)的支持,特別是對神經網絡,以及相關的特殊優化 3. 代碼生成:根據硬件進行代碼生成 算子根據其計算形式的特點可分爲訪存密集型與計算密集型 1. 訪存密集(Memory-Bound)型,指的是在執行過程中主要涉及大量內
很多地方不夠深入,邊緣設備在沒有NPU的情況下需要彙編級別的優化
一本講AI算法硬件加速不錯的書。尤其是模型量化這塊,原理講的很透徹,之前看安霸芯片的量化方式,總是雲裏霧裏,看了這本書之後有種醍醐灌頂的感覺。