跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 07 章从会用到敢用24 分钟14 习题7 自测题

快速上手:安装与第一个 kernel

装好 TileLang 并写出第一个 kernel:安装的三条路径、kernel 的四个部件、如何把生成的 CUDA 源码逐行读回来,以及 JIT、编译缓存与 kernel 复用的行为。

本章进度状态 未读完习题 0 / 14自测 未作答

学习目标

读完本章,你应当能够:

  1. 在三种环境下把 TileLang 装上并验证:pip 安装 wheel、从源码构建、 昇腾 NPU(安装 Ascend 版并让 import tilelang.ascend.language 成功), 并说清每一条路径各自的 Python / CUDA / CANN / libc 要求。
  2. 从零写出第一个 TileLang kernel(逐元素向量加法),并逐行讲清 T.Kernel 的网格、T.alloc_* 的 buffer、T.copy 的搬运、T.Parallel 的 并行域分别在做什么。
  3. 用 kernel.get_kernel_source() 把生成的 CUDA 打印出来,并对照 Python 讲清 三段关系:网格 → blockIdx / __launch_bounds__;T.Parallel → 线程 + 向量化;T.copy → 128-bit 访存(或 TMA / cp.async)。
  4. 说清 @tilelang.jit 的 lazy / eager 两种用法,以及"编译一次、多次复用"在 哪一步发生。
  5. 说清编译缓存的两级结构与 key 的构成,能回答"我改了参数 / 改了 pass / 换了 版本,为什么不命中"。
  6. 写出昇腾侧同一个 C = A + B,并指出它与 CUDA 版的三处结构性差异。
  7. 拿到一条安装或运行报错时,按本章第 8 节的清单定位到具体原因。

前置知识

  • 第 4 章「架构总览与一次编译的全流程」:知道 Python → IRModule → pass → 设备源码 → host wrapper → 可调用对象 这条链,知道后端分岔点在哪。
  • 第 6 章「芯片导论:NV GPU 与昇腾 NPU 的抽象对照」:知道"线程 / warp / SM"与 "core / Cube / Vector / UB / L1"这些词指的是什么。
  • 会写 Python;用过 pip 或 conda 装过包;见过装饰器(@decorator)。
  • 不需要写过 CUDA,也不需要读过 TVM。本章出现的每个硬件词第一次出现时都会 先用一句话解释。

本章只回答一个问题:"我要写第一个能跑的 TileLang 程序,从哪一步开始?" 语言细节在第 8 章,原语语义在第 9 章,调优在第 11 章,昇腾深入在第 19、25 章。


1. 问题的由来

1.1 为什么第一个动手章要从「装 + 跑」开始

TileLang 是一门嵌入式在 Python 里的 DSL:你写的仍然是 Python 函数,但函数体 里的 T.Kernel / T.copy / T.gemm 不是普通函数调用,而是会被解释成一段 IR、再被编译成设备代码。这件事意味着两件事:

  • "能导入"不等于"能编译","能编译"不等于"能跑"。环境(Python 版本、libc、 CUDA 工具链、CANN)错了,失败会发生在三个阶段中的任何一个,报错信息各不相同。
  • 你写的东西和真正执行的东西不是一回事。所以第一个 kernel 的重点不是"写得多 漂亮",而是写完以后能把生成的代码调出来看。

这一章的结构就是照着这两个事实排的:先把环境钉死(第 3 节),再写一个最小 kernel (第 4 节),马上把生成代码调出来对照(第 5 节),最后讲清"编译一次、复用多次" (第 6 节)。

1.2 本章的版本纪律

全书锁定 TileLang v0.1.15(commit a35f8ddf45eba16c21211ec8822d56ce5363036f)与 TileLang-Ascend TileLang-ascend-v0.1.2.000-release(见 版本锁定)。本章严格遵守四条:

  1. 安装说明只抄 v0.1.15 仓库里的 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md(与 corpus 里的站点源文件 TileLang 官方文档 · 资料清单 是同一份文档),昇腾部分只抄 _vendor/tilelang-v0.1.15/tilelang/ascend/README.md 与 _vendor/tilelang-ascend-v0.1.2.000/README.md、install_ascend.sh。
  2. 不把 main 当 v0.1.15。corpus 里那份主仓库快照比 v0.1.15 多一个提交, 凡涉及"有没有某功能"的判断都靠 grep,不靠印象。
  3. 不宣称未验证的运行结果。写这一章的本机是 macOS(Apple Silicon), 没有 NVIDIA GPU,没有昇腾 NPU,也没有安装 tilelang。因此:
    • 需要加速器的示例一律标注 仅静态核对,并给出真机验证步骤;
    • 需要打印的生成代码,凡不是本机产生的,都注明它来自哪一份锁定版本文件;
    • 不出现任何性能数字(本章一个都没有)。
  4. 不写没有出处的路数。pip install、源码构建、昇腾安装的每条命令都能在 锁定版本文件里找到对应段落(见 本章引用)。

顺手说清一件事:v0.1.15 是 tag。它对应的 VERSION 文件内容是 0.1.15, 而你用 pip 装出来的版本字符串可能是 0.1.15 也可能带本地后缀(+cu...git<hash>), 取决于它是从 PyPI 装的还是本地源码构建的(Installation.md:365-376 讲了这套后缀是 怎么来的)。要看内容是否一致,别只看版本字符串。


2. 概念与模型:一个 kernel 的四个部件

先不给代码,先给一张"看代码时的坐标系"。任何一个 TileLang kernel,剥到最里面, 都是四样东西(对照图见 图:02-first-kernel-anatomy):

部件 你写的 它回答的问题 会被编译器兑现成
网格(grid) with T.Kernel(...) as bx: "这段计算被切成多少块并行执行?" CUDA 的 grid,bx → blockIdx.x;昇腾的核编号
buffer T.alloc_shared / T.alloc_fragment "数据放在哪一级存储上?" shared / register 数组;昇腾的 UB / L1 / L0*
搬运(copy) T.copy(src, dst) "一个 tile 怎么从一级存储到另一级?" LDG/STG、cp.async、TMA;昇腾的 MTE
并行域(parallel) for i in T.Parallel(n): "这 n 个元素各做一次,具体谁做?" 线程映射 + 向量化

关键在这里:前两列是人写的,后两列是编译器补的。你只写"要对 256 个元素各做一次 加法",至于用 256 个线程还是 32 个线程各做 8 个、要不要凑成 128-bit 访存,编译器自己 决定。这就是 TileLang 说的"block-level,不是 thread-level" (_vendor/tilelang-v0.1.15/docs/deeplearning_operators/elementwise.md:41 的原文是 "the code inside T.Kernel operates at the block level, not the thread level")。

想要 thread-level 也能写(T.get_thread_binding(),属 expert 层),但那是第 8 章之后 的话题。本章只用 block-level。


3. 安装:三条路径

安装的三条路径你的机器上有什么?有 host CUDA 工具链nvcc 在 PATH没有 host CUDA但能出网昇腾 NPU + CANN路径①:pip 安装 wheelpip install tilelang路径②:源码构建git clone --recursive + pip install . -v路径③:昇腾Ascend 版 wheel / build_wheel_ascend.sh / install_ascend.shimport tilelang 成功import tilelang.ascend.language 成功
按机器上有没有 host CUDA、是不是昇腾,在 wheel / 源码构建 / 昇腾三条路里选一条。
  • 两条硬门槛:Python ≥ 3.10、glibc ≥ 2.28。
  • 路径① 需要 host CUDA ≥ 10.0,或 pip 提供的 CUDA ≥ 13.0。
  • 路径③ 需要 CANN ≥ 8.3.RC1、torch-npu ≥ 2.6.0.RC1(独立仓库路线另需 ASCEND_HOME_PATH)。
  • 要复现本书:把版本钉住,不要用没钉版本的 pip install tilelang。

3.1 环境要求(v0.1.15 的事实)

先看 NVIDIA/CUDA 侧。Installation.md:3-10 列出的 pip 安装前置条件是:

项 要求
glibc 2.28(Ubuntu 20.04 或更新)
Python >= 3.10
NVIDIA CUDA >= 10.0(用 host 上的 CUDA 工具链),或使用 pip 提供的 CUDA 工具链(>= 13.0)
AMD ROCm host 上有一套提供 hipcc 的 ROCm(Linux x86_64 的 PyPI wheel 同时含 CUDA + ROCm)

源码构建的前置条件略有不同(Installation.md:70-76):

项 要求
操作系统 Linux 或 Windows(macOS 不在列;macOS 上默认只编 Metal 后端)
Python >= 3.10
NVIDIA CUDA >= 10.0(host 工具链),或 pip 提供的 CUDA 工具链(>= 13.0)

只有两条硬门槛会卡住大多数人:Python 版本和工具链。先跑一遍体检脚本 (本章 code/06-env-check/)比事后看着报错猜要快得多。

3.2 路径①:pip 安装 wheel(最省事)

# 1) 装
pip install tilelang

# 2) 验证
python -c "import tilelang; print(tilelang.__version__)"

