GPU深度學習效能的三駕馬車:Tensor Core、記憶體頻寬與記憶體層次結構

Baihai_IDP發表於2023-12-04

編者按:近年來,深度學習應用日益廣泛,其需求也在快速增長。那麼,我們該如何選擇合適的 GPU 來獲得最優的訓練和推理效能呢?

今天,我們為大家帶來的這篇文章,作者的核心觀點是:Tensor Core、記憶體頻寬和記憶體層次結構是影響 GPU 深度學習效能的幾個最關鍵因素。

作者詳細解析了矩陣乘法運算在深度學習中的重要性,以及 Tensor Core 如何透過特有的矩陣乘法計算單元極大地提升計算效能。同時,作者還分析了記憶體頻寬對效能的制約作用,比較了不同 GPU 架構在記憶體層次結構方面的差異。

透過這篇文章,我們可以清晰地瞭解 GPU 中與深度學習效能密切相關的硬體指標,這有助於我們在選擇和使用 GPU 時做出更明智的決策。最後,期待 GPU 在這些關鍵效能指標的進一步最佳化和突破。

作者 |Tim Dettmers

編譯 | 嶽揚

本文經原作者授權,由Baihai IDP編譯。如需轉載譯文,請聯絡獲取授權。

原文連結:


???歡迎小夥伴們加入  AI技術軟體及技術交流群  ,追蹤前沿熱點,共探技術難題~


這篇文章可以幫助我們瞭解 GPU 對深度學習效能的多個影響因素,從而幫助我們評估、選用 GPU。本文將按照 GPU 各元件的重要程度順序來進行介紹。Tensor Core(張量計算核心)是最重要的因素,其次是 GPU 的記憶體頻寬和快取層次結構,最後是 GPU 的 FLOPS。

目錄

01 Tensor Core(張量計算核心)

1.1 在沒有張量計算核心的情況下進行矩陣乘法運算

1.2 使用張量計算核心進行矩陣乘法運算

1.3 使用張量計算核心和非同步複製(RTX 30/RTX 40)以及TMA(H100)進行矩陣乘法運算

02 記憶體頻寬

03 二級快取/共享記憶體/一級快取/暫存器

01 Tensor Core(張量計算核心)

Tensor Core(張量計算核心)是一種能執行高效矩陣乘法運算的微小核心。由於矩陣乘法是任何深度神經網路中最耗費計算資源的部分,因此Tensor Core(張量計算核心)非常有用。它的功能非常強大,強大到我不推薦使用任何沒有Tensor Core(張量計算核心)的 GPU。

瞭解它們的工作原理,有助於理解這些特有的矩陣乘法計算單元(computational units)的重要性。下面以一個簡單的 A*B=C 矩陣乘法為例(其中所有矩陣的大小都是 32×32),展示了有張量計算核心和沒有張量計算核心的計算模式。這只是一個簡化後的例子,並不是高效能矩陣乘法核心的精確編寫方式,但它包含了所有基本要素。CUDA 程式設計師會將此作為第一份 "草稿",然後利用雙緩衝(double buffering)、暫存器最佳化(register optimization)、佔用最佳化(occupancy optimization)、指令級並行(instruction-level parallelism)等概念逐步最佳化,在此就不展開討論了。

要完全理解這個例子,你必須理解週期(cycles)的概念。 如果處理器的執行頻率為 1GHz,那麼它每秒可以執行 10^9 個週期。 每個週期都代表一次計算機會。然而,大多數情況下,運算時間都超過一個週期。因此,實際上有一個佇列,下一個操作需要等待上一個操作完成。這也被稱為運算的延遲(the latency of the operation)。

以下是一些重要的運算延遲週期時間(latency cycle timings)。 數值會隨著 GPU 代次的不同而變化。 下面這些數值來自 Ampere 架構的 GPU[1],其快取速度相對較慢。

  • 訪問全域性記憶體(高達80GB):約380個週期

  • 二級快取(L2 cache):約200個週期

  • 一級快取或訪問共享記憶體(每個流式多處理器最多128KB):約34個週期

  • 乘法和加法在指令集層面的結合(fused multiplication and addition,FFMA):4個週期

  • Tensor Core(張量計算核心)矩陣乘法運算:1個週期

