現在的大型語言模型,大部分都是跑在 GPU 上。
那第一個問題就是:
為什麼是 GPU,而不是 CPU?
CPU 當然也可以跑模型,只是 LLM 裡有大量可以平行處理的運算,而 GPU 本來就是為這類工作設計的。
今天先不進入太多 GPU architecture 的細節,只想弄懂三件事:
先用一個很粗略的方式理解 CPU 和 GPU。
CPU 通常有比較少、但功能很強的 core,比較重視單一工作的執行速度。
GPU 則把更多硬體資源拿來做大量平行運算。
[圖 1:CPU vs GPU]

而 LLM 裡剛好充滿矩陣乘法這類可以拆成大量小運算、一起執行的工作。
所以 GPU 的第一個優勢很直覺:
它有很多可以同時工作的運算資源。
不過只有運算單元多還不夠,還要有辦法把這麼多工作有效率地分配出去。
在 CUDA 裡,我們會把一個大的工作拆成很多 threads。
例如有一大堆彼此獨立的計算:
Thread 0 → 工作 0
Thread 1 → 工作 1
Thread 2 → 工作 2
Thread 3 → 工作 3
...
接著 CUDA 會把 threads 組成 Blocks,多個 Blocks 再組成一個 Grid。
真正執行時,Block 會被分配到 GPU 裡面的 SM(Streaming Multiprocessor)。
而到了 SM 裡,threads 又會以 Warp 為單位執行。
目前在 NVIDIA GPU 上:
1 Warp = 32 Threads
所以可以先把整個流程想成:

我們寫 CUDA 程式的時候看到的是 Thread 和 Block,但真正到了硬體執行時,Warp 是一個非常重要的基本單位。
接著把其中一個 SM 放大來看。
一個 SM 裡面可以同時有很多 warps。
[圖 3:SM + Warp Scheduler]
假設 Warp 0 執行到一半,需要去 memory 拿資料。
Memory access 要花時間。
如果 GPU 只能停在那邊等:
Warp 0 → 等資料 → 等 → 等 → 繼續
很多運算資源就會閒著。
但 SM 裡還有其他 warps。
所以 warp scheduler 可以看到:
Warp 0 → Waiting
Warp 1 → Ready
Warp 2 → Ready
然後先去執行 Warp 1。
可以很直覺地想成:
Clock N
Warp 0 → 等 memory
Clock N + 1
Warp 1 → 執行
GPU 上這些 resident warps 的狀態本來就保留在 SM 裡,因此不需要像一般 CPU thread context switch 那樣先把大量狀態存起來再換出去。
所以我們常會說,GPU 可以在 single-clock-cycle scale 快速換到另一個 ready warp。
這也是 GPU 很重要的一個技巧:
Latency Hiding
它不是讓 memory 突然變快,而是:
「你在等資料?那我先去做別人的工作。」
只要同時有足夠多 ready warps,GPU 就可以盡量不要讓運算單元閒著。
Warp scheduler 選好要執行的 warp 之後,最後還是要交給 SM 裡真正負責計算的硬體。
例如:
CUDA Core 可以先粗略理解成負責一般算術運算;Tensor Core 則特別適合矩陣乘加這類運算。
而 LLM 裡剛好充滿矩陣乘法,所以之後講 GEMM 的時候,我們還會再回來看 Tensor Core。