JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

GPU:一台吞吐量機器

我們已經用 SIMD 車道把一顆核心拉得更寬。現在我們要走到底:一塊晶片,蓋出來不是為了把一件事做得快,而是為了讓成千上萬件小事同時不斷流動。來認識 GPU——一台吞吐量機器,它的思維像一千個有耐心的實習生,而不是一個聰明絕頂的天才。我們也誠實地學一學:它擅長什麼,又對什麼一籌莫展。

從更寬的車道,到一台徹底不同的機器

在前兩篇導覽裡,我們把單一一顆 CPU 核心加寬。我們把 SIMD 車道栓進它的資料路徑,把它的向量暫存器從 128 位元長到 512 位元,並倚靠指令集延伸與自動向量化,讓一個運算一次餵過許多個資料元素。這是個強大的把戲,但留意它的天花板:它依然是顆核心,有一個程式計數器、一個分支預測器、一個亂序引擎,只是把每條指令做在一塊更厚的資料板上。我們把那位天才的手變寬了。但如果我們改成雇一千個普通人,會怎樣?

這正是圖形處理器所跨出的那一躍。一顆現代 CPU 把大部分矽片花在算術上,而在替單一執行緒耍聰明上:大快取、深層的亂序機械、精準的分支預測——全是為了讓單獨一條相依指令鏈盡快做完。GPU 幾乎把這一切全扔掉。它用同樣的面積塞滿數百到數千個又小又簡單的算術單元,幾乎沒有任何「替每條執行緒耍聰明」的東西,賭的是你帶來了極其龐大的、彼此獨立的工作,給它們去並行地嚼穿。

這種工作藏在哪裡

GPU 沒有用,除非你真的能餵給它那座獨立工作堆成的山,所以第一項真本事,是認出它。它的歷史老家就是圖形本身:一塊螢幕是數百萬個像素,而為每一個像素上色,大致是同一道小小的計算,沒有哪個像素需要等它的鄰居。這就是純粹的資料層級平行性——同一個運算、許多資料、沒有相依——正是前兩篇導覽所描述的那個形狀,只是規模遠遠大過一個 16 寬的 SIMD 暫存器所能涵蓋的。

同一個形狀,遠遠超出圖形之外也會冒出來。把兩個大矩陣相乘,每一個輸出格都是一個獨立的內積。在格點上模擬物理,每一格都按同一條規則從它的鄰居更新。訓練一個神經網路,你做的是汪洋般的矩陣乘法與逐元素函數。這正是為什麼 GPU 悄悄成了科學計算與現代機器學習的引擎:那些工作負載天生就長成一萬道一模一樣的加總的樣子。當你能把你的問題講成那種形狀,一千個實習生大獲全勝;當你不能,沒有任何 GPU 救得了你。

SIMT:一條指令流,成千上萬條執行緒

GPU 怎麼在沒有一千個獨立控制單元的情況下,組織一千個工人?答案是一條聰明的中間路線,叫做 SIMT——單指令、多執行緒(Single Instruction, Multiple Threads)。身為程式設計師的你,寫程式時就當作是寫給條執行緒:「拿我的元素、把它平方、把結果加上去」。接著你把這個小程式——一個 kernel(核心程式)——一次發射過成千上萬條執行緒,每一條都領到自己的資料索引。它感覺起來像一千個微小、獨立的程式。

在底下,硬體做的事更精打細算。它把執行緒捆成固定大小的群組——NVIDIA 把 32 條一組的稱為一個 warp(執行緒束)——而整個 warp 步伐一致地行進,全部 32 條執行緒在同一個週期執行同一條指令,只是各自做在自己那 32 個不同的資料值上。於是一次取指與一次解碼,被分攤給 32 條算術車道。這就是 SIMT 的交易:對你而言它看起來像獨立的執行緒(好寫),底下卻像寬寬的 SIMD 一樣跑(好造)。它真的是兩個世界的混血,而替它取個名字,能讓我們精確地說出它在哪裡會咬人。

而它正是在這裡咬人:分支。因為一整個 warp 共用一個程式計數器,當它的 32 條執行緒撞上一個 `if`、有些想走這條分支、有些不想時,會發生什麼事?硬體無法在一個週期裡跑兩條不同的指令,於是它做了一件誠實卻昂貴的事——它先跑 `if` 那一邊、把屬於 `else` 的執行緒關掉,再跑 `else` 那一邊、把屬於 `if` 的執行緒關掉,依序把條路徑都執行一遍。這種序列化叫做分支發散,它是 GPU 最重要的單一效能陷阱。

Warp of 8 threads hits:  if (x > 0) A();  else B();

thread:   t0  t1  t2  t3  t4  t5  t6  t7
x>0?      Y   N   Y   Y   N   N   Y   N

cycle 1  run A() : [t0 -- t2 t3 -- -- t6 --]   (N threads idle)
cycle 2  run B() : [-- t1 -- -- t4 t5 -- t7]   (Y threads idle)

Both halves ran one after the other. Threads that
ALL agree on the branch cost nothing extra; a split
warp pays for BOTH paths -> up to 2x slower here.
分支發散:一個分裂的 warp 把 if/else 兩邊都序列化執行,不作用的執行緒乾等。一致的分支不花額外代價;發散的分支則要。

