大模型时代,我们经常会接触到这样一些技术:
PyTorch、CUDA、cuBLAS、FlashAttention、Triton、TVM、TileLang。
它们看起来都和 GPU 计算有关,但实际上处在完全不同的层次。
例如下面这一行 PyTorch:
python
C = torch.matmul(A, B)
从程序员视角,它只是一次矩阵乘法。
但真正落到 GPU 上,需要解决的问题远比 A × B 复杂:
text
矩阵应该如何切块?
↓
一个 Block 算多大的 Tile?
↓
一个 Block 使用多少线程?
↓
数据什么时候从 HBM 搬到 Shared Memory?
↓
Shared Memory 怎么布局?
↓
什么时候放进 Register?
↓
Tensor Core 怎么使用?
↓
load 和 compute 能不能流水线并行?
↓
如何减少访存?
↓
如何隐藏内存访问延迟?
这些问题,构成了深度学习编译器和 GPU Kernel 优化的核心。
TVM、Triton、TileLang,本质上都是在回答同一个问题:
怎样用更高层的方式描述计算,同时让编译器生成接近手写 CUDA 的高性能代码?
只是三者选择的抽象层次不同。
一、为什么深度学习需要专门的编译器?
传统程序的编译过程通常是:
text
C/C++
↓
LLVM IR
↓
机器码
↓
CPU
但是深度学习程序有一个非常明显的特点:
它大量由张量运算组成。
例如:
text
MatMul
Conv
RMSNorm
LayerNorm
Softmax
Attention
GEMM
Reduce
ElementWise
这些运算背后存在大量可以利用的结构信息。
例如矩阵乘法:
C_{i,j}=\\sum_k A_{i,k}B_{k,j}
如果直接让传统编译器处理三层 for 循环:
cpp
for (int i = 0; i < M; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < K; k++)
C[i][j] += A[i][k] * B[k][j];
编译器虽然知道这是循环,却未必知道:
text
这是 GEMM
可以 Tile
可以使用 Shared Memory
可以使用 Tensor Core
可以做 Warp-level MMA
可以进行软件流水线
深度学习编译器的意义,就是保留并利用这些张量计算语义。
二、TVM 解决了什么问题?
Apache TVM 可以理解为一套面向机器学习工作负载的编译基础设施。
当前 TVM 的整体结构,大致可以理解为:
text
PyTorch / ONNX / 其他模型
│
▼
Relax IR
模型/计算图级
│
图优化 / Fusion
│
▼
TensorIR
Kernel / 算子级
│
Schedule / Lowering
│
▼
CUDA / LLVM / OpenCL ...
│
▼
GPU / CPU / NPU
当前 TVM 官方架构中,高层模型程序主要由 Relax 表达,而低层 tensor program 由 TensorIR 表达。Relax 负责图级结构和优化,TensorIR 则包含循环、Buffer、线程绑定、张量指令等更加靠近硬件的信息。(Apache TVM)
所以如果你看到一些较老的 TVM 教程写:
text
Relay
↓
TIR
可以把它理解成 TVM 历史上的经典架构。
目前理解 TVM 更合适的方式是:
text
Relax
↓
TensorIR
三、TVM 最重要的思想:Compute 和 Schedule 分离
这是理解 TVM 的关键。
假设我们计算:
C=A\\times B
首先描述的是:
算什么。
例如:
text
C[i,j] = Σ A[i,k] × B[k,j]
但是你还没有告诉编译器:
怎么算。
例如可以采用:
text
Tile = 16 × 16
也可以:
text
Tile = 32 × 32
甚至:
text
128 × 128
还可以进一步决定:
text
Thread Block 数量
Warp 数量
Shared Memory 大小
Register Tile
Vectorize
Unroll
Pipeline
所以 TVM 把问题拆成了:
text
Compute
│
│ 描述数学计算
▼
Schedule
│
│ 描述如何映射到硬件
▼
Generated Kernel
TensorIR 的 Schedule 就承担了大量这样的变换,例如 tiling、vectorization 和 thread binding;TVM 还提供 MetaSchedule 等机制搜索性能更好的调度。(Apache TVM)
四、为什么 Tiling 如此重要?
假设:
text
A = 4096 × 4096
B = 4096 × 4096
计算:
text
C = A × B
显然不可能把整个矩阵一次放进 GPU 的 Shared Memory。
于是需要切块。
例如:
text
A
┌──────┬──────┬──────┐
│Tile │Tile │Tile │
├──────┼──────┼──────┤
│Tile │Tile │Tile │
├──────┼──────┼──────┤
│Tile │Tile │Tile │
└──────┴──────┴──────┘
假设每个 Block 计算:
text
128 × 128
大小的 C。
那么:
text
C Tile
128 × 128
对应需要不断加载:
text
A Tile:128 × 32
B Tile:32 × 128
于是 GEMM 变成:
text
for k = 0 ... K step 32:
Global Memory
│
▼
A[128 × 32]
B[32 × 128]
│
▼
Shared Memory
│
▼
Tensor Core
│
▼
Register
│
▼
C[128 × 128]
这里的 128 × 128 × 32,就是非常典型的 Tile 参数。
五、Tile 本质上是在匹配 GPU 的内存层次
GPU 并不是一个单纯的"计算器"。
GPU 性能很大程度取决于:
数据在哪里。
可以粗略画成:
text
容量大
▲
│
HBM / DRAM
│
Global Memory
│
─────────────────────
│
Shared Memory
│
─────────────────────
│
Register
│
─────────────────────
│
CUDA Core / Tensor Core
│
▼
计算快
距离计算单元越近:
text
延迟通常越低
带宽通常越高
容量通常越小
因此优化 Kernel 的核心经常不是:
text
少做一次乘法
而是:
text
让数据少去几次 HBM。
例如一个朴素 GEMM 可能重复从 Global Memory 读取 A 和 B。
Tiling 后:
text
HBM
↓
Shared Memory
↓
被很多线程重复使用
一次加载的数据可以参与大量计算。
这就是所谓的:
Data Reuse(数据复用)。
六、问题来了:TVM 为什么还不够?
TVM 非常强大,但对于 Kernel 工程师来说有一个问题:
它的编译器能力很强,但编程模型相对复杂。
为了实现一个高性能 GEMM,你可能需要理解:
text
TensorIR
Block
Loop
Schedule
split
reorder
bind
cache_read
cache_write
vectorize
unroll
Tensor Intrinsic
然后进行大量 schedule transformation。
这对于编译器来说非常优雅。
但对于 CUDA Kernel 工程师而言,真正思考的问题往往不是:
text
我要如何变换这棵 IR?
而是:
text
我的 Block Tile 多大?
A 放 Shared Memory。
B 放 Shared Memory。
C 放 Register。
然后让 load 和 GEMM pipeline 起来。
也就是说,两类人的思维模型存在区别:
text
编译器工程师
Loop
↓
Split
↓
Reorder
↓
Bind
↓
Transform
而 Kernel 工程师更加习惯:
text
Tile
↓
Memory
↓
Thread / Warp
↓
Tensor Core
↓
Pipeline
这也是后来很多 GPU DSL 开始强调 Tile Programming Model 的原因。
七、Triton 带来了新的思路
Triton 的重要贡献之一,就是把 GPU Kernel 编程从:
text
Thread Programming
提升到:
text
Block / Tile Programming
传统 CUDA 中,你经常需要思考:
cpp
int tid = threadIdx.x;
int bid = blockIdx.x;
然后:
text
thread 0 算什么
thread 1 算什么
thread 2 算什么
...
Triton 则更倾向于让开发者描述:
text
这一组线程共同处理什么数据块。
于是编程模型发生了变化:
text
CUDA
Thread
Thread
Thread
Thread
│
▼
程序员自己组织
│
▼
Tile
Triton
Tile
│
▼
Compiler
│
▼
Thread / Warp
这极大降低了高性能 GPU Kernel 的开发门槛。
八、那么 TileLang 又是什么?
TileLang 延续了 Tile-based Programming 的思想,但它选择直接建立在 TVM 的编译基础设施之上。
当前 TileLang 官方文档把 Tile 作为编程模型中的一等对象 ,同时允许开发者显式表达 Shared Memory、register fragment、数据搬运、pipeline 等硬件相关行为。程序随后继续下降为 IR,并生成 CUDA、HIP、LLVM 等目标代码。(TileLang)
因此可以这样理解:
text
TVM
更像:
Compiler Infrastructure
Triton
更像:
GPU Kernel Programming Language
TileLang
更像:
Tile-based Kernel DSL
+
TVM Compiler Infrastructure
TileLang 并不是为了"替代 TVM"。
恰恰相反:
TileLang 在很大程度上利用 TVM 作为自己的编译底座。
官方文档也明确说明,TileLang Kernel 可以生成 TIR/TVM IR 函数,其 DSL 进一步经过 lowering 后生成面向具体硬件的代码。(TileLang)
九、TileLang 最重要的变化:Tile 成为一等公民
考虑 GEMM:
C=A\\times B
Kernel 工程师可能首先决定:
text
block_M = 128
block_N = 128
block_K = 32
意味着一个 GPU Block:
text
计算 C:
128 × 128
每次从 A 加载:
text
128 × 32
从 B 加载:
text
32 × 128
程序在逻辑上就非常自然:
python
A_shared = shared(block_M, block_K)
B_shared = shared(block_K, block_N)
C_local = fragment(block_M, block_N)
for k in pipeline(...):
copy(A_global_tile, A_shared)
copy(B_global_tile, B_shared)
gemm(
A_shared,
B_shared,
C_local
)
copy(C_local, C_global)
你会发现:
这里已经非常接近 GPU Kernel 工程师脑子里的执行过程。
而不是:
text
创建 Loop
↓
split loop
↓
reorder loop
↓
bind thread
↓
cache_read
↓
compute_at
这就是两种抽象方式最大的区别。
十、TileLang 中的 GPU 内存层次
TileLang 允许开发者非常明确地表达数据应该放在哪里。
例如概念上可以写成:
python
A_shared = T.alloc_shared(...)
B_shared = T.alloc_shared(...)
C_local = T.alloc_fragment(...)
其中 alloc_shared 对应 GPU 的片上 Shared Memory,而 fragment 则用于描述更靠近计算单元的寄存器级数据布局;TileLang 还通过 layout inference 将逻辑 Tile 映射到线程和寄存器。(TileLang)
于是:
text
A
Global Memory
│
│ T.copy
▼
A_shared
Shared Memory
│
▼
Tensor Core
│
▼
C_local
Registers
│
│ T.copy
▼
C
Global Memory
这和真正 GPU 的执行模型高度对应。
十一、为什么 Pipeline 又非常重要?
假设一次 GEMM Tile 包含两个阶段:
text
Load Data
Compute
最简单的实现:
text
Load 0
Compute 0
Load 1
Compute 1
Load 2
Compute 2
假设:
text
Load = 100 ns
Compute = 100 ns
那么三轮:
text
200 + 200 + 200
= 600 ns
但 GPU 有异步数据搬运等能力。
于是可以:
text
时间 ─────────────────────►
Load0
Compute0
Load1
Compute1
Load2
Compute2
即:
text
Load(k+1)
与:
text
Compute(k)
重叠执行。
理想情况下:
text
100 + 100 + 100 + 100
≈ 400 ns
而不是:
text
600 ns
这就是:
Software Pipeline。
TileLang 的 T.Pipelined 等抽象,就是为了让这种 GPU Kernel 模式更加直接地进入 DSL。官方 GEMM 示例同样采用了 shared-memory tile、fragment accumulator、pipelined K-loop 和 T.gemm 的结构。(GitHub)
十二、从代码看 TileLang 的思维方式
一个概念化的 TileLang GEMM 可以写成:
python
import tilelang.language as T
@T.prim_func
def matmul(
A: T.Tensor((M, K), "float16"),
B: T.Tensor((K, N), "float16"),
C: T.Tensor((M, N), "float16"),
):
with T.Kernel(
T.ceildiv(N, BLOCK_N),
T.ceildiv(M, BLOCK_M),
threads=128
) as (bx, by):
A_shared = T.alloc_shared(
(BLOCK_M, BLOCK_K),
"float16"
)
B_shared = T.alloc_shared(
(BLOCK_K, BLOCK_N),
"float16"
)
C_local = T.alloc_fragment(
(BLOCK_M, BLOCK_N),
"float32"
)
T.clear(C_local)
for k in T.Pipelined(
T.ceildiv(K, BLOCK_K),
num_stages=3
):
T.copy(
A[
by * BLOCK_M,
k * BLOCK_K
],
A_shared
)
T.copy(
B[
k * BLOCK_K,
bx * BLOCK_N
],
B_shared
)
T.gemm(
A_shared,
B_shared,
C_local
)
T.copy(
C_local,
C[
by * BLOCK_M,
bx * BLOCK_N
]
)
这里只看结构,不纠结具体版本 API。
你会发现整个 Kernel 基本就是:
text
申请 Shared Memory
↓
申请 Register Fragment
↓
Global → Shared
↓
Shared → Tensor Core
↓
累加到 Register
↓
Register → Global
这正是高性能 GEMM 的基本执行框架。
十三、从 TVM 到 TileLang,真正发生了什么变化?
很多人会简单理解为:
text
TVM API 太复杂
↓
TileLang API 简单一点
其实不够准确。
更重要的变化是:
编程抽象发生了变化。
可以这样看:
| 技术 | 核心抽象 | 开发者主要思考 |
|---|---|---|
| CUDA | Thread | 每个线程做什么 |
| TVM TensorIR/Schedule | Loop + Block + Transformation | 如何变换程序 |
| Triton | Block / Tile | 一个数据 Tile 怎么计算 |
| TileLang | Tile + Memory + Pipeline + Hardware Primitive | Tile 如何映射硬件 |
因此 TileLang 的意义,并不仅仅是:
text
少写几行 TVM
而是:
text
让 Kernel 的表达方式
更加接近 GPU 的真实执行模型
十四、为什么这种模式非常适合大模型?
过去 CNN 时代,核心算子相对固定:
text
Conv
Pooling
BatchNorm
GEMM
厂商库基本可以覆盖:
text
cuDNN
cuBLAS
但 LLM 时代出现了大量新的 fused kernel:
text
FlashAttention
PagedAttention
RMSNorm
RoPE
SwiGLU
MoE
TopK
Grouped GEMM
Quantized GEMM
FP8 GEMM
Dequant + GEMM
MLA
FlashMLA
这些算子有一个特点:
算法变化非常快。
如果每次都等待:
text
CUDA Library
↓
Framework
↓
正式发布
开发周期可能过长。
于是大模型 Infra 越来越需要:
text
Python
↓
Kernel DSL
↓
快速开发
↓
JIT Compile
↓
GPU Kernel
这也是 Triton、TileLang 等 DSL 越来越重要的根本原因之一。
TileLang 官方文档目前也已经把 GEMM、FlashMLA 等高性能 AI Kernel 作为典型应用场景。(TileLang)
十五、TVM、Triton、TileLang 到底是什么关系?
可以画成:
text
深度学习模型
│
PyTorch / Framework
│
┌──────────────┴──────────────┐
│ │
▼ ▼
图 / 模型编译 Kernel 开发
│ │
▼ ┌────────┴────────┐
TVM │ │
│ Triton TileLang
│ │ │
Relax IR │ Tile DSL
│ │ │
TensorIR │ TVM IR
│ │ │
└────────────┐ │ ┌────────┘
▼ ▼ ▼
CUDA / LLVM / HIP
│
▼
GPU
当然现实中的编译栈远比这张图复杂,但它非常适合建立第一层认知。
十六、TVM 并没有"过时"
看到 Triton、TileLang 后,有些人容易产生一个误区:
text
既然有 TileLang,
是不是 TVM 不需要了?
其实恰好相反。
TVM 更像:
text
Compiler Infrastructure
而 TileLang 更像:
text
Kernel Programming DSL
TVM 解决的范围更大:
text
Graph IR
Tensor IR
Optimization Pass
Scheduling
Auto Tuning
CodeGen
Runtime
Backend
TileLang 则把注意力集中到:
text
高性能 Kernel
当前 TVM 本身仍然同时提供 Relax、TensorIR、Schedule、MetaSchedule、DLight、目标代码生成等组件。(Apache TVM)
因此二者并不是竞争关系,而更接近:
text
TileLang
│
Kernel Programming
│
▼
Compiler Infrastructure
│
▼
TVM
十七、从 TVM 到 TileLang,其实反映了 AI Compiler 的一个趋势
如果回顾整个过程,会发现一个明显趋势。
早期我们希望:
text
模型
↓
Compiler
↓
自动生成一切
即:
Compiler 尽可能自动决定性能优化。
后来大家发现,对于极致性能 Kernel:
text
MatMul
Attention
MoE
Quantization
很多硬件信息必须让专家参与。
于是逐渐变成:
text
开发者
│
│ Tile / Memory / Pipeline
▼
Compiler
│
│ Layout / Thread Mapping
│ CodeGen / Lowering
▼
Hardware
也就是说,目标已经不是:
"完全隐藏硬件。"
而是:
隐藏没有必要暴露的硬件细节,同时把影响性能的关键控制权交给程序员。
TileLang 官方设计中的多个编程接口正体现了这一点:它希望同时覆盖更高层的 hardware-unaware 编程、利用 Tile Library 的 hardware-aware 开发,以及更底层的 thread primitives,并允许这些层次混合使用。(TileLang)
这其实是一种非常重要的编译器设计思想:
text
Abstraction
≠
把所有硬件隐藏掉
好的 Abstraction
=
只暴露真正重要的硬件信息
十八、一张图理解整个演进
最终可以把这条技术路线压缩成:
text
第一阶段
手写 CUDA
Thread / Warp / Shared Memory
全部自己控制
│
▼
第二阶段
TVM
Compute
+
Schedule
+
Compiler Optimization
│
▼
第三阶段
AutoTVM / Ansor / MetaSchedule
自动搜索 Schedule
│
▼
第四阶段
Triton
Block / Tile Programming
│
▼
第五阶段
TileLang
Tile
+
Memory Hierarchy
+
Pipeline
+
Hardware Primitive
+
TVM Compiler Infrastructure
注意,这不是严格的"上一代淘汰下一代"关系。
更准确地说:
深度学习编译器一直在寻找"开发效率、编译自动化和硬件控制能力"之间最合适的平衡点。
十九、学习 TileLang 前应该先掌握什么?
如果想真正理解 TileLang,建议不要一开始就背 API。
应该先理解这条链路:
text
矩阵乘法
↓
Tiling
↓
GPU Thread Block
↓
Warp
↓
Global Memory
↓
Shared Memory
↓
Register
↓
Tensor Core
↓
Double Buffer
↓
Software Pipeline
掌握这些以后,再看:
text
T.Kernel
T.alloc_shared
T.alloc_fragment
T.copy
T.gemm
T.Pipelined
会发现这些 API 并不是凭空设计出来的。
它们基本都在描述 GPU 中真实存在的东西。
二十、最后总结
如果只记住一句话,可以记成:
TVM 的核心是把张量程序编译到不同硬件,而 TileLang 的核心是让开发者以 Tile 为基本单位,更直接地描述一个高性能 Kernel 应该怎样在硬件上运行。
再进一步:
text
TVM
解决:
如何构建 AI Compiler
TileLang
解决:
如何更舒服地写高性能 AI Kernel
TVM 更偏:
text
Compiler
IR
Schedule
Transformation
CodeGen
TileLang 更偏:
text
Tile
Shared Memory
Register
Tensor Core
Pipeline
Kernel
所以从 TVM 到 TileLang,并不只是:
text
语法越来越简单
真正的变化是:
text
Tensor Expression
↓
Loop / Schedule
↓
Tile
↓
Memory Hierarchy
↓
Hardware Primitive
深度学习编译正在从"让编译器猜怎样跑得快",逐渐发展到"开发者描述关键的高性能计算结构,编译器负责把这些结构可靠、高效地映射到硬件"。
理解这一点,再去学习 FlashAttention、Triton、TileLang、CUTLASS,甚至各种大模型推理框架中的自定义 Kernel,就会发现它们背后的很多设计其实是一脉相承的。