Installation.md:15 给的就是这一行。也可以从 Release Page 下载预先编好的 wheel 再 pip install <wheel 文件>(Installation.md:19-21),或直接从 GitHub 源码装 (Installation.md:25-27,pip install git+https://github.com/tile-ai/tilelang.git —— 注意它装的是那个分支的源码,不是你本地的 checkout)。

要复现本书这一版,把版本号钉住:用与 _vendor/tilelang-v0.1.15/VERSION 一致的 版本字符串安装(例如 pip install "tilelang==0.1.15"),或从 Release Page 取版本 字符串为 0.1.15 的那个 wheel。不要用没钉版本的 pip install tilelang 去复现 本章的结论——那样装到的是别的版本,后面的行号和缓存 key 都可能不一样。

还有一个 nightly 渠道(Installation.md:332-341):

pip install tilelang -f https://tile-ai.github.io/whl/nightly

它给的是滚动构建、可能包含尚未发布的改动,稳定性低于正式发布;本书的所有结论都 不基于 nightly。要不要用它,取决于你是不是在追某个刚合入的修复。

3.3 路径②:从源码构建(要改编译器,或者要自己控制编译选项)

什么时候必须走这条?三种情况:你要改 TileLang 本体的 C++ 或 Python;你要 指定编译选项(比如关掉 CUDA、只编昇腾);host 上没有 CUDA 工具链而你想用 pip 提供的工具链。

最直接的写法(Installation.md:93-99):

git clone --recursive https://github.com/tile-ai/tilelang.git
cd tilelang
pip install . -v

--recursive 不能省:TileLang 依赖一份定制过的 TVM,它作为 submodule 放在 3rdparty/(Installation.md:88-91 的 Note)。-v 打开详细输出,编译要几分钟。

如果你没有 host CUDA,用 pip 提供的 CUDA 包(Installation.md:101-124):

git clone --recursive https://github.com/tile-ai/tilelang.git
cd tilelang
pip install -r requirements-dev.txt
pip install "nvidia-cuda-nvcc>=13" "nvidia-cuda-cccl>=13" "nvidia-cuda-nvrtc>=13"
pip install . -v --no-build-isolation

开发者的推荐做法是手动 cmake + PYTHONPATH(Installation.md:133-158):

mkdir -p build
cd build
cmake .. -DUSE_CUDA=ON            # 用 ninja 的话:cmake .. -DUSE_CUDA=ON -G Ninja
make -j                           # 或者 ninja

export PYTHONPATH=/path/to/tilelang:$PYTHONPATH
python -c "import tilelang; print(tilelang.__version__)"

几个和本章有关的 CMake 开关(Installation.md:160-166):

  • -DUSE_CUDA=ON|OFF:编 CUDA 后端;macOS 上默认关。
  • -DUSE_ASCEND=ON|OFF:编昇腾后端;Linux 上默认开。
  • -DUSE_METAL=ON:Darwin 上默认开。
  • -DUSE_LLVM:CPU codegen 用的 LLVM,默认关,需要 LLVM 15+。

编译缓存放在 ~/.tilelang/cache,并且如果系统里有 ccache 会自动使用 (Installation.md:389-391)。这一条在第 6 节还会用到。

3.4 路径③:昇腾侧(CANN + Ascend 版 tilelang)

昇腾侧有两个来源,先分清再动手:

主干 tilelang(v0.1.15) 独立仓库 tile-ai/tilelang-ascend
主要后端 CUDA(+ ROCm/Metal/CPU/WebGPU),tilelang/ascend/ 是 Ascend 950 后端 昇腾为主
文档 _vendor/tilelang-v0.1.15/tilelang/ascend/README.md _vendor/tilelang-ascend-v0.1.2.000/README.md
本书角色 第 7、25 章用它讲昇腾后端 第 19 章用它讲适配器路线

主干路线(_vendor/tilelang-v0.1.15/tilelang/ascend/README.md:10-51)先说清前置条件:

  • 一套可用的 Ascend 驱动 + CANN,其中要能找到 bisheng(编译器)、支持 CCE 的 ld.lld、以及昇腾运行时库;
  • 兼容的 PyTorch 与 torch_npu,并且 import torch_npu 后 torch.npu.is_available() 返回 True;
  • 通用 Python / 原生构建前置条件(与 3.1 节同)。

bisheng 要在 PATH 或 BISHENG_HOME/bin 里;昇腾运行时库要能被动态链接器找到。 后端对 Ascend 950 默认 dav-3510,所以这个目标上不设 ASCEND_NPU_ARCH 也行 (资料库索引)。从源码构建(资料库索引):

git submodule update --init --recursive
USE_ASCEND=ON USE_CUDA=OFF python -m pip install -v .

# 可编辑开发安装:
python -m pip install -r requirements-dev.txt
USE_ASCEND=ON USE_CUDA=OFF python -m pip install -e . -v --no-build-isolation

USE_CUDA=OFF 让 CUDA 工具链不再是必需项。注意这条命令里的 USE_ASCEND=ON 是 环境变量,与 3.3 节的 -DUSE_ASCEND=ON 是同一件事在两条工具链上的两种写法。

独立仓库路线(_vendor/tilelang-ascend-v0.1.2.000/README.md:76-134)提供三种装法, 并把前置条件写得更具体:

  • CANN 至少 8.3.RC1,torch-npu 至少 2.6.0.RC1;先 source CANN 的 ascend-toolkit/set_env.sh。
  • 方法 1(推荐):装现成 wheel。设置 ASCEND_HOME_PATH 后 pip install tilelang-*.whl。
  • 方法 2:从源码打 wheel。
    export ASCEND_HOME_PATH=/usr/local/Ascend/ascend-toolkit/latest
    ./build_wheel_ascend.sh [--enable-llvm]
    pip install dist/tilelang-*.whl
    
    build_wheel_ascend.sh:29-35 会先检查 ASCEND_HOME_PATH,没设就直接退出。
  • 方法 3:bash install_ascend.sh,然后 source set_env.sh。 install_ascend.sh:37-45 第一件事就是检查 python3 --version 是否 >= 3.10; 脚本自己会 git submodule update、打 TVM 补丁、cmake 并 make (install_ascend.sh:171-243)。

set_env.sh(_vendor/tilelang-ascend-v0.1.2.000/set_env.sh:1-20)做两件事:把仓库根 加进 PYTHONPATH,并设 ACL_OP_INIT_MODE=1(避免在 torch_npu 场景下导入 tvm)。

两个仓库都声明自己是针对特定昇腾代际的:主干后端是 Ascend 950;独立仓库说 "specifically tested and validated on A2 and A3"(资料库索引)。 装之前先确认你的机器在哪一档。


4. 第一个 kernel:从零到能跑

本章 code/01-first-kernel/vecadd_tilelang.py 是这段代码的完整、可复制版本。

一个 kernel 的四个部件部件你写的(Python)它回答的问题编译器兑现成① 网格with T.Kernel(...) as bx这段计算被切成多少块并行执行CUDA grid / blockIdx.x;昇腾的核编号② bufferT.alloc_shared / T.alloc_fragment数据放在哪一级存储上shared / 寄存器数组;昇腾 UB / L1 / L0*③ 搬运T.copy(src, dst)一个 tile 怎么从一级存储到另一级LDG/STG、cp.async、TMA;昇腾 MTE④ 并行域for i in T.Parallel(n):这 n 个元素各做一次,具体谁做线程映射 + 向量化
前两列是人写的、后两列是编译器补的:T.Parallel 是块级意图,线程映射与向量化由编译器决定。
  • 官方原文的表述是:T.Kernel 之内是「block level, not thread level」。
  • 想要 thread-level 也可以,用 T.get_thread_binding(),属 expert 层。
  • 同一个块里的 256 个元素,可能由 32 个线程各做 8 个,也可能由 256 个线程各做 1 个。

4.1 版本 A:最小可读的 kernel

选 C = A + B(逐元素向量加法)而不是 GEMM,是因为它把 kernel 的四个部件全占了、 又不引人任何复杂概念。11 行:

import tilelang
import tilelang.language as T


def vector_add_direct(N, block_N=256, threads=256, dtype="float16"):
    @T.prim_func
    def main(
        A: T.Tensor((N,), dtype),
        B: T.Tensor((N,), dtype),
        C: T.Tensor((N,), dtype),
    ):
        with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx:
            for i in T.Parallel(block_N):
                C[bx * block_N + i] = A[bx * block_N + i] + B[bx * block_N + i]

    return main

它和官方教程 docs/deeplearning_operators/elementwise.md 的第一段示例同构,只是把 一维长度写成 N、把 threads 抽成参数。

4.2 逐行解释

@T.prim_func:把一个 Python 函数标记成"要编译成 TIR 的 kernel"。函数体的 参数注解 A: T.Tensor((N,), dtype) 不是类型提示,而是缓冲区声明——告诉编译器 "A 是一个长度 N、元素类型 dtype 的一维张量"。

with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx::这是网格。 T.Kernel 在锁定版本里的 docstring 原文是 "Construct a kernel launch frame: a grid of tile programs",并且强调 "This is the target-neutral launch: the part every backend shares"(_vendor/tilelang-v0.1.15/tilelang/language/kernel.py:431-435)。 两点要读懂:

  • 网格的尺寸是 ceil(N / block_N)——这个 kernel 被切成了多少个 tile 程序;
  • bx 是当前 tile 程序的编号(从 0 到 ceil(N/block_N)-1)。在 CUDA 上它落到 blockIdx.x;在昇腾上一维核网格落到 core 编号。它是"第几块",不是"第几个线程"。

threads=256 是 CUDA 方言多出来的参数(tilelang/cuda/language/kernel.py:13-36), 只是"每块有多少线程"的一条提示 / 上限,不是"总线程数"。默认 128。

for i in T.Parallel(block_N)::这是并行域。T.Parallel 的 docstring 说它是 "Tools to construct nested parallel for loop … used to create element-wise tensor expression"(tilelang/language/loop.py:13-20)。它的语义是:"这块的 block_N 个元素 各做一次操作";i 不是线程号,而是这块里的元素下标。谁来执行、每个线程做 几个、要不要向量化,都在 LayoutInference 里被推导出来 (loop.py:24-67 解释了布局注解的约束与理由)。

C[bx*block_N + i] = A[...] + B[...]:就是逐元素加法。bx*block_N + i 把 "第几块"和"块内第几个"拼成全局下标。边界不用你管:N 不能整除 block_N 时, 编译器会自己补判断(见第 5.3 节的生成结果)。

4.3 版本 B:加上显式 buffer 与 T.copy

版本 A 直接把全局内存读进来再写回去。更常见的写法是先把 tile 搬进片上 buffer:

def vector_add_buffered(N, block_N=256, threads=256, dtype="float16"):
    @T.prim_func
    def main(A: T.Tensor((N,), dtype), B: T.Tensor((N,), dtype), C: T.Tensor((N,), dtype)):
        with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx:
            A_local = T.alloc_fragment((block_N,), dtype)
            B_local = T.alloc_fragment((block_N,), dtype)
            C_local = T.alloc_fragment((block_N,), dtype)

            T.copy(A[bx * block_N], A_local)
            T.copy(B[bx * block_N], B_local)

            for i in T.Parallel(block_N):
                C_local[i] = A_local[i] + B_local[i]

            T.copy(C_local, C[bx * block_N])

    return main

新增的是buffer 和搬运两个部件:

  • T.alloc_fragment(shape, dtype):分配一块寄存器级别的 buffer(默认 scope="local.fragment",tilelang/language/allocate.py:81)。另一个常用的是 T.alloc_shared(...),默认落在片上共享存储(allocate.py:49)。选哪个是语义, 不是性能开关——第 8 章会展开。
  • T.copy(src, dst):tile 级搬运。它的 docstring 明确说,T.copy 只是构造一个 tile 级操作,"low-level loop construction and any scope-specific decisions happen during lowering"(tilelang/language/copy_op.py:59-113)——也就是说, 你写的是"搬这块",生成的是 cp.async / TMA / 向量化 LDG-STG 之类的具体东西。 注意 A[bx * block_N] 这种写法:源只给首地址,形状从目标 A_local 推出来 (docstring 里的 "Syntactic Sugar" 一段,copy_op.py:96-99)。

版本 A 和 B 的结果一模一样,区别只在"数据先不先落到片上"。对照读它们,是理解 T.copy 与 T.Parallel 分工的最快方式(官方 elementwise.md 也是按这个顺序讲的)。

4.4 二维版本与官方示例

把一维换成二维几乎不用改结构,只多一维网格:

with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=threads) as (bx, by):
    for local_y, local_x in T.Parallel(block_M, block_N):
        y = by * block_M + local_y
        x = bx * block_N + local_x
        C[y, x] = A[y, x] + B[y, x]

