mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-10-21 06:40:23 +08:00
* [JIT] Improve kernel lookup cache keys * [JIT] Require hashable call-form cache keys * [JIT] Reject unsupported unhashable cache keys * [JIT] Avoid exception-driven cache key freezing * [JIT] Document argument binding paths
203 lines
5.9 KiB
Python
203 lines
5.9 KiB
Python
from dataclasses import dataclass
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
import tilelang
|
|
import tilelang.language as T
|
|
|
|
|
|
@tilelang.jit
|
|
def _lazy_kernel_factory(hidden: int, token_stride: int, sf_only: bool = False, cast_only: bool = False):
|
|
@T.prim_func
|
|
def kernel():
|
|
T.evaluate(0)
|
|
|
|
return kernel
|
|
|
|
|
|
def test_lazy_jit_cache_key_canonicalizes_defaults_and_call_forms():
|
|
positional_defaults_key, _ = _lazy_kernel_factory.func.parse_args(512, 512)
|
|
positional_explicit_key, _ = _lazy_kernel_factory.func.parse_args(512, 512, False, False)
|
|
kwargs_key, _ = _lazy_kernel_factory.func.parse_args(
|
|
hidden=512,
|
|
token_stride=512,
|
|
sf_only=False,
|
|
cast_only=False,
|
|
)
|
|
reordered_kwargs_key, _ = _lazy_kernel_factory.func.parse_args(
|
|
cast_only=False,
|
|
sf_only=False,
|
|
token_stride=512,
|
|
hidden=512,
|
|
)
|
|
|
|
expected = ((512, 512, False, False), None)
|
|
assert positional_defaults_key == expected
|
|
assert positional_explicit_key == expected
|
|
assert kwargs_key == expected
|
|
assert reordered_kwargs_key == expected
|
|
|
|
|
|
def test_lazy_jit_call_form_cache_skips_rebinding(monkeypatch):
|
|
sentinel_kernel = object()
|
|
canonical_key, _ = _lazy_kernel_factory.func.parse_args(
|
|
hidden=512,
|
|
token_stride=512,
|
|
)
|
|
_lazy_kernel_factory.mode = "lazy"
|
|
_lazy_kernel_factory.func.set_mode("lazy")
|
|
_lazy_kernel_factory._kernel_cache[canonical_key] = sentinel_kernel
|
|
_lazy_kernel_factory._call_form_cache.clear()
|
|
|
|
assert _lazy_kernel_factory(hidden=512, token_stride=512) is sentinel_kernel
|
|
|
|
def fail_parse_args(*args, **kwargs):
|
|
raise AssertionError("call-form cache hit should skip parse_args")
|
|
|
|
monkeypatch.setattr(_lazy_kernel_factory.func, "parse_args", fail_parse_args)
|
|
assert _lazy_kernel_factory(hidden=512, token_stride=512) is sentinel_kernel
|
|
|
|
|
|
def test_lazy_jit_call_form_cache_requires_hashable_arguments():
|
|
_lazy_kernel_factory.mode = "lazy"
|
|
_lazy_kernel_factory.func.set_mode("lazy")
|
|
_lazy_kernel_factory._call_form_cache.clear()
|
|
|
|
with pytest.raises(TypeError, match="unhashable"):
|
|
_lazy_kernel_factory(hidden=512, token_stride=512, opts=[1, 2])
|
|
|
|
|
|
def test_jit_argument_binding_reports_python_call_errors():
|
|
with pytest.raises(TypeError, match="multiple values"):
|
|
_lazy_kernel_factory.func.parse_args(512, hidden=512, token_stride=512)
|
|
|
|
with pytest.raises(TypeError, match="missing a required argument"):
|
|
_lazy_kernel_factory.func.parse_args(hidden=512)
|
|
|
|
extra_const_key, _ = _lazy_kernel_factory.func.parse_args(
|
|
hidden=512,
|
|
token_stride=512,
|
|
M=1024,
|
|
)
|
|
assert extra_const_key == ((512, 512, False, False, (("M", 1024),)), None)
|
|
|
|
|
|
@tilelang.jit
|
|
def _lazy_var_keyword_factory(M: int, **kwargs):
|
|
@T.prim_func
|
|
def kernel():
|
|
T.evaluate(0)
|
|
|
|
return kernel
|
|
|
|
|
|
def test_lazy_jit_var_keyword_kwargs_are_flattened_and_hashable():
|
|
key, kernel_args = _lazy_var_keyword_factory.func.parse_args(
|
|
128,
|
|
N=64,
|
|
opts={"axis": [1, 2]},
|
|
)
|
|
hash(key)
|
|
assert kernel_args == {}
|
|
|
|
p1_key, tensor_args, compile_kwargs = _lazy_var_keyword_factory.func._parse_phase1_key(
|
|
128,
|
|
N=64,
|
|
opts={"axis": [1, 2]},
|
|
)
|
|
hash(p1_key)
|
|
assert tensor_args == {}
|
|
assert compile_kwargs == {"M": 128, "N": 64, "opts": {"axis": [1, 2]}}
|
|
assert "kwargs" not in compile_kwargs
|
|
|
|
|
|
_UNHASHABLE_DEFAULT_OPTS = [1, 2]
|
|
_UNHASHABLE_DEFAULT_CONFIG = {"axis": [0]}
|
|
|
|
|
|
@dataclass
|
|
class _UnhashableConfig:
|
|
axis: int
|
|
|
|
|
|
@tilelang.jit
|
|
def _lazy_unhashable_default_factory(
|
|
M: int,
|
|
opts=_UNHASHABLE_DEFAULT_OPTS,
|
|
config=_UNHASHABLE_DEFAULT_CONFIG,
|
|
):
|
|
@T.prim_func
|
|
def kernel():
|
|
T.evaluate(0)
|
|
|
|
return kernel
|
|
|
|
|
|
def test_lazy_jit_unhashable_defaults_are_normalized_in_cache_key():
|
|
omitted_key, kernel_args = _lazy_unhashable_default_factory.func.parse_args(128)
|
|
explicit_key, _ = _lazy_unhashable_default_factory.func.parse_args(
|
|
128,
|
|
opts=[1, 2],
|
|
config={"axis": [0]},
|
|
)
|
|
|
|
hash(omitted_key)
|
|
assert kernel_args == {}
|
|
assert omitted_key == explicit_key
|
|
|
|
|
|
def test_lazy_jit_unsupported_unhashable_compile_time_value_errors():
|
|
with pytest.raises(TypeError, match="Unsupported unhashable JIT compile-time cache key value"):
|
|
_lazy_unhashable_default_factory.func.parse_args(
|
|
128,
|
|
config=_UnhashableConfig(axis=0),
|
|
)
|
|
|
|
|
|
@tilelang.jit
|
|
def _copy_kernel(A, B, block: int = 64, dtype: T.dtype = T.float32):
|
|
M, N = T.const("M N")
|
|
A: T.Tensor[[M, N], dtype]
|
|
B: T.Tensor[[M, N], dtype]
|
|
|
|
with T.Kernel(T.ceildiv(M, block), T.ceildiv(N, block), threads=128) as (pid_m, pid_n):
|
|
T.copy(A[pid_m * block, pid_n * block], B[pid_m * block, pid_n * block])
|
|
|
|
|
|
def test_eager_jit_phase1_key_excludes_tensor_args_and_applies_defaults():
|
|
a = torch.empty(8, 8)
|
|
b = torch.empty(8, 8)
|
|
|
|
p1_key, tensor_args, compile_kwargs = _copy_kernel.func._parse_phase1_key(a, b)
|
|
|
|
assert p1_key == (64, T.float32)
|
|
assert tensor_args == {"A": a, "B": b}
|
|
assert compile_kwargs == {"block": 64, "dtype": T.float32}
|
|
|
|
|
|
@tilelang.jit
|
|
def _copy_kernel_with_var_keywords(A, B, block: int = 64, **metadata):
|
|
M, N = T.const("M N")
|
|
A: T.Tensor[[M, N], T.float32]
|
|
B: T.Tensor[[M, N], T.float32]
|
|
|
|
with T.Kernel(T.ceildiv(M, block), T.ceildiv(N, block), threads=128) as (pid_m, pid_n):
|
|
T.copy(A[pid_m * block, pid_n * block], B[pid_m * block, pid_n * block])
|
|
|
|
|
|
def test_eager_jit_var_keyword_compile_kwargs_are_flattened():
|
|
a = torch.empty(8, 8)
|
|
b = torch.empty(8, 8)
|
|
|
|
p1_key, tensor_args, compile_kwargs = _copy_kernel_with_var_keywords.func._parse_phase1_key(
|
|
a,
|
|
b,
|
|
opts={"axis": [1, 2]},
|
|
)
|
|
|
|
hash(p1_key)
|
|
assert tensor_args == {"A": a, "B": b}
|
|
assert compile_kwargs == {"block": 64, "opts": {"axis": [1, 2]}}
|
|
assert "metadata" not in compile_kwargs
|