Files
tilelang/testing/python/language/test_tilelang_language_tma_store.py
T
2026-05-31 03:51:36 +08:00

221 lines
6.6 KiB
Python

"""Tests for TMA store (shared -> global).
Explicit T.tma_copy(shared_buf, global_buf) emits tma_store + tma_store_arrive
(no wait). The user must explicitly call T.tma_store_wait() for
synchronization.
Plain T.copy(shared_buf, global_buf) may also auto-lower to tma_store when the
store-side TMA constraints are satisfied. In that case lowering emits both
tma_store_arrive and tma_store_wait automatically.
"""
from tilelang import tvm as tvm
import tilelang.testing
import tilelang.language as T
import tilelang
def matmul_tma_store(
M,
N,
K,
block_M,
block_N,
block_K,
in_dtype,
out_dtype,
accum_dtype,
threads,
num_stages,
):
"""GEMM with T.copy for loads and T.tma_copy for the final store (shared -> global)."""
A_shape = (M, K)
B_shape = (K, N)
@T.prim_func
def main(
A: T.Tensor(A_shape, in_dtype),
B: T.Tensor(B_shape, in_dtype),
C: T.Tensor((M, N), out_dtype),
):
with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=threads) as (bx, by):
A_shared = T.alloc_shared((block_M, block_K), in_dtype)
B_shared = T.alloc_shared((block_K, block_N), in_dtype)
C_shared = T.alloc_shared((block_M, block_N), out_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=num_stages):
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)
# Store: fragment -> shared, then shared -> global via T.tma_copy
T.copy(C_local, C_shared)
T.tma_copy(C_shared, C[by * block_M, bx * block_N])
T.tma_store_wait()
return main
def run_gemm_tma_store(num_stages):
M, N, K = 1024, 1024, 1024
block_M, block_N, block_K = 128, 128, 32
in_dtype = T.float16
out_dtype = T.float16
accum_dtype = T.float32
threads = 128
program = matmul_tma_store(
M,
N,
K,
block_M,
block_N,
block_K,
in_dtype,
out_dtype,
accum_dtype,
threads,
num_stages,
)
kernel = tilelang.compile(
program,
out_idx=[2],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
kernel_source = kernel.get_kernel_source()
print(kernel_source)
# exit()
# Verify that the generated kernel contains tma_store_arrive but NOT tma_store_wait
# (the wait is issued separately by the user via T.tma_store_wait)
assert "tma_store_arrive" in kernel_source, "Expected tma_store_arrive in kernel source"
profiler = kernel.get_profiler()
def ref_program(A, B):
import torch
C = torch.matmul(A.to(torch.float), B.to(torch.float))
return C.to(torch.__getattribute__(out_dtype))
profiler.assert_allclose(ref_program, atol=1e-2, rtol=1e-2)
def auto_tma_store_copy(M, N, block_M, block_N, dtype, threads):
@T.prim_func
def main(
A: T.Tensor((M, N), dtype),
C: T.Tensor((M, N), dtype),
):
with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=threads) as (bx, by):
A_shared = T.alloc_shared((block_M, block_N), dtype)
T.copy(A[by * block_M, bx * block_N], A_shared)
T.copy(A_shared, C[by * block_M, bx * block_N])
return main
def full_shape_tma_store_1d(dtype, threads):
@T.prim_func
def main(
C: T.Tensor((128, 128), dtype),
):
with T.Kernel(threads=threads):
C_shared = T.alloc_shared((128, 128), dtype)
T.copy(C_shared, C)
return main
def tma_store_wait_read_option(read):
@T.prim_func
def main():
with T.Kernel(threads=128):
T.tma_store_wait(read=read)
return main
def run_auto_tma_store_copy():
M = N = 256
block_M = block_N = 128
dtype = T.float16
threads = 128
program = auto_tma_store_copy(M, N, block_M, block_N, dtype, threads)
kernel = tilelang.compile(
program,
out_idx=[1],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
kernel_source = kernel.get_kernel_source()
assert "tma_store_arrive" in kernel_source, "Expected auto tma_store_arrive in kernel source"
assert "tma_store_wait" in kernel_source, "Expected auto tma_store_wait in kernel source"
assert "tl::tma_store_wait<0, true>();" in kernel_source, "Expected auto tma_store_wait to use default read wait"
profiler = kernel.get_profiler()
def ref_program(A):
return A
profiler.assert_allclose(ref_program, atol=1e-2, rtol=1e-2)
def run_full_shape_tma_store_1d_codegen():
program = full_shape_tma_store_1d(T.float32, 128)
kernel = tilelang.compile(
program,
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
kernel_source = kernel.get_kernel_source()
assert "tl::tma_store" in kernel_source, "Expected TMA store in kernel source"
assert "CUtensorMap" not in kernel_source, "Expected pointer-based 1D TMA store"
def run_tma_store_wait_read_option_codegen():
default_kernel = tilelang.compile(
tma_store_wait_read_option(True),
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
no_read_kernel = tilelang.compile(
tma_store_wait_read_option(False),
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
assert "tl::tma_store_wait<0, true>();" in default_kernel.get_kernel_source()
assert "tl::tma_store_wait<0, false>();" in no_read_kernel.get_kernel_source()
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_tma_store_2_stages():
run_gemm_tma_store(num_stages=2)
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_tma_store_3_stages():
run_gemm_tma_store(num_stages=3)
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_plain_copy_auto_tma_store():
run_auto_tma_store_copy()
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_plain_copy_full_shape_tma_store_uses_1d():
run_full_shape_tma_store_1d_codegen()
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(9, 0)
def test_tma_store_wait_read_option_codegen():
run_tma_store_wait_read_option_codegen()
if __name__ == "__main__":
# tilelang.testing.main()
test_tma_store_2_stages()