1. 程式人生 > 其它 >CPU 和 GPU - 異構計算的演進與發展

CPU 和 GPU - 異構計算的演進與發展

世界上大多數事物的發展規律是相似的,在最開始往往都會出現相對通用的方案解決絕大多數的問題,隨後會出現為某一場景專門設計的解決方案,這些解決方案不能解決通用的問題,但是在某些具體的領域會有極其出色的表現。而在計算領域中,CPU(Central Processing Unit)和 GPU(Graphics Processing Unit)分別是通用的和特定的方案,前者可以提供最基本的計算能力解決幾乎所有問題,而後者在圖形計算和機器學習等領域內表現優異。

圖 1 - CPU 和 GPU

異構計算是指系統同時使用多種處理器或者核心,這些系統通過增加不同的協處理器(Coprocessors)提高整體的效能或者資源的利用率[^1],這些協處理器可以負責處理系統中特定的任務,例如用來渲染圖形的 GPU 以及用來挖礦的 ASIC 積體電路。

中心處理單元(Central Processing Unit、CPU)[^2]一詞誕生於 1955 年,已經誕生 70 多年的 CPU 在今天已經是很成熟的技術了,不過它雖然能夠很好地處理通用的計算任務,但是因為核心數量的限制在圖形領域卻遠遠不如圖形處理單元(Graphics Processing Unit、GPU)[^3],複雜的圖形渲染、全域性光照等問題仍然需要 GPU 來解決,而大資料、機器學習和人工智慧等技術的發展也推動著 GPU 的演進。

今天的軟體工程師,尤其是資料中心和雲端計算的工程師因為異構計算的發展面對著更加複雜的場景,我們在這篇文章中主要談一談 CPU 和 GPU 的演進過程,重新回顧一下在過去幾十年的時間裡,工程師為它們增加了哪些有趣的功能。

CPU

更高、更快和更強是人類永恆的追求,在科技上的進步也不例外,CPU 的主要演進方向其實只有一個:消耗最少的能源實現最快的計算速度,無數工程師的工作都是為了實現這個看起來簡單的目的。然而在 CPU 已經逐漸成熟的今天,想要提高它的效能需要花費極大的努力,我們在這一節簡單展示歷史上引入了哪些技術來提高 CPU 的效能。

製程

當我們討論 CPU 的發展時,製程(Fabrication Process)[^4]是繞不開的關鍵字,相信不瞭解計算機的人也都聽說過 Intel 處理器 10nm、7nm 的製程,而目前各個 CPU 製造廠商也都有各自的路線圖來實現更小的製程,例如臺積電準備在 2022 和 2023 年分別實現 3nm 和 2nm 的製造工藝。

[^4]: Wikipedia: Semiconductor device fabrication https://en.wikipedia.org/wiki/Semiconductor_device_fabrication

圖 2 - Intel CPU 製程

在大多數人眼中,彷彿 CPU 的製程越少就越先進,效能也會越好,但是製程並不是衡量 CPU 效能的標準,最起碼製程的演進不會直接提高 CPU 的效能。工藝製程的每次提升,都可以讓我們在單位面積內容納更多的電晶體(Transistor),只有越多的電晶體才意味著越強的效能。

越小的電晶體在開關時消耗的能量越少,既然電晶體需要一些時間充電和放電,那麼消耗的能量也就越少,速度也越快,而這也解釋了為什麼增加 CPU 的電壓可以提高它的執行速度。除此之外,更小的電晶體間隔使得訊號的傳輸變得更快,這也能夠加快 CPU 的處理速度[^5]。

快取

快取也是 CPU 的重要組成部分,它能夠減少 CPU 訪問記憶體所需要的時間,相信很多開發者都看過如下所示的表格,我們可以看到從 CPU 的一級快取中讀取資料大約是主存的 200 倍,哪怕是二級快取也有將近 30 倍的提升:

WorkLatency
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns
Mutex lock/unlock 25 ns
Main memory reference 100 ns
Compress 1K bytes with Zippy 3,000 ns
Send 1K bytes over 1 Gbps network 10,000 ns
Read 4K randomly from SSD* 150,000 ns
Read 1 MB sequentially from memory 250,000 ns
Round trip within same datacenter 500,000 ns
Read 1 MB sequentially from SSD* 1,000,000 ns
Disk seek 10,000,000 ns
Read 1 MB sequentially from disk 20,000,000 ns
Send packet CA->Netherlands->CA 150,000,000 ns

表 1 - 2012 年延遲數字對比[^6]

今天的 CPU 一般都包含 L1、L2 和 L3 三級快取,CPU 訪問這些快取的速度僅次於訪問暫存器,雖然快取的速度很快,但是因為高效能需要保證儘可能靠近 CPU,所以它的成本異常昂貴。Intel 等 CPU 廠商也會通過增加 CPU 快取的方式提高效能,更大的 CPU 快取意味著更高的快取命中率,也意味著更快的速度。

