mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-10-20 06:30:07 +08:00
* [BugFix] tvm_ffi: handle unused nullable buffers with shared symbolic shapes * [BugFix] ArgBinder: relax shared-shape binding for unused nullable buffers
145 lines
4.3 KiB
Python
145 lines
4.3 KiB
Python
import torch
|
|
import tilelang
|
|
import tilelang.testing
|
|
from tilelang import language as T
|
|
|
|
|
|
def test_nullable_shared_shape():
|
|
"""Test that buffers sharing a shape variable can be nullable."""
|
|
|
|
@tilelang.jit
|
|
def get_kernel():
|
|
m = T.dynamic("m")
|
|
|
|
@T.prim_func
|
|
def test_kernel(
|
|
a: T.Tensor[(m,), T.int32],
|
|
b: T.Tensor[(m,), T.int32],
|
|
c: T.Tensor[(m,), T.int32],
|
|
):
|
|
with T.Kernel(1, threads=64):
|
|
tx = T.get_thread_binding()
|
|
if tx == 0:
|
|
T.print(m)
|
|
|
|
return test_kernel
|
|
|
|
m = 200
|
|
kernel = get_kernel()
|
|
|
|
# Create test tensors
|
|
tensor_a = torch.randn((m,), device="cuda", dtype=torch.float32).to(torch.int32)
|
|
tensor_b = torch.randn((m,), device="cuda", dtype=torch.float32).to(torch.int32)
|
|
tensor_c = torch.randn((m,), device="cuda", dtype=torch.float32).to(torch.int32)
|
|
|
|
print("Test 1: All tensors provided")
|
|
kernel(tensor_a, tensor_b, tensor_c)
|
|
print("✓ PASS: All tensors provided")
|
|
|
|
print("\nTest 2: Only first tensor provided")
|
|
kernel(tensor_a, None, None)
|
|
print("✓ PASS: Only first tensor provided")
|
|
|
|
print("\nTest 3: Only middle tensor provided")
|
|
kernel(None, tensor_b, None)
|
|
print("✓ PASS: Only middle tensor provided")
|
|
|
|
print("\nTest 4: Only last tensor provided")
|
|
kernel(None, None, tensor_c)
|
|
print("✓ PASS: Only last tensor provided")
|
|
|
|
print("\nTest 5: First and last tensors provided")
|
|
kernel(tensor_a, None, tensor_c)
|
|
print("✓ PASS: First and last tensors provided")
|
|
|
|
print("\nTest 6: All tensors are None (should fail)")
|
|
try:
|
|
kernel(None, None, None)
|
|
print("✗ FAIL: Should have raised an error")
|
|
return False
|
|
except RuntimeError as e:
|
|
if "at least one non-null buffer" in str(e):
|
|
print(f"✓ PASS: Correctly rejected with error: {e}")
|
|
else:
|
|
print(f"✗ FAIL: Wrong error message: {e}")
|
|
return False
|
|
|
|
print("\n" + "=" * 60)
|
|
print("All tests passed!")
|
|
return True
|
|
|
|
|
|
def test_nullable_single_source_shape():
|
|
"""Test that a single buffer with a symbolic shape var must be non-null.
|
|
|
|
This guards against the previous segfault when binding m from x.shape[0]
|
|
with x == None.
|
|
"""
|
|
|
|
@tilelang.jit
|
|
def get_kernel():
|
|
m = T.dynamic("m")
|
|
|
|
@T.prim_func
|
|
def sample_kernel(x: T.Tensor[(m,), T.int32]):
|
|
with T.Kernel(1, threads=1):
|
|
tx = T.get_thread_binding()
|
|
if tx == 0:
|
|
T.print(m)
|
|
|
|
return sample_kernel
|
|
|
|
m = 16
|
|
kernel = get_kernel()
|
|
|
|
# Provide a valid tensor: should run
|
|
x = torch.randn((m,), device="cuda", dtype=torch.float32).to(torch.int32)
|
|
kernel(x)
|
|
|
|
# Passing None should not segfault; m binds to 0 and kernel is a no-op
|
|
kernel(None)
|
|
|
|
|
|
def test_nullable_shared_shape_with_no_source_buffers_but_other_tensor_present():
|
|
"""Test that unused buffers sharing a symbolic shape var can both be None.
|
|
|
|
Repro for:
|
|
- Two (or more) unused buffers have shape (m,)
|
|
- All buffers that mention `m` are passed as None
|
|
- Another (non-null) tensor argument exists, but does not mention `m`
|
|
|
|
TVM requires at least one non-null buffer to bind `m` when it appears in multiple
|
|
buffers. TileLang should handle this gracefully for truly-unused nullable buffers.
|
|
"""
|
|
|
|
@tilelang.jit(execution_backend="tvm_ffi")
|
|
def get_kernel():
|
|
m = T.dynamic("m")
|
|
|
|
@T.prim_func
|
|
def test_kernel(
|
|
a: T.Tensor[(m,), T.float16],
|
|
b: T.Tensor[(m,), T.float16],
|
|
out: T.Tensor[(1,), T.float16],
|
|
):
|
|
with T.Kernel(1, threads=32):
|
|
fragment = T.alloc_fragment((1,), T.float32)
|
|
T.copy(out[0], fragment)
|
|
T.copy(fragment, out[0])
|
|
|
|
return test_kernel
|
|
|
|
kernel = get_kernel()
|
|
|
|
out = torch.randn((1,), device="cuda", dtype=torch.float16)
|
|
out_ref = out.clone()
|
|
|
|
# Both `a` and `b` are None; they also share the symbolic shape var `m`.
|
|
# This should run because `a`/`b` are unused by the kernel body.
|
|
kernel(None, None, out)
|
|
torch.testing.assert_close(out, out_ref)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
tilelang.testing.main()
|