昨天介紹了從工作組織、硬體執行、資料存取三個角度拆解 NVIDIA GPU 的 grid、block、warp、thread 是工作,SM 是執行工作的硬體,register、shared memory、global memory 則是資料位置。
今天我會先用向量加法看 CUDA programming model,再看 cuBLAS、cuDNN、CUTLASS 替開發者處理哪些工作,接著用同一個 tile-based 視角比較 CuTe、cuTile 與 Triton 這些 domain-specific languages(DSLs),之後重點會放在 Triton compile flow。
CUDA(Compute Unified Device Architecture)是 NVIDIA 在 2006 年推出的 GPU 運算平台與 programming model。CUDA 出現以前,GPU 主要藉由圖形 API 使用,開發者若想把一般運算放到 GPU,必須把問題轉成圖形 pipeline 能處理的形式, CUDA 提供一套通用運算介面,開發者可以直接撰寫 GPU kernel,再由 CPU 端配置記憶體與啟動工作。
CUDA 涵蓋的範圍比程式語言更廣。平常所說的 CUDA C++ 包含 C++ language extensions、runtime API、driver API 與編譯工具鏈。__global__、threadIdx 和 <<<...>>> 屬於程式介面,下列名詞會在後續的 compiler flow 一直出現。
| 名稱 | 全名 | 在編譯流程中的工作 |
|---|---|---|
nvcc |
NVIDIA CUDA Compiler | CUDA compiler driver,負責協調 host compiler 與 GPU 編譯工具,可以編譯 CUDA C++ 與 PTX |
| PTX | Parallel Thread Execution | NVIDIA GPU 的 virtual instruction set architecture(virtual ISA)與高階 assembly language,還不綁定某一個實體 GPU 指令集 |
ptxas |
PTX assembler | 把 PTX 組譯成指定 GPU architecture 可執行的 machine code |
| cubin | CUDA binary | 針對單一 GPU architecture 的 ELF device binary,由 CUDA Driver 載入 |
nvcc 可以保留 PTX,也可以呼叫 ptxas 產生 cubin。PTX 提供跨部分 GPU 世代的 virtual ISA。
cubin 已經針對 sm_XX 目標組譯,距離 GPU 最終執行的 machine code 更近。
CUDA C++ source
-> NVIDIA CUDA Compiler (nvcc)
-> Parallel Thread Execution (PTX)
-> PTX assembler (ptxas)
-> CUDA binary (cubin)
-> CUDA Driver
-> GPU 執行 kernel
這個生態的重要性在於,上層的 cuBLAS、cuDNN、CUTLASS 和 Python DSL 雖然提供不同抽象,最後還是要建立 GPU 可執行程式、由 CUDA Driver 啟動,並受到 SM、warp 與 memory hierarchy 限制。
CUDA 把系統看成 host 與 device,Host 通常是 CPU,負責配置記憶體、準備資料與啟動工作,device 是 GPU,負責執行大量平行運算。GPU 上執行的函式稱為 kernel。
下面是一個向量加法 kernel
__global__ void add(float* a, float* b, float* c, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
c[i] = a[i] + b[i];
}
}
Host 端會指定 grid 與 block 的大小,再啟動 kernel
int threads_per_block = 256;
int blocks = (n + threads_per_block - 1) / threads_per_block;
add<<<blocks, threads_per_block>>>(a, b, c, n);
<<<blocks, threads_per_block>>> 是 execution configuration。這次 launch 會建立一個 grid,裡面有 blocks 個 thread blocks,每個 block 有 256 個 threads。
Host code
-> 準備 device memory
-> launch kernel <<<grid, block>>>
-> GPU 執行大量 threads
-> 等待或繼續安排其他工作
Kernel launch 對 host 通常是 asynchronous。CPU 啟動 kernel 後可以繼續工作,需要讀回結果或量測時間時,才在正確的位置同步。
CUDA 的 thread hierarchy 可以先記成
Grid
-> Thread Block
-> Thread
-> hardware 以 warp 為單位執行
每個 thread 會根據自己的位置決定要處理哪一筆資料
int i = blockIdx.x * blockDim.x + threadIdx.x;
threadIdx.x:thread 在 block 內的位置。blockIdx.x:block 在 grid 內的位置。blockDim.x:每個 block 的 thread 數量。gridDim.x:grid 的 block 數量。同一個 block 的 threads 會在同一個 SM 上執行,可以使用 shared memory 交換資料,也能用 __syncthreads() 做 block 內同步。不同 blocks 的排程順序沒有保證,因此一般 kernel 不能假設另一個 block 已經完成。
CUDA 採用 SIMT(Single Instruction, Multiple Threads)模型。開發者撰寫單一 thread 要做的工作,硬體會把 threads 組成 warp 執行。同一個 warp 走到不同分支時會發生 branch divergence,部分 lanes 必須等待其他路徑完成。
昨天介紹的 HBM、L2、shared memory 與 registers,到了 CUDA 會變成可以在程式中安排的資料位置。這裡先保留寫 kernel 直接會碰到的三種,完整的可見範圍、生命週期與實體位置可以回到昨天的 memory hierarchy 表格複習
| Memory | 可見範圍 | Kernel 中的常見用途 |
|---|---|---|
| Global memory | 整個 device | 存放輸入、輸出與大型 tensor |
| Shared memory | 同一個 thread block | 暫存 tile,讓 threads 重用資料 |
| Registers | 單一 thread | 累加值、索引與 thread-local 暫存資料 |
Global memory 容量大,但存取延遲較高。相鄰 threads 若能存取相鄰位址,硬體較容易形成 coalesced memory access。Shared memory 位在 SM 上,適合暫存 GEMM tile;使用時還要處理同步、容量與 bank conflict。Registers 最接近運算單元,但使用量過高可能降低同一個 SM 能同時執行的 warps 數量。
前面的向量加法很短,仍然已經出現 GPU kernel 常見的效能問題
blockDim.x 會影響每個 block 有多少 threadsi 的計算會決定 global memory access 是否連續CUDA C++ 可以直接決定 thread mapping、shared memory layout、synchronization 和 inline PTX。要把 GEMM 或 attention kernel 寫快,還要一起處理資料搬移、運算切分與硬體 pipeline。接下來介紹的函式庫與 DSL,都是在不同位置接手這些工作。
理解 CUDA programming model 後,再看 NVIDIA 提供的函式庫,會比較容易理解它們替開發者處理了哪些工作。表格中的 GEMM 是 general matrix-matrix multiplication,也就是通用矩陣乘法。
| 函式庫 | 解決的問題 | 開發者看到的介面 |
|---|---|---|
| cuBLAS/cuBLASLt | dense linear algebra、GEMM | 呼叫 BLAS/matmul API |
| cuDNN | convolution、attention、normalization 等 deep neural network(DNN)primitives | 建立 operation graph 或呼叫 DNN API |
| CUTLASS C++ | 組合特殊 GEMM 與相關高效 kernel | CUDA C++ template building blocks |
BLAS(Basic Linear Algebra Subprograms)是一套已建立數十年的線性代數介面規範。Level 1 處理 vector–vector 運算,Level 2 處理 matrix–vector 運算,Level 3 處理 matrix–matrix 運算。深度學習的 linear layer 與 attention 會用到大量矩陣乘法,因此 Level 3 的 GEMM 特別重要。
cuBLAS 是 NVIDIA 在 CUDA 上的 BLAS 實作。呼叫者提供輸入指標、matrix shape、transpose 設定、資料型別與 CUDA stream,函式庫在內部選擇適合的 kernel。對 AI 工程來說,cuBLAS 最常處理的是 GEMM
C = A @ B
標準 GEMM 通常可以先用 cuBLAS 建立效能 baseline。這類 API 的優點是介面穩定,且不必自行處理 thread mapping、Tensor Core 指令與完整 GEMM pipeline。限制也很明確:你是呼叫 NVIDIA 已實作的運算,無法像撰寫 kernel 那樣任意改寫運算內部。
cuBLASLt 是以 matmul 為中心的較彈性介面。開發者可以用 descriptors 表達 matrix layout、資料型別、batched matmul 與 epilogue,再由 heuristic 找出候選演算法。例如 GEMM 後緊接 bias 或 activation 時,支援的 epilogue 可以減少額外 kernel launch 與中間資料寫回 global memory 的機會。
framework 或 application
-> 建立 matmul descriptors
-> heuristic 回傳候選演算法
-> 呼叫選定的內部 kernel
-> 送進 CUDA stream
cuBLAS 擅長線性代數,但深度學習模型還有 convolution、pooling、normalization、activation 與 attention 等運算。這些運算會隨 tensor layout、資料型別、shape 與硬體不同,產生許多實作組合。cuDNN(CUDA Deep Neural Network library)就是 NVIDIA 針對 deep neural network 建立的 GPU primitives 與後端函式庫。PyTorch 或 TensorFlow 這類框架可以在後端呼叫它,上層使用者通常不會直接看到裡面的 CUDA kernel。
早期的 cuDNN 使用者常以單一 operation 的 API 呼叫 convolution 或 activation。cuDNN 8.0 引入 Graph API 後,框架可以先把多個 operations 與 tensor 關係建成 operation graph,再請 heuristics 找出候選 engine configuration,最後建立 execution plan。編譯器或框架因此能提供更大的運算範圍,讓函式庫判斷是否能使用 fused implementation。
operations + tensors
-> operation graph
-> heuristics 選擇 engine configuration
-> execution plan
-> 在 CUDA stream 執行
以 attention 為例,運算包含 matmul、scaling、mask、softmax 與資料搬移。框架若能把這段 graph 交給 cuDNN,就有機會使用 NVIDIA 已針對支援硬體與資料型別調校的實作。如果 operation 形狀非常特殊,或者需要研究新的資料流與量化方法,開發者才會繼續往 CUTLASS 或自訂 kernel 移動。
CUTLASS(CUDA Templates for Linear Algebra Subroutines and Solvers)從 2017 年起提供 CUDA C++ template abstractions,起點是把 NVIDIA 內部高效 GEMM 實作中重複出現的結構變成可重用元件。直接呼叫 cuBLAS 時,開發者選擇一個已建立的 operation;使用 CUTLASS 時,開發者可以調整這個 operation 內部的 tile shape、memory layout、multiply-accumulate(MMA)、pipeline 與 epilogue。
C++ templates 適合這類工作,因為 data type、tile shape、layout 與 GPU architecture 可以在編譯時期成為專用實作。不需要的分支可以在編譯時期移除,也能選擇特定硬體支援的 Tensor Core 指令。因此,CUTLASS 是一套產生與組合 CUDA kernel 的 C++ template library,提供的粒度比單一 GEMM 函式更細。
它把高效 GEMM 常見的 tile、資料搬移、MMA、pipeline 與 epilogue 拆成可組合元件。開發者選擇 shape、layout、資料型別與硬體架構後,C++ compiler 會藉由 template instantiation 產生對應的 CUDA kernel。
選擇 data type、tile shape、layout、MMA、pipeline、epilogue
↓ C++ template instantiation
產生特定 GPU 架構使用的 CUDA kernel
CUTLASS C++ 位於直接呼叫函式庫與自行撰寫 CUDA kernel 之間。開發者仍然使用 CUDA C++,但不必自己實作所有 GEMM 元件。資料路徑可以簡化成:
global memory
-> shared memory
-> registers
-> Tensor Core MMA
開發者仍要理解 CUDA 的 thread hierarchy 與 memory hierarchy,但可以重用 CUTLASS 已整理好的 building blocks,組出特定 shape、layout、資料型別或 fused epilogue 的 kernel。
代價是 C++ template 類型很長、錯誤訊息難讀,而且大量 template instantiation 會增加編譯與迭代時間。CUTLASS 3.0 把 CuTe 納入底層抽象,CUTLASS 4 再加入 Python DSL,就是為了保留這些布局與硬體元件,同時縮短開發迴圈。
CUTLASS 3.0 引入的 CuTe C++ 以 layout algebra 為核心,用 shape 與 stride 描述邏輯座標如何對應到記憶體位置,也可用來表達資料如何映射給 threads。這讓 tensor layout、tiled copy 與 tiled MMA 能使用同一組組合規則描述。
這種寫法提供很細的控制,卻也把 C++ template metaprogramming 的複雜度帶給 kernel 開發者。NVIDIA 於 2025 年 6 月 釋出 CUTLASS 4.0,其中首次加入 CuTe DSL(CuTe Domain-Specific Language)。
CuTe DSL 讓開發者在 Python 環境中表達 CuTe 的 layout、tensor、copy、MMA 與 pipeline。Python 負責 metaprogramming 與 just-in-time(JIT)特化,底層仍保留能對應 NVIDIA GPU 記憶體與 Tensor Core 的抽象。
CuTe DSL 還是會讓你面對 thread 與資料的硬體階層。開發者需要安排 tile、memory layout、copy atom、MMA atom 與 pipeline,只是把複雜的 C++ template metaprogramming 換成 Python。它適合需要細部硬體控制,又想縮短 kernel 開發與編譯迭代時間的工作。
CUDA 長期以 SIMT 與 thread hierarchy 為中心,這種模型能完整暴露 GPU,也要求開發者把運算切到 threads、處理合作與同步。新 GPU 不斷加入更複雜的非同步資料搬移與 matrix instructions,同一個 tile operation 在不同架構上的理想 thread mapping 也可能改變。CUDA Tile 因此改用 tile-based programming model,開發者先描述整塊資料的運算,編譯器再負責將它映射到 block 內的 threads。
NVIDIA 在 2025 年 12 月發布的 CUDA Toolkit 13.1 首次推出 CUDA Tile,內容包含 CUDA Tile IR 與 cuTile。CUDA Tile IR 是 tile-based virtual ISA,cuTile 是建立在它上面的 Python DSL,使用 cuda.tile API。初始版本以 Blackwell GPU 為目標,後續 CUDA 13.x 才逐步擴大架構支援。
cuTile 程式以 logical grid of blocks 執行,每個 block 對 tile 做 load、store、矩陣乘法、reduction 與 shape transformation。開發者仍然決定 grid 如何切分整體問題,但只描述 block-level tile 運算,不直接指定 block 內每個 thread 的工作。
把前面的向量加法改用 cuTile,可以寫成下面這樣。這裡為了專注在程式模型,先假設向量長度是 TILE_SIZE 的整數倍
import cuda.tile as ct
TILE_SIZE = 256
@ct.kernel
def add_kernel(a, b, output):
block_id = ct.bid(0)
a_tile = ct.load(a, index=(block_id,), shape=(TILE_SIZE,))
b_tile = ct.load(b, index=(block_id,), shape=(TILE_SIZE,))
ct.store(output, index=(block_id,), tile=a_tile + b_tile)
這裡的 block_id 選擇資料 tile,ct.load 載入整塊資料。cuTile 的 block 是 logical execution unit,tile 是資料單位;一個 block 可以操作多個 tiles。Block 內的 array operations 由 threads 共同執行,語言不暴露個別 thread,也不允許開發者在 block 內寫明確同步。
cuTile 還區分 global array 與 tile value。Global array 對應有 shape、stride 與可變儲存空間的記憶體,tile 則是 block 內的 immutable value,shape 在編譯時期已知。開發者不必先宣告 tile 一定放在 registers 或 shared memory,這些物理儲存與 thread mapping 由編譯器決定。這是 cuTile 能簡化程式的來源,也是效能分析時需要檢查 compiler output 的原因。
CuTe DSL 和 cuTile 都用 Python 寫 GPU kernel,但控制界線不同
| Python DSL | 開發者主要操作的抽象 | thread-level mapping |
|---|---|---|
| CuTe DSL | layout、tensor、copy atom、MMA atom、pipeline | 開發者需要理解並控制 |
| cuTile | array、tile、block-level operation | 主要交給編譯器 |
cuTile 和 Triton 都讓開發者先描述一個 program instance 要處理的資料 tile,再由編譯器映射到底層 GPU。兩者使用不同的 API、IR 與編譯器。接著用 Triton 的向量加法對照。
已有的 GPU 函式庫可以快速處理標準 GEMM 與常見 DNN operations,但框架開發者還是會遇到 fused operator、特殊 data layout、sparse computation 或新演算法。如果每次都改寫 CUDA C++,開發者必須同時處理運算邏輯、thread scheduling、memory coalescing 與硬體指令。Triton 的出發點是讓程式設計者描述 blocked algorithm,再由 compiler 從 block-level data flow 分析資料局部性與平行性。
Triton 的起點可以從原始開發者 Philippe Tillet,他有來台灣讀過書,在 2014 年取得交大碩士學位。
之後在哈佛大學研究 GPU 上的 blocked algorithms 和 compiler。有個傳聞是他在 2018 年啟動 Triton,起因是用 CUDA 撰寫矩陣乘法 auto-tuner 時遇到開發困難。2019 年,Philippe Tillet、Hsiang-Tsung Kung 與 David Cox 發表 Triton: An Intermediate Language and Compiler for Tiled Neural Network Computations,公開 Triton 的語言與 compiler 設計。他在 2020 年完成博士論文 Blocked Algorithms for Neural Networks: Design and Implementation on GPUs。
Philippe Tillet 在 2020 年加入 OpenAI,繼續全職開發 Triton compiler。OpenAI 延續改進這個系統,並於 2021 年 7 月 釋出 Triton 1.0。
Triton 是 Python-based GPU programming language 與 compiler。這兩個身分可以分開看
@triton.jit、tl.program_id、tl.arange、tl.load、tl.store 與 tl.dot 等語法和語意,讓開發者使用 Python 語法描述 GPU kernel。因此,@triton.jit function 不會像一般 Python function 那樣逐行由 interpreter 在 CPU 上處理 tensor elements。Decorator 會把 function 交給 Triton frontend,compiler 再根據資料型別、constexpr 與 meta-parameters 建立專用版本。這是 Triton 同時被稱為 language、compiler 與 JIT compiler 的原因。
程式模型主要以 blocked program 組織平行工作。CUDA 範例中的一個程式實例對應一個 thread,Triton 的一個程式實例通常一次處理一整個 data block。這個 block 是語言中可以分析的 value 集合,編譯器因此比只看單一 scalar thread 擁有更多資料存取與運算結構的資訊。
同樣是向量加法,Triton kernel 可以寫成
import triton
import triton.language as tl
@triton.jit
def add_kernel(x, y, output, n_elements, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(axis=0)
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
x_block = tl.load(x + offsets, mask=mask)
y_block = tl.load(y + offsets, mask=mask)
tl.store(output + offsets, x_block + y_block, mask=mask)
pid 決定這個程式實例要處理哪一段資料,tl.arange 建立一組 offsets,tl.load 與 tl.store 則一次操作一個 block of values。開發者不需要替每個 GPU thread 寫索引,編譯器會把 block-level operations 映射到 threads、warps 與硬體指令。
Triton 仍然需要 CUDA 章節建立的硬體直覺。開發者要選擇 BLOCK_SIZE、安排連續存取、減少不必要的資料搬移,並觀察 registers、shared memory、occupancy 與 Tensor Core 使用情況。抽象提高了,效能問題仍會回到同一套 GPU 硬體限制。
Triton compiler 主要接手程式實例內的 memory coalescing、shared memory management 與 thread scheduling。開發者還是要決定 grid、tiling strategy 與 program instances 之間的工作分配。
cuTile 和 Triton 都用 Python 描述一塊資料要做的運算,差異可以從 block、資料模型與可調參數來看
| 比較項目 | cuTile | Triton |
|---|---|---|
| 所屬生態 | NVIDIA CUDA Tile | 獨立的Triton language和compiler(主要支援NVIDIA/AMD GPU) |
| 執行單位 | logical block | program instance |
| 資料模型 | global array 加上 immutable tile value | pointer 加上 block of values |
| 個別 thread | 語言不暴露 block 內的 thread | kernel 不直接替每個 thread 寫 scalar code,由 compiler 做 thread mapping |
| 常見程式參數 | tile shape、padding、load/store hints | BLOCK_SIZE、num_warps、num_stages、其他 meta-parameters |

ct.launch() 看到實際 launch arguments 後,會為 @ct.kernel 進行 specialization,產生 CUDA Tile IR,也可表示成 TileIR bytecode。後續由 tileiras、libNVVM 與 ptxas 組成的 NVIDIA compiler toolchain 產生 cubin。
CUDA Tile IR 是 cuTile 前端與後端 toolchain 之間的主要公開邊界。開發者可以看到 tile-level program,而 tileiras 內部如何將 tile 映射到 threads、warps 與硬體指令,官方文件沒有列出和 Triton 同等細節的中間 IR 階段。
cuTile 也提供 export_kernel()。開發者可以 ahead-of-time(AOT)匯出 TileIR bytecode,或直接產生指定 GPU target 的 cubin,不一定要等到應用程式執行 ct.launch() 才開始編譯。
Triton kernel 以 grid 呼叫時,JIT compiler 會根據 argument signature、constexpr 與 meta-parameters 建立特化版本。前端先產生 Triton IR(TTIR),這個階段還保留 tl.load、tl.store 與 tl.dot 這類 block-level operations。
進入 Triton GPU IR(TTGIR)後,target、layout、memory coalescing、num_warps、matmul acceleration 與 software pipeline 等決策開始具體化。後續再 lower 到 LLVM dialect 與 LLVM IR,產生這裡簡寫為 LLIR 的產物;NVIDIA backend 接著生成 PTX,最後由 ptxas 組譯成 cubin。
Triton 將這些階段分開後,開發者可以對照 TTIR、TTGIR、LLIR、PTX 與最後的 cubin,追蹤某個 layout 或 pipeline 決策在哪個 lowering 階段出現,我們之後會來實驗走過整個流程。
| Compile flow 問題 | cuTile | Triton NVIDIA backend |
|---|---|---|
| JIT 何時觸發 | ct.launch() 遇到具體 launch arguments |
@triton.jit kernel 以 grid 呼叫,依 signature 與 meta-parameters 編譯 |
| 公開的主要 IR 邊界 | CUDA Tile IR/TileIR bytecode | TTIR、TTGIR、LLIR、PTX |
| Target-specific mapping | 交給 CUDA Tile IR compiler tileiras |
主要從 TTGIR 與 NVIDIA backend passes 逐步具體化 |
| NVIDIA binary 產生 | tileiras 配合 libNVVM、ptxas 產生 cubin |
NVIDIA backend 產生 PTX,再由 ptxas 產生 cubin |
| 可觀察產物 | 可匯出 TileIR bytecode 或 cubin | 可檢查 TTIR、TTGIR、LLIR、PTX 與 cubin |
| 比較項目 | CUDA C++ | Triton |
|---|---|---|
| 使用語言 | C++ language extensions 與 CUDA APIs | Python DSL |
| 主要程式單位 | 一個 thread 執行 scalar program | 一個 program instance 處理 data block |
| Launch 結構 | grid → thread block → thread | grid → program instances |
| 索引方式 | 使用 blockIdx、blockDim、threadIdx 計算每個 thread 的工作 |
使用 tl.program_id 與 tl.arange 建立整個 data block 的 offsets |
| Thread mapping | 開發者明確安排 threads 與 warps | 編譯器把 block operations 映射到 threads 與 warps |
| 資料操作 | scalar、vector type、pointer 與明確 memory space | block of values、tl.load、tl.store、tl.dot |
| Shared memory 與同步 | 可直接配置 shared memory,使用 barrier、atomic 等同步工具 | 常見資料相依由編譯器排程,底層資源配置較少直接暴露 |
| 硬體控制力 | 高,可使用 CUDA intrinsics 與 inline PTX | 較高階,仍能調整 block size、warps、stages 等編譯參數 |
| 常見開發情境 | 需要完整硬體控制、特殊同步或 CUDA-specific feature | 快速開發 fused AI operators、matmul、softmax、normalization、attention |
CUDA 和 Triton 都需要處理 tiling、memory access、data reuse 與 parallelism。主要差異在於工作切分由誰表達,CUDA 開發者從 thread 出發,Triton 開發者從 data block 出發,再由 compiler 完成較細的 thread-level mapping。
nvcc、PTX、ptxas 與 cubin 則位在編譯工具鏈的不同位置。tileiras;Triton NVIDIA backend 則依序產生 TTIR、TTGIR、LLIR、PTX 與 cubin。在進入 triton compile flow 介紹之前,要先學 MLIR,所以明天會先讀 MLIR Paper,理解 dialect、operation、lowering 與 pass pipeline