每次進行運算總是由一組執行緒(32個)執行,這組執行緒被稱為執行緒束(warp)。執行緒束通常以同步模式(c pattern)執行,執行緒束內的執行緒必須等待彼此。GPU上的所有記憶體操作都針對執行緒束進行了最佳化。例如,從全域性記憶體中載入的粒度是32*4位元組,恰好是32個浮點數,每個執行緒束中的每個執行緒恰好一個浮點數。在一個流式多處理器(SM)中,可以有最多32個執行緒束(即1024個執行緒),這相當於CPU核心的GPU等效部分。流式多處理器(SM)的資源被分配給所有活躍的執行緒束。這意味著,有時我們希望執行較少的執行緒束,以便每個執行緒束擁有更多的暫存器/共享記憶體/張量計算核心資源。

在下面的兩個示例中,我們假設擁有相同的計算資源。在這個 32×32 矩陣乘法的小例子中,我們使用了8個流式多處理器(SM)(大約是RTX 3090的10%)和每個SM中的8個執行緒束。

為了理解週期延遲(cycle latencies)如何與每個SM的執行緒和共享記憶體等資源相互作用,現在來看一些矩陣乘法的示例。 雖然下面這些示例大致遵循了有和沒有張量計算核心的矩陣乘法的計算步驟順序,但請注意,這些都是非常簡化的示例。實際的矩陣乘法運算涉及更大的共享記憶體塊,計算模式也略有不同。

1.1 在沒有張量計算核心的情況下進行矩陣乘法運算

以一個簡單的 A*B=C 矩陣乘法為例(其中每個矩陣的大小都是 32×32),我們會將反覆訪問的資料載入到共享記憶體(shared memory)中,這樣做的主要原因是共享記憶體的延遲約為全域性記憶體的六分之一(200 個週期 vs 34 個週期)。(譯者注:為了加快訪問速度,我們可以將這些經常訪問的資料載入到共享記憶體中。共享記憶體是位於GPU的每個SM(流式多處理器)上的一塊較小的快取記憶體,其延遲較低。)共享記憶體中的記憶體塊通常被直接稱為 memory tile 或簡稱為 tile。透過使用2個執行緒束,每個執行緒束有32個執行緒,可以並行地將兩個32×32的浮點數載入到共享記憶體 tile 中。如果有8個 SM,每個 SM 有8個執行緒束,透過並行化(parallelization)技術,我們只需要從全域性記憶體一次順序載入到共享記憶體中,整個過程只需200個週期。

為了進行矩陣乘法運算,我們現在需要從共享記憶體A和共享記憶體B中載入兩個包含 32 個數字的向量,並執行乘法和加法在指令集層面的結合(fused multiplication and addition,FFMA),然後將輸出儲存在暫存器C中。我們將這項工作分割成多部分,使每個 SM 進行8次點積運算(32×32)來計算出 C 的8個輸出。為什麼是恰好8次而不是舊演算法的4次,這是一個非常專業的問題。建議閱讀Scott Gray關於矩陣乘法運算[2]的博文來了解更多細節。這意味著要訪問 8 次共享記憶體,每次訪問的需要 34 個週期,以及 8 次FFMA操作(並行進行32次),每次操作需要 4 個週期。因此,總共需要:

200個週期(訪問全域性記憶體)+ 8 34個週期(訪問共享記憶體)+ 8 4個週期(FFMA操作)= 504個週期

現在讓我們看一下使用張量計算核心進行該矩陣乘法運算需要多少個週期。

1.2 使用張量計算核心進行矩陣乘法運算

透過使用張量計算核心,我們可以在一個週期內執行 4×4 的矩陣乘法。為了實現這一目標,我們首先需要將記憶體資料傳輸到張量計算核心中。與前文類似,我們需要從全域性記憶體中讀取資料(需要200個週期),然後儲存到共享記憶體中。對於 32×32 的矩陣乘法運算,我們需要進行 64 次張量計算核心運算(即8×8=64次)。每個 SM 具有 8 個張量計算核心,如果有 8 個 SM,就有 64 個張量計算核心,這正好符合我們的需求!我們可以透過一次記憶體傳輸(需要34個週期)將資料從共享記憶體傳輸到張量計算核心,然後進行64次並行的張量計算核心操作(僅需1個週期)。因此,在這種情況下,使用張量計算核心進行矩陣乘法運算總共需要:

200個週期(訪問全域性記憶體)+ 34個週期(訪問共享記憶體)+ 1個週期(使用張量計算核心)= 235個週期。

