mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-10-21 06:40:23 +08:00
* [Feature] Add pass_visualizer: structure-tree pass browser Add an interactive, pass-by-pass IR structure-tree visualizer under tilelang/utils/pass_visualizer. It complements the existing text-level pass_diff tool by rendering the SBlock structure tree and expanding tile ops by field name, with per-class operator highlighting (tile op / sync / lowered hardware intrinsic) across the CUDA lowering prologue. Includes a CLI entry point, an example gemm_relu kernel, a pytest suite, and a docs section contrasting it with pass_diff. Co-Authored-By: Claude <noreply@anthropic.com> * Move pass_visualizer to tilelang/tools and fix prologue + review issues - Move package from tilelang/utils to tilelang/tools per review - Add missing MaterializeKernelLaunch pass (fixes CUDA CI modulo-by-zero) - Escape "</" in embedded JSON to prevent <script> injection in HTML report - Use explicit utf-8 encoding for source/output file I/O - Guard _fmt_shape against symbolic shapes and validate module spec Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
165 lines
5.2 KiB
Python
165 lines
5.2 KiB
Python
# type: ignore
|
|
"""Tests for the pass_visualizer debugging tool.
|
|
|
|
Covers:
|
|
- Core helpers: build_module, build_pass_stages, inspect_structure
|
|
- Structure-tree capture and per-pass diffing (build_pass_data via viewer)
|
|
- HTML / text emission (emit_html, emit_txt)
|
|
|
|
These run the CUDA lowering prologue on a small kernel. An explicit cuda target
|
|
is passed so the pipeline does not depend on auto target detection.
|
|
"""
|
|
|
|
import tilelang
|
|
import tilelang.testing
|
|
import tilelang.language as T
|
|
from tilelang import tvm
|
|
|
|
from tilelang.tools.pass_visualizer import (
|
|
build_module,
|
|
build_pass_stages,
|
|
inspect_structure,
|
|
)
|
|
from tilelang.tools.pass_visualizer.viewer import (
|
|
_capture_tree,
|
|
build_pass_data,
|
|
emit_html,
|
|
emit_txt,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _gemm_relu_kernel():
|
|
"""A small fused GEMM + bias + ReLU @tilelang.jit kernel."""
|
|
|
|
@tilelang.jit(out_idx=[-1])
|
|
def gemm_relu(M, N, K, block_M, block_N, block_K, dtype="float16", accum_dtype="float32"):
|
|
|
|
@T.prim_func
|
|
def main(
|
|
A: T.Tensor((M, K), dtype),
|
|
B: T.Tensor((K, N), dtype),
|
|
bias: T.Tensor((N,), dtype),
|
|
C: T.Tensor((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_K, block_N), dtype)
|
|
C_local = T.alloc_fragment((block_M, block_N), accum_dtype)
|
|
|
|
T.clear(C_local)
|
|
for k in T.Pipelined(T.ceildiv(K, block_K), num_stages=3):
|
|
T.copy(A[by * block_M, k * block_K], A_shared)
|
|
T.copy(B[k * block_K, bx * block_N], B_shared)
|
|
T.gemm(A_shared, B_shared, C_local)
|
|
|
|
for i, j in T.Parallel(block_M, block_N):
|
|
C_local[i, j] = T.max(C_local[i, j] + bias[bx * block_N + j], 0)
|
|
|
|
T.copy(C_local, C[by * block_M, bx * block_N])
|
|
|
|
return main
|
|
|
|
return gemm_relu
|
|
|
|
|
|
def _build_small_module():
|
|
"""Build an IRModule + resolved cuda target for the small kernel."""
|
|
kernel = _gemm_relu_kernel()
|
|
func = kernel.get_tir(M=128, N=128, K=128, block_M=64, block_N=64, block_K=32)
|
|
cuda_target = tvm.target.Target("cuda", host="llvm")
|
|
return build_module(func, target=cuda_target)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Core helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
def test_build_pass_stages_nonempty():
|
|
"""build_pass_stages returns an ordered list of (name, transform) pairs."""
|
|
_mod, target = _build_small_module()
|
|
stages = build_pass_stages(target)
|
|
|
|
assert len(stages) > 0
|
|
names = [name for name, _ in stages]
|
|
# The prologue must run LayoutInference and LowerTileOp.
|
|
assert "LayoutInference" in names
|
|
assert "LowerTileOp" in names
|
|
for name, transform in stages:
|
|
assert isinstance(name, str)
|
|
assert callable(transform)
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
def test_inspect_structure_renders_tree(capsys):
|
|
"""inspect_structure prints the SBlock tree with tile ops expanded by field."""
|
|
mod, _target = _build_small_module()
|
|
inspect_structure(mod)
|
|
out = capsys.readouterr().out
|
|
|
|
assert "PrimFunc" in out
|
|
assert "SBlock" in out
|
|
# gemm is expanded by field name, not printed as one positional line.
|
|
assert "T.gemm" in out
|
|
assert "a_region" in out
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
def test_capture_tree_returns_lines():
|
|
"""_capture_tree turns inspect_structure output into a list of text lines."""
|
|
mod, _target = _build_small_module()
|
|
lines = _capture_tree(mod)
|
|
|
|
assert isinstance(lines, list)
|
|
assert len(lines) > 0
|
|
assert any("PrimFunc" in ln for ln in lines)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# build_pass_data + emission (file-driven)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
def test_build_pass_data_and_emit(tmp_path):
|
|
"""End-to-end: run the example kernel through the pipeline and emit HTML/txt."""
|
|
import os
|
|
|
|
kernel_path = os.path.join(
|
|
os.path.dirname(tilelang.tools.pass_visualizer.__file__),
|
|
"examples",
|
|
"gemm_relu.py",
|
|
)
|
|
with open(kernel_path) as f:
|
|
source = f.read()
|
|
|
|
kwargs = {"M": 128, "N": 128, "K": 128, "block_M": 64, "block_N": 64, "block_K": 32}
|
|
name, stages = build_pass_data(kernel_path, None, "auto", kwargs, source)
|
|
|
|
assert name == "gemm_relu"
|
|
# source + (input) + at least one pass.
|
|
assert len(stages) >= 3
|
|
for st in stages:
|
|
assert "name" in st and "flag" in st and "rows" in st
|
|
flags = {st["flag"] for st in stages}
|
|
assert "source" in flags
|
|
assert "input" in flags
|
|
|
|
html = emit_html(name, stages)
|
|
assert "Pass browser" in html
|
|
assert "gemm_relu" in html
|
|
|
|
txt = emit_txt(name, stages)
|
|
assert "kernel: gemm_relu" in txt
|
|
assert "T.gemm" in txt
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tilelang.testing.main()
|