Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions examples/visual_layout_inference/visual_layout_inference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import tilelang
import tilelang.language as T


# use pass_configs to enable layout visualization
@tilelang.jit(out_idx=[-1], pass_configs={tilelang.PassConfigKey.TL_ENABLE_LAYOUT_VISUAL: True})
def matmul(M, N, K, block_M, block_N, block_K, dtype="float16", accum_dtype="float"):

@T.prim_func
def gemm(
A: T.Tensor((M, K), dtype),
B: T.Tensor((K, 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)

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

return gemm


def main():
kernel = matmul(128, 128, 128, 32, 32, 32)

import torch

a = torch.randn(128, 128).cuda().half()
b = torch.randn(128, 128).cuda().half()

c = kernel(a, b)

ref_c = a @ b

torch.testing.assert_close(c, ref_c, rtol=1e-2, atol=1e-2)
print("All check passed.")

# print the layout visualization result and save figures to ./tmp.
'''
C_local layout inference:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be better to be C_local inferenced layout:

Shape: [32, 32] -> [8]
Thread: _j // 16 * 64 + _i // 16 * 32 + _i % 8 * 4 + _j % 8 // 2
Index: [_j % 16 // 8 * 4 + _i % 16 // 8 * 2 + _j % 2]
'''


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions src/op/builtin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ TVM_REGISTER_PASS_CONFIG_OPTION(kDisableVectorize256, Bool);
TVM_REGISTER_PASS_CONFIG_OPTION(kDisableWGMMA, Bool);
TVM_REGISTER_PASS_CONFIG_OPTION(kDisableShuffleElect, Bool);
TVM_REGISTER_PASS_CONFIG_OPTION(kStorageRewriteDetectInplace, Bool);
TVM_REGISTER_PASS_CONFIG_OPTION(kEnableLayoutVisual, Bool);

DataType cuTensorMapType() { return DataType::UInt(8, 128); }

Expand Down
1 change: 1 addition & 0 deletions src/op/builtin.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ static constexpr const char *kDisableWGMMA = "tl.disable_wgmma";
static constexpr const char *kDisableShuffleElect = "tl.disable_shuffle_elect";
static constexpr const char *kStorageRewriteDetectInplace =
"tl.storage_rewrite_detect_inplace";
static constexpr const char *kEnableLayoutVisual = "tl.enable_layout_visual";
/*!
* \brief Whether to disable dynamic tail split
*
Expand Down
1 change: 1 addition & 0 deletions tilelang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def _load_tile_lang_lib():
transform, # noqa: F401
language, # noqa: F401
engine, # noqa: F401
tools, # noqa: F401
)
from .autotuner import autotune # noqa: F401
from .transform import PassConfigKey # noqa: F401
Expand Down
4 changes: 4 additions & 0 deletions tilelang/engine/lower.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from tilelang.engine.param import KernelParam, CompiledArtifact
from tilelang.utils.target import determine_target
from tilelang.engine.phase import (
LayoutVisual,
PreLowerSemanticCheck,
LowerAndLegalize,
OptimizeForTarget,
Expand Down Expand Up @@ -249,6 +250,9 @@ def lower(
# Phase 1: Lower and legalize the IR
mod = LowerAndLegalize(mod, target)

# Visualize the layout
LayoutVisual(mod)

# Phase 2: Optimize the IR for the target
mod = OptimizeForTarget(mod, target)

Expand Down
13 changes: 13 additions & 0 deletions tilelang/engine/phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ def should_force_let_inline(pass_ctx: PassContext | None = None) -> bool:
return bool(pass_ctx and pass_ctx.config.get(tilelang.PassConfigKey.TL_FORCE_LET_INLINE, False))


def should_enable_layout_visual(pass_ctx: PassContext | None = None) -> bool:
if pass_ctx is None:
pass_ctx = tilelang.transform.get_pass_context()
return bool(
pass_ctx and pass_ctx.config.get(tilelang.PassConfigKey.TL_ENABLE_LAYOUT_VISUAL, False))


def LayoutVisual(mod: IRModule) -> None:
"""Apply layout visualization pass if enabled."""
if should_enable_layout_visual():
tilelang.tools.LayoutVisual()(mod)


def PreLowerSemanticCheck(mod: IRModule) -> None:
"""
Check whether the module is valid before lowering. If not, raise a user-friendly error
Expand Down
1 change: 1 addition & 0 deletions tilelang/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .plot_layout import plot_layout # noqa: F401
from .Analyzer import *
from .layout_visual import LayoutVisual # noqa: F401
50 changes: 50 additions & 0 deletions tilelang/tools/layout_visual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import tilelang.language as T
from tvm import tir
from tvm.tir import PyStmtExprVisitor

from tvm.tir.transform import prim_func_pass
from tilelang.tools.plot_layout import plot_layout


def print_layout_format(layout: T.Fragment) -> str:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only works for fragment layout, I think we should rename it into print_fragment_format and do some type check there.

input_shape = layout.get_input_shape()
output_shape = layout.get_output_shape()
lines = [
f" Shape: {input_shape} -> {output_shape}", f" Thread: {layout.forward_thread}",
f" Index: {layout.forward_index}"
]

return "\n".join(lines)


@tir.functor.visitor
class _LayoutVisualVisitor(PyStmtExprVisitor):

def __init__(self):
super().__init__()
self.layout_found = []
self.processed_layouts = set()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused layout_found attribute.

The layout_found list is initialized but never used anywhere in the class. Remove it to avoid confusion.

     def __init__(self):
         super().__init__()
-        self.layout_found = []
         self.processed_layouts = set()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(self):
super().__init__()
self.layout_found = []
self.processed_layouts = set()
def __init__(self):
super().__init__()
self.processed_layouts = set()
🤖 Prompt for AI Agents
In tilelang/tools/layout_visual.py around lines 23 to 26, the __init__ defines
self.layout_found which is never used; remove that attribute initialization
(delete the self.layout_found = [] line) so only self.processed_layouts = set()
remains, and run tests/lint to ensure no references to layout_found exist
elsewhere.


def visit_block_(self, op: tir.Block) -> None:
if "layout_map" in op.annotations:
layout_map = op.annotations["layout_map"]

for key, layout in layout_map.items():
if isinstance(layout, T.Fragment):
layout_id = str(layout)
if layout_id not in self.processed_layouts:
print(f"{key} layout inference:")
print(print_layout_format(layout))
plot_layout(layout, name=f"{key}_layout")
self.processed_layouts.add(layout_id)

self.visit_stmt(op.body)


def LayoutVisual():

def pass_fn(func: tir.PrimFunc, mod, ctx):
_LayoutVisualVisitor().visit_stmt(func.body)
return func

return prim_func_pass(pass_fn, opt_level=0)
8 changes: 7 additions & 1 deletion tilelang/tools/plot_layout.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import tilelang.language as T


def plot_layout(layout: T.Layout,
def plot_layout(layout: T.Fragment,
save_directory="./tmp",
name: str = "layout",
colormap: str = "RdPu",
Expand Down Expand Up @@ -82,6 +82,12 @@ def plot_layout(layout: T.Layout,
raw_colors = [cmap(i) for i in range(num_threads)]
colors = raw_colors.copy()

# Show the distribution of registers in each thread of a warp.
warp_size = 32
spectral_camp = plt.get_cmap("hsv", warp_size * 6)
for i in range(warp_size):
colors[i] = spectral_camp(i * 6)
Comment on lines +88 to +101

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard warp coloring when thread count < warp size

The new warp-aware coloring loop assumes at least 32 threads, but colors is sized to num_threads; when a layout has fewer than 32 threads (common for small tiles), iterating for i in range(warp_size) writes past the end of the list and raises IndexError, so visualization fails before any plots are saved.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Comment thread
LeiWang1999 marked this conversation as resolved.
# Determine the number of rows and columns in the input shape
nrows, ncols = input_shape
# Adjust figure size to maintain square cells
Expand Down
3 changes: 3 additions & 0 deletions tilelang/transform/pass_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ class PassConfigKey(str, Enum):
TL_FORCE_LET_INLINE = "tl.force_let_inline"
"""Force TileLang to inline let bindings during simplification. Default: False"""

TL_ENABLE_LAYOUT_VISUAL = "tl.enable_layout_visual"
"""Enable layout inference visualization. Default: False"""

TL_STORAGE_REWRITE_DETECT_INPLACE = "tl.storage_rewrite_detect_inplace"
"""Control StorageRewrite inplace detection.

Expand Down
Loading