Kernel Dev 训练营/ 提示与讲解
T1 提示与讲解 · Track T · Triton · 对照 solutions/t1_add_gelu.py 逐行读

T1 讲解:Triton 的六个基本动作

用 Triton 重写 C1 的融合激活。逐行读参考解:program_id、tl.arange、mask、指针加法、dtype 转换、grid 和 constexpr,再看编译器把“一块数据”拆给线程时实际做了什么,以及同一个 kernel 怎么在 CPU 上跑。

怎么读这一页

对照 drillst1_add_gelu.py 读。如果你刚做完 C1,第 ① 节的对照表能帮你把 CUDA 的概念一一换成 Triton 的概念。

① 先看全貌:从“一个线程”到“一块数据”

C1 里你写的是一个线程的代码:我是第几号,我处理哪个元素。Triton 里你写的是一个 program 的代码:我处理哪一块元素。program 相当于 CUDA 的 block,但 block 里的线程你看不到,由编译器负责。

做什么 C1(CUDA) T1(Triton)
我是谁 blockIdx.x * blockDim.x + threadIdx.x:一个下标 pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE):一个下标向量
越界 if (i < n) 分支 mask = offs < n,传给 load / store
读 x[i] tl.load(x_ptr + offs, mask=mask)
写 y[i] = v tl.store(out_ptr + offs, v, mask=mask)
启动 kernel<<<blocks, threads>>>(...) kernel[grid](...)
每个 block 几个线程 你定(256) 编译器定,num_warps × 32,默认 4 × 32 = 128
编译 提前用 nvcc 编译 第一次调用时 JIT 编译,之后缓存
一次 add_gelu 调用发生了什么悬停节点查看连接
你只写到“每个 program 处理 1024 个元素”这一层。1024 个元素怎么分给 4 个 warp、每个线程拿哪几个、用不用 128 位向量化读取,都是编译器根据 layout 决定的(G1 会专门讲 layout)。
类比 · 下单,而不是派活

CUDA 像是你亲自给每个快递员(线程)派活:“你送 3 号楼 201”。Triton 像是你在系统里给每个站点(program)下一张单:“这个站点负责 0–1023 号包裹”,站点内部怎么分给快递员由系统排。你能控制的是单子怎么拆(BLOCK_SIZE)和每个站点配几个人(num_warps),而不是某个人送哪一件。

② kernel 逐行讲

drillsadd_gelu_kernel(16–34 行)

签名:指针和 constexpr

@triton.jit
def add_gelu_kernel(x_ptr, y_ptr, out_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
类比 · 印刷的版次

constexpr 参数像书的“版次”:BLOCK_SIZE = 1024 印一版,改成 2048 就得重新排版再印一版(重新编译)。版次一旦印好就存起来(缓存),下次同样的版次直接拿。普通参数(n_elements)则像读者在书上填的空:同一版书,每次填的内容可以不同,不需要重印。

我负责哪一块

drills18–20 行

pid  = tl.program_id(axis=0)                           # 第几个 program:0, 1, 2, ...
offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)     # [BLOCK_SIZE] 个下标
mask = offs < n_elements                               # [BLOCK_SIZE] 个 True/False

tl.arange(0, 1024) 是一个长度 1024 的向量 [0, 1, …, 1023],加上一个标量之后还是向量。所以 offs 和 mask 都是一整块。

n = 2500,BLOCK_SIZE = 1024 时:

pid 0offs 0 … 1023 · 全部 True
pid 1offs 1024 … 2047 · 全部 True
pid 22048 … 2499
mask = False2500 … 3071

grid = cdiv(2500, 1024) = 3。最后一个 program 的 1024 个下标里只有前 452 个是 True,后 572 个的 load 和 store 都被屏蔽。

和 C1 的 if (i < n) 是同一件事,区别是这里不写分支,而是把 mask 交给 load 和 store。

读、算、写

drills23–34 行