圖 3 - CPU 快取

Intel 的處理器就在過去幾十年的時間中不斷增加 L1、L2 和 L3 的快取大小、將 L1 和 L2 快取整合在 CPU 中以提高訪問速度並在 L1 快取中區分資料快取和指令快取以提高快取的命中率。今天的 Core i9 處理器每個核心都有 64 KB 的 L1 快取和 256 KB 的 L2 快取,所有的 CPU 還會共享 16 MB 的 L3 快取[^7]。

平行計算

多執行緒程式設計在今天幾乎已經是工程師的必修課了,主機上越來越多的 CPU 核心讓工程師不得不去思考如何才能通過多執行緒儘可能利用硬體的潛力,很多人可能都認為 CPU 會按照編寫的程式序列執行命令,但是真正的現實往往比這複雜得多,早在很多年前嵌入式工程師就開始嘗試在單個 CPU 上並行執行指令。

從軟體工程師的角度,我們確實可以認為每一條彙編指令都是原子操作,而原子操作意味著該操作要麼處於未執行的狀態,要麼處於已執行的狀態,而資料庫事務、日誌以及併發控制都建立在原子操作上。不過如果再次放大指令的執行過程,我們會發現指令執行的過程並不是原子的:

圖 4 - 指令執行的步驟

不同機器架構執行指令的過程會有所差別,上面是經典的精簡指令集架構(RISC)中命令執行需要經過的 5 個步驟,其中包括獲取指令、解碼指令、執行、訪問記憶體以及寫回暫存器。

超標量處理器是可以實現指令級別並行的 CPU,它通過向處理器上的其他執行單元派發指令在一個時鐘週期內同時執行多條指令[^8],這裡的執行單元是 CPU 內的資源,例如算術邏輯單元、浮點數單元等[^9]。

超標量設計意味著處理器會在一個時鐘週期發出多條指令,該技術往往都與指令流水線一起使用[^10],流水線會將執行拆分成多個步驟,而處理器的不同部分會分別負責這些步驟的處理,例如:因為指令的獲取和解碼由不同的執行單元處理,所以它們可以並行執行。

圖 5 - 超標量和流水線

除了超標量和流水線技術之外,嵌入式工程師們還引入了亂序執行以及分支預測等更加複雜的技術,其中亂序執行也被稱作動態執行,因為 CPU 執行指令時需要先將資料載入到暫存器中,所以我們分析 CPU 的暫存器操作確定哪些指令可以亂序執行。

圖 6 - 亂序執行

如上圖所示,其中包含R1 = R0 + R1R2 = R1 - R0R3 = R3 + R5三條指令,其中第三條指令使用的兩個暫存器與前兩條無關,所以該指令可以與前兩條指令並行執行,也就能減少這段程式碼執行所需要的時間。

因為分支條件是程式中的常見邏輯,當我們在 CPU 的執行中引入流水線和亂序執行之後,如果遇到條件分支仍然需要等待分支確定才繼續執行後面的程式碼,那麼處理器可能會浪費很多時鐘週期等待條件的確定。在計算機架構中,分支預測器是用來在分支確定前預判的數位電路,在遇到條件跳轉指令時,它會預測條件的執行結果並選擇分支執行[^11]:

  • 如果預判正確,可以節約等待所需要的時鐘週期,提高 CPU 的利用率;
  • 如果預判失敗,需要丟棄預判執行的全部或者部分結果,重新執行正確的分支;

因為預判失敗需要付出較大的代價,一般在 10 ~ 20 個時鐘週期之間,所以如何提高分支預測器的準確率成為了比較重要的課題,常見的實現包括靜態分支預測、動態分支預測和隨機分支預測等。

上面的這些指令級並行僅僅存在於實現細節中,CPU 的使用者在外界觀察時仍然會得到序列執行的觀察結果,所以工程師可以認為 CPU 是能夠序列執行指令的黑箱。想要充分利用多個 CPU 的資源,仍然需要工程師理解多執行緒模型並掌握作業系統中一些併發控制機制。

單核的超標量處理器一般被分類為單指令單資料流(Single Instruction stream, Single Data stream、SISD)處理器,而如果處理器支援向量操作,就被分為單指令多資料流(Single Instruction stream, Multiple Data streams、SIMD)處理器,而 CPU 廠商會引入 SIMD 指令來提高 CPU 的處理能力。

片內佈局

前端匯流排是 Intel 在 1990 年在晶片中使用的通訊介面,AMD 在 CPU 中也引入了類似的介面,它們的作用都是在 CPU 和記憶體控制器中心(也被稱作北橋)之間傳遞資料。前端匯流排在剛設計時不僅靈活,而且成本很低,但是這種設計很難支援晶片中越來越多的 CPU:

圖 6 - 常見晶片佈局

如果 CPU 不能從主存中快速獲取指令和資料,那麼它會花費大量的事件等待讀寫主存中的資料,所以越高階的處理器越需要高頻寬和低延遲,而速度較慢的前端匯流排無法滿足這樣的需求。Intel 和 AMD 分別引入了點對點連線的 HyperTransport 和 QuickPath Interconnect(QPI)機制解決這個問題,上圖中的南橋被新的傳輸機制取代了,CPU 通過整合在內部的記憶體控制訪問記憶體,通過 QPI 連線其他 CPU 以及 I/O 控制器。

圖 7 - Intel QPI

使用 QPI 讓 CPU 直接連線其他元件確實可以提高效率,但是隨著 CPU 核心數量的增加,這種連線的方式限制了核心的數量,所以 Intel 在 Sandy Bridge 微架構中引入瞭如下所示的環形匯流排(Ring Bus)[^12]:

圖 8 - 環形匯流排

Sandy Bridge 在架構中引入了片內的 GPU 和視訊解碼器,這些元件也需要與 CPU 共享 L3 快取,如果所有的元件都與 L3 快取直接連線,那麼片內會出現大量的連線,而這是晶片工程師不能接受的。片內環形匯流排連線了 CPU、GPU、L3 快取、PCIe 控制器、DMI 和記憶體等部分,其中包含四個功能各異的環:資料、請求、確認和監聽[^13],這種設計減少了不同元件內部的連線同時也具有較好的可擴充套件性。

然而隨著 CPU 核心數量的繼續增加,環形的連線會不斷變大,這會增加環的大小進而影響整個環上元件之間的訪問延遲,導致該設計遇到瓶頸。Intel 由此引入了一種新的網格微架構(Mesh Interconnect Architecture)[^14]:

圖 9 - 網格架構

如上所示,Intel 的 Mesh 架構是一個二維的 CPU 陣列,網路中遊戲賬號轉讓地圖有兩種不同的元件,一種是上圖中藍色的 CPU 核心,另一種是上圖中黃色的整合記憶體控制器,這些元件不會直接相連,相鄰的模組會通過聚合網格站(Converged Mesh Stop、CMS)連線,這與我們今天看到的服務網格非常相似。

當不同元件需要傳輸資料時,資料包會由 CMS 負責傳輸,先縱向路由後水平路由,資料到達目標元件後,CMS 會將資料傳給 CPU 或者整合的記憶體控制器。

GPU

圖形處理單元(Graphics Processing Unit、GPU)是在緩衝區中快速操作和修改記憶體的專用電路,因為可以加速圖片的建立和渲染,所以在嵌入式系統、移動裝置、個人電腦以及工作站等裝置上應用都很廣泛[^15]。然而隨著機器學習和大資料的發展,很多公司都會使用 GPU 加速訓練任務的執行,這也是今天資料中心中比較常見的用例。

大多數的 CPU 不僅期望在儘可能短的時間內更快地完成任務以降低系統的延遲,還需要在不同任務之間快速切換保證實時性,正是因為這樣的需求,CPU 往往都會序列地執行任務。GPU 的設計與 CPU 完全不同,它期望提高系統的吞吐量,在同一時間竭盡全力處理更多的任務,而設計理念上的差異最終反映到了 CPU 和 GPU 的核心數量上[^16]:

圖 10 - CPU 和 GPU 的核心

雖然 GPU 在過去幾十年的時間有著很大的發展,但是不同 GPU 的架構大同小異,我們在這裡簡單介紹下面的流式多處理器中不同元件的作用:

圖 11 - 流式多處理器

流式多處理器(Streaming Multiprocessor、SM)是 GPU 的基本單元,每個 GPU 都由一組 SM 構成,SM 中最重要的結構就是計算核心 Core,上圖中的 SM 包含以下組成部分:

  • 執行緒排程器(Warp Scheduler):執行緒束(Warp)是最基本的單元,每個執行緒束中包含 32 個並行的執行緒,它們使用不同的資料執行相同的命令,排程器會負責這些執行緒的排程;
  • 訪問儲存單元(Load/Store Queues):在核心和記憶體之間快速傳輸資料;
  • 核心(Core):GPU 最基本的處理單元,也被稱作流處理器(Streaming Processor),每個核心都可以負責整數和單精度浮點數的計算;

除了上述這些元件之外,SM 中還包含特殊函式的計算單元(Special Functions Unit、SPU)以及用於儲存和快取資料的暫存器檔案(Register File)、共享記憶體(Shared Memory)、一級快取和通用快取。

水平擴容

