tilelang: implement copy and vector_add kernels

This commit is contained in:
yyda 2026-07-16 11:51:19 +08:00
parent 287867a34e
commit e17e12c1cf
3 changed files with 133 additions and 0 deletions

View File

@ -0,0 +1,124 @@
# TileLang 基础知识点
## 1. 整体执行模型
TileLang kernel 的执行分为两个层级:**块block** 和 **线程thread**
```
T.Kernel(块数, threads=每块线程数)
├─ Block 0 (pid=0) ── N个线程 ── 处理自己的数据切片
├─ Block 1 (pid=1) ── N个线程 ── 处理自己的数据切片
├─ Block 2 (pid=2) ── N个线程 ── 处理自己的数据切片
└─ ...
```
- **块之间完全独立**,各自算各自的,互不通信
- **每个块执行同一份代码**,通过 `pid`program ID区分"我是哪个块"
---
## 2. `T.Kernel` 的两个参数
```python
with T.Kernel(块数, threads=256) as pid_n:
...
```
| 参数 | 含义 | 例子 |
|------|------|------|
| 第一个位置参数 | **块数**block count | `N // BLOCK_N` |
| `threads=` | **每块线程数**(硬件并行度) | `256` |
| `as pid_n` | 当前块的编号0, 1, 2, ... | — |
---
## 3. `pid_n` 是什么
`pid_n` = **program ID**,当前线程块的编号。从 0 开始递增。
每个块用自己的 `pid_n` 算出该处理哪段数据:
```python
base = pid_n * BLOCK_N # 块0→0, 块1→256, 块2→512, ...
```
---
## 4. `BLOCK_N` vs `threads` —— 数据 vs 硬件
| 参数 | 含义 | 类比 |
|------|------|------|
| `BLOCK_N` | **tile 大小**——每块处理多少元素 | 一份外卖要炒几个菜 |
| `threads=256` | **硬件线程数**——每块里多少工人 | 后厨里有几个厨师 |
它们**不需要相等**。`BLOCK_N` 管数据分块,`threads` 管并行度。
`BLOCK_N > threads`每个线程会自动多轮循环grid-stride loop
```
BLOCK_N=1024, threads=256 → 每个线程跑 1024/256 = 4 个元素
线程 0 → i=0, i=256, i=512, i=768
线程 1 → i=1, i=257, i=513, i=769
...
线程255 → i=255, i=511, i=767, i=1023
```
---
## 5. `T.Parallel` vs `T.Serial`
```python
for i in T.Parallel(BLOCK_N): # 并行——迭代间无依赖
out[i] = a[i] + b[i]
for i in T.Serial(M // BLOCK_M): # 串行——迭代间有依赖(如累加)
acc += T.reduce_sum(tile)
```
| | `T.Parallel` | `T.Serial` |
|---|---|---|
| 执行方式 | 所有迭代同时进行 | 必须一步步来 |
| 适用场景 | 逐元素操作copy, vector_add | 有累积状态的操作reduce_sum, softmax |
| 对应概念 | GPU 线程并行 | 顺序循环,依赖前一次结果 |
---
## 6. `T.copy` 的正确用法
`T.copy` 搬的是**切片tile**,不是整个 tensor。
```python
# ❌ 错误:每个块都搬整个 tensor
T.copy(src, out)
# ✅ 正确:每个块只搬自己的切片
T.copy(
src[pid_n * BLOCK_N : (pid_n + 1) * BLOCK_N],
out[pid_n * BLOCK_N : (pid_n + 1) * BLOCK_N],
)
```
---
## 7. 常用验证命令
```bash
conda activate py312
# 单算子 TileLang 测试
PYTHONPATH=python:. CAMP_BUILD_DIR=build-nvidia \
pytest tests/op_tests/test_<op>.py -v --backend tilelang
```
---
## 8. 算子学习顺序
| 顺序 | 算子 | 关键词 | 新概念 |
|------|------|--------|--------|
| 1 | `copy` | `T.Kernel` + `T.copy` | 块、pid、tile 切片 |
| 2 | `vector_add` | `for i in T.Parallel(...)` | 手写并行循环 |
| 3 | `reduce_sum` | `T.Serial` + `T.reduce_sum` | 顺序循环、fragment、规约 |
| 4 | `softmax` | online softmax + `exp2`/`log2` | 两遍扫描、log-sum-exp |

View File

@ -15,5 +15,10 @@ def copy_kernel(src, BLOCK_N: int, dtype):
# Suggested steps:
# 1. Launch one TileLang kernel over the N // BLOCK_N tiles.
# 2. Use T.copy to move one tile from src to out.
with T.Kernel(N // BLOCK_N, threads = 256) as pid_n:
T.copy(
src[pid_n * BLOCK_N : (pid_n + 1) * BLOCK_N],
out[pid_n * BLOCK_N : (pid_n + 1) * BLOCK_N],
)
return out

View File

@ -17,5 +17,9 @@ def vector_add_kernel(a, b, BLOCK_N: int, dtype):
# 1. Launch one TileLang kernel over the N // BLOCK_N tiles.
# 2. Compute the tile base offset.
# 3. Use T.Parallel(BLOCK_N) to fill out[base + i] = a[base + i] + b[base + i].
with T.Kernel(N // BLOCK_N, threads = 256) as pid_n:
base = pid_n * BLOCK_N
for i in T.Parallel(BLOCK_N):
out[base + i] = a[base + i] + b[base + i]
return out