用工作把延遲淹掉,藉此把它藏起來

在 GPU 如何保持忙碌這件事裡,藏著一個漂亮的點子,而它正好是 CPU 策略的反面。CPU 痛恨記憶體延遲,並花費巨大的力氣去避開它:深層快取、預取器、亂序執行,好在一次快取未命中期間找別的活幹。GPU 大多根本懶得去避開延遲——它反而靠著手上隨時備著夠多的 warp 來藏住它,於是永遠有另一個 warp 可以切過去。

  1. 一個 warp 發出一筆對緩慢 DRAM 的載入,得等上數百個週期,資料才會抵達。
  2. 排程器不讓它卡住,而是瞬間把那個 warp 停泊一旁,切換到另一個資料早已就緒的 warp——然後跑它。
  3. 當那個也撞上一筆載入,就再切一次——再一次——在數十個 warp 之間輪轉,算術單元從不閒坐著。
  4. 等排程器繞回來時,第一個 warp 的那筆載入早就完成了,於是它毫無等待地接續下去。延遲是真實存在的,但它被埋在了其他工作底下。

這正是為什麼一顆 GPU 需要成千上萬條在途的執行緒,遠遠多過它擁有的車道:這些多出來的並非為了原始的平行性,而是作為一座深深的工作蓄水池,用來藏住每一次停頓。這也正是為什麼這套策略在你一旦沒有足夠的獨立工作時就會失靈——只有一個 warp 時,那筆載入會讓整台機器卡住,GPU 就那樣呆坐著,數百條快車道結凍,像一顆可能最慢的 CPU 那樣乾等。吞吐量式的藏匿,只在工作持續流動時才管用。

屋頂線:你究竟有沒有在用這台機器?

假設你的問題確實長得像 GPU,而你跑了它。你怎麼知道這塊晶片到底有沒有被好好利用,還是在挨餓?有一幅優雅的圖,能同時替 CPU 與 GPU 回答這個問題:屋頂線模型。它立足於一個叫做算術強度的單一比值——你每從記憶體取一個位元組的資料,所執行的有用算術運算個數。高強度意味著每個位元組做很多算術(一小塊資料被重複使用許多次);低強度意味著你還沒算幾下,就又跑回記憶體去拿更多。

把可達到的效能對著那個強度畫出來,你會得到一個分成兩段的屋頂。左邊、在低強度處,你是記憶體受限:一道隨強度升高的斜屋頂,因為你的速度被記憶體頻寬能多快送出位元組所封頂——車道大多在等。右邊、在高強度處,你是運算受限:一道停在晶片算術峰值的平屋頂,因為現在你每個位元組有夠多的算術,足以讓每條車道都忙著。每一個工作負載都坐在這片屋頂底下某處,而這個模型告訴你,你正撞上的是哪一面牆。

這個教訓嚴峻而誠實:一顆 GPU 那個巨大的峰值數字,只有當你的算術強度高到能站在屋頂平坦的那一段底下時,才搆得著。極其大量的真實程式卡在斜的、記憶體受限的那一側,所以不管晶片有多少條車道,它們只摸到所標榜速度的一小片。這也正是為什麼在 CPU 與 GPU 之間搬資料、走 PCIe 連結,會悄悄主宰你的執行時間——如果你把一個大陣列運過去,只為對它做一點點算術,你的強度慘不忍睹,而真正的代價是那趟傳輸,不是那點計算。屋頂線讓你對「你到底是在用這台機器、還是只是擁有它」這件事保持誠實。

誠實的判決:一台吞吐量機器,而不是一顆更快的 CPU

把它湊起來,一個乾淨的對比就浮現了。CPU 是一台延遲機器:它把電晶體傾注在盡快完成一條相依的工作鏈上,而它在序列、多分支、充滿決策的程式上出色絕倫。GPU 是一台吞吐量機器:它把電晶體傾注在每秒做出極龐大量的獨立工作上,而它只在那種工作既豐沛、又一致、又充滿算術時才出色。它們不是同一條「快」線上的兩個點;它們是為相反的問題而調校的。

所以這裡有一條行銷投影片永遠不會直白寫出的真相:GPU 在「恰恰是 CPU 擅長的那些事」上,是真的很糟。交給它序列程式——第二步需要第一步的答案——它那數千條車道就閒坐著,只有一條一瘸一拐地往前。交給它充滿無法預測的 `if` 的多分支程式,warp 會發散到你爬行為止。交給它對延遲敏感的工作——現在就回應這一個請求——它沒有任何聰明手段能讓單一一條執行緒快快做完。GPU 不是一顆更快的 CPU;它是一種不同形狀的機器,只是剛好被接進了同一個機箱裡。

那個對比,正是為什麼現代系統是異質的,把一顆 CPU 與一顆 GPU 配成一對,讓各自去做它被塑造來做的事。CPU 跑程式那條序列的脊樑、與它那些雜亂的決策;它把又肥、又一致、又平行的迴圈遞給 GPU 去吞食。接下來兩篇導覽會把這磨利:第四篇以機械般的細節剖析 SIMT、warp 與發散,第五篇則為「該把某段工作放到哪台機器上」建一張實用的檢核表。在那之前,先握住這一句話的總結:GPU 用「在一件事上的卓越」,換來「在一百萬件事上的不屈不撓」。