與 CPU 一樣,增加架構中的核心數目是提高 GPU 效能和吞吐量最簡單粗暴的手段。Fermi[^17] 是 Nvidia 早期圖形處理器的微架構,在如下所示的架構中,共包含 16 個流式多處理器,512 個 CUDA 核心以及 3,000,000,000 個電晶體:

圖 12 - Nvidia Fermi 架構

除了 512 個 CUDA 核心之外,上述架構中還包含 256 個用於傳輸資料的訪問儲存單元和 64 個特殊函式單元。如果我們把 2010 年釋出的 Fermi 架構和 2020 年釋出的 Ampere 做一個簡單的對比,就可以發現兩者核心數量的巨大差別:

圖 12- Nvidia Ampere 架構

Ampere 架構中的流式多處理器增加到了 128 個,而每個處理器中的核心數也增加到了 64 個,整張顯示卡上一共包含 8,192 個 CUDA 核心,是 Fermi 架構中核心數量的 16 倍。為了提高系統的吞吐量,新的 GPU 架構不只擁有了更多的核心數量,它還需要更大的暫存器、記憶體、快取以及頻寬滿足計算和傳輸的需求。

專用核心

最初的 GPU 僅僅是為了更快地建立和渲染圖片,它們廣泛存在於個人主機上承擔著影象渲染的任務,但是隨著機器學習等技術的發展,GPU 中出現了更多種類的專用核心來支撐特定的場景,我們在這裡介紹兩種 GPU 中存在的專用核心:張量核心(Tensor Core)和光線追蹤核心(Ray-Tracing Core):

圖 13 - 專用核心

與個人電腦上的 GPU 不同,資料中心中的 GPU 往往都會用來執行高效能運算和 AI 模型的訓練任務。正是因為社群有了類似的需求,Nvidia 才會在 GPU 中加入張量核心(Tensor Core)[^19]專門處理相關的任務。

張量核心與普通的 CUDA 核心其實有很大的區別,CUDA 核心在每個時鐘週期都可以準確的執行一次整數或者浮點數的運算,時鐘的速度和核心的數量都會影響整體效能。張量核心通過犧牲一定的精度可以在每個時鐘計算執行一次 4 x 4 的矩陣運算,它的引入使得遊戲中的實時深度學習任務成為了可能,能夠加速度影象的生成和渲染[^20]。

計算機圖形領域的聖盃是實時的全域性光照,實現更好的光線追蹤可以幫助我們在螢幕上渲染更加真實的影象,然而全域性光照需要 GPU 進行大量的計算,而實時的全域性光照更是對效能有著非常高的要求。傳統的 GPU 架構並不擅長光線追蹤等任務,所以 Nvidia 在 Turing 架構中首次引入了光線追蹤核心(Ray-Tracing Core、RT Core)。

圖 15 - 光線追蹤核心

Nvidia 的光線追蹤核心實際上是為追蹤光線設計的特殊電路,光線追蹤中比較常見的演算法就是 Bounding Volume Hierarchy(BVH)遍歷和光線三角形相交測試,使用流式多處理器計算該演算法每條光線都會花費上千條指令[^21],而光線追蹤核心可以加速這一過程。

多租戶

今天 GPU 的效能已經非常強大,但是無論使用資料中心提供的 GPU 例項,還是自己搭建伺服器執行計算任務都很昂貴,然而 GPU 算力的拆分在目前仍然是一個比較複雜的問題,執行簡單的訓練任務可能佔用整塊 GPU,在這種情況下每提升一點 GPU 的利用率都可以降低一些成本。

圖 16 - 多例項 GPU

Nvidia 最新的 Ampere 架構支援多例項 GPU(Multi-Instance GPU、MIG)技術,它能夠水平切分 GPU 資源[^18]。每個 A100 GPU 都可以被拆分成 7 個 GPU 例項,每個例項都有隔離的記憶體、快取和計算核心,這不僅可以滿足資料中心分割 GPU 資源的需要,還能在同一張顯示卡上並行執行不同的訓練任務。

總結

從 CPU 和 GPU 的演進過程我們可以看到,所有的計算單元都受益於更精細的製作工藝,我們嘗試在相同的面積內放入更多的電晶體並增加更多的計算單元、使用更大的快取,當這種『簡單粗暴』的方式因為物理上的瓶頸逐漸變得困難時,我們開始為特定領域設計專門的計算單元。

文中沒有提到的 ASIC 和 FPGA 是更加特殊的電路,在影象渲染領域之外,我們可以通過設計適用於特定領域的 ASIC 和 FPGA 電路提高某一項任務的效能,OSDI ’20 的最佳論文 hXDP: Efficient Software Packet Processing on FPGA NICs[^23] 就研究瞭如何使用可程式設計的 FPGA 更高效地處理資料包的轉發,而在未來越來越多的任務會使用專門的硬體。