跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 29 章 编译器工程:写 pass、测试与回归

/code/29-compiler-engineering/

01-python-pass/hoist_constant_broadcast.py3892 字节
"""一个最小但完整的 TileLang Python pass:HoistConstantBroadcast。

它做的事情只有一件:把 buffer store 里出现的「立即数广播」
``T.Broadcast(<IntImm|FloatImm>, lanes)`` 提取成一个 ``Bind`` 定义的变量,
在 store 之前定义一次。

Before::

    A_shared[0:8] = T.Broadcast(T.float8_e4m3fn(1.2), 8) + T.Broadcast(T.float8_e4m3fn(3.4), 8)

After::

    broadcast_value:   T.float8_e4m3fn = T.float8_e4m3fn(1.2)
    broadcast_value_1: T.float8_e4m3fn = T.float8_e4m3fn(3.4)
    A_shared[0:8] = T.Broadcast(broadcast_value, 8) + T.Broadcast(broadcast_value_1, 8)

写作依据:这是一个**简化并改名**的版本,原型是锁定版本里的
``_vendor/tilelang-v0.1.15/tilelang/transform/hoist_broadcast_values.py:75-99``
(pass 主体)与 ``:16-72``(mutator)。所有 API 名都在 v0.1.15 里逐个核对过。

验证级别:**仅静态核对**。本机没有 tilelang/tvm,模块只做到
``python3 -m py_compile`` 通过。注意:本文件在顶层 ``from tvm import tirx``,
所以在没有装 tilelang/tvm 的机器上**连 import 都会失败**——
那正是「静态检查」与「运行」的分界线。要真正运行,见同目录 README.md。
"""

from __future__ import annotations

from tvm import tirx
from tvm.tirx import (
    Broadcast,
    BufferStore,
    PrimFunc,
    PyStmtExprMutator,
    SeqStmt,
    Var,
)
from tvm.tirx.stmt import Bind
from tvm.tirx.transform import prim_func_pass

from tilelang.ir import get_stmt_span, stamp_stmt_spans


@tirx.functor.mutator
class HoistConstantBroadcastMutator(PyStmtExprMutator):
    """把立即数广播替换成变量,并把定义收集到当前语句之前。

    ``pending`` 是「本语句内待定义的新变量」。它必须在进入每一条 store 时
    清空、在离开时恢复,否则嵌套语句里的定义会漏到外层,生成引用了未定义
    变量的 IR。
    """

    def __init__(self) -> None:
        super().__init__()
        self.pending: list[tuple[Var, tirx.PrimExpr]] = []

    def visit_broadcast_(self, op: Broadcast):
        # 只处理立即数广播:这类值的求值与位置无关,提到语句前不改变语义。
        if isinstance(op.value, (tirx.IntImm, tirx.FloatImm)):
            value = self.visit_expr(op.value)
            new_var = Var("broadcast_value", dtype=value.dtype)
            self.pending.append((new_var, value))
            return Broadcast(new_var, op.lanes)
        # 其他广播原样递归。
        return Broadcast(self.visit_expr(op.value), self.visit_expr(op.lanes))

    def visit_buffer_store_(self, op: BufferStore):
        saved_pending = self.pending
        self.pending = []

        new_indices = [self.visit_expr(index) for index in op.indices]
        new_stmt = BufferStore(op.buffer, self.visit_expr(op.value), new_indices)

        if self.pending:
            stmts: list[tirx.Stmt] = [Bind(var, value) for var, value in self.pending]
            stmts.append(new_stmt)
            new_stmt = SeqStmt(stmts)

        self.pending = saved_pending
        # 重写过的节点会丢掉原来的 span,重新盖回去,保证报错还能指回 Python 行。
        stamp_stmt_spans(new_stmt, get_stmt_span(op))
        return new_stmt


def HoistConstantBroadcast():
    """返回一个可挂进任意 backend pipeline 的 ``PrimFunc`` pass。

    ``prim_func_pass`` 是 tvm 侧构造 pass 的工厂:它接收一个
    ``(func, mod, ctx) -> func`` 的函数,返回 ``tvm.transform.Pass``。
    ``opt_level=0`` 表示这个 pass 属于「必须执行」的语义 pass,
    ``name`` 会出现在 ``TL_LOWER_TRACE`` 与 ``TL_PASS_PROFILE`` 的报告里,
    所以取一个能一眼认出用途的名字。
    """

    def pass_fn(func: PrimFunc, mod, ctx) -> PrimFunc:
        mutator = HoistConstantBroadcastMutator()
        return func.with_body(mutator.visit_stmt(func.body), span=func.span)

    return prim_func_pass(pass_fn, opt_level=0, name="tl.HoistConstantBroadcast")


def _api_self_check() -> None:
    """不 import tilelang 的静态自检:确认本文件用到的名字都能对上 v0.1.15。"""
    expected = {
        "tvm.tirx": ["Broadcast", "BufferStore", "PrimFunc", "PyStmtExprMutator", "SeqStmt", "Var"],
        "tvm.tirx.stmt": ["Bind"],
        "tvm.tirx.transform": ["prim_func_pass"],
        "tilelang.ir": ["get_stmt_span", "stamp_stmt_spans"],
    }
    print("本文件依赖的 API(逐个在 _vendor/tilelang-v0.1.15/ 里核对):")
    for module, names in expected.items():
        print(f"  {module}: {', '.join(names)}")
    print("  tirx.functor.mutator(装饰器)")


if __name__ == "__main__":
    _api_self_check()
    print()
    print("要运行这个 pass,需要一棵装好的 TileLang 源码树,见同目录 README.md。")

← 浏览本章全部代码