因此,透過使用張量計算核心,我們將矩陣乘法運算的時間成本從 504 個週期大大降低到了 235 個週期。在這個簡化的案例中,使用張量計算核心的方法減少了共享記憶體的訪問和FFMA操作的時間成本。

這個例子是簡化後的,通常情況下,當將資料從全域性記憶體傳輸到共享記憶體時,每個執行緒需要計算要讀取和寫入的記憶體位置。 有了新的Hooper(H100)架構,Tensor Memory Accelerator(TMA)可以在硬體中計算這些索引,從而幫助每個執行緒專注於更多的運算而不是索引計算。

1.3 使用張量計算核心和非同步複製(RTX 30/RTX 40)以及TMA(H100)進行矩陣乘法運算

RTX 30 Ampere和RTX 40 Ada系列的 GPU 還支援在全域性記憶體和共享記憶體之間進行非同步傳輸。H100 Hopper GPU透過引入 Tensor Memory Accelerator(TMA)單元進一步擴充套件了這一功能。 TMA 單元同時結合了非同步複製和索引計算,因此每個執行緒無需再計算下一個要讀取的元素,而是可以專注於進行更多的矩陣乘法運算。 具體如下所示。

TMA 單元從全域性記憶體中獲取資料並傳輸到共享記憶體,該過程需要耗費200個週期。資料到達後,TMA單元從全域性記憶體非同步獲取下一個資料塊。在此過程中,執行緒從共享記憶體中載入資料,並透過張量計算核心執行矩陣乘法運算。執行緒完成後,等待 TMA 單元完成下一次資料傳輸,然後再次進行這個過程。

因此,由於非同步的特性,當執行緒處理當前共享記憶體 tile 時,TMA 單元已經開始進行第二次全域性記憶體讀取。這意味著,第二次讀取只需要200 - 34 - 1 = 165個週期。

由於我們進行了多次讀取,因此只有第一次記憶體訪問的速度會比較慢,其他記憶體訪問都會與TMA單元部分重疊。因此,平均減少了 35 個週期的時間。

165個週期(等待非同步複製完成)+ 34個週期(訪問共享記憶體)+ 1個週期(使用張量計算核心)= 200個週期。

這又將矩陣乘法運算的速度提高了 15% 左右。

從這些例子中,可以清楚地看出為什麼下一個將要介紹的屬性——記憶體頻寬(Memory Bandwidth)對於配備有張量計算核心的GPU非常重要。由於訪問全域性記憶體(global memory),是目前使用張量計算核心進行矩陣乘法運算消耗時間成本最大的一種方法。如果能減少全域性記憶體的延遲,開發者甚至可以擁有更快的GPU。可以透過增加記憶體的時脈頻率(每秒更多週期,但也會產生更多的熱量和擁有更高的能耗)增加同一時間可傳輸的元素數量(匯流排寬度)來實現這一目標。

02 記憶體頻寬

從前文我們可以看出,張量計算核心的運算速度非常快。它們太快了,以至於大部分時間都處於空閒狀態,因為需要等待從全域性記憶體中傳輸來的資料。例如, 在訓練規模為GPT-3級別的大型神經網路時使用了大矩陣(由於矩陣越大,對張量計算核心進行運算越有利),即便是這種情況下, 張量計算核心的利用率約為45-65%,這說明即使是訓練大型神經網路,張量計算核心也有 約 50% 的時間處於閒置狀態

這一點說明, 在比較兩個都配備有張量計算核心的GPU時,需要關注的一個重要效能指標就是它們的記憶體頻寬。 例如,A100 GPU的記憶體頻寬為1555 GB/s,而V100為900 GB/s。因此,A100 相對於 V100 的速度提升估計是1555/900 = 1.73倍。

03 二級快取/共享記憶體/一級快取/暫存器

由於將資料傳輸到張量計算核心的速度不高,是GPU效能的重要限制因素,因此需要尋找一種能夠透過其他 GPU 屬性解決該限制的方法,以加快向張量計算核心傳輸資料的速度。二級快取、共享記憶體、一級快取和使用的暫存器數量都是與此相關的 GPU 屬性。透過了解GPU上矩陣乘法運算的執行過程,我們可以更好地理解當前記憶體層次結構(memory hierarchy)如何提高記憶體傳輸速度。(譯者注:記憶體層次結構(memory hierarchy)是指計算機系統中不同級別的儲存器元件按照速度和容量進行層次化排列的結構。該架構劃分為多個層次,從較大但較慢的儲存器(如主存)到較小但更快的儲存器(如快取記憶體)以及暫存器。這種設計旨在透過將最常用的資料儲存在更快的儲存器級別中,從而提高資料訪問速度和系統效能。微觀GPU記憶體層次結構示例如下圖所示)

