單指令多執行緒模型(SIMT)
/ sim-tee /
SIMD 要你用車道思考:一條指令、八個數字,塞進一個你得親手管理的寬暫存器。SIMT 則讓你改用執行緒思考:你為一個元素寫一支小程式——「拿我的元素,對它做這件事」——而硬體自動跑那同一支小程式的數千份拷貝,每個資料元素一份,並讓一大群保持同步。SIMT 是單指令多執行緒(single instruction, multiple threads)的縮寫:它是 GPU 的程式設計模型,感覺像在寫普通的執行緒程式,而硬體在底下以 SIMD 風格執行它。
這個把戲是這樣運作的。程式設計師寫一個函式(一個「核心程式」kernel),描述一個執行緒對一個元素做什麼。GPU 啟動龐大數量的執行緒——譬如每個像素或每個陣列元素一個——並把它們捆成固定大小的群組(NVIDIA 把 32 個一組稱為一個 warp;AMD 把 64 個稱為一個 wavefront)。一捆之中所有執行緒共用單一的指令提取單元、步調一致地行進:每一步硬體提取一條指令,捆裡每個執行緒對自己的資料執行它,正是 SIMD 構想,但每個執行緒也有自己的暫存器與自己的程式計數器,所以感覺像獨立的。你得到執行緒這個輕鬆的心智模型,又有「一條指令驅動許多」的效率。
SIMT 要求的誠實,在於「步調一致」要付什麼代價。因為一整捆共用一條指令流,只有當它們全都想做同一件事時,這些執行緒才真正平行。一旦它們意見分歧——有些走 if 分支、有些走 else 分支——硬體無法在共用的提取硬體上同時跑兩邊,於是它跑一邊、讓另一邊的執行緒閒置,再跑另一邊,把兩條路徑序列化。那就是分支發散(branch divergence),SIMT 的核心稅。所以 SIMT 給初學者一個舒適、像執行緒的模型,但效能仍服從底下的 SIMD 現實:規律、意見一致的執行緒飛快;發散、多分支的執行緒爬行。
一個向量加法核心程式:每個執行緒算 c[id] = a[id] + b[id],其中 id 是它自己的索引。每個陣列元素啟動一個執行緒;GPU 把它們分成每 32 個一組的 warp,warp 內全部 32 個執行緒步調一致地對各自的 32 個不同元素執行載入、加法與儲存。
寫一個執行緒份的程式;硬體跑它的數千份拷貝,捆起來讓一條指令驅動整個 warp。
SIMT 像執行緒的視角是一種方便,不是獨立性的保證。一個 warp 裡的執行緒共用一條指令流,所以當它們分支不同時,硬體會把路徑序列化(發散)。執行緒模型藏住了 SIMD 機器,卻逃不出它的規則。