这段取自 elementwise.md:57-87(官方 2-D 向量加示例)。有一个很容易踩的坑: T.Kernel 的第一个参数对应 bx(blockIdx.x,沿 N 方向),第二个对应 by (blockIdx.y,沿 M 方向)——参数顺序是"N 在前、M 在后",和第 4 章讲 GEMM 时 的约定一致。写反了不会报错,只会算错位置。

锁定版本仓库里还有一个"编译 + 校验 + 打印源码"的完整示例: _vendor/tilelang-v0.1.15/examples/elementwise/example_elementwise_add.py。它用的是 @tilelang.jit 的 eager 写法(in_dtype / out_dtype 都当参数传),并且 main() 里自带 torch.testing.assert_close——这是把"能不能跑对"写进示例里的好习惯。

4.5 再进一步:官方 quickstart.py 的 GEMM + ReLU

当你把向量加法读顺了,下一步就是 _vendor/tilelang-v0.1.15/examples/quickstart.py 里的 GEMM + ReLU(第 4 章正文也引过 它)。它只比版本 B 多四件事:

T.clear(C_local)                                    # 累加器清零
for ko in T.Pipelined(T.ceildiv(K, block_K), num_stages=3):   # 软件流水 + 多缓冲
    T.copy(A[by * block_M, ko * block_K], A_shared)
    T.copy(B[ko * block_K, bx * block_N], B_shared)
    T.gemm(A_shared, B_shared, C_local)             # tile 级矩阵乘
for i, j in T.Parallel(block_M, block_N):           # 逐元素 ReLU,直接复用 T.Parallel
    C_local[i, j] = T.max(C_local[i, j], 0)
T.copy(C_local, C[by * block_M, bx * block_N])

T.Pipelined(tilelang/language/loop.py:112)、T.gemm (tilelang/language/gemm_op.py:160)、T.clear(tilelang/language/fill_op.py:48) 的细节分别属于第 9、11、14 章。这里你只需要注意到:T.Parallel 既能做内存搬运的 循环,也能做这样的逐元素 epilogue(后处理),这是它最通用的用法。


5. 读懂生成的源码

5.1 把它打印出来

拿到 kernel 对象后,三种方式看源码(tilelang/jit/kernel.py:532-600):

src = kernel.get_kernel_source()   # 返回字符串
kernel.show_source("kernel")       # 直接打印到 stdout;还支持 "host" / "both"
kernel.export_sources(kernel_path="k.cu", host_path="host.c")   # 写到文件

另一种等价写法:给 @tilelang.jit(debug_root_path="/tmp/tl-debug"),编译时会自动把 tilelang_jit_kernel_<name>.c 与 tilelang_jit_program_<name>.py(后者的 prim_func.script() 文本)写下来(tilelang/jit/__init__.py:466-505)。

5.2 Python 与生成代码怎么对照

拿官方的向量加示例(elementwise_add(1024, threads=256, dtype=bfloat16))当例子。 源码在 elementwise.md:155-172,逐条对照如下(同一张表在本章 code/02-inspect-source/ 里是可运行的文本):

Python 生成的 CUDA 为什么
with T.Kernel(T.ceildiv(N, threads), threads=threads) as (b_x): extern "C" __global__ void __launch_bounds__(256) main_kernel(...) 网格大小变成 gridDim.x;threads=256 变成 __launch_bounds__(256);b_x 变成 blockIdx.x
for i in T.Parallel(threads): if (((int)threadIdx.x) < 32) { ... } T.Parallel 是块级并行域;编译器发现每线程能做 8 个元素,于是只用 32 个线程(虽然仍然 launch 了 256 个)
C[b_x*threads+i] = A[...] + B[...] uint4 v_ = *(uint4*)(A + ...); ... ((nv_bfloat162*)...)->x = ... 逐元素代码被向量化成 128-bit 访存(uint4 = 16 字节 = 8 个 bfloat16)与成对加法
(Python 里根本没有 threadIdx.x) ((int)threadIdx.x) * 8 TileLang自动插入线程索引;stride 就是向量宽度 8。循环变量 i 在生成代码里消失了

这张表的最后一行最值得盯住:你从没写过 threadIdx.x,它却出现在生成代码里。 这正是"block-level"的含义——T.Parallel 把你的意图翻译成线程映射,中间还顺手做了 向量化。你想验证"我的 kernel 有没有被向量化",看这一处就够了:出现 uint4 / float4 / __half2 这类类型,说明命中了向量化。

5.3 两种形态:向量化版 vs 边界判断版

同一份 Python,N 能不能整除 threads 会让生成代码长出两种形态。官方文档给了对比 (elementwise.md:104-130、:180-211):

能整除、且编译器选择向量化时(elementwise.md:155-172):32 个线程各处理 8 个 元素,uint4 一次搬 16 字节。

不能整除时(elementwise.md:120-130,示例取 N=2047):

extern "C" __global__ void __launch_bounds__(256) main_kernel(bfloat16_t* __restrict__ A, bfloat16_t* __restrict__ B, bfloat16_t* __restrict__ C) {
  #pragma unroll
  for (int i = 0; i < 8; ++i) {
    if (((i * 256) + ((int)threadIdx.x)) < 2047) {           // ← 编译器补的边界判断
      C[((i * 256) + ((int)threadIdx.x))] = (A[((i * 256) + ((int)threadIdx.x))] + B[((i * 256) + ((int)threadIdx.x))]);
    }
  }
}

文档在 elementwise.md:133 的评语是:边界判断会挡住向量化——除了尾部那一组, 其余线程本来都能跑更优的代码。这就是"为什么 tile 尺寸要和 shape 对齐"的第一课: 不是为了好看,是为了让编译器敢向量化。


6. JIT、编译缓存与 kernel 复用

@tilelang.jit 一次调用的生命周期与缓存命中未命中命中未命中调用 build_add(N, block_N, threads)JITImpl.__call__算 cache keysha256(func.script + out_idx + args_repr + target + backend + pass_configs + version)memory cache 命中?直接返回旧 JITKerneldisk cache 命中?~/.tilelang/cache加载已编译产物走完整编译frontend → passes → codegen → host wrapper写回 disk cachekernel(a, b)JITKernel.__call__ → adapter.func在 GPU / NPU 上执行
先算 cache key,再查内存级与磁盘级缓存;只有未命中才走完整编译,并把结果写回磁盘。
  • 只改注释或空白 → func.script() 不变 → 同一 key;改 tile 参数或 pass 开关 → 新 key。
  • lazy 模式返回 PrimFunc(拿到 JITKernel 再手动调用);eager 模式直接返回输出张量。
  • 清了缓存还不生效,先怀疑缓存:TILELANG_DISABLE_CACHE=1 是最快的排除法。

6.1 两个入口

锁定版本里编译入口有两个:

  • 模块级 tilelang.compile(prim_func, out_idx=-1, target="cuda"):适合"我已经有一个 构造好的 PrimFunc"。返回一个 JITKernel(tilelang/jit/__init__.py:94)。
  • 装饰器 @tilelang.jit:把函数包成 JITImpl(tilelang/jit/__init__.py:588), 调用它时再决定"编译"还是"直接执行"。

