Skip to content

Commit 89013a0

Browse files
committed
[Fix] Harden row-expand layouts and clean up legacy tooling 🤖
Fold contiguous leading row-expand dimensions into rows while validating the 256-byte row, packed-scalar, workspace, dtype, and contiguity contracts. Remove the obsolete string-based init_flag/clear_flag frontend and codegen path, and make set_env.sh robust when sourced from Bash or Zsh without duplicating PYTHONPATH. Keep CI coverage focused with shared-helper validation, one two-backend multistage numerical regression, and a legacy-export check.
1 parent 2c6e118 commit 89013a0

6 files changed

Lines changed: 250 additions & 52 deletions

File tree

‎set_env.sh‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
#!/bin/bash
22

3-
TL_ROOT=$(readlink -f "${BASH_SOURCE[0]}")
4-
export TL_ROOT=$(dirname "$TL_ROOT")
5-
export PYTHONPATH=${TL_ROOT}:$PYTHONPATH
3+
if [ -n "${ZSH_VERSION:-}" ]; then
4+
tilelang_env_script_path="${(%):-%N}"
5+
else
6+
tilelang_env_script_path="${BASH_SOURCE[0]}"
7+
fi
8+
9+
TL_ROOT=$(dirname "$(readlink -f "$tilelang_env_script_path")")
10+
export TL_ROOT
11+
case "${PYTHONPATH:-}" in
12+
"$TL_ROOT"|"$TL_ROOT":*) ;;
13+
*) PYTHONPATH="$TL_ROOT${PYTHONPATH:+:$PYTHONPATH}" ;;
14+
esac
15+
export PYTHONPATH
616

717
# disable the import of tvm when using torch_npu
818
export ACL_OP_INIT_MODE=1
19+
20+
unset tilelang_env_script_path

