-
Notifications
You must be signed in to change notification settings - Fork 681
[Tool] Provide layout visualization tool #1353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
b59e6e8
3f8ab91
70519cd
67f27dd
695009b
79b0914
00feb64
3495e81
18c93c4
360854b
bb9d49f
eac27c9
fd25d67
86eb08a
2da20b0
03dc6b8
5d075f2
7b1532d
581beab
376b63c
cf9b3c7
c1230a4
9282abd
d897a95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import tilelang | ||
| import tilelang.language as T | ||
|
|
||
| tilelang.disable_cache() | ||
|
|
||
|
|
||
| # use pass_configs to enable layout visualization | ||
| @tilelang.jit( | ||
| out_idx=[-1], pass_configs={tilelang.PassConfigKey.TL_ENABLE_LAYOUT_VISUALIZATION: "False"}) | ||
| 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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,26 @@ 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() | ||
|
|
||
|
Comment on lines
+70
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden Right now A small defensive tweak keeps bool configs working while handling strings safely: def should_enable_layout_visual(pass_ctx: PassContext | None = None) -> bool:
if pass_ctx is None:
pass_ctx = tilelang.transform.get_pass_context()
- enabled = pass_ctx.config.get(tilelang.PassConfigKey.TL_LAYOUT_VISUALIZATION_ENABLE, False)
- return enabled
+ value = pass_ctx.config.get(tilelang.PassConfigKey.TL_LAYOUT_VISUALIZATION_ENABLE, False)
+ if isinstance(value, str):
+ v = value.strip().lower()
+ return bool(v and v != "false")
+ return bool(value)This keeps the default “disabled unless explicitly enabled” behavior while avoiding surprises from accidental string usage. 🤖 Prompt for AI Agents |
||
| config_value = pass_ctx.config.get(tilelang.PassConfigKey.TL_ENABLE_LAYOUT_VISUALIZATION.value, | ||
| "") | ||
|
|
||
| if config_value is None: | ||
| return False | ||
|
|
||
| config_str = str(config_value).strip().lower() | ||
| return bool(config_str and config_str != "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 | ||
|
|
||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import tilelang | ||
| 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 | ||
|
|
||
|
LeiWang1999 marked this conversation as resolved.
Outdated
|
||
|
|
||
| def print_layout_format(layout: T.Fragment) -> str: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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, formats: str = "png"): | ||
| super().__init__() | ||
| self.layout_found = [] | ||
| self.processed_layouts = set() | ||
| self.formats = formats | ||
|
|
||
| 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", formats=self.formats) | ||
| self.processed_layouts.add(layout_id) | ||
|
|
||
| self.visit_stmt(op.body) | ||
|
|
||
|
|
||
| def LayoutVisual(): | ||
|
|
||
| def pass_fn(func: tir.PrimFunc, mod, ctx): | ||
| pass_ctx = tilelang.transform.get_pass_context() | ||
| config_value = pass_ctx.config.get( | ||
| tilelang.PassConfigKey.TL_ENABLE_LAYOUT_VISUALIZATION.value) | ||
|
|
||
| config_str = str(config_value).strip().lower() | ||
| if not config_str or config_str == "false": | ||
| return func | ||
| elif config_str == "true": | ||
| formats = "all" | ||
| else: | ||
| formats = config_str | ||
|
|
||
| _LayoutVisualVisitor(formats=formats).visit_stmt(func.body) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| return func | ||
|
|
||
| return prim_func_pass(pass_fn, opt_level=0) | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,11 +1,13 @@ | ||||||
| from __future__ import annotations | ||||||
|
LeiWang1999 marked this conversation as resolved.
|
||||||
| 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", | ||||||
| verbose: bool = False) -> None: | ||||||
| verbose: bool = False, | ||||||
| formats: str | list[str] = "png") -> None: | ||||||
|
LeiWang1999 marked this conversation as resolved.
|
||||||
| """ | ||||||
| Plot the layout of a buffer. | ||||||
|
|
||||||
|
|
@@ -21,9 +23,10 @@ def plot_layout(layout: T.Layout, | |||||
| The colormap to use for visualization (default is "RdPu"). | ||||||
| verbose : bool, optional | ||||||
| If True, prints additional information about the mapping (default is False). | ||||||
|
|
||||||
| formats : str | list[str], optional | ||||||
| The formats to save the image in (default is "png"). | ||||||
| Returns | ||||||
| ------- | ||||||
| -------s | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| None | ||||||
| """ | ||||||
| import os | ||||||
|
|
@@ -82,6 +85,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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new warp-aware coloring loop assumes at least 32 threads, but Useful? React with 👍 / 👎.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @Cunxiao2002 |
||||||
|
|
||||||
|
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 | ||||||
|
|
@@ -191,17 +200,30 @@ def plot_layout(layout: T.Layout, | |||||
| # Save the figure in multiple formats | ||||||
| plt.tight_layout() | ||||||
|
|
||||||
| # Save as PDF | ||||||
| pdf_path = tmp_directory / f"{name}.pdf" | ||||||
| plt.savefig(pdf_path, bbox_inches="tight") | ||||||
| print(f"Saved pdf format into {pdf_path}") | ||||||
|
|
||||||
| # Save as PNG | ||||||
| png_path = tmp_directory / f"{name}.png" | ||||||
| plt.savefig(png_path, bbox_inches="tight", transparent=False, dpi=255) | ||||||
| print(f"Saved png format into {png_path}") | ||||||
|
|
||||||
| # Save as SVG | ||||||
| svg_path = tmp_directory / f"{name}.svg" | ||||||
| plt.savefig(svg_path, bbox_inches="tight", format="svg") | ||||||
| print(f"Saved svg format into {svg_path}") | ||||||
| if isinstance(formats, str): | ||||||
| formats_str = formats.strip().lower() | ||||||
| if formats_str == 'all': | ||||||
| formats_list = ['pdf', 'png', 'svg'] | ||||||
| elif "," in formats_str: | ||||||
| formats_list = [f.strip() for f in formats_str.split(',')] | ||||||
| else: | ||||||
| formats_list = [formats_str] | ||||||
| else: | ||||||
| raise TypeError(f"Expected str, but got {type(formats).__name__}. " | ||||||
| f"Please pass a string like 'png', 'pdf', 'svg', 'all', or 'png,pdf'.") | ||||||
|
Comment on lines
+218
to
+222
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Useful? React with 👍 / 👎. |
||||||
|
|
||||||
| # Save the figure | ||||||
| if 'pdf' in formats_list: | ||||||
| pdf_path = tmp_directory / f"{name}.pdf" | ||||||
| plt.savefig(pdf_path, bbox_inches="tight") | ||||||
| print(f"Saved pdf format into {pdf_path}") | ||||||
|
|
||||||
| if 'png' in formats_list: | ||||||
| png_path = tmp_directory / f"{name}.png" | ||||||
| plt.savefig(png_path, bbox_inches="tight", transparent=False, dpi=255) | ||||||
| print(f"Saved png format into {png_path}") | ||||||
|
|
||||||
| if 'svg' in formats_list: | ||||||
| svg_path = tmp_directory / f"{name}.svg" | ||||||
| plt.savefig(svg_path, bbox_inches="tight", format="svg") | ||||||
| print(f"Saved svg format into {svg_path}") | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.