跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 12 章 调试与性能分析

/code/12-debugging-and-profiling/

04-lower-trace/gemm_lower_trace.py5346 字节
#!/usr/bin/env python3
"""第 12 章 示例 4:看一个 pass 前后的变化(IR Lower Trace / Pass Diff)。

「某个 lowering 阶段之后 IR 就错了」是 TileLang 里最常见的生成类问题。三种看法:

  1. `TL_LOWER_TRACE`(推荐):环境变量一开,捕获**每一个** pass 的前后 IR,
     再带最终 codegen;输出 terminal diff 或自包含 HTML。
  2. `lower_trace()` 程序 API:只对给定的一串 pass 做前后对比。
  3. `pass_diff`(legacy,仅向后兼容):和 2 同构,`TILELANG_PASS_DIFF` 已不再演进。

关键时序:`TL_LOWER_TRACE` 是在 **import tilelang 时**被读取的,
所以本脚本先解析参数、设置环境变量、再导入 tilelang。

用法:

    python3 gemm_lower_trace.py --static-only          # 任何机器:语法 + 入口核对
    python3 gemm_lower_trace.py --mode trace-env       # 整条 pipeline,写 HTML 报告
    python3 gemm_lower_trace.py --mode trace-api       # 只比指定的一串 pass
    python3 gemm_lower_trace.py --mode pass-diff       # legacy Pass Diff

报告位置:默认 `./tmp/lower_trace_dir/<脚本名>/report.html`(可用 TL_LOWER_TRACE_DIR 改)。

作者实际做到的验证级别:**仅静态核对**(`py_compile` 通过;`lower_trace` / `enable`
/ `pass_diff` / `Simplify` 四个入口已在 v0.1.15 源码中核对;本机未安装 tilelang,
未生成过任何报告)。
"""

from __future__ import annotations

import argparse
import os
import py_compile
import sys
from pathlib import Path


def build_kernel(T, tilelang):
    @tilelang.jit
    def matmul(A, B, block_M: int, block_N: int, block_K: int):
        M, N, K = T.const("M, N, K")
        dtype = T.float16
        accum_dtype = T.float32
        A: T.Tensor((M, K), dtype)
        B: T.Tensor((N, K), dtype)
        C = T.empty((M, N), dtype)

        with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (bx, by):
            A_shared = T.alloc_shared((block_M, block_K), dtype)
            B_shared = T.alloc_shared((block_N, block_K), dtype)
            C_local = T.alloc_fragment((block_M, block_N), accum_dtype)
            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[bx * block_N, ko * block_K], B_shared)
                T.gemm(A_shared, B_shared, C_local, transpose_B=True)
            T.copy(C_local, C[by * block_M, bx * block_N])
        return C

    return matmul(1024, 1024, 1024, 128, 128, 32)


def static_only() -> int:
    py_compile.compile(str(Path(__file__)), doraise=True)
    print("py_compile: ok")
    print("需要与锁定版本核对的入口(逐个在 _vendor/tilelang-v0.1.15/ 下确认):")
    for name, where in (
        ("TL_LOWER_TRACE / TL_LOWER_TRACE_DIR", "tilelang/env.py:390-392"),
        ("lower_trace()", "tilelang/tools/lower_trace/__init__.py:49"),
        ("enable() / disable()", "tilelang/tools/lower_trace/core.py:663-705"),
        ("pass_diff()", "tilelang/utils/pass_diff.py:1030"),
        ("transform.Simplify", "tilelang/transform/simplify.py:20"),
    ):
        print(f"  {name:<38} {where}")
    print("(本机未安装 tilelang:这里只核对语法与入口存在性,不生成任何报告。)")
    return 0


def run_trace_env(target: str, mode: str) -> int:
    # 必须在 import tilelang 之前设置:TileLang 在 import 时读取该变量。
    os.environ["TL_LOWER_TRACE"] = mode
    os.environ.setdefault("TL_LOWER_TRACE_DIR", "./tmp/lower_trace_dir")
    import tilelang
    import tilelang.language as T

    func = build_kernel(T, tilelang)
    # tilelang.lower() 默认不做 device 编译(enable_device_compile=False),
    # 因此即使本机没有 nvcc,也能把 lowering 走完并抓取 IR。
    tilelang.lower(func, target=target)
    print(f"TL_LOWER_TRACE={mode},报告目录:{os.environ['TL_LOWER_TRACE_DIR']}/")
    print("打开 <脚本名>/report.html 即可按 pass 逐步查看 before/after。")
    return 0


def run_trace_api(_target: str) -> int:
    import tilelang
    import tilelang.language as T
    from tilelang.tools import lower_trace

    func = build_kernel(T, tilelang)
    steps = lower_trace.lower_trace(
        func,
        [("Simplify", tilelang.transform.Simplify())],
        mode="terminal",
    )
    for step in steps:
        print(
            f"pass={step['name']:<12} changed={step['changed']} "
            f"+{step['insertions']} -{step['deletions']}"
        )
    print("注意:文本变了不等于语义或性能变了——它只是一个可定位的线索。")
    return 0


def run_pass_diff(_target: str) -> int:
    import tilelang
    import tilelang.language as T
    from tilelang.utils.pass_diff import pass_diff

    func = build_kernel(T, tilelang)
    steps = pass_diff(func, tilelang.transform.ThreadSync("shared"), mode="terminal")
    for step in steps:
        print(
            f"pass={step['name']:<12} changed={step['changed']} "
            f"+{step['insertions']} -{step['deletions']}"
        )
    print("Pass Diff 已被 IR Lower Trace 取代,新代码建议直接用 TL_LOWER_TRACE。")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--target", default="cuda", help="编译目标(cuda 只需 codegen,不需要 GPU)")
    parser.add_argument(
        "--mode",
        choices=("trace-env", "trace-api", "pass-diff"),
        default="trace-env",
        help="trace-env = TL_LOWER_TRACE 全流程;trace-api = lower_trace();pass-diff = legacy",
    )
    parser.add_argument("--static-only", action="store_true", help="只做语法与入口核对,不导入 tilelang")
    args = parser.parse_args()

    if args.static_only:
        return static_only()
    if args.mode == "trace-env":
        return run_trace_env(args.target, "html")
    if args.mode == "trace-api":
        return run_trace_api(args.target)
    return run_pass_diff(args.target)


if __name__ == "__main__":
    sys.exit(main())

← 浏览本章全部代码