mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-11-01 08:23:24 +08:00
* [Feature] Batched AllReduce for better T.reduce performance (#1928)
When each thread holds multiple values to reduce, the previous code
emitted one AllReduce call per element inside a loop, causing redundant
synchronization barriers (e.g. 8 values × 6 syncs = 48 syncs).
This change adds batch support to AllReduce so all per-thread values are
reduced in a single butterfly pass sharing barriers (6 syncs total).
- Add `batch_size` and `workspace_stride` template parameters to
`tl::AllReduce` (defaults preserve existing scalar behaviour).
- In ReduceOpNode::Lower, detect when batching is beneficial
(batch_size > 1 and cross-warp reduction) and restructure into three
phases: loop(init + local reduce), batched AllReduce, loop(copy-back).
- Apply same optimisation to FinalizeReducerOpNode::Lower.
- Mirror template changes in HIP AllReduce.
Closes #1928
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [Test] Add codegen tests for batched AllReduce
Verify that the batched AllReduce path (batch_size > 1, workspace_stride)
is emitted in the generated CUDA source for reduce_max, reduce_sum, and
reduce_min with cross-warp reductions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [Bugfix] Fix batched AllReduce codegen for HIP/ROCm targets
HIP AllReduce template has no Barrier type parameter, so the batched
path must omit tl::SyncThreadsBarrier for ROCm targets. Adds
TargetIsRocm() checks in both reduce.cc and finalize_reducer.cc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [Fix] Use target-aware warp size for cross-warp reduction threshold
Address CodeRabbit review: ROCm wavefronts are 64-wide, so the cross-warp
threshold should be 64 (not 32) on HIP targets. This avoids unnecessary
workspace allocation and batched codegen for single-wavefront reductions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [Style] Fix ruff format for test assertion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [Bugfix] Support batched AllReduce in CuteDSL Python codegen
The CuteDSL codegen translates C++ AllReduce template calls to Python.
With the new batch_size and workspace_stride template params, the
generated Python calls AllReduce() with 7 args instead of 5. Add
batch_size and workspace_stride params to the Python AllReduce function
and implement batched reduction logic for both standard and Hopper paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* [Feature] Add explicit batch parameter to T.reduce for user-controlled batched AllReduce
Expose a `batch` parameter on T.reduce (and all T.reduce_* wrappers,
default 1 = scalar / current behaviour). When batch > 1 the compiler:
- validates batch divides the per-thread output element count N;
- emits ceil(N/batch) calls to the new AllReduce::run_batch interface,
each sharing a single pair of barriers across all batch elements;
- allocates workspace of size reducing_threads * batch (only for cross-
warp reduce, i.e. reducing_threads > 32).
Key differences from the existing auto-detection approach:
* The user controls when batching is enabled — no silent smem increase.
* workspace_stride = reducing_threads (not block_size), so workspace is
minimal and does not blow past the GPU shared-memory limit.
* run_batch (not run) avoids C++ overload-resolution ambiguity when a
pointer is passed as the first argument.
Files changed:
- src/op/reduce.h: add `int batch{1}` field to ReduceOpNode
- src/op/reduce.cc: read "batch" annotation; validate; emit N/batch
batched AllReduce calls with address_of per-chunk pointers
- src/tl_templates/cuda/reduce.h: rename batch run() -> run_batch(),
fix recursive call
- src/tl_templates/hip/reduce.h: same
- tilelang/language/reduce_op.py: add batch param to reduce() and all
reduce_max/min/sum/absmax/abssum/bitand/bitor/bitxor wrappers
- testing/python/language/test_tilelang_language_reduce.py: update
test_batched_allreduce_codegen to use explicit batch=2 and match
run_batch in generated source
* [Fix] Use run_batch in finalize_reducer batched AllReduce codegen
finalize_reducer.cc also generates batched AllReduce calls (used by
T.alloc_reducer / T.finalize_reducer), but was still emitting ::run
instead of ::run_batch, causing a compiler error because the batched
pointer-based interface is now named run_batch to avoid overload
ambiguity.
* rocm fix
* [Feature] Add explicit batch parameter to T.finalize_reducer
Mirrors the batch= API already available on T.reduce:
- batch=1 (default) keeps the existing scalar AllReduce::run path
- batch>1 emits AllReduce::run_batch, sharing barriers across batch
output elements and reducing barrier count by batch×
C++ changes:
- FinalizeReducerOpNode gains an int batch{1} field (registered via
reflection) and reads it from the 'batch' annotation in the constructor
- Lower() validates batch against the layout output-element count and
uses effective_batch to drive use_batch / codegen
Python changes:
- finalize_reducer(reducer, batch=1) – new optional argument
- batch < 1 raises ValueError (consistent with T.reduce)
Tests: add test_batched_finalize_reducer_codegen covering sum/max/min
with explicit batch values, verifying run_batch is emitted with the
correct template argument.
* [Test] Add comprehensive tests for T.finalize_reducer batch parameter
Covers all cases introduced by the explicit batch= API:
Codegen tests:
- batch>1 emits AllReduce::run_batch with correct template argument
- batch=1 (default) does NOT emit run_batch (scalar path)
Correctness tests:
- batch=1 scalar path: sum/max/min across float32/float16 (PASS)
- batch==block_M full-batch: correct by coincidence (PASS)
- batch>1 partial-batch: xfail(strict=True) – known bug where
run_batch receives buffer->data directly but fragment layout is
non-contiguous per thread; root cause documented in
work/bug-finalize-reducer-batch.md
Error-input tests:
- batch=0 / batch<0 raise ValueError at Python layer
- batch > total output elements raises at compile/lower time
- batch that does not evenly divide output elements raises
* [Test] Rewrite finalize_reducer tests to follow clean outline
Replace the previous over-engineered test suite (4 case lists, 2 split
correctness functions, per-case xfail) with a minimal set organised by
the three categories that matter:
1. Codegen – single parametrised test covers both batch=1 (no run_batch)
and batch>1 (run_batch with correct template arg), reusing one case
list for both codegen and correctness.
2. Correctness – only batch=1 (scalar path), which is currently correct.
batch>1 correctness tests are omitted until the underlying fragment
layout bug is fixed (tracked in work/bug-finalize-reducer-batch.md).
3. Error inputs – batch<1 collapsed into one parametrised test; compile-
time errors (batch>output, batch not divisible) each get one test
using a shared _make_error_kernel helper.
* [Test] Rewrite reduce tests following clean outline
Replace fragmented per-function tests with four parametrized suites:
- test_reduce: op × dtype × src_scope × dst_scope × threads, covers
fragment→fragment, shared→shared, fragment→shared for sum/max/min/
abssum/absmax with float32/float16/int32/int64
- test_reduce_clear: op × src_scope × dst_scope with clear=False,
verifies pre-filled dst is accumulated correctly
- test_reduce_batch_codegen: verify batch>1 emits AllReduce::run_batch
- test_finalize_reducer_codegen/correctness/invalid_batch: batch
parameter feature tests, sharing one FINALIZE_REDUCER_CASES list
Use tilelang.jit lazy style and torch.testing.assert_close directly,
no profiler wrapper.
* [Test] Merge batch codegen+correctness into test_reduce
Add batch column to REDUCE_CASES; batch>1 cases verify both that
run_batch appears in generated source and that numerical output matches
reference, within the same test_reduce parametrization.
Remove the now-redundant test_reduce_batch_codegen.
* [Test] Add more batch>1 reduce cases covering all ops and dtypes
Add batch=2/4/8 cases for sum/max/min/abssum/absmax across float32/
float16/bfloat16, with both fragment and shared src/dst scopes.
Relax tolerance to 1e-1 for float16/bfloat16 to account for larger
rounding error in multi-element reductions.
* [Test] Limit int64 input range to avoid ROCm shfl_xor 64-bit bug
* Revert "[Test] Limit int64 input range to avoid ROCm shfl_xor 64-bit bug"
This reverts commit 9d60d6b197.
* fix(HIP): skip __launch_bounds__ for sub-wavefront threads to avoid miscompile of 64-bit shuffle ops
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: LeiWang1999 <leiwang1999@outlook.com>
Co-authored-by: kurisu6912 <kurisu6912@users.noreply.github.com>
312 lines
12 KiB
Python
312 lines
12 KiB
Python
import tilelang
|
||
import tilelang as tl
|
||
import tilelang.language as T
|
||
import tilelang.testing
|
||
import pytest
|
||
import torch
|
||
|
||
tilelang.testing.set_random_seed()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Helpers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _make_input(M, N, dtype):
|
||
torch_dtype = getattr(torch, dtype)
|
||
if torch_dtype in (torch.int32, torch.int64):
|
||
return torch.randint(-100, 100, (M, N), dtype=torch_dtype).cuda()
|
||
return torch.randn(M, N, dtype=torch_dtype).cuda()
|
||
|
||
|
||
def _ref(A, op):
|
||
if op == "sum":
|
||
return A.sum(dim=1).to(A.dtype)
|
||
if op == "max":
|
||
return A.max(dim=1).values
|
||
if op == "min":
|
||
return A.min(dim=1).values
|
||
if op == "abssum":
|
||
return A.abs().sum(dim=1).to(A.dtype)
|
||
if op == "absmax":
|
||
return A.abs().max(dim=1).values
|
||
raise ValueError(op)
|
||
|
||
|
||
def _reduce_op(T, op, src, dst, dim, batch=1):
|
||
kwargs = {} if batch == 1 else {"batch": batch}
|
||
if op == "sum":
|
||
T.reduce_sum(src, dst, dim=dim, **kwargs)
|
||
elif op == "max":
|
||
T.reduce_max(src, dst, dim=dim, **kwargs)
|
||
elif op == "min":
|
||
T.reduce_min(src, dst, dim=dim, **kwargs)
|
||
elif op == "abssum":
|
||
T.reduce_abssum(src, dst, dim=dim, **kwargs)
|
||
elif op == "absmax":
|
||
T.reduce_absmax(src, dst, dim=dim, **kwargs)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# test_reduce (op × dtype × src_scope × dst_scope × threads × batch)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
# int types only support sum (no abssum/absmax for int in tilelang)
|
||
REDUCE_CASES = [
|
||
# (op, dtype, M, N, src_scope, dst_scope, threads, batch)
|
||
("sum", T.float32, 128, 128, "fragment", "fragment", 32, 1),
|
||
("sum", T.int32, 128, 128, "fragment", "fragment", 32, 1),
|
||
("sum", T.int64, 192, 64, "fragment", "fragment", 32, 1),
|
||
("sum", T.float32, 192, 64, "fragment", "fragment", 32, 1),
|
||
("sum", T.float32, 32, 32, "fragment", "fragment", 16, 1),
|
||
("sum", T.float32, 16, 16, "fragment", "fragment", 8, 1),
|
||
("sum", T.float32, 32, 32, "shared", "shared", 32, 1),
|
||
("sum", T.float32, 32, 32, "fragment", "shared", 32, 1),
|
||
("max", T.float32, 128, 128, "fragment", "fragment", 32, 1),
|
||
("max", T.int64, 128, 128, "fragment", "fragment", 32, 1),
|
||
("max", T.float32, 32, 32, "shared", "shared", 32, 1),
|
||
("min", T.float32, 128, 128, "fragment", "fragment", 32, 1),
|
||
("min", T.int64, 128, 128, "fragment", "fragment", 32, 1),
|
||
("abssum", T.float32, 128, 128, "fragment", "fragment", 32, 1),
|
||
("abssum", T.int64, 128, 128, "fragment", "fragment", 32, 1),
|
||
("absmax", T.float32, 128, 128, "fragment", "fragment", 32, 1),
|
||
("absmax", T.int64, 128, 128, "fragment", "fragment", 32, 1),
|
||
# batch > 1: verify run_batch codegen and correctness together
|
||
("sum", T.float32, 128, 64, "shared", "fragment", 256, 2),
|
||
("sum", T.float32, 128, 64, "shared", "fragment", 256, 4),
|
||
("sum", T.float16, 64, 128, "fragment", "fragment", 256, 4),
|
||
("max", T.bfloat16, 128, 64, "shared", "fragment", 256, 2),
|
||
("max", T.float32, 128, 128, "fragment", "fragment", 256, 4),
|
||
("min", T.float32, 64, 128, "shared", "fragment", 128, 2),
|
||
("min", T.float16, 128, 128, "fragment", "fragment", 256, 8),
|
||
("abssum", T.float32, 128, 128, "fragment", "fragment", 256, 4),
|
||
("absmax", T.float32, 128, 128, "fragment", "fragment", 256, 4),
|
||
]
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("op", "dtype", "M", "N", "src_scope", "dst_scope", "threads", "batch"),
|
||
REDUCE_CASES,
|
||
ids=[
|
||
f"{op}-{dtype}-{M}x{N}-{src_scope[0]}2{dst_scope[0]}-t{threads}-b{batch}"
|
||
for op, dtype, M, N, src_scope, dst_scope, threads, batch in REDUCE_CASES
|
||
],
|
||
)
|
||
def test_reduce(op, dtype, M, N, src_scope, dst_scope, threads, batch):
|
||
import re
|
||
|
||
@tilelang.jit(out_idx=-1)
|
||
def kernel(M, N, dtype, op, src_scope, dst_scope, threads, batch):
|
||
@T.prim_func
|
||
def main(A: T.Tensor((M, N), dtype), B: T.Tensor((M,), dtype)):
|
||
with T.Kernel(1, threads=threads):
|
||
if src_scope == "fragment":
|
||
src = T.alloc_fragment((M, N), dtype)
|
||
else:
|
||
src = T.alloc_shared((M, N), dtype)
|
||
if dst_scope == "fragment":
|
||
dst = T.alloc_fragment((M,), dtype)
|
||
else:
|
||
dst = T.alloc_shared((M,), dtype)
|
||
T.copy(A, src, disable_tma=src_scope == "shared")
|
||
_reduce_op(T, op, src, dst, dim=1, batch=batch)
|
||
T.copy(dst, B)
|
||
|
||
return main
|
||
|
||
jit_kernel = kernel(M, N, dtype, op, src_scope, dst_scope, threads, batch)
|
||
|
||
if batch > 1:
|
||
src = jit_kernel.get_kernel_source()
|
||
m = re.search(r",\s*(\d+)\s*,\s*\d+\s*>::run_batch\(", src)
|
||
assert m is not None, f"Expected run_batch in generated source.\n{src}"
|
||
assert int(m.group(1)) > 1, f"Expected batch_size > 1, got {m.group(1)}.\n{src}"
|
||
|
||
A = _make_input(M, N, dtype)
|
||
B = jit_kernel(A)
|
||
# float16/bfloat16 accumulate more rounding error over large reductions
|
||
tol = 1e-1 if dtype in (T.float16, T.bfloat16) else 1e-2
|
||
torch.testing.assert_close(B, _ref(A, op), atol=tol, rtol=tol)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# test_reduce_clear (op × src_scope × dst_scope, clear=False)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
REDUCE_CLEAR_CASES = [
|
||
# (op, dtype, M, N, src_scope, dst_scope, init, ref_fn_extra)
|
||
# sum: init=1, ref = A.sum(dim=1) + 1
|
||
("sum", T.float32, 128, 128, "fragment", "fragment"),
|
||
("sum", T.float32, 128, 128, "fragment", "shared"),
|
||
("sum", T.float32, 32, 32, "shared", "shared"),
|
||
# max: init=-inf, ref = A.max(dim=1).values (max(-inf, x) = x)
|
||
("max", T.float16, 128, 128, "fragment", "fragment"),
|
||
]
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("op", "dtype", "M", "N", "src_scope", "dst_scope"),
|
||
REDUCE_CLEAR_CASES,
|
||
ids=[f"{op}-{dtype}-{M}x{N}-{src_scope[0]}2{dst_scope[0]}" for op, dtype, M, N, src_scope, dst_scope in REDUCE_CLEAR_CASES],
|
||
)
|
||
def test_reduce_clear(op, dtype, M, N, src_scope, dst_scope):
|
||
@tilelang.jit(out_idx=-1)
|
||
def kernel(M, N, dtype, op, src_scope, dst_scope):
|
||
@T.prim_func
|
||
def main(A: T.Tensor((M, N), dtype), B: T.Tensor((M,), dtype)):
|
||
with T.Kernel(1, threads=32):
|
||
if src_scope == "fragment":
|
||
src = T.alloc_fragment((M, N), dtype)
|
||
else:
|
||
src = T.alloc_shared((M, N), dtype)
|
||
if dst_scope == "fragment":
|
||
dst = T.alloc_fragment((M,), dtype)
|
||
else:
|
||
dst = T.alloc_shared((M,), dtype)
|
||
T.copy(A, src, disable_tma=src_scope == "shared")
|
||
if op == "sum":
|
||
T.fill(dst, 1)
|
||
T.reduce_sum(src, dst, dim=1, clear=False)
|
||
elif op == "max":
|
||
T.fill(dst, -T.infinity(dtype))
|
||
T.reduce_max(src, dst, dim=1, clear=False)
|
||
T.copy(dst, B)
|
||
|
||
return main
|
||
|
||
torch_dtype = getattr(torch, dtype)
|
||
A = torch.randn(M, N, dtype=torch_dtype).cuda()
|
||
B = kernel(M, N, dtype, op, src_scope, dst_scope)(A)
|
||
if op == "sum":
|
||
ref = A.sum(dim=1) + 1
|
||
elif op == "max":
|
||
ref = A.max(dim=1).values
|
||
torch.testing.assert_close(B, ref, atol=1e-2, rtol=1e-2)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# T.finalize_reducer tests
|
||
# ---------------------------------------------------------------------------
|
||
|
||
_COMPILE_FLAGS = {
|
||
tl.PassConfigKey.TL_DISABLE_WARP_SPECIALIZED: True,
|
||
tl.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
|
||
}
|
||
|
||
FINALIZE_REDUCER_CASES = [
|
||
# (op, dtype, block_M, block_N, batch)
|
||
("sum", T.float32, 128, 64, 1),
|
||
("sum", T.float32, 128, 64, 4),
|
||
("max", T.float16, 64, 128, 1),
|
||
("max", T.float16, 64, 128, 8),
|
||
("min", T.float32, 128, 128, 1),
|
||
("min", T.float32, 128, 128, 16),
|
||
]
|
||
|
||
|
||
def _make_finalize_reducer_kernel(block_M, block_N, dtype, op, batch):
|
||
@T.prim_func
|
||
def kernel(A: T.Tensor((block_M, block_N), dtype), B: T.Tensor((block_M,), dtype)):
|
||
with T.Kernel(1, threads=256):
|
||
o_reducer = T.alloc_reducer(block_M, dtype, op=op, replication="all")
|
||
T.clear(o_reducer)
|
||
A_smem = T.alloc_shared((block_M, block_N), dtype)
|
||
T.copy(A, A_smem)
|
||
A_frag = T.alloc_fragment((block_M, block_N), dtype)
|
||
T.copy(A_smem, A_frag)
|
||
for i, j in T.Parallel(block_M, block_N):
|
||
if op == "sum":
|
||
o_reducer[i] += A_frag[i, j]
|
||
elif op == "max":
|
||
o_reducer[i] = T.max(o_reducer[i], A_frag[i, j])
|
||
else:
|
||
o_reducer[i] = T.min(o_reducer[i], A_frag[i, j])
|
||
T.finalize_reducer(o_reducer, batch=batch)
|
||
T.copy(o_reducer, B)
|
||
|
||
return kernel
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("op", "dtype", "block_M", "block_N", "batch"),
|
||
FINALIZE_REDUCER_CASES,
|
||
ids=[f"{op}-{dtype}-{bM}x{bN}-b{batch}" for op, dtype, bM, bN, batch in FINALIZE_REDUCER_CASES],
|
||
)
|
||
def test_finalize_reducer_codegen(op, dtype, block_M, block_N, batch):
|
||
"""batch=1 → scalar run; batch>1 → run_batch with correct template arg."""
|
||
import re
|
||
|
||
src = tl.compile(
|
||
_make_finalize_reducer_kernel(block_M, block_N, dtype, op, batch),
|
||
out_idx=-1,
|
||
pass_configs=_COMPILE_FLAGS,
|
||
).get_kernel_source()
|
||
|
||
if batch == 1:
|
||
assert "run_batch" not in src, f"batch=1 must not emit run_batch.\n{src}"
|
||
else:
|
||
m = re.search(r",\s*(\d+)\s*,\s*\d+\s*>::run_batch\(", src)
|
||
assert m is not None, f"Expected run_batch in generated source.\n{src}"
|
||
assert int(m.group(1)) == batch, f"Expected batch={batch}, got {m.group(1)}.\n{src}"
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("op", "dtype", "block_M", "block_N", "batch"),
|
||
[c for c in FINALIZE_REDUCER_CASES if c[4] == 1],
|
||
ids=[f"{op}-{dtype}-{bM}x{bN}" for op, dtype, bM, bN, batch in FINALIZE_REDUCER_CASES if batch == 1],
|
||
)
|
||
def test_finalize_reducer_correctness(op, dtype, block_M, block_N, batch):
|
||
"""Numerical correctness (batch=1 scalar path; batch>1 blocked by fragment layout bug)."""
|
||
A = torch.randn(block_M, block_N, dtype=getattr(torch, dtype)).cuda()
|
||
B = tl.compile(
|
||
_make_finalize_reducer_kernel(block_M, block_N, dtype, op, batch),
|
||
out_idx=-1,
|
||
pass_configs=_COMPILE_FLAGS,
|
||
)(A)
|
||
torch.testing.assert_close(B, _ref(A, op), atol=1e-2, rtol=1e-2)
|
||
|
||
|
||
# (batch, exc_type, match)
|
||
FINALIZE_REDUCER_INVALID_CASES = [
|
||
(0, ValueError, "batch must be >= 1"),
|
||
(-1, ValueError, "batch must be >= 1"),
|
||
(128, Exception, "exceeds total output elements"), # block_M=64, batch=128
|
||
(3, Exception, "must evenly divide"), # block_M=64, batch=3
|
||
]
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("batch", "exc_type", "match"),
|
||
FINALIZE_REDUCER_INVALID_CASES,
|
||
ids=["zero", "negative", "exceeds", "not-divisible"],
|
||
)
|
||
def test_finalize_reducer_invalid_batch(batch, exc_type, match):
|
||
block_M = 64
|
||
|
||
def make_kernel():
|
||
@T.prim_func
|
||
def kernel(A: T.Tensor((block_M, 64), T.float32), B: T.Tensor((block_M,), T.float32)):
|
||
with T.Kernel(1, threads=256):
|
||
o_reducer = T.alloc_reducer(block_M, T.float32, op="sum", replication="all")
|
||
T.clear(o_reducer)
|
||
A_smem = T.alloc_shared((block_M, 64), T.float32)
|
||
T.copy(A, A_smem)
|
||
A_frag = T.alloc_fragment((block_M, 64), T.float32)
|
||
T.copy(A_smem, A_frag)
|
||
for i, j in T.Parallel(block_M, 64):
|
||
o_reducer[i] += A_frag[i, j]
|
||
T.finalize_reducer(o_reducer, batch=batch)
|
||
T.copy(o_reducer, B)
|
||
|
||
return kernel
|
||
|
||
with pytest.raises(exc_type, match=match):
|
||
# batch<1 raises at prim_func definition time; others at compile time
|
||
k = make_kernel()
|
||
tl.compile(k, out_idx=-1, pass_configs=_COMPILE_FLAGS)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
tilelang.testing.main()
|