第 4 节的两个版本都能配 tilelang.compile 用;本章 code/01-first-kernel/ 就是这种 写法。

6.2 @tilelang.jit 的两种模式

JITImpl 的 docstring 写得很清楚(tilelang/jit/__init__.py:271-330):模式自动 推断,看修饰的函数是返回 PrimFunc(lazy)还是用 DSL 直接写 body(eager)。

lazy 模式——函数自己返回 @T.prim_func:

@tilelang.jit(out_idx=-1, target="cuda")
def build_add(N, block_N=256, threads=256, dtype="float16"):
    @T.prim_func
    def main(A: T.Tensor((N,), dtype), B: T.Tensor((N,), dtype), C: T.Tensor((N,), dtype)):
        with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx:
            for i in T.Parallel(block_N):
                C[bx * block_N + i] = A[bx * block_N + i] + B[bx * block_N + i]
    return main

kernel = build_add(1 << 20)   # 返回的是一个 JITKernel
c = kernel(a, b)              # 再手动调用

eager 模式——函数用 DSL 直接写、返回输出张量:

@tilelang.jit
def add_eager(A, B, block_N=256, threads=256):
    N = T.const("N")              # 形状声明成符号
    dtype = "float16"
    A: T.Tensor((N,), dtype)
    B: T.Tensor((N,), dtype)
    C = T.empty((N,), dtype)
    with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx:
        for i in T.Parallel(block_N):
            C[bx * block_N + i] = A[bx * block_N + i] + B[bx * block_N + i]
    return C

c = add_eager(a, b)           # 编译并立刻执行

两种模式在锁定版本里都有真实使用:官方 examples/elementwise/example_elementwise_add.py 与 examples/quickstart.py 都是 eager(T.const + 参数注解),而 examples/ascend/example_vecadd.py 是 lazy(返回 @T.prim_func)。要复用、要看 源码、要接 autotuner,用 lazy;要"像调用普通函数",用 eager。

6.3 编译缓存:两级,一个 key

编译缓存分两级(_vendor/tilelang-v0.1.15/tilelang/cache/kernel_cache.py):

层 位置 命中后
内存级 KernelCache._memory_cache(进程内字典) 直接返回 JITKernel 对象,磁盘都不碰
磁盘级 env.TILELANG_CACHE_DIR(默认 ~/.tilelang/cache) 加载已编译产物(源码 / so / cubin 等)

key 由 KernelCache._generate_key() 生成(kernel_cache.py:243-284),内容是 sha256(json({...})),进 key 的字段有:

func.script(show_meta=True) 的 sha256   # 你这份 kernel 的 TIR 文本
out_idx                                 # 输出在参数里的位置
args_repr                               # 编译参数(N、block_N、threads …)
target / target_host                    # "cuda" / "ascend" / ...
execution_backend                       # "tvm_ffi" / "cython" / "nvrtc" / ...
pass_configs / compile_flags
version / platform                      # 来自 _get_base_key(),macOS 还含 torch 版本

从这张表可以直接推出第 8 节里几个"为什么没生效"的答案:

  • 改了注释或空白 → func.script() 不变 → 同一 key,命中。
  • 改了 tile 参数(如 block_N)→ args_repr 变 → 新 key,不命中。
  • 改了 pass 开关 → pass_configs 变 → 新 key,不命中。
  • 换了 tilelang 版本 → version 变 → 全部不命中(这是好事,避免拿到旧产物)。

所以调试编译器时,第一件该怀疑的事是缓存:pass_configs 变了 key 确实会变, 但如果你改的是 C++ pass 本体,而 TILELANG_KERNEL_CACHE_USE_LIB_STAMP 没打开, key 里就不含库内容哈希(env.py:375-377 与 kernel_cache.py:141-142),你可能 命中一条旧产物。此时用下面任一方式强制重编:

import tilelang
tilelang.disable_cache()      # 关掉缓存(env.py:559)
...
tilelang.enable_cache()       # 用完记得开回来(env.py:558)

或环境变量:

TILELANG_DISABLE_CACHE=1 python my_kernel.py     # env.py:372-374
TILELANG_CACHE_DIR=/tmp/tl-cache python my_kernel.py   # env.py:368,换个缓存目录

6.4 复用与"探查"

缓存带来的直接好处是复用:同一个 (kernel, 参数) 组合,第一次编译、之后是命中。 在 JITKernel 上还能:

  • kernel(a, b) 直接调用(tilelang/jit/kernel.py:206-220,__call__ 转发到 adapter.func);
  • kernel.get_profiler().do_bench() 做延迟/吞吐测量(kernel.py:516);
  • get_kernel_source() / get_host_source() 看两份产物;
  • export_sources(...) 落盘。

一个必须记牢的边界:主干 v0.1.15 的 tilelang/cache 里没有顶层 clear_cache()。tilelang.cache 模块只导出了一个 cached() 入口 (tilelang/cache/__init__.py:75),清缓存的能力是 KernelCache 这个类上的 实例方法(kernel_cache.py:455)。而独立仓库 tilelang-ascend 有 tilelang.cache.clear_cache()(_vendor/tilelang-ascend-v0.1.2.000/tilelang/cache/__init__.py:70), 它的示例里也是这么调的。照抄独立仓库的示例到主干上会在这一行报 AttributeError——这类"两个仓库同名不同面"的坑,第 19 章还会遇到。本章 code/04-api-surface-check/ 把这条差异做成了可复现的检查。


7. 昇腾侧的第一个 kernel

7.1 主干写法:同一个 C = A + B

完整代码见 code/05-ascend-first-kernel/vecadd_ascend.py,结构与官方 examples/ascend/example_vecadd.py:11-44 同构。核心是:

import tilelang
import tilelang.ascend.language as T      # ← 方言换成昇腾

@T.prim_func
def main(A: T.Tensor((N,), "float32"), B: T.Tensor((N,), "float32"), C: T.Tensor((N,), "float32")):
    with T.Kernel(num_blocks) as bx:      # ← 一维核网格,没有 threads=
        a = T.alloc_shared((tile,), "float32")    # ← alloc_shared = UB(统一缓冲)
        b = T.alloc_shared((tile,), "float32")
        c = T.alloc_shared((tile,), "float32")
        for step in T.Pipelined(iterations, num_stages=2):
            begin = (step * num_blocks + bx) * tile
            T.copy(A[begin : begin + tile], a)
            T.copy(B[begin : begin + tile], b)
            with T.SimtVF(threads=threads):           # ← 线程域单独声明
                for i in T.Parallel(tile):
                    c[i] = a[i] + b[i]
            T.copy(c, C[begin : begin + tile])

三处结构性差异,每一处都不是语法糖:

  1. 方言:import tilelang.ascend.language as T。主干的 tilelang/language/__init__.py 是把 CUDA 方言再导出的 (tilelang/language/__init__.py:11-12 的 from tilelang.cuda.language import *), 所以昇腾必须在 import 那一行换名字。混用两个 T 会拿到签名不同的 T.Kernel。
  2. 网格是一维核网格:昇腾的 T.Kernel(*blocks) 没有 threads= 参数, 而且明确只接受一个网格维度:kernel.py:139-174 的 docstring 写着 "a 1-D grid of AI cores",多给一维会直接 raise ValueError。线程域改由 with T.SimtVF(threads=...) 在 kernel 内部单独声明 (tilelang/ascend/language/frame.py:107)。也就是说:CUDA 上"网格 + 线程"是 一次 launch 里的两层,昇腾上"核网格 + 线程域"是两个独立的东西。
  3. 片上存储的起点不同:CUDA 上 T.alloc_shared 就是 shared memory;昇腾上 T.alloc_shared 落到 UB(Unified Buffer,统一缓冲),而 T.alloc_l1 / alloc_l0a / alloc_l0b / alloc_l0c 才是 L1 与 Cube 的各级缓冲 (tilelang/ascend/language/allocate.py:15,26,37,46,55)。同一个函数名在两台机器 上指向不同物理存储——这正是第 6 章讲的"抽象是共享的、物理是各自的"。

昇腾的 T.Kernel docstring 里甚至自带一个最小例子(ascend/language/kernel.py:165-168):

with T.Kernel(NUM_CORES) as bx:
    with T.SimtVF(threads=128):
        for i in T.Parallel(128):
            out[bx * 128 + i] = x[bx * 128 + i] * 2.0

比本章的版本还短,值得对着读。

一处有意的简化要说明:官方 examples/ascend/example_vecadd.py 还在每个 UB buffer 上加了一行 T.annotate_buffer_versions({...})。本章的示例没写它,因为按该 API 的 docstring,num_stages > 1 时多缓冲份数是由自动调度器算出来的,这条注解只是 「锁定份数 / 控制索引模式」的可选手段( _vendor/tilelang-v0.1.15/tilelang/ascend/language/annotations.py:19-38)。 这是本章唯一一处「应当等价、但作者未在 NPU 上验证」的取舍,第 11、19、23 章会展开。

7.2 与独立仓库方言的差异(一句话版)

_vendor/tilelang-ascend-v0.1.2.000 的示例长得不一样:

with T.Kernel(m_num * n_num, is_npu=True) as (cid, vid):   # ← 独立仓库方言
    a_ub = T.alloc_ub((...), dtype)                        # ← alloc_ub
    with T.Scope("V"):
        T.barrier_all()
        T.tile.add(c_ub, a_ub, b_ub)

对照点(本项已做成可复现检查,见 code/04-api-surface-check/):

