第一章:从 CUDA 到 Triton —— 异构计算的新视角
1. 异构计算的痛点
在 GPGPU 算子开发领域,开发人员长期面临着高吞吐量与低开发成本之间的双重折磨。
传统的开发模式主要分为两类:
- Thread-level SPMD (以 CUDA/HIP 为代表): 开发者需要精细控制每一个 Thread。为了实现极致的性能,必须手动管理 Shared Memory 的分配、处理数据对齐与访存合并 (Memory Coalescing)、使用 Warp-level shuffle 原语,以及处理繁琐的 Thread 同步 (
__syncthreads())。这种模式下,心智负担极高,且代码的可维护性极差。 - High-level DSL/Compiler (以 TVM/Halide/OpenMP 为代表): 通过将算法描述与调度优化分离(Separation of Concerns),大大降低了开发难度。但在实际生产中,由于其底层生成逻辑较为通用,在面对如 FlashAttention 等复杂非规则访存/高度定制化算法时,往往无法生成最优的代码。
Triton 的出现提供了一条全新的通路。它摒弃了 Thread-level 的微观控制,引入了 Block-level dataflow 编程模型。
2. Triton 的核心概念:Block-Level Dataflow
在 CUDA 中,我们编写的 Kernel 是针对单个线程 (Thread) 的,然后通过 threadIdx、blockIdx 确定当前线程处理的数据指针:
1 | // CUDA 元素相加:每个线程处理一个元素 |
而在 Triton 中,我们编写的 Kernel 是针对一个数据块 (Block/Tile) 的。整个计算被表述为 Block 在各个寄存器、共享内存和全局内存之间的流动与操作:
1 | # Triton 元素相加:每个 Kernel 实例处理一个 Block(包含多个元素) |
为什么 Block-Level 编程更具优势?
- 隐式内存管理:在 Triton 中,你不需要手动声明和分配 Shared Memory。Triton 编译器在进行中间表示分析(TritonGPU IR)时,会自动推导数据流的生命周期,并在合适的时间节点分配共享内存、插入同步指令(如
bar.sync)。 - **自动流水线优化 (Pipelining)**:Triton 编译器支持在多缓冲(Multi-buffering)架构下自动重排指令,实现全局内存加载(Global Load)、共享内存加载(Shared Load)与 Tensor Core 计算(MMA)之间的流水线交叠,最大限度隐藏访存延迟。
- **向量化与合并访存 (Coalescing)**:Triton 在编译阶段会自动分析 Block 的偏置和跨步(Stride),自动推导最优的向量化加载指令(如
LDG.E.128),无需开发者手动对齐指针。
3. Triton 编译器流水线 (Compilation Pipeline)
作为一个编译器工程师,我们需要理解 Triton 是如何将这段 Python 代码转换为高效的机器码的。
Triton 的核心是一个专用编译器,它的编译流水线如下所示:
graph TD
Python[Python AST / Triton JIT] -->|Parsing| TTIR[Triton IR / TTIR]
TTIR -->|Convert to Target GPU Layout| TTGIR[TritonGPU IR / TTGIR]
TTGIR -->|Layout Optimizations / Pipelining| OptTTGIR[Optimized TTGIR]
OptTTGIR -->|Lowering| LLVM[LLVM IR]
LLVM -->|NVPTX Backend| PTX[PTX Code]
PTX -->|ptxas / Driver| SASS[SASS Machine Code]
编译的关键步骤:
- **TTIR (Triton IR)**:这是 Triton 编译器的前端表示,它保留了硬件无关的 Block 操作语义,类似于普通的静态单赋值 (SSA) 形式。
- **TTGIR (TritonGPU IR)**:这一步引入了硬件相关的特征。编译器会分析不同的 Block 布局(Layouts),如
BlockedLayout、SharedLayout、MmaLayout(对应 Tensor Core 布局)。在这一层,Triton 进行了最核心的优化:分配 Shared Memory 缓冲区、优化寄存器溢出、实现计算与访存流水线。 - LLVM IR:TTGIR 会被翻译为 LLVM IR,主要是为了利用 LLVM 强大的优化器和后端的代码生成能力。
- PTX/SASS:最终通过 NVIDIA 的 NVPTX 目标生成虚拟汇编 PTX,再经由驱动或
ptxas汇编成在 GPU 上执行的机器码 SASS。
4. CUDA 与 Triton 核心概念对比
为了方便大家从 CUDA 开发无缝切换到 Triton,下表总结了两者在概念上的映射:
| 维度 | CUDA / HIP | Triton |
|---|---|---|
| 执行模型 | SPMD (Single Program Multiple Data),针对 Thread 编程 | Block-level Dataflow,针对 Block/Tile 编程 |
| 共享内存 | 手动分配 __shared__,手动同步 __syncthreads() |
编译器自动分析分配与插入同步(隐式) |
| 内存对齐 | 手动保证 (128-byte alignment) 或使用向量类型 (如 float4) |
编译器分析 offset 自动进行向量化合并访存 |
| Tensor Core | 通过 WMMA API 或 mma.sync 汇编指令精细控制 |
使用 tl.dot(A, B) 自动生成硬件 MMA 指令 |
| 并发度控制 | 手动设计 Grid & Block 维度 | 自动计算 Warp 数量,提供 num_warps 等超参数微调 |
5. 下一步规划
在接下来的章节中,我们将通过动手编写实际代码来加深理解。
第二章我们将实现一个完整的 Vector Addition (向量加法) 算子。除了学习如何调用 Triton JIT 执行计算外,我们还将把 Triton 的 JIT 编译器剖开,一步步导出并阅读它在编译过程中生成的 TTIR、LLVM IR 以及 PTX 代码,揭开 Triton 神秘的面纱。
敬请期待!
作者简介:Jing Zhou,资深 LLVM GPGPU 编译器工程师,专注于 GPU 编译器后端代码优化与高性能异构计算。欢迎在下方留言讨论!
第一章:从 CUDA 到 Triton —— 异构计算的新视角
https://zhoujingfighting.github.io/triton/triton-series-introduction/