跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

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

/code/03-design-philosophy/

01-layer-inventory/layer_inventory.py6532 字节
#!/usr/bin/env python3
"""第 03 章 示例 1:三层接口 API 清点表(纯标准库,可在无 GPU 的机器上运行)。

这个脚本不导入 tilelang(本机也装不上:没有 CUDA/NPU,编译好的 .so 不存在),
它只做一件事:**在锁定版本 v0.1.15 的源码里,逐个确认本章声称存在的 API 名字
确实出现在语言层的导出面上**,然后把结果打出来。

它回答的是第 03 章最容易被写成空话的那类问题:
「beginner / developer / expert 三层各自到底有哪些构造?」
把这个问题变成一张可以复算的表,而不是一段形容词。

用法:

    python3 layer_inventory.py                # 自动定位书根目录
    python3 layer_inventory.py --book-root /path/to/tilelang-book
    python3 layer_inventory.py --list-missing  # 只打印找不到的名字

退出码:0 = 所有名字都能找到;1 = 有名字找不到(说明本章的结论过期了)。

作者实际做到的验证级别:**CPU 逻辑验证**。脚本在写作机上实际运行过,输出见同目录
README.md。注意它验证的是「名字出现在源码里」,**不是**「这个 API 在你的机器上能用」——
后者需要真实安装并运行 TileLang。
"""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

VENDOR_REL = Path("_vendor/tilelang-v0.1.15")

# 语言层的「导出面」:这些文件里出现的 `"name"`(导出清单)或 `def name` 都算数。
SURFACE_GLOBS = (
    "tilelang/language/*.py",
    "tilelang/language/*/*.py",
    "tilelang/cuda/language/*.py",
)

# 三层接口各自的名字。清单来自第 03 章正文的表,来源逐个写在 references.md。
LAYERS: tuple[tuple[str, tuple[str, ...]], ...] = (
    (
        "beginner (eager 前端,层本身未完成)",
        (
            "const",  # examples/eager_jit/eagerjit.en.ipynb: M, N, K = T.const("M, N, K")
            "dynamic",  # 同上 notebook 的 T.dynamic(...) 单元格
            "empty",  # T.empty((M, N), dtype)
            "Tensor",  # A: T.Tensor[[M, K], T.float16]
            "StridedTensor",
            "macro",
            "prim_func",
        ),
    ),
    (
        "developer (tile 库)",
        (
            "Kernel",
            "Parallel",
            "Pipelined",
            "Persistent",
            "copy",
            "gemm",
            "gemm_sp",
            "gemm_blockscaled",
            "reduce",
            "reduce_max",
            "reduce_sum",
            "cumsum",
            "cummax",
            "fill",
            "clear",
            "alloc_shared",
            "alloc_fragment",
            "annotate_layout",
            "use_swizzle",
            "GemmWarpPolicy",
        ),
    ),
    (
        "expert (thread 原语与内建)",
        (
            "get_thread_binding",
            "get_block_binding",
            "alloc_local",
            "alloc_var",
            "serial",
            "vectorized",
            "unroll",
            "sync_threads",
            "atomic_add",
            "tvm_thread_allreduce",
            "comm_reducer",
            "attr",
            "evaluate",
            "ptx_wait_group",
        ),
    ),
    (
        "CUDA 方言扩展(只有 CUDA target 才有)",
        (
            "ClusterKernel",
            "copy_cluster",
            "mbarrier_wait_parity",
            "tma_load",
            "ldg128",
            "alloc_tmem",
        ),
    ),
)


def find_book_root(start: Path) -> Path:
    """向上找一个含 _vendor/tilelang-v0.1.15 的目录。"""
    for candidate in (start, *start.parents):
        if (candidate / VENDOR_REL).is_dir():
            return candidate
    raise SystemExit(f"找不到 {VENDOR_REL}:请用 --book-root 指定书根目录")


def surface_files(vendor: Path) -> list[Path]:
    files: list[Path] = []
    for pattern in SURFACE_GLOBS:
        files.extend(sorted(vendor.glob(pattern)))
    return sorted(set(files))


def locate(name: str, files: list[Path], root: Path) -> tuple[str, str] | None:
    """返回 (证据形式, `相对路径:行号`);优先找函数定义,其次找导出清单里的字符串。

    证据形式只有两种,都写进输出里,避免读者把「出现在导出清单里」误读成「这里定义了它」:
      * ``def`` —— 该文件里有 ``def <name>``;
      * ``export`` —— 该文件把 "<name>" 写进了导出清单或再导出语句。
    """
    quoted = re.compile(r"""["']""" + re.escape(name) + r"""["']""")
    defined = re.compile(r"\bdef " + re.escape(name) + r"\b")
    sources: list[str] = []
    for path in files:
        try:
            text = path.read_text(encoding="utf-8", errors="ignore")
        except OSError:
            continue
        sources.append(text)

    for kind, pattern in (("def", defined), ("export", quoted)):
        for path, text in zip(files, sources):
            for lineno, line in enumerate(text.splitlines(), start=1):
                if pattern.search(line):
                    try:
                        shown = path.relative_to(root)
                    except ValueError:
                        shown = path
                    return kind, f"{shown}:{lineno}"
    return None


def check_doc_note(vendor: Path) -> tuple[bool, str]:
    """核对「beginner 层未完成」这句官方说明是否还在。"""
    doc = vendor / "docs/get_started/overview.md"
    needle = "not yet fully implemented"
    if not doc.is_file():
        return False, f"缺少 {doc}"
    for lineno, line in enumerate(doc.read_text(encoding="utf-8", errors="ignore").splitlines(), start=1):
        if needle in line:
            return True, f"docs/get_started/overview.md:{lineno}  含 “{needle}”"
    return False, f"docs/get_started/overview.md 里找不到 “{needle}”"


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--book-root", type=Path, default=None)
    parser.add_argument("--list-missing", action="store_true", help="只打印找不到的名字")
    args = parser.parse_args()

    start = args.book_root if args.book_root is not None else Path(__file__).resolve().parent
    book_root = find_book_root(start)
    vendor = book_root / VENDOR_REL
    files = surface_files(vendor)
    if not files:
        print(f"在 {vendor} 下没找到任何语言层源文件", file=sys.stderr)
        return 1

    print(f"书根目录 : {book_root}")
    print(f"锁定版本 : {VENDOR_REL}  (VERSION = {(vendor / 'VERSION').read_text().strip()})")
    print(f"扫描文件 : {len(files)} 个语言层源文件")
    print()

    total = 0
    missing: list[tuple[str, str]] = []
    for layer, names in LAYERS:
        print(f"== {layer} ==")
        for name in names:
            total += 1
            found = locate(name, files, vendor)
            if found is None:
                missing.append((layer, name))
                if not args.list_missing:
                    print(f"  [缺失] {name}")
            elif not args.list_missing:
                kind, where = found
                print(f"  [有]   {name:<22} {kind:<7} {where}")
        print()

    ok_doc, doc_msg = check_doc_note(vendor)
    print("== 官方文档核对:beginner 层的实现状态 ==")
    print(f"  [{'有' if ok_doc else '缺失'}] {doc_msg}")
    print()

    print(f"合计 {total} 个名字,找到 {total - len(missing)} 个,缺失 {len(missing)} 个。")
    if missing:
        print("缺失清单(说明本章结论需要重新核对):")
        for layer, name in missing:
            print(f"  - {name}  [{layer}]")
    return 0 if not missing and ok_doc else 1


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

← 浏览本章全部代码