主干 tilelang.ascend.language 独立仓库方言
网格 T.Kernel(n),1-D 核网格 T.Kernel(n, is_npu=True) as (cid, vid)
UB 分配 T.alloc_shared T.alloc_ub
线程域 T.SimtVF / T.SimdVF frame T.Scope("V") + T.barrier_all
向量运算 T.Parallel / T.simd.* T.tile.add 等 T.tile.* 家族
清缓存 顶层没有 clear_cache tilelang.cache.clear_cache()

这两套不是同义词,不能互相抄。 本章第 7 节用主干写法,因为本章的其它内容都基于 v0.1.15。要把同一段代码搬到独立仓库的方言上,以及理解两套方言背后的两条技术路线 (Ascend C & PTO / AscendNPU IR),见第 19、25 章。


8. 常见安装 / 运行错误与排查

按"先环境、再导入、再编译、再运行、再数值"的顺序排查。每条都给出看哪一行。

① 装不上 / 装完导入失败

现象 最可能的原因 怎么办
pip install 直接失败 Python < 3.10(Installation.md:8) 换 3.10+;python -VV 确认是哪个解释器在跑
安装报 libc 相关错 glibc < 2.28(Installation.md:7) 升级到 Ubuntu 20.04+;用 ldd --version 看
源码构建找不到 CUDA 没装 host CUDA 且没用 pip 工具链 走 Installation.md:101-124 的 pip CUDA 路线,或 -DUSE_CUDA=OFF(同时也要关掉其它后端)
--recursive 忘了加,编译报缺 TVM submodule 没拉全 git submodule update --init --recursive
import tilelang 报找不到 libtilelang.so 走了 PYTHONPATH 路线但没 build,或 PYTHONPATH 指错 Installation.md:135-158:先在 build/ 里 cmake + make,再设 PYTHONPATH;开发模式下日志会提示 "Loading tilelang libs from dev root"

② 导入成功但一编译就报错

现象 最可能的原因 怎么办
报 target 相关错 / 找不到目标 目标不匹配(例如在 macOS 上编 CUDA) 显式给 target=;或先看 tilelang.__version__ 里嵌的 SDK 是不是你想要的
报 layout 相关错 某个 T.Parallel 在全流程结束后仍没有合法布局注解 提示信息会带源码 span(TILELANG_ENABLE_IR_SPAN,env.py:400),直接跳到那一行看
昇腾编译报容量 / 约束不可解 片上存储放不下(L1/UB 是硬约束) 减 num_stages 或 tile 尺寸;错误通常发生在编译期而不是运行期

③ 编译过了但跑起来不对 / 看着"改了没生效"

现象 最可能的原因 怎么办
明明改了参数却很快 命中缓存,你改的字段没进 key 见 6.3;确认改的是 func.script() / args_repr / pass_configs 里出现的东西
改了 C++ pass 没生效 缓存 key 默认不含库内容哈希 TILELANG_DISABLE_CACHE=1,或打开 TILELANG_KERNEL_CACHE_USE_LIB_STAMP(env.py:375)
数值不对,且只在边界错 tile 尺寸和 shape 不整除 + 写出了越界 看生成代码有没有 if (... < N);第 12 章的定位方法会展开
昇腾上"编译通过但跑不出来" 缺 bisheng / ld.lld,或 ASCEND_HOME_PATH 没设 3.4 节三条前置条件逐条查;code/06-env-check/check_env.py --ascend
torch.npu.is_available() 为 False torch_npu 版本不匹配或 CANN 没 source 先 source .../ascend-toolkit/set_env.sh,再确认 torch-npu >= 2.6.0.RC1
照抄独立仓库示例报 AttributeError: clear_cache 主干没有顶层 tilelang.cache.clear_cache() 见 6.4;主干请用 tilelang.disable_cache()

④ 想自己动手查

python code/06-env-check/check_env.py --ascend     # 环境体检(本机可跑)
python code/04-api-surface-check/check_api_surface.py   # API 是否真的存在(本机可跑)

更系统的编译错误定位、lower_trace / pass_diff 与 profiling 属第 12 章。


9. 本章小结

把本章压成一页:

① 安装三条路
   wheel   : pip install tilelang                         (Python>=3.10, glibc>=2.28, CUDA>=10.0 或 pip CUDA>=13)
   源码    : git clone --recursive + pip install . -v     (或 cmake + PYTHONPATH,-DUSE_CUDA / -DUSE_ASCEND)
   昇腾    : 主干 USE_ASCEND=ON USE_CUDA=OFF pip install -v .
             独立仓库 wheel / build_wheel_ascend.sh / install_ascend.sh(要 CANN>=8.3.RC1、torch-npu>=2.6.0.RC1)

② 一个 kernel 的四个部件
   网格   T.Kernel(..., threads=)    → grid / blockIdx / __launch_bounds__
   buffer T.alloc_shared / fragment  → shared / register(昇腾:UB / L1 / L0*)
   搬运   T.copy(src, dst)           → cp.async / TMA / 向量化 LDG-STG(昇腾:MTE)
   并行域 T.Parallel(n)              → 线程映射 + 向量化("block level, not thread level")

③ 读生成代码
   kernel.get_kernel_source()。看三处:网格 → blockIdx/__launch_bounds__;
   向量化 → uint4 / nv_bfloat162;边界 → if (... < N)。
   能整除 + 对齐 → 向量化;不整除 → 补边界判断、丢掉向量化。

④ JIT 与缓存
   @tilelang.jit 的 lazy(返回 PrimFunc)/ eager(返回输出张量)自动推断。
   缓存两级:内存 _memory_cache + 磁盘 ~/.tilelang/cache。
   key = sha256(func.script + out_idx + args_repr + target + backend + pass_configs + version + platform)。
   改注释不变 key;改参数/pass/版本会变 key。

⑤ 昇腾的第一个 kernel(主干写法)
   换方言 tilelang.ascend.language;T.Kernel 一维、无 threads=;
   线程域用 T.SimtVF(threads=...);alloc_shared 落到 UB。
   注意:主干没有 tilelang.cache.clear_cache()(独立仓库有)。

如果只能记住三句话:

  1. 先跑 check_env.py,再动手装——大部分失败是环境,不是代码。
  2. 写完第一个 kernel 的第一件事是 get_kernel_source(),把 Python 和 CUDA 对上看。
  3. "改了没生效"优先怀疑缓存,TILELANG_DISABLE_CACHE=1 是最快的排除法。

延伸阅读

本章所有引用的完整清单与核验状态见 本章引用。

习题

14 题 · 答案在提交后展开

先在纸上或输入框里写下你的答案,再展开参考答案对照。客观判断请自己打勾:这三颗按钮 只影响本机保存的学习进度。

E1概念难度 ★无需硬件概念题 不需要硬件

本章 3.1 节列出了 pip 安装与源码构建两套前置条件。请回答:

  1. 哪一条要求对两条路径都成立?它的具体数值是多少?依据在哪个文件的哪一行?
  2. 「CUDA >= 10.0」与「pip 提供的 CUDA 工具链 >= 13.0」分别在什么情况下用?
  3. 一个人说:「我在 macOS 上按 3.3 节从源码构建,要编 CUDA 后端。」 这句话哪里有问题?给出依据。

E2概念难度 ★无需硬件概念题 不需要硬件

本章 2 节说一个 kernel 剥到最里面是四个部件。请:

  1. 把四个部件、它们在 Python 里的写法、以及它们"回答的问题"列成一张表。
  2. 说明其中哪两个是"人写的"、哪两个是"编译器补的",并各举一个本章代码里的例子。
  3. 如果去掉「搬运」这个部件(即写成版本 A 那样完全不显式搬运),程序还成立吗? 为什么?这说明四个部件里哪一个才是必需的?

E3概念难度 ★★无需硬件概念题 不需要硬件

T.Kernel 的 CUDA 方言有 threads= 参数。请回答:

  1. 本章 4.2 节说 threads=256 "不是总线程数"。那它是什么?给了默认值是多少?
  2. 官方文档说 T.Kernel 里的代码运行在 block level,不是 thread level。 请用本章 5.2 节的对照表说明:既然你从没写过 threadIdx.x,它为什么会出现在 生成代码里?
  3. 网格大小(T.ceildiv(N, block_N))与线程数(threads)是两个独立的量吗? 在版本 A 里 block_N == threads == 256,是巧合还是必须?

E4代码阅读难度 ★★无需硬件代码阅读题 不需要硬件

读下面这段(本章 4.1 节版本 A 的核心,N=1024, block_N=256, threads=256):

with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx:
    for i in T.Parallel(block_N):
        C[bx * block_N + i] = A[bx * block_N + i] + B[bx * block_N + i]

请回答:

  1. 这个网格有多少个 tile 程序?bx 的取值范围是什么?
  2. 第 bx 个 tile 程序负责 C 的哪一段下标?请用 bx 写出这段区间的表达式。
  3. i 是线程号吗?如果不是,它是什么?
  4. 如果 block_N 从 256 改成 512、而 threads 保持 256,网格会变成几块?每个 tile 程序负责的元素数会变吗?语义是否仍然正确?

E5代码阅读难度 ★★无需硬件代码阅读题 不需要硬件

下面是官方文档给出的生成结果(elementwise.md:155-172), Python 输入是 elementwise_add(1024, threads=256, dtype=bfloat16), T.Parallel(threads) 里是 C[b_x*threads+i] = A[...] + B[...]:

