跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 03 章 TileLang 的设计哲学与编程模型

/code/03-design-philosophy/

02-one-kernel-two-levels/one_kernel_two_levels.py5609 字节
#!/usr/bin/env python3
"""第 03 章 示例 2:同一个 kernel 里的两层(composable 的最小实证)。

这个文件只做一件事:把一个 kernel 写成「developer 层的 GEMM + expert 层的线程级部分和」,
并在注释里逐行标出每句属于哪一层。

依赖:tilelang v0.1.15、torch(CUDA)、一张 NVIDIA GPU。
无 GPU 时用 `--static-only`,只做语法与 API 核对。

作者验证级别:仅静态核对(见同目录 README.md)。
"""

from __future__ import annotations

import argparse
import sys

# --- 静态核对清单:每个 API -> (层次, 在锁定版本里的定义位置) ------------------
API_CHECKLIST: tuple[tuple[str, str, str], ...] = (
    ("T.Kernel", "developer", "tilelang/language/kernel.py:431"),
    ("T.alloc_shared", "developer", "tilelang/language/allocate.py:49"),
    ("T.alloc_fragment", "developer", "tilelang/language/allocate.py:81"),
    ("T.alloc_local", "expert", "tilelang/language/allocate.py:67"),
    ("T.clear", "developer", "tilelang/language/fill_op.py:48"),
    ("T.copy", "developer", "tilelang/language/copy_op.py:59"),
    ("T.gemm", "developer", "tilelang/language/gemm_op.py:160"),
    ("T.Pipelined", "developer", "tilelang/language/loop.py:112"),
    ("T.get_thread_binding", "expert", "tilelang/language/kernel.py:587"),
    ("T.serial", "expert", "tilelang/language/loop.py:189"),
)


def build_kernel(block_M: int = 64, block_N: int = 64, block_K: int = 32, num_stages: int = 2):
    """构造 kernel(lazy 模式:返回 PrimFunc)。导入被推迟到调用时。"""
    import tilelang
    import tilelang.language as T

    @tilelang.jit(out_idx=[2, 3])
    def mixed_level_gemm(M: int, N: int, K: int, threads: int = 128):
        n_tiles_n = T.ceildiv(N, block_N)
        n_tiles_m = T.ceildiv(M, block_M)

        @T.prim_func
        def main(
            A: T.Tensor((M, K), "float16"),
            B: T.Tensor((K, N), "float16"),
            C: T.Tensor((M, N), "float16"),
            Partial: T.Tensor((n_tiles_n, n_tiles_m, threads), "float32"),
        ):
            with T.Kernel(n_tiles_n, n_tiles_m, threads=threads) as (bx, by):
                # ================= developer 层:tile 库 =================
                # 全程没有线程索引,也没有同步原语。
                A_s = T.alloc_shared((block_M, block_K), "float16")
                B_s = T.alloc_shared((block_K, block_N), "float16")
                C_frag = T.alloc_fragment((block_M, block_N), "float32")
                C_s = T.alloc_shared((block_M, block_N), "float16")

                T.clear(C_frag)
                for ko in T.Pipelined(T.ceildiv(K, block_K), num_stages=num_stages):
                    T.copy(A[by * block_M, ko * block_K], A_s)
                    T.copy(B[ko * block_K, bx * block_N], B_s)
                    T.gemm(A_s, B_s, C_frag)
                T.copy(C_frag, C_s)  # fragment -> shared,协作写入

                # ================= expert 层:thread 原语 =================
                # 同一个 kernel 体里直接下探到线程级,没有任何模式切换。
                # 这一段只用来演示「可以混用」,不参与 C 的计算。
                tx = T.get_thread_binding(0)  # tx = threadIdx.x
                part = T.alloc_local((1,), "float32")  # 线程私有
                part[0] = 0
                for jr in T.serial(T.ceildiv(block_N, threads)):
                    j = jr * threads + tx
                    if j < block_N:
                        part[0] += C_s[0, j]
                Partial[bx, by, tx] = part[0]  # 每个线程写自己的槽位,无竞争

                # ================= 回到 developer 层 =================
                T.copy(C_frag, C[by * block_M, bx * block_N])

        return main

    return mixed_level_gemm


def static_only() -> int:
    print("第 03 章示例 2:同一个 kernel 里的两层(仅静态核对)")
    print("-" * 56)
    print("本文件在无 GPU 时只做语法与 API 核对,不能编译/运行 kernel。")
    print()
    print("用到的 API 与层次归属(v0.1.15):")
    for name, layer, where in API_CHECKLIST:
        print(f"  {name:<22} [{layer:<9}] {where}")
    print()
    print("层次分布:developer", sum(1 for _, l, _ in API_CHECKLIST if l == "developer"),
          "个;expert", sum(1 for _, l, _ in API_CHECKLIST if l == "expert"), "个。")
    print("两者出现在同一个 T.Kernel 体内 —— 这就是 composable。")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--static-only", action="store_true", help="只打印 API 核对清单,不编译 kernel")
    parser.add_argument("--M", type=int, default=512)
    parser.add_argument("--N", type=int, default=512)
    parser.add_argument("--K", type=int, default=512)
    args = parser.parse_args()

    if args.static_only:
        return static_only()

    try:
        import torch  # noqa: F401  (仅为提前给出更清楚的错误信息)
        import tilelang  # noqa: F401
    except ImportError as exc:
        print(f"缺少依赖:{exc}", file=sys.stderr)
        print("本示例需要 NVIDIA GPU + CUDA 版 PyTorch + tilelang v0.1.15。", file=sys.stderr)
        print("无 GPU 时请改用:python3 one_kernel_two_levels.py --static-only", file=sys.stderr)
        return 2

    if not torch.cuda.is_available():
        print("检测不到 CUDA 设备。", file=sys.stderr)
        print("无 GPU 时请改用:python3 one_kernel_two_levels.py --static-only", file=sys.stderr)
        return 3

    torch.manual_seed(0)
    M, N, K = args.M, args.N, args.K
    a = torch.randn(M, K, device="cuda", dtype=torch.float16)
    b = torch.randn(K, N, device="cuda", dtype=torch.float16)

    kernel = build_kernel()(M, N, K, threads=128)
    c, partial = kernel(a, b)

    torch.testing.assert_close(c.float(), (a @ b).float(), rtol=1e-2, atol=1e-2)
    print("GEMM 输出与 PyTorch 参考一致。")

    # Partial[0, 0, :] 的和应等于 C 第一个 (block_M, block_N) 分块第一行的和。
    tile_row = c[0, :64].float()
    print(f"分块第一行和 = {tile_row.sum().item():.4f},"
          f"线程部分和之和 = {partial[0, 0, :].sum().item():.4f}"
          "(本示例不把这两者当作断言,仅作为人工核对提示)。")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

← 浏览本章全部代码