Files
tilelang/testing/python/language/test_tilelang_language_cluster.py
T
27f1f81c61 [Feature][Example] Introduce CLC tile schedule and add example for sm100 GEMM (#2029)
* [Feature] Add Blackwell Cluster Launch Control (CLC) primitives

Expose cluster launch control query operations (try_cancel,
try_cancel_multicast, is_canceled, get_first_ctaid_{x,y,z}) in both
the C++ codegen path and the Python tilelang.language API, plus a new
mbarrier_arrive_expect_tx builtin used with CLC multicast completion.
Enables writing persistent SM100 kernels that use the hardware scheduler
to fetch the next tile coordinate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* [Example] Add SM100 persistent 2-CTA GEMM example using CLC scheduling

Adds examples/gemm_sm100/gemm_tcgen5mma_ws_clc.py, a warp-specialized
persistent GEMM for Blackwell (sm_100) that uses tcgen05 2-CTA MMA
with cluster launch control tile scheduling. The kernel is split into
producer/MMA/scheduler/epilogue warp groups, with the scheduler warp
running on both CTAs and feeding next-tile coordinates via the CLC
hardware multicast into schedule_arrived directly (no intermediate
barrier). Storage sync is disabled and all syncs are placed manually.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* lint

* typo

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* remove unnecessary test

* Add CLC query intrinsics and async fence

* Refactor CLC response handling by introducing CLCResponseDecode struct. Update clc_get_first_ctaid_x/y/z and clc_is_canceled functions to utilize the new decode response method for improved clarity and maintainability.

* Enhance CLC scheduling in persistent GEMM by introducing shared variables for schedule validity and tile ID. Update cancellation checks to utilize the new shared variables, improving clarity and maintainability of the scheduling logic.

* test fix

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Zhiwen Mo <zm125@ic.ac.uk>
Co-authored-by: LeiWang1999 <leiwang1999@outlook.com>
2026-04-17 15:19:04 +08:00

133 lines
4.2 KiB
Python

import tilelang
import tilelang.language as T
import tilelang.testing
import torch
def matmul(M, N, K, block_M, block_N, block_K, dtype=T.float16, accum_dtype=T.float32):
@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, cluster_dims=(2, 1, 1)) 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
@tilelang.jit(out_idx=-1)
def get_cta_rank_in_cluster(cluster_size=4):
assert 128 % cluster_size == 0
@T.prim_func
def main(A: T.Tensor((128), T.int32)):
with T.Kernel(128, cluster_dims=(cluster_size, 1, 1)) as bx:
T.cluster_arrive()
T.cluster_wait()
cta_rank_in_cluster = T.block_rank_in_cluster()
if T.get_thread_binding() == 0:
A[bx] = cta_rank_in_cluster
T.cluster_sync()
return main
@tilelang.jit(out_idx=-1)
def barrier_kernel():
@T.prim_func
def main(A: T.Tensor((128), T.int32)):
with T.Kernel(128, threads=128, cluster_dims=(4, 1, 1)):
mbar = T.alloc_cluster_barrier([256])
T.cluster_sync()
T.mbarrier_arrive(mbar, 0)
if T.block_rank_in_cluster() == 0:
T.mbarrier_wait_parity(mbar, 0)
T.cluster_sync()
return main
def _get_clc_query_codegen_source() -> str:
@T.prim_func
def main(A: T.Tensor((2,), T.int32)):
with T.Kernel(1, threads=1):
result = T.alloc_shared((4,), T.uint32)
A[0] = T.clc_is_canceled(result)
A[1] = T.Cast("int32", T.clc_get_first_ctaid_x(result))
artifact = tilelang.lower(main, target="cuda")
return artifact.kernel_source
def run_cython_cluster_launch():
kernel = matmul(1024, 1024, 1024, 128, 128, 32)
mod = tilelang.compile(kernel, execution_backend="cython")
assert "clusterDim = {2, 1, 1}" in mod.get_host_source()
def run_tvm_ffi_cluster_launch():
kernel = matmul(1024, 1024, 1024, 128, 128, 32)
mod = tilelang.compile(kernel, execution_backend="tvm_ffi")
check_str = r"""
(((TVMFFIAny*)stack_ffi_any)[3].type_index) = 1;
(((TVMFFIAny*)stack_ffi_any)[3].zero_padding) = 0;
(((TVMFFIAny*)stack_ffi_any)[3].v_int64) = ((int64_t)2);
(((TVMFFIAny*)stack_ffi_any)[4].type_index) = 1;
(((TVMFFIAny*)stack_ffi_any)[4].zero_padding) = 0;
(((TVMFFIAny*)stack_ffi_any)[4].v_int64) = ((int64_t)1);
(((TVMFFIAny*)stack_ffi_any)[5].type_index) = 1;
(((TVMFFIAny*)stack_ffi_any)[5].zero_padding) = 0;
(((TVMFFIAny*)stack_ffi_any)[5].v_int64) = ((int64_t)1);
"""
assert check_str in mod.get_host_source()
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_cluster_launch():
run_cython_cluster_launch()
run_tvm_ffi_cluster_launch()
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_cluster_launch_intrinsics(cluster_size=4):
kernel = get_cta_rank_in_cluster(cluster_size)
result = kernel()
ref = torch.arange(128, dtype=torch.int32, device="cuda") % cluster_size
assert torch.all(result == ref)
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_cluster_barrier():
kernel = barrier_kernel()
kernel()
@tilelang.testing.requires_cuda
def test_clc_query_codegen_includes_cluster_header():
src = _get_clc_query_codegen_source()
print("=== clc query codegen ===")
print(src)
assert "#include <tl_templates/cuda/cluster.h>" in src
assert "tl::clc_is_canceled(" in src
assert "tl::clc_get_first_ctaid_x(" in src
if __name__ == "__main__":
tilelang.testing.main()