"""Tests for T.gemm on CPU target (GemmScalar path). Verifies that T.gemm works correctly with target="c" for various matrix sizes, block sizes, and transpose combinations. """ import pytest import torch import tilelang import tilelang.testing from tilelang import tvm as tvm import tilelang.language as T def matmul(M, N, K, block_M, block_N, block_K, trans_A=False, trans_B=False, dtype=T.float32, accum_dtype=T.float32): A_shape = (K, M) if trans_A else (M, K) B_shape = (N, K) if trans_B else (K, N) A_local_shape = (block_K, block_M) if trans_A else (block_M, block_K) B_local_shape = (block_N, block_K) if trans_B else (block_K, block_N) @T.prim_func def main( A: T.Tensor(A_shape, dtype), B: T.Tensor(B_shape, dtype), C: T.Tensor((M, N), dtype), ): with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M)) as (bx, by): A_local = T.alloc_local(A_local_shape, dtype) B_local = T.alloc_local(B_local_shape, dtype) C_local = T.alloc_local((block_M, block_N), accum_dtype) T.clear(C_local) for ko in T.serial(T.ceildiv(K, block_K)): if trans_A: T.copy(A[ko * block_K, by * block_M], A_local) else: T.copy(A[by * block_M, ko * block_K], A_local) if trans_B: T.copy(B[bx * block_N, ko * block_K], B_local) else: T.copy(B[ko * block_K, bx * block_N], B_local) T.gemm(A_local, B_local, C_local, trans_A, trans_B) T.copy(C_local, C[by * block_M, bx * block_N]) return main def ref_matmul(A, B, trans_A, trans_B): if trans_A: A = A.T if trans_B: B = B.T return torch.matmul(A.float(), B.float()).to(A.dtype) def run_gemm_codegen(M, N, K, block_M, block_N, block_K, trans_A=False, trans_B=False): func = matmul(M, N, K, block_M, block_N, block_K, trans_A, trans_B) with tvm.target.Target("c"): artifact = tilelang.lower(func, target="c", target_host="c") code = artifact.kernel_source assert code is not None, "Code generation failed" assert "matmul" in code or "main" in code, "Generated code missing kernel function" return code def run_gemm_compile(M, N, K, block_M, block_N, block_K, trans_A=False, trans_B=False, dtype=T.float32): func = matmul(M, N, K, block_M, block_N, block_K, trans_A, trans_B, dtype=dtype) with tvm.target.Target("c"): kernel = tilelang.compile(func, out_idx=[2], target="c", target_host="c", execution_backend="cython") torch_dtype = torch.__getattribute__(dtype) A_shape = (K, M) if trans_A else (M, K) B_shape = (N, K) if trans_B else (K, N) A = torch.randn(A_shape, dtype=torch_dtype) B = torch.randn(B_shape, dtype=torch_dtype) C = kernel(A, B) C_ref = ref_matmul(A, B, trans_A, trans_B) tilelang.testing.torch_assert_close(C, C_ref, atol=1e-2, rtol=1e-2) # --- Codegen tests --- def test_codegen_basic(): run_gemm_codegen(128, 128, 128, 64, 64, 64) def test_codegen_rectangular(): run_gemm_codegen(256, 512, 128, 64, 64, 64) def test_codegen_trans_A(): run_gemm_codegen(128, 128, 128, 64, 64, 64, trans_A=True) def test_codegen_trans_B(): run_gemm_codegen(128, 128, 128, 64, 64, 64, trans_B=True) # --- Compile + correctness tests --- @pytest.mark.parametrize( "M,N,K,block_M,block_N,block_K", [ (128, 128, 128, 64, 64, 64), (256, 256, 256, 64, 64, 64), (256, 512, 128, 64, 64, 64), (512, 512, 512, 128, 128, 128), ], ) def test_gemm_f32_nn(M, N, K, block_M, block_N, block_K): run_gemm_compile(M, N, K, block_M, block_N, block_K) def test_gemm_f32_tn(): run_gemm_compile(128, 128, 128, 64, 64, 64, trans_A=True) def test_gemm_f32_nt(): run_gemm_compile(128, 128, 128, 64, 64, 64, trans_B=True) def test_gemm_f32_tt(): run_gemm_compile(128, 128, 128, 64, 64, 64, trans_A=True, trans_B=True) if __name__ == "__main__": tilelang.testing.main()