mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-10-31 08:13:15 +08:00
TVM lowers 16-bit math operations to CUDA's half-style names, but CUDA provides overloads only for native __half and __nv_bfloat16 values. Calls with CUTLASS half_t and bfloat16_t wrappers therefore failed to compile. Add explicit forwarding overloads for hexp, hexp2, hexp10, hlog, hlog2, hlog10, hsin, hcos, hfloor, hceil, hrint, and htrunc. Each overload converts the wrapper to its native CUDA type, calls the existing intrinsic, and wraps the result back in the original type. Fold the previous standalone bfloat16 hexp bridge into the complete overload set. Codegen and the fast-math path remain unchanged. Add scalar runtime coverage for both wrapper types using materialized temporaries, separate valid input domains, generated-call assertions, and torch references.
142 lines
4.4 KiB
Python
142 lines
4.4 KiB
Python
import pytest
|
|
import torch
|
|
|
|
import tilelang
|
|
import tilelang.language as T
|
|
import tilelang.testing
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
def test_language_ldg_codegen():
|
|
N = 128
|
|
|
|
@T.prim_func
|
|
def main(
|
|
x: T.Tensor((N,), T.float32),
|
|
y: T.Tensor((N,), T.float32),
|
|
):
|
|
with T.Kernel(N, threads=32) as pid:
|
|
# Explicitly request read-only cache load for x[pid]
|
|
y[pid] = T.__ldg(x[pid]) + 1.0
|
|
|
|
# Compile for CUDA and retrieve generated CUDA source
|
|
kernel = tilelang.compile(main, out_idx=[1], target="cuda")
|
|
src = kernel.get_kernel_source()
|
|
print(src)
|
|
# Assert that codegen uses __ldg on CUDA backend
|
|
# We look for the intrinsic call with address-of argument
|
|
assert "__ldg(" in src, "Expected __ldg call in generated CUDA source"
|
|
assert "__ldg(&" in src or "__ldg(&(" in src, "Expected address-of form in __ldg call"
|
|
|
|
|
|
def run_ldg_roundtrip(dtype, N=128):
|
|
@T.prim_func
|
|
def main(
|
|
x: T.Tensor((N,), dtype),
|
|
y: T.Tensor((N,), dtype),
|
|
):
|
|
with T.Kernel(N, threads=32) as pid:
|
|
y[pid] = T.__ldg(x[pid])
|
|
|
|
kernel = tilelang.compile(main, out_idx=[1], target="cuda")
|
|
# Match the address-of form emitted by CUDA codegen.
|
|
assert "__ldg(&(" in kernel.get_kernel_source()
|
|
|
|
x = torch.randn(N, device="cuda").to(dtype.as_torch())
|
|
assert torch.equal(kernel(x), x)
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@pytest.mark.parametrize("dtype", [T.float16, T.bfloat16])
|
|
def test_language_ldg_roundtrip(dtype):
|
|
run_ldg_roundtrip(dtype)
|
|
|
|
|
|
# (op, lowered h* intrinsic, torch reference, input index), in the row order
|
|
# half_math_kernel writes.
|
|
HALF_MATH_OPS = (
|
|
("exp", "hexp", torch.exp, 0),
|
|
("exp2", "hexp2", torch.exp2, 0),
|
|
("exp10", "hexp10", lambda x: torch.pow(10.0, x), 0),
|
|
("log", "hlog", torch.log, 0),
|
|
("log2", "hlog2", torch.log2, 0),
|
|
("log10", "hlog10", torch.log10, 0),
|
|
("sin", "hsin", torch.sin, 1),
|
|
("cos", "hcos", torch.cos, 1),
|
|
("floor", "hfloor", torch.floor, 1),
|
|
("ceil", "hceil", torch.ceil, 1),
|
|
("round", "hrint", torch.round, 1),
|
|
("trunc", "htrunc", torch.trunc, 1),
|
|
)
|
|
|
|
|
|
def half_math_kernel(dtype, N):
|
|
@T.prim_func
|
|
def main(
|
|
A: T.Tensor((N,), dtype),
|
|
C: T.Tensor((N,), dtype),
|
|
B: T.Tensor((len(HALF_MATH_OPS), N), dtype),
|
|
):
|
|
with T.Kernel(1, threads=N):
|
|
i = T.get_thread_binding()
|
|
# Keep the temporaries: they copy-initialize, a store only assigns.
|
|
exp_value = T.exp(A[i])
|
|
exp2_value = T.exp2(A[i])
|
|
exp10_value = T.exp10(A[i])
|
|
log_value = T.log(A[i])
|
|
log2_value = T.log2(A[i])
|
|
log10_value = T.log10(A[i])
|
|
sin_value = T.sin(C[i])
|
|
cos_value = T.cos(C[i])
|
|
floor_value = T.floor(C[i])
|
|
ceil_value = T.ceil(C[i])
|
|
round_value = T.round(C[i])
|
|
trunc_value = T.trunc(C[i])
|
|
B[0, i] = exp_value
|
|
B[1, i] = exp2_value
|
|
B[2, i] = exp10_value
|
|
B[3, i] = log_value
|
|
B[4, i] = log2_value
|
|
B[5, i] = log10_value
|
|
B[6, i] = sin_value
|
|
B[7, i] = cos_value
|
|
B[8, i] = floor_value
|
|
B[9, i] = ceil_value
|
|
B[10, i] = round_value
|
|
B[11, i] = trunc_value
|
|
|
|
return main
|
|
|
|
|
|
@tilelang.testing.requires_cuda
|
|
@pytest.mark.parametrize("dtype", [T.float16, T.bfloat16])
|
|
def test_half_math_intrinsics(dtype):
|
|
N = 128
|
|
kernel = tilelang.compile(half_math_kernel(dtype, N), target="cuda")
|
|
body = kernel.get_kernel_source()
|
|
body = body[body.rindex("__global__") :]
|
|
|
|
torch_dtype = dtype.as_torch()
|
|
# [0.3, 4.3) keeps exp10 finite in float16; the mixed-sign input avoids
|
|
# integers and rounding ties.
|
|
a = (torch.arange(N, device="cuda") * 0.03125 + 0.3).to(torch_dtype)
|
|
c = ((torch.arange(N, device="cuda") - 63.5) * 0.0625).to(torch_dtype)
|
|
b = torch.empty(len(HALF_MATH_OPS), N, device="cuda", dtype=torch_dtype)
|
|
kernel(a, c, b)
|
|
|
|
inputs = (a, c)
|
|
for row, (name, intrinsic, ref_fn, which) in enumerate(HALF_MATH_OPS):
|
|
assert body.count(f"{intrinsic}(") == 1
|
|
ref = ref_fn(inputs[which].float()).to(torch_dtype)
|
|
torch.testing.assert_close(
|
|
b[row].float(),
|
|
ref.float(),
|
|
atol=2e-2,
|
|
rtol=2e-2,
|
|
msg=lambda base, name=name: f"T.{name} mismatch\n{base}",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tilelang.testing.main()
|