Files
tilelang/testing/python/language/test_tilelang_language_copy.py
T
Tong WUandLeiWang1999 52995c4b7f [Feature] Support stmatrix m16n8 on Blackwell (#2417)
* Add stmatrix m16n8 support

* lint

* Refactor stmatrix code generation to handle optional shape argument

Updated the handling of the shape argument in the stmatrix function across CUDA, CuTeDSL, and HIP code generators. The shape is now checked as the last argument, and the payload start index is adjusted accordingly. This change improves consistency and clarity in the code generation process for tile language operations.

* Refactor stmatrix function naming for consistency

Updated the naming convention of stmatrix functions in CUDA and related files to improve clarity and consistency. The function names now follow a uniform pattern, placing the shape descriptor before the axis identifier. This change enhances readability and aligns with the recent updates in code generation logic.

* Enhance extern call statement printing in CUDA codegen

Updated the `VisitExpr_` method in `codegen_cuda.cc` to improve the clarity of extern call statements. The function now includes comments indicating that the transformation, number, and shape are encoded in the helper name, and only the shared memory pointer and value operands are printed. This change aims to enhance code readability and maintainability.

* Refactor extern call expression printing in CUDA codegen

Introduced a helper function to streamline the printing of extern call expressions in the `VisitExpr_` method of `codegen_cuda.cc`. This change enhances code readability by reducing redundancy and improving the clarity of the generated output for specific function calls, such as `tl::pack_b8x4`.

* Refactor shape argument handling in stmatrix code generation

Updated the handling of the shape argument in the `VisitExpr_` method for both CUDA and CuTeDSL code generators. The shape is now checked as the fourth argument instead of the last, improving clarity and consistency in extern call statement printing. This change enhances the robustness of the code generation process for tile language operations.

* Refactor shape argument handling in stmatrix code generation

Updated the handling of the shape argument in the `VisitExpr_` method for both CUDA and CuTeDSL code generators. The shape is now checked as the last argument and validated accordingly, improving clarity and consistency in the code generation process for tile language operations.

* Refactor tilelang lower function context management

Updated the context management in the `test_tilelang_copy_uses_stmatrix_m16n8_for_sm100_int8_shared_store` test by removing the unnecessary `PassContext` wrapper. This change simplifies the code and enhances clarity in the test setup for tile language operations.

---------

Co-authored-by: LeiWang1999 <leiwang1999@outlook.com>
2026-06-23 14:21:07 +08:00

252 lines
8.9 KiB
Python

import tilelang
import tilelang.language as T
import torch
import tilelang.testing
from tilelang import tvm
from tilelang.layout import make_gemm_fragment_8x8_transposed
print(torch.__version__)
# add decorator @tilelang.jit if you want to return a torch function
# @tilelang.jit
def tilelang_copy(M, N, block_M, block_N, src_dtype=T.float16, dst_dtype=T.float16):
@T.prim_func
def main(
A: T.Tensor((M, N), src_dtype),
B: T.Tensor((M, N), dst_dtype),
):
# Initialize Kernel Context
with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (bx, by):
T.copy(
A[by * block_M : (by + 1) * block_M, bx * block_N : (bx + 1) * block_N],
B[by * block_M : (by + 1) * block_M, bx * block_N : (bx + 1) * block_N],
)
return main
def run_tilelang_copy(M=1024, N=1024, block_M=128, block_N=128, dtype=T.float16):
program = tilelang_copy(M, N, block_M, block_N, src_dtype=dtype, dst_dtype=dtype)
kernel = tilelang.compile(
program,
out_idx=[1],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
source = kernel.get_kernel_source()
print(source)
a = torch.randn(M, N, device="cuda", dtype=getattr(torch, dtype))
b = kernel(a)
torch.testing.assert_close(b, a, rtol=1e-2, atol=1e-2)
def test_tilelang_copy():
run_tilelang_copy(M=1024, N=1024, block_M=128, block_N=128)
run_tilelang_copy(M=1024, N=576, block_M=32, block_N=576)
run_tilelang_copy(M=1024, N=576, block_M=32, block_N=576, dtype=T.float32)
def run_tilelang_copy_cross_dtype(M=256, N=256, block_M=128, block_N=128, src_dtype=T.float16, dst_dtype=T.bfloat16):
program = tilelang_copy(M, N, block_M, block_N, src_dtype=src_dtype, dst_dtype=dst_dtype)
kernel = tilelang.compile(program, out_idx=[1])
a = torch.randn(M, N, device="cuda", dtype=getattr(torch, src_dtype))
b = kernel(a)
torch.testing.assert_close(b, a.to(getattr(torch, dst_dtype)), rtol=1e-2, atol=1e-2)
@tilelang.testing.requires_cuda
def test_tilelang_copy_cross_dtype():
run_tilelang_copy_cross_dtype(src_dtype=T.float16, dst_dtype=T.bfloat16)
run_tilelang_copy_cross_dtype(src_dtype=T.bfloat16, dst_dtype=T.float16)
def tilelang_copy_with_stride(M, N, NN, block_M, block_N, dtype=T.float16):
@T.prim_func
def main(
A: T.StridedTensor((M, N), (NN, 1), dtype),
B: T.Tensor((M, N), dtype),
):
# Initialize Kernel Context
with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (bx, by):
for i, j in T.Parallel(block_M, block_N):
B[by * block_M + i, bx * block_N + j] = A[by * block_M + i, bx * block_N + j]
return main
def run_tilelang_copy_with_stride(M=1024, N=1024, NN=2048, block_M=128, block_N=128, dtype=T.float16):
if isinstance(NN, int):
assert NN > N, "NN must be greater than N"
program = tilelang_copy_with_stride(M, N, NN, block_M, block_N, dtype)
kernel = tilelang.compile(
program,
out_idx=[1],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
if isinstance(NN, T.Var):
NN = N * 2
a = torch.randn(M, NN, device="cuda", dtype=getattr(torch, dtype))
b = kernel(a[:, :N])
torch.testing.assert_close(b, a[:, :N], rtol=1e-2, atol=1e-2)
def test_tilelang_copy_with_stride():
run_tilelang_copy_with_stride(M=1024, N=1024, NN=2048, block_M=128, block_N=128)
run_tilelang_copy_with_stride(M=1024, N=1024, NN=T.dynamic("NN"), block_M=128, block_N=128)
def tilelang_copy_bufferload(num_tokens, dtype=T.float16):
@T.prim_func
def main(
indices: T.Tensor((num_tokens,), T.int32),
x: T.Tensor((num_tokens,), dtype),
):
with T.Kernel(num_tokens, threads=32) as pid:
idx = T.alloc_local([1], T.int32)
T.copy(indices[pid], idx[0])
x[idx[0]] = x[idx[0]] + 1
return main
def run_tilelang_copy_bufferload(num_tokens=128, dtype=T.float16):
program = tilelang_copy_bufferload(num_tokens, dtype)
# test compilation only
tilelang.compile(
program,
out_idx=[1],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
def test_tilelang_copy_bufferload():
run_tilelang_copy_bufferload(num_tokens=128)
def tilelang_copy_buffer_load_with_parallel(M, N, block_M, block_N, dtype=T.float16):
@T.prim_func
def main(
A: T.Tensor((M, N), dtype),
B: T.Tensor((M, N), dtype),
):
# Initialize Kernel Context
with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (bx, by):
for i, j in T.Parallel(block_M, block_N):
T.copy(A[by * block_M + i, bx * block_N + j], B[by * block_M + i, bx * block_N + j])
return main
def run_tilelang_copy_buffer_load_with_parallel(M=1024, N=1024, block_M=128, block_N=128, dtype=T.float16):
program = tilelang_copy_buffer_load_with_parallel(M, N, block_M, block_N, dtype)
kernel = tilelang.compile(
program,
out_idx=[1],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
a = torch.randn(M, N, device="cuda", dtype=getattr(torch, dtype))
b = kernel(a)
torch.testing.assert_close(b, a, rtol=1e-2, atol=1e-2)
def test_tilelang_copy_buffer_load_with_parallel():
run_tilelang_copy_buffer_load_with_parallel(M=1024, N=1024, block_M=128, block_N=128)
def tilelang_copy_shape_mismatched(M, N, src_dtype=T.float16, dst_dtype=T.float16):
@T.prim_func
def main(
A: T.Tensor((M, N), src_dtype),
B: T.Tensor((M, N), dst_dtype),
):
# Initialize Kernel Context
with T.Kernel(1, threads=128):
T.copy(A[:, :2], B[:, :3])
return main
def run_tilelang_copy_shape_mismatched(M=1024, N=1024, dtype=T.float16):
program = tilelang_copy_shape_mismatched(M, N, src_dtype=dtype, dst_dtype=dtype)
kernel = tilelang.compile(
program,
out_idx=[1],
pass_configs={tilelang.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True},
)
a = torch.randn(M, N, device="cuda", dtype=getattr(torch, dtype))
b = kernel(a)
torch.testing.assert_close(b[:, :1], a[:, :1], rtol=1e-2, atol=1e-2)
def test_tilelang_copy_shape_mismatched():
run_tilelang_copy_shape_mismatched(M=128, N=128)
def run_tilelang_copy_fp8_e8m0(M=1024, N=1024, block_M=128, block_N=128, src_dtype=T.float8_e8m0fnu, dst_dtype=T.float8_e8m0fnu):
program = tilelang_copy(M, N, block_M, block_N, src_dtype=src_dtype, dst_dtype=dst_dtype)
kernel = tilelang.compile(
program,
out_idx=[1],
)
source = kernel.get_kernel_source()
assert "fp8_e8_t" in source
dummy_input = torch.randint(0, 100, (M, N), device="cuda", dtype=torch.int8).view(torch.float8_e8m0fnu)
output = kernel(dummy_input)
assert output is not None
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(10, 0)
def test_tilelang_copy_fp8_e8m0():
run_tilelang_copy_fp8_e8m0(src_dtype=T.float8_e8m0fnu, dst_dtype=T.float8_e8m0fnu)
def run_tilelang_copy_fp4(M=1024, N=1024, block_M=128, block_N=128, src_dtype=T.float4_e2m1fn, dst_dtype=T.float4_e2m1fn):
program = tilelang_copy(M, N, block_M, block_N, src_dtype=src_dtype, dst_dtype=dst_dtype)
kernel = tilelang.compile(
program,
out_idx=[1],
)
source = kernel.get_kernel_source()
assert "fp4_e2_t" in source
# For FP4, use same shape as kernel expects, since int8 is used as storage type
dummy_input = torch.randint(0, 100, (M, N // 2), device="cuda", dtype=torch.int8)
output = kernel(dummy_input)
if src_dtype == dst_dtype:
assert torch.allclose(output.view(torch.int8), dummy_input)
assert output is not None
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_ge(10, 0)
def test_tilelang_copy_fp4():
run_tilelang_copy_fp4(src_dtype=T.float4_e2m1fn, dst_dtype=T.float4_e2m1fn)
run_tilelang_copy_fp4(src_dtype=T.float4_e2m1fn, dst_dtype=T.float16)
run_tilelang_copy_fp4(src_dtype=T.float4_e2m1fn, dst_dtype=T.bfloat16)
@tilelang.testing.requires_cuda
def test_tilelang_copy_uses_stmatrix_m16n8_for_sm100_int8_shared_store():
@T.prim_func
def main(A: T.Tensor((16, 8), T.int8)):
with T.Kernel(1, threads=32):
frag = T.alloc_fragment((16, 8), T.int8)
smem = T.alloc_shared((16, 8), T.int8)
T.annotate_layout({frag: make_gemm_fragment_8x8_transposed().repeat([2, 1], repeat_on_thread=False)})
for i, j in T.Parallel(16, 8):
frag[i, j] = A[i, j]
T.copy(frag, smem)
target = {"kind": "cuda", "arch": "sm_100a"}
with tvm.target.Target(target):
artifact = tilelang.lower(main, target=target)
assert "tl::ptx_stmatrix_m16n8_x1_trans" in artifact.kernel_source
if __name__ == "__main__":
tilelang.testing.main()