‎src/target/codegen_ascend.cc‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -770,20 +770,6 @@ void CodeGenTileLangAscend::VisitStmt_(const AttrStmtNode *op) {
770770
}
771771
this->VisitStmt(op->body);
772772
return;
773-
} else if (op->attr_key == "init_flag" || op->attr_key == "clear_flag") {
774-
const StringImmNode *instn = op->value.as<StringImmNode>();
775-
776-
std::string inst = std::string(instn->value);
777-
size_t st = 0;
778-
for (size_t i = 0; i < inst.size(); ++i) {
779-
if (inst[i] == '\n') {
780-
this->PrintIndent();
781-
stream << inst.substr(st, i - st) << "\n";
782-
st = i + 1;
783-
}
784-
}
785-
this->VisitStmt(op->body);
786-
return;
787773
} else if (op->attr_key == "resource_scope") {
788774
auto resource_id = Downcast<IntImm>(op->value)->value;
789775
auto resource_name = resource_id == 0 ? "AIC" : "AIV";

‎testing/python/language/test_tilelang_ascend_language_elementwise.py‎

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5137,6 +5137,121 @@ def row_expand_div_experiment_kernel(M, N, dtype="float16"):
51375137
return _row_expand_binop_experiment_kernel(M, N, "row_expand_div_experiment", dtype)
51385138

51395139

5140+
def row_expand_mul_multistage_region_kernel():
5141+
stages = 2
5142+
rows_per_stage = 8
5143+
cols = 64
5144+
lanes = 8
5145+
5146+
@T.prim_func
5147+
def main(
5148+
A: T.Tensor((stages, rows_per_stage, cols), "float"), # type: ignore
5149+
S: T.Tensor((stages, rows_per_stage, lanes), "float"), # type: ignore
5150+
C: T.Tensor((stages, rows_per_stage, cols), "float"), # type: ignore
5151+
):
5152+
with T.Kernel(1, is_npu=True):
5153+
a_ring = T.alloc_ub((stages + 1, rows_per_stage, cols), "float")
5154+
s_ring = T.alloc_ub((stages + 1, rows_per_stage, lanes), "float")
5155+
c_ring = T.alloc_ub((stages + 1, rows_per_stage, cols), "float")
5156+
5157+
for stage in T.serial(stages):
5158+
T.copy(A[stage, :, :], a_ring[stage + 1, :, :])
5159+
T.copy(S[stage, :, :], s_ring[stage + 1, :, :])
5160+
T.tile.row_expand_mul_experiment(
5161+
c_ring[1:3, :, :],
5162+
a_ring[1:3, :, :],
5163+
s_ring[1:3, :, :],
5164+
)
5165+
for stage in T.serial(stages):
5166+
T.copy(c_ring[stage + 1, :, :], C[stage, :, :])
5167+
5168+
return main
5169+
5170+
5171+
def test_row_expand_experiment_folds_contiguous_leading_dimensions():
5172+
op = T.tile.row_expand_mul_experiment
5173+
stage = tir.Var("stage", "int32")
5174+
dst_ring = tir.decl_buffer((3, 8, 64), "float32", scope="shared.ub")
5175+
src_ring = tir.decl_buffer((3, 8, 64), "float32", scope="shared.ub")
5176+
scalar_ring = tir.decl_buffer((3, 8, 8), "float32", scope="shared.ub")
5177+
5178+
singleton = op(dst_ring[stage, :, :], src_ring[stage, :, :], scalar_ring[stage, :, :])
5179+
multistage = op(dst_ring[1:3, :, :], src_ring[1:3, :, :], scalar_ring[1:3, :, :])
5180+
whole_buffer = op(dst_ring, src_ring, scalar_ring)
5181+
5182+
assert int(singleton.args[1].args[3]) == 8 * 64
5183+
assert int(singleton.args[3].args[3]) == 8 * 8
5184+
assert int(multistage.args[1].args[3]) == 2 * 8 * 64
5185+
assert int(multistage.args[3].args[3]) == 2 * 8 * 8
5186+
assert int(whole_buffer.args[1].args[3]) == 3 * 8 * 64
5187+
assert int(whole_buffer.args[3].args[3]) == 3 * 8 * 8
5188+
5189+
5190+
def test_row_expand_experiment_rejects_invalid_folded_regions():
5191+
op = T.tile.row_expand_mul_experiment
5192+
dst = tir.decl_buffer((64, 64), "float32", scope="shared.ub")
5193+
src = tir.decl_buffer((64, 64), "float32", scope="shared.ub")
5194+
extra_rows = tir.decl_buffer((2, 64, 8), "float32", scope="shared.ub")
5195+
5196+
mismatch = r"src1 scalar count must match dst rows: src1=128, dst\[0\]=64"
5197+
with pytest.raises(ValueError, match=mismatch):
5198+
op(dst, src, extra_rows)
5199+
5200+
scalar_ring = tir.decl_buffer((2, 128, 8), "float32", scope="shared.ub")
5201+
with pytest.raises(ValueError, match="outer dimensions must be contiguous"):
5202+
op(dst, src, scalar_ring[0:2, 0:64, :])
5203+
5204+
with pytest.raises(ValueError, match="requires a 256-byte last dimension"):
5205+
op(
5206+
tir.decl_buffer((8, 128), "float32", scope="shared.ub"),
5207+
tir.decl_buffer((8, 128), "float32", scope="shared.ub"),
5208+
tir.decl_buffer((8, 8), "float32", scope="shared.ub"),
5209+
)
5210+
5211+
5212+
def test_row_expand_experiment_validates_src1_and_tmp_layouts():
5213+
op = T.tile.row_expand_mul_experiment
5214+
dst = tir.decl_buffer((16, 64), "float32", scope="shared.ub")
5215+
src = tir.decl_buffer((16, 64), "float32", scope="shared.ub")
5216+
packed = tir.decl_buffer((16, 8), "float32", scope="shared.ub")
5217+
scalar = tir.decl_buffer((16,), "float32", scope="shared.ub")
5218+
tmp = tir.decl_buffer((16, 8), "float32", scope="shared.ub")
5219+
5220+
op(dst, src, packed)
5221+
op(dst, src, scalar, tmp)
5222+
5223+
with pytest.raises(ValueError, match="packed src1 shape"):
5224+
op(dst, src, scalar)
5225+
with pytest.raises(ValueError, match="scalar src1 shape"):
5226+
op(dst, src, packed, tmp)
5227+
with pytest.raises(ValueError, match="tmp must contain 128 elements"):
5228+
op(dst, src, scalar, tir.decl_buffer((15, 8), "float32", scope="shared.ub"))
5229+
5230+
strided_packed = tir.decl_buffer((16, 16), "float32", scope="shared.ub")
5231+
with pytest.raises(ValueError, match="must be contiguous when flattened"):
5232+
op(dst, src, strided_packed[:, :8])
5233+
5234+
5235+
@pytest.mark.parametrize("target", ["ascendc", "pto"])
5236+
def test_row_expand_mul_experiment_multistage_region(target):
5237+
func = tilelang.compile(
5238+
row_expand_mul_multistage_region_kernel(),
5239+
out_idx=[-1],
5240+
pass_configs=pass_configs,
5241+
target=target,
5242+
)
5243+
5244+
a = torch.arange(1, 2 * 8 * 64 + 1, dtype=torch.float32).reshape(2, 8, 64) / 64
5245+
scalars = torch.arange(1, 2 * 8 + 1, dtype=torch.float32).reshape(2, 8) / 4
5246+
packed_scalars = scalars.unsqueeze(-1).expand(2, 8, 8).contiguous()
5247+
5248+
c = func(a.npu(), packed_scalars.npu())
5249+
torch.npu.synchronize()
5250+
ref_c = a * scalars.unsqueeze(-1)
5251+
5252+
torch.testing.assert_close(c.cpu(), ref_c, rtol=1e-5, atol=1e-5)
5253+
5254+
51405255
@pytest.mark.parametrize("dtype,shape", [("float16", (16, 128)), ("float", (16, 64))])
51415256
@pytest.mark.parametrize("target", ["ascendc", "pto"])
51425257
def test_row_expand_mul_experiment(dtype, target, shape):
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Regression tests for removal of legacy string-based flag helpers."""
2+
3+
import tilelang.language as T
4+
5+
6+
def test_legacy_flag_helpers_are_not_exported():
7+
assert not hasattr(T, "init_flag")
8+
assert not hasattr(T, "clear_flag")

‎tilelang/language/__init__.py‎

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -173,32 +173,6 @@ def import_source(source: str | None = None):
173173
return block_attr({"pragma_import_c": source}) if source is not None else None
174174

175175

176-
def init_flag(fmap):
177-
inst = ""
178-
for src, d in fmap.items():
179-
for dst, stages in d.items():
180-
for stage in stages:
181-
inst += f"AscendC::SetFlag<AscendC::HardEvent::{src}_{dst}>({stage});\n"
182-
183-
return attr(None, "init_flag", inst)
184-
185-
186-
def clear_flag(fmap):
187-
inst = ""
188-
for src, d in fmap.items():
189-
for dst, stages in d.items():
190-
for stage in stages:
191-
inst += f"AscendC::WaitFlag<AscendC::HardEvent::{src}_{dst}>({stage});\n"
192-
193-
@macro
194-
def _get_inst():
195-
with attr(None, "clear_flag", inst):
196-
call_extern("handle", "...")
197-
198-
# return attr(call_extern("handle", "..."), "clear_flag", inst)
199-
return _get_inst()
200-
201-
202176
def npu_use_swizzle(cid, m, n, k, block_m, block_n, off=1, dir=0, in_loop=False):
203177
# If order is row, use rasterization2DRow, otherwise use rasterization2DColumn
204178
# The panel size is the number of threads in a warp

‎tilelang/language/ascend_tile.py‎

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def _handle_buffer_region_2d(br: BufferRegion, mask):
7676
7777
Leading dimensions are folded into rows; the innermost dimension is kept as cols.
7878
"""
79+
_validate_buffer_region_outer_contiguity(br)
7980
bf = br.buffer
8081
indices = [x.min for x in br.region]
8182
offset = bf.offset_of(indices)[0]
@@ -2300,6 +2301,49 @@ def _shapes_equal(shape1, shape2) -> bool:
23002301
return all(_const_equal(x, y) for x, y in zip(shape1, shape2))
23012302

23022303

2304+
def _fold_nd_shape_to_2d(shape):
2305+
"""Fold all leading dimensions of a shape into a single row dimension."""
2306+
shape = list(shape)
2307+
if len(shape) <= 1:
2308+
return shape
2309+
return [math.prod(shape[:-1]), shape[-1]]
2310+
2311+
2312+
def _validate_buffer_region_outer_contiguity(br: BufferRegion) -> None:
2313+
"""Validate that folding a region's outer dimensions does not cross gaps.
2314+
2315+
The final dimension is a row window and may be narrower than the physical
2316+
buffer because row-expand codegen retains the physical row stride. Once an
2317+
outer dimension spans multiple entries, however, every following outer
2318+
dimension must be selected in full.
2319+
"""
2320+
spans_multiple_entries = False
2321+
for axis, region in enumerate(br.region[:-1]):
2322+
if spans_multiple_entries:
2323+
starts_at_zero = _const_equal(region.min, 0)
2324+
has_full_extent = _const_equal(region.extent, br.buffer.shape[axis])
2325+
full_axis = starts_at_zero and has_full_extent
2326+
if not full_axis:
2327+
requirement = "BufferRegion outer dimensions must be contiguous"
2328+
raise ValueError(f"{requirement}; axis {axis} is not selected in full.")
2329+
elif not _is_const_one(region.extent):
2330+
spans_multiple_entries = True
2331+
2332+
2333+
def _validate_buffer_region_flat_contiguity(br: BufferRegion) -> None:
2334+
"""Validate that a region can be consumed as one flat contiguous stream."""
2335+
spans_multiple_entries = False
2336+
for axis, region in enumerate(br.region):
2337+
if spans_multiple_entries:
2338+
starts_at_zero = _const_equal(region.min, 0)
2339+
has_full_extent = _const_equal(region.extent, br.buffer.shape[axis])
2340+
if not (starts_at_zero and has_full_extent):
2341+
requirement = "BufferRegion must be contiguous when flattened"
2342+
raise ValueError(f"{requirement}; axis {axis} is not selected in full.")
2343+
elif not _is_const_one(region.extent):
2344+
spans_multiple_entries = True
2345+
2346+
23032347
def _row_expand_binop_experiment(
23042348
dst,
23052349
src0,
@@ -2319,31 +2363,54 @@ def _row_expand_binop_experiment(
23192363
dst_ptr, dst_shape = _handle_buffer_region_2d(dst, "w")
23202364
else:
23212365
dst_ptr = dst.access_ptr("w")
2322-
dst_shape = list(dst.shape[-2:])
2366+
dst_shape = _fold_nd_shape_to_2d(dst.shape)
23232367

23242368
if isinstance(src0, BufferRegion):
23252369
src0_ptr, src0_shape = _handle_buffer_region_2d(src0, "r")
23262370
else:
23272371
src0_ptr = src0.access_ptr("r")
2328-
src0_shape = list(src0.shape[-2:])
2372+
src0_shape = _fold_nd_shape_to_2d(src0.shape)
23292373

23302374
if isinstance(src1, BufferRegion):
2331-
src1_ptr, src1_nd_extent = _handle_buffer_region(src1, "r")
2332-
src1_full_shape = [src1_nd_extent[-1]] if len(src1_nd_extent) >= 2 else src1_nd_extent
2375+
src1_ptr, src1_full_shape = _handle_buffer_region_2d(src1, "r")
23332376
else:
23342377
src1_ptr = src1.access_ptr("r")
2335-
src1_full_shape = list(src1.shape)
2378+
src1_full_shape = _fold_nd_shape_to_2d(src1.shape)
23362379

2337-
if len(src1_full_shape) == 1:
2380+
src0_buffer = src0.buffer if isinstance(src0, BufferRegion) else src0
2381+
src1_buffer = src1.buffer if isinstance(src1, BufferRegion) else src1
2382+
if DataType(src1_buffer.dtype) != DataType(src0_buffer.dtype):
2383+
mismatch = f"src1={src1_buffer.dtype}, src0={src0_buffer.dtype}"
2384+
raise ValueError(f"src1 and src0 dtypes must match: {mismatch}")
2385+
if isinstance(src1, BufferRegion):
2386+
_validate_buffer_region_flat_contiguity(src1)
2387+
2388+
dtype_bits = DataType(src0_buffer.dtype).bits
2389+
block_bits = 32 * 8
2390+
if block_bits % dtype_bits != 0:
2391+
raise ValueError(f"{op_name} does not support dtype {src0_buffer.dtype}.")
2392+
elems_per_block = block_bits // dtype_bits
2393+
2394+
if tmp is None:
2395+
if len(src1_full_shape) != 2:
2396+
requirement = f"packed src1 shape [R, {elems_per_block}] when tmp is omitted"
2397+
raise ValueError(f"{op_name} requires {requirement}; got {src1_full_shape}.")
2398+
s0, s1 = src1_full_shape[-2], src1_full_shape[-1]
2399+
src1_len = s0
2400+
if not _const_equal(s1, elems_per_block):
2401+
requirement = f"packed src1 shape [R, {elems_per_block}] when tmp is omitted"
2402+
raise ValueError(f"{op_name} requires {requirement}; got {src1_full_shape}.")
2403+
elif len(src1_full_shape) == 1:
23382404
src1_len = src1_full_shape[0]
23392405
elif len(src1_full_shape) == 2:
23402406
s0, s1 = src1_full_shape[-2], src1_full_shape[-1]
23412407
if _is_const_one(s0):
23422408
src1_len = s1
2343-
elif _is_const_one(s1) or _const_equal(s0, dst_shape[0]):
2409+
elif _is_const_one(s1):
23442410
src1_len = s0
23452411
else:
2346-
raise ValueError(f"src1 must be 1D [R], [1, R], or [R, 1]; got {src1_full_shape}")
2412+
requirement = "scalar src1 shape [R], [1, R], or [R, 1] when tmp is provided"
2413+
raise ValueError(f"{op_name} requires {requirement}; got {src1_full_shape}.")
23472414
else:
23482415
raise ValueError(f"src1 must be 1D or 2D, got shape {src1_full_shape}")
23492416

@@ -2354,9 +2421,33 @@ def _row_expand_binop_experiment(
23542421
raise ValueError(f"dst and src0 shapes must match: dst={dst_shape}, src0={src0_shape}")
23552422

23562423
if not _const_equal(src1_len, dst_shape[0]):
2357-
raise ValueError(f"src1 scalar count must match dst rows: src1={src1_len}, dst[0]={dst_shape[0]}")
2424+
mismatch = f"src1={src1_len}, dst[0]={dst_shape[0]}"
2425+
raise ValueError(f"src1 scalar count must match dst rows: {mismatch}")
2426+
2427+
if tmp is not None:
2428+
tmp_buffer = tmp.buffer if isinstance(tmp, BufferRegion) else tmp
2429+
if DataType(tmp_buffer.dtype) != DataType(src0_buffer.dtype):
2430+
mismatch = f"tmp={tmp_buffer.dtype}, src0={src0_buffer.dtype}"
2431+
raise ValueError(f"tmp and src0 dtypes must match: {mismatch}")
2432+
if isinstance(tmp, BufferRegion):
2433+
_validate_buffer_region_flat_contiguity(tmp)
2434+
tmp_shape = [region.extent for region in tmp.region]
2435+
else:
2436+
tmp_shape = list(tmp.shape)
2437+
tmp_size = math.prod(tmp_shape)
2438+
expected_tmp_size = dst_shape[0] * elems_per_block
2439+
if not _const_equal(tmp_size, expected_tmp_size):
2440+
requirement = f"tmp must contain {expected_tmp_size} elements"
2441+
raise ValueError(f"{op_name} {requirement}; got {tmp_size}.")
23582442

23592443
dtype = _dtype(src0)
2444+
row_bits = 256 * 8
2445+
expected_cols = row_bits // dtype_bits
2446+
if not _const_equal(dst_shape[1], expected_cols):
2447+
dtype_name = src0_buffer.dtype
2448+
requirement = f"a 256-byte last dimension ({expected_cols} {dtype_name} elements)"
2449+
raise ValueError(f"{op_name} requires {requirement}, got {dst_shape[1]}.")
2450+
23602451
args = [
23612452
f"{op_name}<{dtype}>",
23622453
dst_ptr,
@@ -2387,6 +2478,10 @@ def row_expand_mul_experiment(
23872478
23882479
AscendC: brcb(src1→tmp) + mul_mask(dst, src0, tmp).
23892480
PTO: TROWEXPANDMUL_row_vec(dst, src0, src1).
2481+
2482+
Contiguous leading dimensions of dst/src0 are folded into rows; each row
2483+
must be 256 bytes. Without tmp, src1 is packed as one 32-byte block per
2484+
row. With tmp, src1 is scalar-linear and tmp supplies those packed blocks.
23902485
"""
23912486
return _row_expand_binop_experiment(
23922487
dst,
@@ -2409,6 +2504,10 @@ def row_expand_sub_experiment(
24092504
24102505
AscendC: brcb(src1→tmp) + sub_mask(dst, src0, tmp).
24112506
PTO: TROWEXPANDSUB_row_vec(dst, src0, src1).
2507+
2508+
Contiguous leading dimensions of dst/src0 are folded into rows; each row
2509+
must be 256 bytes. Without tmp, src1 is packed as one 32-byte block per
2510+
row. With tmp, src1 is scalar-linear and tmp supplies those packed blocks.
24122511
"""
24132512
return _row_expand_binop_experiment(
24142513
dst,
@@ -2431,6 +2530,10 @@ def row_expand_div_experiment(
24312530
24322531
AscendC: brcb(src1→tmp) + div_mask(dst, src0, tmp).
24332532
PTO: TROWEXPANDDIV_row_vec(dst, src0, src1).
2533+
2534+
Contiguous leading dimensions of dst/src0 are folded into rows; each row
2535+
must be 256 bytes. Without tmp, src1 is packed as one 32-byte block per
2536+
row. With tmp, src1 is scalar-linear and tmp supplies those packed blocks.
24342537
"""
24352538
return _row_expand_binop_experiment(
24362539
dst,

0 commit comments

Comments
 (0)