長期以來,我們所有的程式都是基於 CPU 和順序執行架構的,我們每個人都熟悉如何針對 CPU 進行調優以及如何除錯順序執行。 不過現在情況正在慢慢改變,由於機器學習習和人工智慧的發展,大規模平行計算的GPU被凸顯為基礎計算架構,而現在人工智慧和大模型的時代正在爭奪,其實就是打算力、打GPU的時代, 英偉達之所以能夠一飛沖天,正是因為其H100這樣的高效能GPU能力,才是贏得本次比賽的關鍵。而軟體開發工程師也面臨著乙個選擇,如何成為人工智慧的潮流引領者,而不是被人工智慧取代第一點是了解AI、高效能平行計算和GPU,本文就是習和你一起學習這些基礎知識。
CPU 和 GPU 之間的最大區別在於它們的原始設計目的根本不同。 CPU 設計用於執行順序指令。 為了提高順序執行效能,多年來在CPU設計中引入了許多優化指令。 重點是減少指令執行延遲,以便 CPU 能夠盡快執行指令序列,具有指令流水線、無序執行、推測執行和多級快取等優化點。
另一方面,GPU 專為大規模並行性和高吞吐量而設計,但在執行速度方面具有中等到高的指令延遲。 因此,它更適合用於遊戲、圖形、數值計算和深度習計算,所有這些都需要以非常快的速度執行大量的線性代數和數值計算,特別是在大和非常大規模的吞吐量和併行執行下。
由於指令延遲較低,CPU 可以比 GPU 更快地新增兩個數字,從而比 GPU 更快地連續執行多個此類計算。 在進行數百萬或數十億次此類計算時,由於其巨大的並行性,GPU 比 CPU 更快地完成這些計算。 具體來說,NVIDIA Ampere A100 提供 32 位精度為 19 位5tflops 的吞吐量。 相比之下,英特爾的 24 核處理器的 32 位精度吞吐量為 066 tflops。
此外,GPU 和 CPU 之間的吞吐量效能差距正在逐年擴大。
CPU和GPU計算結構對比:
如圖所示,CPU 將大量晶元面積專用於減少指令延遲的功能,例如大型多級快取、更少的 ALU 和更多的控制單元。 相比之下,GPU 使用大量 ALU 來最大限度地提高其計算能力和吞吐量。 它們使用非常小的晶元面積作為快取和控制單元,從而減少了 CPU 延遲。
延遲容限和高吞吐量
GPU 如何以每筆訂單的高延遲提供高效能的答案是他們的答案平行計算。GPU擁有大量的執行緒和強大的計算能力,可以保證高併發和高吞吐量。 即使單個指令具有很高的延遲,GPU 也會有效地安排執行緒執行,以便它們在每個時間點利用計算能力。 例如,當某些執行緒正在等待指令結果時,GPU 將切換到執行其他非等待執行緒,以確保 GPU 上的計算單元在所有時間點都以最大容量執行,從而提供高吞吐量。
GPU計算的特點是高吞吐量和高併發。 該架構在設計時就考慮到了這一原則和目的。
GPU 由一系列流式多處理器 (SM) 組成。 每個 SM 又由多個流計算器、核心和執行緒組成。 例如,NVIDIA H100 GPU 有 132 個 SM,每個 SM 有 64 個核心,總共有 8448 個核心。
每個 SM 都有有限數量的片上儲存器,通常稱為共享儲存器或暫存器,在所有核心之間共享。 同樣,SM 上的控制單元資源由所有核心共享。 此外,每個 SM 都配備了乙個基於硬體的執行緒排程器來執行執行緒。
除此之外,每個 SM 都有多個功能單元或其他加速計算單元,例如張量核心或光線追蹤單元,以滿足 GPU 滿足的工作負載的特定計算需求。
GPU 具有多層不同型別的記憶體,每層都有其特定的用例。 下圖顯示了 GPU 中 SM 的記憶體層次結構。
其主要組成單位是:
註冊:GPU 中的每個 SM 單元都有大量暫存器。 例如,NVIDIA A100 和 H100 型號每個 SM 有 65536 個暫存器。 這些暫存器在核心之間共享,並根據執行緒的要求動態分配給它們。 在執行過程中,分配給執行緒的暫存器是該執行緒專用的,即它們不能被其他執行緒讀取或寫入。
常量快取:用於快取在 SM 上執行的 ** 使用的常量資料。 為了利用這些快取,程式設計師必須在 ** 中顯式宣告物件為常量,以便 GPU 可以快取它們並將它們儲存在常量快取中。
共享記憶體:每個SM還具有乙個共享儲存器或暫存器,它是您可能熟悉的少量快速和低延遲片上可程式設計SRAM儲存器。 它被設計為由在 SM 上執行的執行緒塊共享。 共享記憶體背後的想法是,如果多個執行緒需要處理同一段資料,則只有乙個執行緒應該從全域性記憶體載入它,而其他執行緒共享它。 謹慎使用共享記憶體可以減少全域性記憶體上的冗餘載入操作,並提高核心執行效能。 共享記憶體的另乙個用途是作為塊內執行的執行緒之間的同步機制。
L1 快取:每個 SM 還有乙個 L1 快取,用於在 L2 快取中快取經常訪問的資料。
L2 快取:有乙個由所有 SMsm 共享的 L2 快取。 它將經常訪問的資料快取在全域性記憶體中,以減少延遲。 請注意,L1 和 L2 快取對 SM 都是透明的,即 SM 不需要知道它是從 L1 還是 L2 獲取資料,對於 SM,它是從全域性記憶體獲取資料。 這類似於 L1、L2、L3 快取在 CPU 中的工作方式。
全域性記憶體:GPU 還具有片外全域性記憶體,這是一種高容量、高頻寬的 DRAM。 例如,NVIDIA H100 具有 80GB 的高頻寬記憶體 (HBM),頻寬為 3000GB 秒。 由於距離SM較遠,全域性記憶體的延遲相當高。 但是,多層快取和大量計算單元可以消除這些延遲。
要了解 GPU 如何執行核心,您首先需要了解核心是什麼以及它的配置是什麼。
核心和執行緒塊簡介
CUDA 是 NVIDIA 提供的程式設計介面,用於為其 GPU 編寫程式。 在 CUDA 中,要在 GPU 上執行的計算以類似 C++ 的函式的形式表示,稱為核心。 核心與數字向量並行執行,數字向量作為函式引數提供給它。 乙個簡單的例子是執行向量加法的核心,即核心將兩個數字向量作為輸入,通過元素將它們相加並將結果寫入第三個向量。
為了在 GPU 上執行核心,需要啟動多個執行緒,這些執行緒統稱為網格。 但網格的意義遠不止於此。 網格由乙個或多個執行緒塊(有時簡稱為塊)組成,每個塊由乙個或多個執行緒組成。
塊和執行緒的數量取決於資料的大小和我們想要的並行度。 例如,在向量加法示例中,如果要新增維度為 256 的向量,則可以決定配置具有 256 個執行緒的執行緒塊,以便每個執行緒對向量的乙個元素進行操作。 對於較大的問題,GPU 可能無法同時提供足夠的可用執行緒來滿足需求。
就實現而言,編寫核心需要兩部分。 乙個是在 CPU 上執行的主機。 這是載入資料、在 GPU 上分配記憶體以及使用配置的執行緒網格啟動核心的地方。 第二部分是編寫在 GPU 上執行的裝置 (GPU)
作為向量加法的示例,下圖顯示了主機**。
下面是裝置,它定義了實際的核心函式。
對於CUDA,受篇幅限制,就不深入介紹了,有興趣的同學可以參考官方文件。
將資料從主機複製到裝置
在排程核心執行之前,必須將它需要的所有資料從主機(CPU)的記憶體複製到GPU(裝置)的全域性記憶體中。 儘管如此,在最新的 GPU 硬體中,也可以使用統一虛擬記憶體直接從主機記憶體讀取資料。
SM 上的執行緒塊排程
當 GPU 的記憶體中包含所有必要的資料時,它會將執行緒塊分配給 SM。 乙個塊中的所有執行緒都由同乙個 SM 同時處理。 為了實現這一點,GPU 必須在 SM 上為這些執行緒保留資源,然後才能開始執行它們。 在實踐中,可以將多個執行緒塊分配給同乙個 SM 以同時執行。
由於 SM 數量有限,並且大型核心可能有大量塊,因此無法一次分配所有塊執行。 GPU 維護乙個等待分配和執行的塊列表。 當任何塊完成執行時,GPU 會在等待列表中分配乙個塊來執行。
單指令、多執行緒和變形
塊的所有執行緒都分配給同乙個 SM。 但在那之後還有另乙個級別的執行緒。 這些執行緒進一步分為 32 個大小的翹曲,並一起分配以在一組稱為處理塊的核心上執行。
SM 通過獲取並向所有執行緒發出相同的指令,一起執行 WARP 中的所有執行緒。 然後,這些執行緒同時執行該指令,但針對資料的不同部分。 在向量加法示例中,翹曲中的所有執行緒都可能正在執行加法指令,但它們將對向量的不同索引進行操作。
此 WARP 的執行模型也稱為單指令多執行緒 (SIMT),因為多個執行緒正在執行同一指令。
從 Volta 開始,新一代 GPU 提供了一種稱為獨立執行緒排程的替代指令排程機制。 它允許執行緒之間的完全併發,無論翹曲如何。 它可用於更好地利用執行資源,或用作執行緒之間的同步機制。
WARP 排程和延遲容限
關於 WARP 的工作原理,有一些有趣的細節值得討論。
儘管 SM 中的所有處理塊(核心組)都在處理 WARP,但在任何給定時刻,只有少數塊在主動執行指令。 發生這種情況的原因是 SM 中可用的執行單元數量有限。
但有些指令需要更長的時間才能完成,導致在等待結果時出現翹曲。 在這種情況下,SM 將等待的 WARP 置於睡眠狀態,並開始執行另乙個不需要等待任何操作的 WARP。 這使 GPU 能夠最大限度地利用所有可用計算並提供高吞吐量。
零開銷排程
由於每個執行緒綑綁包中的每個執行緒都有自己的一組暫存器,因此當 SM 從乙個執行緒綑綁包切換到另乙個執行緒綑綁包時,不會產生開銷。
這與上下文在 CPU 上的程序之間切換的方式形成鮮明對比。 如果乙個程序正在等待長時間執行的操作,則 CPU 會同時在該核心上排程另乙個程序。 但是,CPU 中的上下文切換成本很高,因為 CPU 需要將暫存器儲存到主儲存器並恢復其他程序的狀態。
將生成的資料從裝置複製到主機記憶體
最後,當核心的所有執行緒都完成執行後,最後一步是將結果複製回主機記憶體。
GPU 中使用指標來衡量 GPU 資源的利用率。 此指標表示分配給 SM 的 WARP 量與其可以支援的最大量之比。 為了實現最大的吞吐量,我們希望有 100% 的入住率。 然而,在實踐中,由於各種制約因素,這很難實現。
SM 有一組固定的執行資源,包括暫存器、共享記憶體、執行緒塊插槽和執行緒插槽。 這些資源線上程要求和 GPU 限制之間動態分配。 例如,在 NVIDIA H100 上,每個 SM 可以處理 32 個塊、64 個 WARP(即 2048 個執行緒)和每個塊 1024 個執行緒。 如果啟動塊大小為 1024 個執行緒的網格,則 GPU 會將 2048 個可用執行緒槽拆分為 2 個塊。
動態分割槽允許更有效地使用 GPU 中的計算資源。 如果將其與固定分割槽方案進行比較,其中每個執行緒塊接收固定數量的執行資源,那麼它可能並不總是最有效的。 在某些情況下,執行緒分配的資源可能超過其需要的資源,從而導致資源浪費和吞吐量降低。
我們通過乙個示例來了解資源分配如何影響 SM 占用。 假設使用 32 個執行緒的塊大小,總共需要 2048 個執行緒,則將有 64 個這樣的塊。 但是,每個 SM 一次只能處理 32 個塊。 因此,即使 SM 可以執行 2048 個執行緒,它一次也只能執行 1024 個執行緒,導致使用率只有 50%。
同樣,每個 SM 有 65,536 個暫存器。 要同時執行 2048 個執行緒,每個執行緒最多可以有 32 個暫存器 (65536 2048 = 32)。 如果核心要求每個執行緒有 64 個暫存器,那麼每個 SM 只能執行 1024 個執行緒,這也只有 50% 的佔用率。
佔用率欠佳的挑戰在於,它可能無法提供實現峰值硬體效能所需的必要延遲容限或計算吞吐量。
高效建立 GPU 核心是一項複雜的任務。 必須適當分配資源,以保持高佔用率,同時最大限度地減少延遲。 例如,擁有多個暫存器可以使其執行得更快,但可能會降低使用率,因此仔細優化很重要。
在本文中,我們將普及與 GPU 和 GPU 計算相關的基本支援和架構設計
GPU 由多個流式多處理器 (SM) 組成,每個處理器都有多個處理核心。
有乙個片外全域性儲存器,即 HBM 或 DRAM。 晶元上的SM距離較遠,延遲較高。
有乙個片外 L2 快取和乙個片上 L1 快取。 這些 L1 和 L2 快取的執行方式與 CPU 中的 L1 L2 快取類似。
每個 SM 上都有少量可配置的共享記憶體。 這是在核心之間共享的。 通常,執行緒塊中的執行緒將一段資料載入到共享記憶體中,然後重用它,而不是從全域性記憶體中再次載入它。
每個 SM 都有大量的暫存器,這些暫存器根據執行緒的要求在程式之間分割槽。 例如,NVIDIA H100 每個 SM 有 65,536 個暫存器。
為了在 GPU 上執行核心,GPU 還設計了乙個執行緒網格。 網格由乙個或多個執行緒塊組成,每個執行緒塊由乙個或多個執行緒組成。
GPU 根據資源可用性分配乙個或多個塊,以便在 SM 上執行。 乙個塊的所有執行緒都在同乙個 SM 上分配和執行。 這是為了利用執行緒之間的資料區域性性和同步。
分配給 SM 的螺紋進一步分為 32 種尺寸,稱為翹曲。 翹曲中的所有執行緒同時執行相同的指令,但在資料的不同部分 (simt) 中執行。
GPU 根據每個執行緒的需求和 SM 的限制執行動態資源分割槽。 程式設計師需要仔細優化**,以確保 SM 佔用率在執行期間處於最高水平。