extern "C" __global__ void __launch_bounds__(256) main_kernel(bfloat16_t* __restrict__ A, bfloat16_t* __restrict__ B, bfloat16_t* __restrict__ C) {
  if (((int)threadIdx.x) < 32) {
    uint4 __1;
      uint4 v_ = *(uint4*)(A + ((((int)blockIdx.x) * 256) + (((int)threadIdx.x) * 8)));
      uint4 v__1 = *(uint4*)(B + ((((int)blockIdx.x) * 256) + (((int)threadIdx.x) * 8)));
      ((nv_bfloat162*)(&(__1.x)))->x = (((nv_bfloat162*)(&(v_.x)))->x+((nv_bfloat162*)(&(v__1.x)))->x);
      ...   // 其余成对加法
    *(uint4*)(C + ((((int)blockIdx.x) * 256) + (((int)threadIdx.x) * 8))) = __1;
  }
}

请回答:

  1. blockIdx.x 在 Python 里对应哪个名字?它的最大值是多少(N=1024, threads=256)?
  2. if (threadIdx.x < 32) 里的 32 是怎么来的?256 个线程里另外 224 个去做什么了?
  3. uint4 是多少字节?为什么一次能搬 8 个 bfloat16?
  4. Python 里那个循环变量 i 去哪了?请在生成代码里找出它"变形后"的位置。
  5. 如果作者本来想"256 个线程各做 1 个元素",这份生成代码是不是违背了他的意图? 官方文档对这种情况给出的建议是什么(提示:num_per_thread)?

E6代码阅读难度 ★★无需硬件代码阅读题 不需要硬件

下面是同一个 kernel 在 N=2047 时的生成结果(elementwise.md:122-130):

extern "C" __global__ void __launch_bounds__(256) main_kernel(bfloat16_t* __restrict__ A, bfloat16_t* __restrict__ B, bfloat16_t* __restrict__ C) {
  #pragma unroll
  for (int i = 0; i < 8; ++i) {
    if (((i * 256) + ((int)threadIdx.x)) < 2047) {
      C[((i * 256) + ((int)threadIdx.x))] = (A[((i * 256) + ((int)threadIdx.x))] + B[((i * 256) + ((int)threadIdx.x))]);
    }
  }
}

请回答:

  1. 这个边界判断是谁加的?Python 里出现过类似的条件吗?
  2. 与 E5 的向量化版本相比,这一版每个线程处理几个元素?访存宽度是多少字节?
  3. 官方文档对这份代码的评语是什么(用一句话概括)?为什么边界判断会挡住向量化?
  4. 由此回答:为什么"tile 尺寸要和 shape 对齐"?这是性能问题还是正确性问题?

E7动手难度 ★★无需硬件动手题 不需要硬件

把本章 4.1 节版本 A 的一维向量加改成二维版本:输入 A、B、输出 C 都是 (M, N),输出满足 C[m, n] = A[m, n] + B[m, n]。要求:

  1. 写出完整的 TileLang kernel 骨架(@T.prim_func + T.Kernel + T.Parallel)。
  2. 明确写出 T.Kernel 两个维度参数的顺序:哪个是 bx、哪个是 by, 并对应到哪一维。
  3. 说明如果你把 T.Kernel 的两个参数写反,会发生什么(会报错吗?会算错吗?)。

(提示:本章 4.4 节有官方二维示例;注意参数顺序是"N 在前、M 在后"。)


E8动手难度 ★★无需硬件动手题 不需要硬件

从版本 A 出发,改写成版本 B:先 T.alloc_fragment 分配三个 buffer,用 T.copy 把 A、B 的 tile 搬进来,算完再 T.copy 搬回 C。请:

  1. 写出改动后的 kernel 骨架。
  2. 逐条说明 T.copy(A[bx * block_N], A_local) 里,源只写了 A[bx * block_N] (一个"首地址"),编译器是怎么知道要搬多少个元素的。
  3. 版本 A 与版本 B 的计算结果是否相同?它们在语义上真正的差别是什么?

E9概念难度 ★★无需硬件概念题 不需要硬件

@tilelang.jit 有 lazy 与 eager 两种模式,自动推断。请回答:

  1. 判断依据是什么?(看被修饰函数的哪一个特征?)
  2. 分别写出两种模式的一段最小代码,并指出「编译在哪一行发生、执行在哪一行发生」。
  3. 什么场景该选 lazy?什么场景该选 eager?各举一个本章提到的官方示例。

E10概念难度 ★★★无需硬件概念题 不需要硬件

本章 6.3 节给出了缓存 key 的构成。假设你的 kernel 是版本 A,参数 N=1<<20, block_N=256, threads=256。有人做了下面四种改动,请分别判断"缓存命中/ 不命中",并说明依据是 key 里的哪个字段:

  1. 只在 kernel 的 docstring 里加了一行注释。
  2. 把 block_N 从 256 改成 512。
  3. 把 target 从 "cuda" 改成 "ascend"。
  4. 改了 src/transform/ 里某个 C++ pass 的实现(pass_configs 不变, 且 TILELANG_KERNEL_CACHE_USE_LIB_STAMP 没打开)。
  5. 对第 4 种情况,给出两种"强制重新编译"的办法。

E11动手难度 ★★无需硬件动手题 不需要硬件

写一段 Python 脚本(可以在本机跑,不需要 tilelang):

  1. 打印"Python ↔ 生成 CUDA"的对照条目,至少覆盖:网格 → blockIdx / __launch_bounds__;T.Parallel → 线程 + 向量宽度;T.copy → 向量化访存。
  2. 打印一份检查清单,列出"拿到真实机器上的 get_kernel_source() 输出后,按顺序 该看的 5 个地方"。

(参考本章 code/02-inspect-source/inspect_source.py,但请自己组织内容。)


E12概念难度 ★★无需硬件概念题 不需要硬件

本章第 7 节写了昇腾侧的第一个 kernel。请回答:

  1. 与 CUDA 版相比,列出三处结构性差异,每处说明"为什么它不是语法糖"。
  2. 昇腾的 T.Kernel 如果传两个维度参数会怎样?依据在哪个文件的哪个函数?
  3. 主干 tilelang.ascend.language 与独立仓库 tilelang-ascend 的方言,至少列出 三处不同写法,并说明"为什么不能把独立仓库的示例直接抄到主干上"。

E13代码阅读难度 ★★无需硬件代码阅读题 不需要硬件

下面是主干昇腾 T.Kernel docstring 里自带的最小例子 (_vendor/tilelang-v0.1.15/tilelang/ascend/language/kernel.py:165-168):

with T.Kernel(NUM_CORES) as bx:
    with T.SimtVF(threads=128):
        for i in T.Parallel(128):
            out[bx * 128 + i] = x[bx * 128 + i] * 2.0

请回答:

  1. NUM_CORES 与 128(T.SimtVF 的 threads)分别是什么?两者是同一层的东西吗?
  2. 为什么 CUDA 版写 T.Kernel(..., threads=128),昇腾版却要分成两层?
  3. 这段代码里没有任何 T.copy。这说明前面 E2 的"四个部件"里哪一个对昇腾也是可选的?

E14综合难度 ★★★需要加速器

在一台有 NVIDIA GPU 的机器上(或一台昇腾机器上,二选一):

  1. 按本章第 3 节装好对应的 TileLang,并用 code/06-env-check/check_env.py 确认环境。
  2. 跑 code/01-first-kernel/vecadd_tilelang.py(或昇腾版 code/05-ascend-first-kernel/vecadd_ascend.py), 把它打印的源码保存下来。
  3. 与本章 code/02-inspect-source/ 打印的两份官方生成结果对比:命中"向量化"还是 "带边界判断"?把差异点逐条写下来。
  4. 把 N 改成 2047,重跑一次,再用 get_kernel_source() 看源码变化。
  5. 记录你的环境信息(Python 版本、CUDA 版本 / CANN 版本、GPU / NPU 型号、 tilelang.__version__)。不要写任何你没有实测过的性能数字。

(提示:这是一道"测量口径自洽"题——环境、shape、dtype、backend 都要记全, 下次才能复现。)

章节自测

7 题 · 提交后本地评分

单选题。提交后立即给出分数与逐题解析;重做会清空选择,但会保留上一次的成绩记录。

  1. 1. 本章说一个 kernel 剥到最里面就是四个部件,其中哪些是「人写的」?

  2. 2. 按 v0.1.15 的安装文档,pip 安装的两条硬门槛是什么?

  3. 3. 在 with T.Kernel(T.ceildiv(N, block_N), threads=threads) as bx 里,bx 到底是什么?

  4. 4. 关于 for i in T.Parallel(block_N): 里的 i,下面哪句符合本章的说明?

  5. 5. 想在生成的 CUDA 里确认「这段逐元素代码被向量化了」,看哪里最快?

  6. 6. 在同一个 tilelang 版本下,哪种改动会让编译缓存 key 变化、从而触发重新编译?

  7. 7. 用主干 v0.1.15 写昇腾 kernel 时,下面哪一条与 CUDA 侧的差异描述正确?

本章代码

13 个文件

正文里提到的每个示例都能在这里直接阅读(带语法高亮),也可以整章浏览。

浏览本章全部代码 →

本章引用

源码链接指向锁定 tag

本章写到的每个源码位置与外部资料都在下面列出:源码链接指向锁定 tag 的 GitHub 文件, 资料链接指向原文。查看全部资料索引 →

展开引用清单

第 07 章 参考资料

本章所有结论都基于下表中的本地资料或锁定版本源码。版本以 版本锁定 为准:TileLang v0.1.15 (commit a35f8ddf45eba16c21211ec8822d56ce5363036f)、TileLang-Ascend TileLang-ascend-v0.1.2.000-release(commit 89a9364e47a341fffdd65ed94786b1d4f9b6a78c)。 文中不出现「最新(发布)版」之类的模糊表述。

