Files
tilelang/testing/python/kernel/test_tilelang_kernel_sm75_gemm_fma.py
T
Chennes 7fd95363cc [CUDA] Extend the GEMM FMA fallback to SM75 (#2811)
On SM75, SelectInst routed GEMMs to the plain MMA path even when their
dtype combination had no native Turing mma.sync atom. In particular,
fp32, fp64, and bf16 selected SM80-only atoms: bf16 failed an nvcc
static_assert, while fp32 and fp64 compiled into runtime traps that
invalidated the CUDA context.

Add AllowTuringMma alongside the Volta check introduced in #2339 and
select the existing cuda.fma implementation when SM75 has no native
atom. Keep the check dtype-only because the SM75 emitter supports the
same operand scopes and transposes as the generic MMA path. Valid f16
combinations remain on native MMA rather than being demoted to the
slower and less accurate serial FMA fallback.

Add compile-only dispatch checks pinned to an explicit sm_75 target,
so they run on any CUDA runner, verifying that fp32, fp64, and bf16
use the fallback while f16 remains on MMA. Add execution checks gated
on compute capability 7.5.
2026-07-30 13:05:55 +08:00

103 lines
4.0 KiB
Python

import torch
import tilelang
import tilelang.language as T
import tilelang.testing
_SM75 = {"kind": "cuda", "arch": "sm_75"}
def _make_ss_gemm(M, N, K, in_dtype, accum_dtype, threads=128):
@T.prim_func
def main(
A: T.Tensor((M, K), in_dtype),
B: T.Tensor((K, N), in_dtype),
C: T.Tensor((M, N), accum_dtype),
):
with T.Kernel(1, threads=threads):
A_shared = T.alloc_shared((M, K), in_dtype)
B_shared = T.alloc_shared((K, N), in_dtype)
C_local = T.alloc_fragment((M, N), accum_dtype)
T.copy(A, A_shared)
T.copy(B, B_shared)
T.clear(C_local)
T.gemm(A_shared, B_shared, C_local, policy=T.GemmWarpPolicy.FullRow)
T.copy(C_local, C)
return main
# ---------------------------------------------------------------------------
# Compile-only dispatch checks pinned to an explicit sm_75 target, so they run
# on any CUDA runner regardless of the host GPU. Turing has no fp32/tf32,
# fp64, or bf16 mma.sync atoms; these dtypes must lower through the FMA
# fallback instead of tl::mma_sync (which fails nvcc's static_assert for bf16
# and compiles into a cutlass runtime trap for fp32/fp64).
# ---------------------------------------------------------------------------
@tilelang.testing.requires_cuda
def test_sm75_fp32_gemm_compiles_to_fma_fallback():
kernel = tilelang.compile(_make_ss_gemm(64, 64, 32, T.float32, T.float32), target=_SM75, out_idx=[2])
assert "tl::mma_sync" not in kernel.get_kernel_source()
@tilelang.testing.requires_cuda
def test_sm75_fp64_gemm_compiles_to_fma_fallback():
kernel = tilelang.compile(_make_ss_gemm(32, 32, 32, T.float64, T.float64), target=_SM75, out_idx=[2])
assert "tl::mma_sync" not in kernel.get_kernel_source()
@tilelang.testing.requires_cuda
def test_sm75_bf16_gemm_compiles_to_fma_fallback():
kernel = tilelang.compile(_make_ss_gemm(64, 64, 32, T.bfloat16, T.float32), target=_SM75, out_idx=[2])
assert "tl::mma_sync" not in kernel.get_kernel_source()
@tilelang.testing.requires_cuda
def test_sm75_fp16_gemm_keeps_mma_path():
"""f16 has native Turing atoms; the fallback must not demote it."""
kernel = tilelang.compile(_make_ss_gemm(64, 64, 32, T.float16, T.float32), target=_SM75, out_idx=[2])
assert "tl::mma_sync" in kernel.get_kernel_source()
# ---------------------------------------------------------------------------
# Execution checks on real Turing hardware.
# ---------------------------------------------------------------------------
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
def test_sm75_fp32_gemm_fma_matches_torch():
kernel = tilelang.compile(_make_ss_gemm(64, 64, 32, T.float32, T.float32), target="cuda", out_idx=[2])
a = torch.randn((64, 32), device="cuda", dtype=torch.float32)
b = torch.randn((32, 64), device="cuda", dtype=torch.float32)
c = kernel(a, b)
tilelang.testing.torch_assert_close(c, a @ b, rtol=1e-4, atol=1e-4)
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
def test_sm75_fp64_gemm_fma_matches_torch():
kernel = tilelang.compile(_make_ss_gemm(32, 32, 32, T.float64, T.float64), target="cuda", out_idx=[2])
a = torch.randn((32, 32), device="cuda", dtype=torch.float64)
b = torch.randn((32, 32), device="cuda", dtype=torch.float64)
c = kernel(a, b)
tilelang.testing.torch_assert_close(c, a @ b, rtol=1e-8, atol=1e-8)
@tilelang.testing.requires_cuda
@tilelang.testing.requires_cuda_compute_version_eq(7, 5)
def test_sm75_bf16_gemm_fma_matches_torch():
kernel = tilelang.compile(_make_ss_gemm(64, 64, 32, T.bfloat16, T.float32), target="cuda", out_idx=[2])
a = torch.randn((64, 32), device="cuda", dtype=torch.bfloat16)
b = torch.randn((32, 64), device="cuda", dtype=torch.bfloat16)
c = kernel(a, b)
ref = a.float() @ b.float()
tilelang.testing.torch_assert_close(c, ref, rtol=1e-2, atol=1e-2, max_mismatched_ratio=0.01)
if __name__ == "__main__":
tilelang.testing.main()