x = tl.load(x_ptr + offs, mask=mask, other=0.0).to(tl.float32)
y = tl.load(y_ptr + offs, mask=mask, other=0.0).to(tl.float32)
z = x + y
inner = 0.7978845608028654 * (z + 0.044715 * z * z * z)
t = 2.0 * tl.sigmoid(2.0 * inner) - 1.0          # = tanh(inner)
out = 0.5 * z * (1.0 + t)
tl.store(out_ptr + offs, out.to(out_ptr.dtype.element_ty), mask=mask)

所有运算(+、*、tl.sigmoid)都是对整个向量逐元素进行的,和 NumPy 一样。

③ host 端:grid 和启动语法

drillsadd_gelu(37–49 行)

x, y = x.contiguous(), y.contiguous()     # kernel 把内存当一维数组读,必须连续
out = torch.empty_like(x)
grid = (triton.cdiv(n, block_size),)       # 一维 grid,program 个数
add_gelu_kernel[grid](x, y, out, n, BLOCK_SIZE=block_size)

④ 编译器替你做了什么

你写的是 1024 长的向量运算,GPU 上实际执行的是 num_warps × 32 个线程(默认 4 × 32 = 128)。编译器把 1024 个元素按 layout 分给这 128 个线程,每个线程 8 个。fp32 的典型分法是:每个线程一次拿连续 4 个(正好一次 128 位读取),128 个线程一轮覆盖 512 个元素,再绕一圈覆盖后 512 个:

BLOCK_SIZE = 1024 在硬件上的样子(num_warps = 4)悬停节点查看连接
这是 fp32 下的典型分法(G1 里叫 BlockedLayout,sizePerThread = 4)。实际 layout 由编译器根据 BLOCK_SIZE、num_warps、数据类型和指针对齐决定。每个线程拿连续 4 个 fp32,就能用 128 位的向量化读写;同一轮里 32 个 lane 读连续的 512 字节,合并访存也自然满足。

这就是 Triton 省掉的工作:在 CUDA 里想做到这一点,你得自己写 float4 读取、自己算每个线程的偏移。代价是你看不到也很难精确控制这一层,G1 会讲怎么读懂它。

在 CPU 上跑:TRITON_INTERPRET=1

本仓库没有 GPU 时自动设 TRITON_INTERPRET=1(见 drillscommon/env.py)。这时 @triton.jit 不编译,而是用 NumPy 逐个 program 解释执行:tl.arange 变成 NumPy 数组,tl.load 变成数组索引。所以正确性可以在笔记本上测,还能在 kernel 里直接 print。实现见 tritonInterpretedFunction。性能数字只有在 GPU 上才有意义。

⑤ 对照真实代码

⑥ 常见坑与自测

自测 1:n = 1,000,000,block_size = 1024,一共几个 program?最后一个 program 有几个有效元素?

cdiv(1,000,000, 1024) = 977。前 976 个 program 覆盖 976 × 1024 = 999,424 个元素,最后一个有 1,000,000 − 999,424 = 576 个有效元素。

自测 2:block_size 从 1024 改成 4096,program 数和每个线程处理的元素数怎么变?

program 数变成 1/4(245 个)。num_warps 不变时,每个线程从 8 个元素变成 32 个,寄存器占用变大。block 太大时一般要同时调大 num_warps,题目页“测完之后”建议的扫参就是在看这个权衡。

源码与资料索引

本仓库:drillssolutions/t1_add_gelu.py · drillsmine/t1_add_gelu.py · drillstests/test_t1_add_gelu.py · drillscontent/t1.md · drillscontent/basics_triton.md(Triton 基础)

Triton:tritontutorial 01-vector-add · triton解释器 · API:tl.program_id · tl.arange · tl.load · tl.store · tl.sigmoid · triton.language 全部 API

FlashInfer:flashinfersilu_and_mul_kernel · flashinferhost 端 wrapper