写作环境:macOS (Apple Silicon)、Python 3.11;没有 NVIDIA GPU,也没有昇腾 NPU, 也没有安装 tilelang。因此本章所有涉及加速器的示例最高验证级别是「仅静态核对」—— API 名与签名逐个在 _vendor/ 中比对过,没有任何一条运行结果或性能数字来自本机实测。


锁定的源码引用(_vendor/tilelang-v0.1.15/)

本章正文引用的范围较广,这里按「安装 / 前端 / 运行时 / 昇腾 / 示例与文档」分组。 「已核验」的含义是:在锁定版本工作树上用 grep -n / sed -n 定位到该行,并阅读了 完整签名或段落;行号与文件内容一致。这不是运行验证。

A. 安装与环境(正文第 3 节)

id 引用位置 用途 核验方式与状态
S1 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:3-10 pip 安装前置条件(glibc 2.28、Python >= 3.10、CUDA >= 10.0 或 pip CUDA >= 13.0) 已核验(2026-10-01):逐行阅读
S2 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:70-76 源码构建前置条件(Linux/Windows、Python >= 3.10、CUDA) 已核验(2026-10-01)
S3 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:88-91 --recursive 与定制 TVM submodule 的说明 已核验(2026-10-01)
S4 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:93-99 host CUDA 工具链下的 pip install . -v 已核验(2026-10-01)
S5 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:101-124 pip 提供 CUDA 工具链(无 host CUDA)的两种选项 已核验(2026-10-01)
S6 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:133-158 PYTHONPATH + cmake/ninja 开发路线与验证命令 已核验(2026-10-01)
S7 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:160-166 CMake 选项 USE_CUDA/USE_ROCM/USE_ASCEND/USE_METAL/USE_LLVM 已核验(2026-10-01)
S8 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:332-341 nightly 渠道(滚动构建,非本书依据) 已核验(2026-10-01)
S9 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:365-376 版本字符串里的 SDK/git 后缀(NO_VERSION_LABEL) 已核验(2026-10-01)
S10 _vendor/tilelang-v0.1.15/docs/get_started/Installation.md:389-391 编译缓存默认路径 ~/.tilelang/cache 与 ccache 已核验(2026-10-01)
S11 _vendor/tilelang-v0.1.15/VERSION 内容为 0.1.15 已核验(2026-10-01):cat VERSION

B. 昇腾(正文第 3.4、7 节)

id 引用位置 用途 核验方式与状态
S12 _vendor/tilelang-v0.1.15/tilelang/ascend/README.md:10-51 昇腾 950 前置条件(CANN/bisheng/ld.lld/torch_npu)与源码构建 已核验(2026-10-01);未在 NPU 上运行
S13 _vendor/tilelang-v0.1.15/tilelang/ascend/README.md:27-31 bisheng 在 PATH / BISHENG_HOME/bin;默认 dav-3510 已核验(2026-10-01)
S14 _vendor/tilelang-v0.1.15/tilelang/ascend/README.md:138-154 昇腾写法要点与「与 Ascend C 的分工」对照表 已核验(2026-10-01)
S15 _vendor/tilelang-ascend-v0.1.2.000/README.md:51-52 「在 A2 与 A3 上验证过」 已核验(2026-10-01)
S16 _vendor/tilelang-ascend-v0.1.2.000/README.md:76-134 三种安装方式(wheel / 打 wheel / install_ascend.sh)与 CANN、torch-npu 版本要求 已核验(2026-10-01)
S17 _vendor/tilelang-ascend-v0.1.2.000/install_ascend.sh:37-45 脚本的 Python >= 3.10 门 已核验(2026-10-01)
S18 _vendor/tilelang-ascend-v0.1.2.000/install_ascend.sh:171-243 submodule、TVM 补丁、cmake/make 流程 已核验(2026-10-01)
S19 _vendor/tilelang-ascend-v0.1.2.000/build_wheel_ascend.sh:29-35 要求先设 ASCEND_HOME_PATH 已核验(2026-10-01)
S20 _vendor/tilelang-ascend-v0.1.2.000/set_env.sh:1-20 设 PYTHONPATH 与 ACL_OP_INIT_MODE=1 已核验(2026-10-01)
S21 _vendor/tilelang-ascend-v0.1.2.000/tilelang/cache/__init__.py:70 独立仓库有顶层 clear_cache()(与主干不同) 已核验(2026-10-01)
S22 _vendor/tilelang-ascend-v0.1.2.000/VERSION 内容为 0.1.4(以 tag 为准) 已核验(2026-10-01):cat VERSION
S23 _vendor/tilelang-ascend-v0.1.2.000/examples/elementwise/elementwise_add.py:31-46 独立仓库方言:is_npu=True、alloc_ub、T.Scope("V")、barrier_all 已核验(2026-10-01)
S23a _vendor/tilelang-v0.1.15/tilelang/ascend/language/annotations.py:19-38 T.annotate_buffer_versions 的语义:num_stages > 1 时多缓冲份数由自动调度器计算,该注解用于锁定份数 / 索引模式 已核验(2026-10-01):逐行阅读 docstring;由此确定本章昇腾示例省略它是「语义应当等价、未在 NPU 上验证」的取舍

C. 前端:网格、buffer、copy、parallel(正文第 2、4 节)

id 引用位置 用途 核验方式与状态
S24 _vendor/tilelang-v0.1.15/tilelang/language/kernel.py:431-455 T.Kernel 的 "grid of tile programs" / "target-neutral launch" 语义与参数说明 已核验(2026-10-01):grep -n "^def Kernel" → 431
S25 _vendor/tilelang-v0.1.15/tilelang/cuda/language/kernel.py:13-52 CUDA 方言的 threads=(默认 128)、T.get_thread_binding() 与 docstring 示例 已核验(2026-10-01)
S26 _vendor/tilelang-v0.1.15/tilelang/language/loop.py:13-80 T.Parallel 是块级并行域;布局注解约束由 ParallelLoopLayoutValidator 检查 已核验(2026-10-01)
S27 _vendor/tilelang-v0.1.15/tilelang/language/loop.py:112 T.Pipelined(...) 签名 已核验(2026-10-01)
S28 _vendor/tilelang-v0.1.15/tilelang/language/copy_op.py:59-132 T.copy 是 tile op;形状推断;「首地址」语法糖 已核验(2026-10-01):docstring "Syntactic Sugar" 在 :99
S29 _vendor/tilelang-v0.1.15/tilelang/language/allocate.py:49 T.alloc_shared(shape, dtype, scope="shared.dyn") 已核验(2026-10-01)
S30 _vendor/tilelang-v0.1.15/tilelang/language/allocate.py:81 T.alloc_fragment(shape, dtype, scope="local.fragment") 已核验(2026-10-01)
S31 _vendor/tilelang-v0.1.15/tilelang/language/fill_op.py:48 T.clear(buffer) 已核验(2026-10-01)
S32 _vendor/tilelang-v0.1.15/tilelang/language/gemm_op.py:160 T.gemm(...) 签名 已核验(2026-10-01)
S33 _vendor/tilelang-v0.1.15/tilelang/language/tir/entry.py:10 @T.prim_func 的定义 已核验(2026-10-01)
S34 _vendor/tilelang-v0.1.15/tilelang/language/symbolics.py:13 T.dynamic(name, dtype) 已核验(2026-10-01)
S35 _vendor/tilelang-v0.1.15/tilelang/language/__init__.py:1-32 tilelang.language 是 CUDA 方言的 re-export,__tilelang_dialect__ = "cuda" 已核验(2026-10-01)
S36 _vendor/tilelang-v0.1.15/tilelang/ascend/language/kernel.py:139-174 昇腾 T.Kernel:1-D 核网格、无 threads=、多于 1 维直接报错 已核验(2026-10-01):raise 在 :172-173
S37 _vendor/tilelang-v0.1.15/tilelang/ascend/language/kernel.py:165-168 docstring 自带的昇腾最小示例 已核验(2026-10-01)
S38 _vendor/tilelang-v0.1.15/tilelang/ascend/language/frame.py:107,179 T.SimtVF(threads=128, latency=0)、T.SimdVF(latency=0) 已核验(2026-10-01)
S39 _vendor/tilelang-v0.1.15/tilelang/ascend/language/allocate.py:15,26,37,46,55 alloc_shared(UB)/alloc_l1/alloc_l0a/alloc_l0b/alloc_l0c 的默认 scope 已核验(2026-10-01)
S40 _vendor/tilelang-v0.1.15/tilelang/ascend/language/copy_op.py:53 T.dual_copy 定义(L0C→UB 专用搬运) 已核验(2026-10-01)
S41 _vendor/tilelang-v0.1.15/tilelang/ascend/language/__init__.py:126 __tilelang_dialect__ = "ascend" 已核验(2026-10-01)

D. 运行时、JIT 与缓存(正文第 5、6 节)

