mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-10-21 06:40:23 +08:00
The SM75 GEMM emitter derives MMA shapes from input dtype bits alone, so it already emits the (16,8,8) shape for fp16->fp16 accumulation (identical to the registered fp16->fp32 case) and the (8,8,16) shape for uint8 inputs (identical to signed int8). But the C++ dispatcher table only covered the three configs tested by #2198 (fp16->fp32, int8, int4), so requesting fp16->fp16-accum or uint8 GEMM on Turing failed at nvcc with an opaque `tl::mma_sync: unsupported configuration` static_assert. Add the two missing tl::detail SM75 atoms and register their dispatchers, following the precedent set by #2198's hand-written int4/uint4 structs (CuTe ships only fp16->fp32 and signed-int8 SM75 atoms). The uint8 atom uses the non-saturating PTX form to match the existing signed-int8 path. Verified on a TITAN RTX (SM75): emitted PTX is mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 and mma.sync.aligned.m8n8k16.row.col.s32.u8.u8.s32, and end-to-end GEMM numerics match torch.
139 lines
5.0 KiB
Python
139 lines
5.0 KiB
Python
import torch
|
|
|
|
import tilelang
|
|
import tilelang.language as T
|
|
import tilelang.testing
|
|
|
|
|
|
def _make_gemm_kernel(M, N, K, block_M, block_N, block_K, in_dtype, out_dtype, accum_dtype):
|
|
@T.prim_func
|
|
def main(
|
|
A: T.Tensor((M, K), in_dtype),
|
|
B: T.Tensor((N, K), in_dtype),
|
|
C: T.Tensor((M, N), out_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), in_dtype)
|
|
B_shared = T.alloc_shared((block_N, block_K), in_dtype)
|
|
C_local = T.alloc_fragment((block_M, block_N), accum_dtype)
|
|
|
|
T.clear(C_local)
|
|
for ko in T.serial(T.ceildiv(K, block_K)):
|
|
T.copy(A[by * block_M, ko * block_K], A_shared)
|
|
T.copy(B[bx * block_N, ko * block_K], B_shared)
|
|
T.gemm(A_shared, B_shared, C_local, transpose_B=True)
|
|
T.copy(C_local, C[by * block_M, bx * block_N])
|
|
|
|
return main
|
|
|
|
|
|
def _assert_mma_sync_shape(source, a_type, c_type, m, n, k):
|
|
expected = f"tl::mma_sync<tl::DataType::{a_type}, tl::DataType::{a_type}, tl::DataType::{c_type}, {m}, {n}, {k}, false, true>"
|
|
assert expected in source
|
|
assert "mma_sync_sm70" not in source
|
|
|
|
|
|
def _pack_int4(tensor: torch.Tensor) -> torch.Tensor:
|
|
tensor_i16 = tensor.to(torch.int16)
|
|
packed = (tensor_i16[..., ::2] & 0x0F) | ((tensor_i16[..., 1::2] & 0x0F) << 4)
|
|
return packed.to(torch.int8).contiguous()
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
|
|
def test_sm75_f16_gemm_uses_m16n8k8_and_matches_torch():
|
|
M = N = K = 128
|
|
kernel = tilelang.compile(
|
|
_make_gemm_kernel(M, N, K, 64, 64, 32, T.float16, T.float32, T.float32),
|
|
target="cuda",
|
|
out_idx=[2],
|
|
)
|
|
_assert_mma_sync_shape(kernel.get_kernel_source(), "kFloat16", "kFloat32", 16, 8, 8)
|
|
|
|
a = torch.randn((M, K), device="cuda", dtype=torch.float16)
|
|
b = torch.randn((N, K), device="cuda", dtype=torch.float16)
|
|
c = kernel(a, b)
|
|
ref = a.float() @ b.float().T
|
|
|
|
tilelang.testing.torch_assert_close(c, ref, rtol=1e-2, atol=1e-2, max_mismatched_ratio=0.01)
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
|
|
def test_sm75_f16_gemm_f16_accum_uses_m16n8k8_and_matches_torch():
|
|
M = N = K = 128
|
|
kernel = tilelang.compile(
|
|
_make_gemm_kernel(M, N, K, 64, 64, 32, T.float16, T.float16, T.float16),
|
|
target="cuda",
|
|
out_idx=[2],
|
|
)
|
|
_assert_mma_sync_shape(kernel.get_kernel_source(), "kFloat16", "kFloat16", 16, 8, 8)
|
|
|
|
a = torch.randn((M, K), device="cuda", dtype=torch.float16) * 0.1
|
|
b = torch.randn((N, K), device="cuda", dtype=torch.float16) * 0.1
|
|
c = kernel(a, b)
|
|
ref = (a.float() @ b.float().T).to(torch.float16)
|
|
|
|
tilelang.testing.torch_assert_close(c, ref, rtol=1e-2, atol=1e-2, max_mismatched_ratio=0.01)
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
|
|
def test_sm75_uint8_gemm_uses_m8n8k16_and_matches_torch():
|
|
M = N = K = 128
|
|
kernel = tilelang.compile(
|
|
_make_gemm_kernel(M, N, K, 64, 64, 64, T.uint8, T.int32, T.int32),
|
|
target="cuda",
|
|
out_idx=[2],
|
|
)
|
|
_assert_mma_sync_shape(kernel.get_kernel_source(), "kUInt8", "kInt32", 8, 8, 16)
|
|
|
|
a = torch.randint(0, 256, (M, K), device="cuda", dtype=torch.uint8)
|
|
b = torch.randint(0, 256, (N, K), device="cuda", dtype=torch.uint8)
|
|
c = kernel(a, b)
|
|
ref = (a.cpu().to(torch.int32) @ b.cpu().to(torch.int32).T).to(device="cuda")
|
|
|
|
tilelang.testing.torch_assert_close(c, ref, rtol=0, atol=0)
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
|
|
def test_sm75_int8_gemm_uses_m8n8k16_and_matches_torch():
|
|
M = N = K = 128
|
|
kernel = tilelang.compile(
|
|
_make_gemm_kernel(M, N, K, 64, 64, 64, T.int8, T.int32, T.int32),
|
|
target="cuda",
|
|
out_idx=[2],
|
|
)
|
|
_assert_mma_sync_shape(kernel.get_kernel_source(), "kInt8", "kInt32", 8, 8, 16)
|
|
|
|
a = torch.randint(-8, 8, (M, K), device="cuda", dtype=torch.int8)
|
|
b = torch.randint(-8, 8, (N, K), device="cuda", dtype=torch.int8)
|
|
c = kernel(a, b)
|
|
ref = (a.cpu().to(torch.int32) @ b.cpu().to(torch.int32).T).to(device="cuda")
|
|
|
|
tilelang.testing.torch_assert_close(c, ref, rtol=0, atol=0)
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
|
|
def test_sm75_int4_gemm_uses_m8n8k32_and_matches_torch():
|
|
M = N = K = 128
|
|
kernel = tilelang.compile(
|
|
_make_gemm_kernel(M, N, K, 64, 64, 64, T.int4, T.int32, T.int32),
|
|
target="cuda",
|
|
out_idx=[2],
|
|
)
|
|
_assert_mma_sync_shape(kernel.get_kernel_source(), "kInt4", "kInt32", 8, 8, 32)
|
|
|
|
a = torch.randint(-8, 8, (M, K), device="cuda", dtype=torch.int8)
|
|
b = torch.randint(-8, 8, (N, K), device="cuda", dtype=torch.int8)
|
|
c = kernel(_pack_int4(a), _pack_int4(b))
|
|
ref = (a.cpu().to(torch.int32) @ b.cpu().to(torch.int32).T).to(device="cuda")
|
|
|
|
tilelang.testing.torch_assert_close(c, ref, rtol=0, atol=0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tilelang.testing.main()
|