GPU深度學習效能的三駕馬車:Tensor Core、記憶體頻寬與記憶體層次結構

圖片由譯者附。GeForce GTX780 (Kepler)記憶體層次結構。Mei, X., & Chu, X. (2015). Dissecting GPU Memory Hierarchy Through Microbenchmarking. IEEE Transactions on Parallel and Distributed Systems, 28, 72-86.

要執行矩陣乘法運算,我們需要合理利用 GPU 的記憶體層次結構,從速度較慢的全域性記憶體到較快的二級快取,再到快速的本地共享記憶體,最後到速度快如閃電的暫存器。然而,記憶體越快,其記憶體大小就越小。

雖然從邏輯上講,二級快取和一級快取是相似的,但二級快取更大,因此檢索快取行所需的平均物理距離也更長。我們可以將一級快取和二級快取比喻為倉庫,我們從中檢索所需的物品。即使我們知道物品在哪裡,對於較大的倉庫,平均而言到達目標位置需要更長的時間。這是一級快取和二級快取之間的基本區別。 Large = slow, small = fast.

對於矩陣乘法運算,我們可以利用這種記憶體分層的方法,將其分解為越來越小、速度更快的記憶體塊,使其能夠執行非常快速的矩陣乘法運算。 為此,我們需要將大的矩陣乘法運算劃分為較小的子矩陣乘法運算。這些記憶體塊被稱為memory tiles,通常簡稱為 tiles。

我們在本地共享記憶體中用這些較小的 tiles 執行矩陣乘法,本地共享記憶體速度快且位於流式多處理器(SM)附近——類似於 CPU 核心。使用張量計算核心,我們可以更進一步:將每個 tiles 的一部分載入到由暫存器直接定址的張量計算核心中。二級快取中的矩陣記憶體 tiles 比 GPU 全域性記憶體(GPU RAM)快  3 到 5 倍,共享記憶體比 GPU 全域性記憶體快約 7 到 10 倍,而張量計算核心的暫存器比 GPU 全域性記憶體快約 200 倍。

較大的 tiles 意味著我們可以重複使用更多的記憶體空間,在我撰寫的關於 TPU vs GPU的部落格文章[3]中詳細介紹了這一點。實際上,你可以看到 TPU 的每個張量計算核心都有非常非常大的 tiles 。因此, TPU 每次從全域性記憶體傳輸資料時可以重複使用更多的記憶體,這使得它們在矩陣乘法運算方面比 GPU 更加高效 。

每個 tiles 的大小由每個流式多處理器(SM)的記憶體和所有 SM 上的二級快取大小決定。以下是不同架構 GPU 上的共享記憶體大小:

  • Volta(Titan V):128KB 共享記憶體/6MB 二級快取

  • Turing(RTX 20系列):96KB 共享記憶體/5.5MB 二級快取

  • Ampere(RTX 30系列):128KB 共享記憶體/6MB 二級快取

  • Ada(RTX 40系列):128KB 共享記憶體/72MB 二級快取

我們可以看到, Ada 架構的 GPU 具有更大的二級快取,可以容納更大的 tiles 尺寸,從而減少了對全域性記憶體的訪問 例如,在 BERT large 的訓練過程中,任何矩陣乘法運算的輸入和權重矩陣都可以完全適應 Ada 架構的二級快取(但其他架構不行)。因此,資料只需要從全域性記憶體中載入一次,然後就可以透過二級快取獲取資料,這使得對於 Ada 架構,矩陣乘法運算速度提高了約 1.5 到 2.0 倍。對於較大的模型,在訓練過程中速度的提升可能較低,但存在某些 sweetspots (譯者注:意味著存在某些特定的模型大小、batch size或其他引數設定,使得在該點或區域上的模型訓練速度更快或效能更好),可能使某些模型訓練速度更快。 對於 batch size 大於 8 的大模型推理任務,具有更大的二級快取能夠提高推理效率。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70018536/viewspace-2998603/,如需轉載,請註明出處,否則將追究法律責任。