id 引用位置 用途 核验方式与状态
S42 _vendor/tilelang-v0.1.15/tilelang/jit/__init__.py:94 模块级 tilelang.compile(...) 已核验(2026-10-01)
S43 _vendor/tilelang-v0.1.15/tilelang/jit/__init__.py:270-330 JITImpl 与 lazy / eager 两种模式的说明 已核验(2026-10-01):class 定义在 :270
S44 _vendor/tilelang-v0.1.15/tilelang/jit/__init__.py:466-505 JITImpl.compile 与 debug_root_path 落盘行为 已核验(2026-10-01)
S45 _vendor/tilelang-v0.1.15/tilelang/jit/__init__.py:519-560 JITImpl.__call__ 与 mode 自动推断、kernel 复用 已核验(2026-10-01):def __call__ 在 :519
S46 _vendor/tilelang-v0.1.15/tilelang/jit/__init__.py:571-635 @tilelang.jit 装饰器签名(out_idx/target/execution_backend/pass_configs …) 已核验(2026-10-01)
S47 _vendor/tilelang-v0.1.15/tilelang/jit/kernel.py:206-220 JITKernel.__call__ 转发到 adapter.func 已核验(2026-10-01)
S48 _vendor/tilelang-v0.1.15/tilelang/jit/kernel.py:516 JITKernel.get_profiler() 已核验(2026-10-01)
S49 _vendor/tilelang-v0.1.15/tilelang/jit/kernel.py:532-600 get_kernel_source / get_host_source / show_source / export_sources 已核验(2026-10-01):四者分别在 :532/:545/:557/:592
S50 _vendor/tilelang-v0.1.15/tilelang/cache/kernel_cache.py:243-284 _generate_key:进 key 的字段与 sha256 构造 已核验(2026-10-01)
S51 _vendor/tilelang-v0.1.15/tilelang/cache/kernel_cache.py:141-142 key 里是否含库内容哈希由 should_use_kernel_cache_lib_stamp() 决定 已核验(2026-10-01)
S52 _vendor/tilelang-v0.1.15/tilelang/cache/kernel_cache.py:455 KernelCache.clear_cache() 是实例方法 已核验(2026-10-01)
S53 _vendor/tilelang-v0.1.15/tilelang/cache/__init__.py:24-97 两条 dispatch map(普通 / 昇腾)与模块级 cached();无顶层 clear_cache 已核验(2026-10-01):grep -n "^def clear_cache\\(" 无输出
S54 _vendor/tilelang-v0.1.15/tilelang/env.py:368 TILELANG_CACHE_DIR 默认 ~/.tilelang/cache 已核验(2026-10-01)
S55 _vendor/tilelang-v0.1.15/tilelang/env.py:372-377 TILELANG_DISABLE_CACHE、TILELANG_KERNEL_CACHE_USE_LIB_STAMP 已核验(2026-10-01)
S56 _vendor/tilelang-v0.1.15/tilelang/env.py:400 TILELANG_ENABLE_IR_SPAN(诊断时回报源码 span) 已核验(2026-10-01)
S57 _vendor/tilelang-v0.1.15/tilelang/env.py:438-441,558-561 enable_cache / disable_cache 的实现与顶层导出 已核验(2026-10-01)

E. 官方示例与文档(正文第 4、5 节)

id 引用位置 用途 核验方式与状态
S58 _vendor/tilelang-v0.1.15/examples/quickstart.py:1-86 GEMM + ReLU 的完整示例(含 get_kernel_source() 与 do_bench) 已核验(2026-10-01):逐行阅读;未运行(无 GPU)
S59 _vendor/tilelang-v0.1.15/examples/elementwise/example_elementwise_add.py:11-63 官方 1-D 逐元素加示例(eager 写法 + assert_close) 已核验(2026-10-01);未运行
S60 _vendor/tilelang-v0.1.15/examples/ascend/example_vecadd.py:11-44 官方昇腾 vecadd(SIMT / SIMD 两种模式) 已核验(2026-10-01);未在 NPU 上运行
S61 _vendor/tilelang-v0.1.15/examples/ascend/README.md:19-25 昇腾示例的性能口径(带宽 GB/s 的计法) 已核验(2026-10-01)
S62 _vendor/tilelang-v0.1.15/docs/deeplearning_operators/elementwise.md:26-41 最小向量加示例与「block level, not thread level」的官方表述 已核验(2026-10-01)
S63 _vendor/tilelang-v0.1.15/docs/deeplearning_operators/elementwise.md:57-87 二维向量加示例(bx/by 参数顺序) 已核验(2026-10-01)
S64 _vendor/tilelang-v0.1.15/docs/deeplearning_operators/elementwise.md:104-133 边界处理与「边界判断挡住向量化」的评语 已核验(2026-10-01)
S65 _vendor/tilelang-v0.1.15/docs/deeplearning_operators/elementwise.md:155-172 向量化版生成 CUDA(uint4 + nv_bfloat162) 已核验(2026-10-01):逐行比对
S66 _vendor/tilelang-v0.1.15/docs/deeplearning_operators/elementwise.md:176-211 num_per_thread 建议与对应的生成 CUDA 已核验(2026-10-01)
S67 _vendor/tilelang-v0.1.15/docs/programming_guides/language_basics.md @T.prim_func、T.Tensor、动态维度(第 8 章前置) 已核验(2026-10-01):文件存在,正文与示例逐段阅读
S68 _vendor/tilelang-v0.1.15/docs/tutorials/debug_tools_for_tilelang.md 编译失败 / 数值错误的排查路线(第 12 章前置) 已核验(2026-10-01)

corpus 资料引用(资料库索引)

抓取日期:2026-09-30(Asia/Shanghai),见 corpus 的 资料库索引。

id 本地路径 上游链接 用途 核验状态
R1 TileLang 官方文档 · 资料清单 https://tilelang.com/get_started/Installation.html 官方安装指南的站点源文件;与 S1–S10 引用的仓库内文档内容相同 已核验(2026-10-01):与 _vendor/.../docs/get_started/Installation.md 做 diff,差异只出现在 4 个含 em-dash(—)的行上(corpus 侧是重复编码),正文其余部分逐行一致
R2 TileLang 官方文档 · 资料清单 https://tilelang.com/get_started/overview.html 三层编程接口与六步编译流程(第 4 章已引;本章第 2 节的心智模型与之对齐) 已核验(2026-10-01)
R3 TileLang 官方文档 · 资料清单 https://tilelang.com/get_started/targets.html target 的取值与 auto 探测(本章 3.x、6.x 的 target= 参数背景) 已核验(2026-10-01)
R4 TileLang 官方文档 · 资料清单 https://tilelang.com/deeplearning_operators/elementwise.html 逐元素算子教程的站点源文件;与 S62–S66 引用的仓库内文档内容相同 已核验(2026-10-01):与 _vendor/.../docs/deeplearning_operators/elementwise.md 做 diff,差异均为 em-dash(—)编码,正文与示例代码一致
R5 TileLang 官方文档 · 资料清单 https://tilelang.com/programming_guides/language_basics.html 语言基础(第 8 章前置) 已核验(2026-10-01)
R6 TileLang 官方文档 · 资料清单 https://tilelang.com/tutorials/debug_tools_for_tilelang.html 调试工具(第 12 章前置) 已核验(2026-10-01)
R7 tile-ai/tilelang/docs/get_started/Installation.md https://github.com/tile-ai/tilelang 主仓库快照里的同类文档,用于对照「corpus 的 main 快照比 v0.1.15 多一个提交」这一事实 已核验(2026-10-01):文件存在;仅用于对照,不作为本章依据
R8 tilelang-ascend/README.md https://github.com/tile-ai/tilelang-ascend 昇腾适配器仓库的安装说明(与 S15–S16 引用的 _vendor 快照同源) 已核验(2026-10-01)
R9 tilelang-ascend/docs/TileLang-Ascend Programming Guide.md https://github.com/tile-ai/tilelang-ascend/blob/master/docs/TileLang-Ascend Programming Guide.md 昇腾编程指南;本章只在 7.2 节做定位性引用,细节留到第 19 章 已核验(2026-10-01)

上游链接核验说明

  • 上表「已核验」的链接,核验方式是:确认对应 corpus 文件在本地存在,且其头部记录了 抓取日期与原始 URL。本章写作时未重新发起网络请求(corpus 抓取日期 2026-09-30, 本章写作日期 2026-10-01,相差 1 天)。
  • R1、R4 与 _vendor 内的同名文档做了逐行 diff,确认是同一份内容;两者可互相 印证,因此本章把「安装说明来自 v0.1.15」这条判断建立在两份文件一致的基础上。
  • R7 是 main 快照,不是 v0.1.15;本章只在「版本边界」语境下引用它,不作为 API 是否存在判据。

关于性能数字

本章不出现任何性能数字。 所有涉及"快慢"的表述都是定性的(例如"边界判断会挡住 向量化"),其依据是官方文档的原文评语,而非本机或第三方的测量数据。第 11、12、19 章 涉及实测时会各自给出测量口径。


待补资料

  1. PyPI 上 0.1.15 是否存在,未在本机核实。 本章 3.2 节只给出「把版本号钉住」 的方法(pip install "tilelang==0.1.15")与「从 Release Page 取版本字符串为 0.1.15 的 wheel」这条替代路径,没有断言 PyPI 上一定有该版本。建议后续版本补一条 对 https://pypi.org/project/tilelang/#history(或对应 Release 页)的抓取,以确认 可安装的具体版本清单。
  2. pip install git+https://github.com/tile-ai/tilelang.git 装的是哪个分支, 未核实。 v0.1.15 的 Installation.md:25-27 给了这条命令,但没有写分支;本章据此 提醒读者"它装的是那个分支的源码,不是你本地的 checkout",但没给出该分支名。
  3. 昇腾 wheel 的发布位置与版本列表未在 corpus 中。 本章 3.4 节只说"下载现成 wheel",未给出具体下载地址(资料库索引 也没写死 URL)。 第 19 章若需要精确到文件,建议补抓昇腾侧的 Release 页。
  4. 昇腾 950 的 dav-3510 与 A2/A3 的对应关系未核实。 主干文档写"默认 dav-3510 对应 Ascend 950"(S13),独立仓库文档写"在 A2/A3 上验证"(S15),两者未在同一份 材料里被明确对应。本章只分别引用,未做合并结论。

本章共 12 个二级小节;右上角目录在桌面端会跟随滚动高亮。