This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-tirx.git
The following commit(s) were added to refs/heads/main by this push:
new 4f57008f3 [FIX] Preserve offsets when sharing iterator padding (#65)
4f57008f3 is described below
commit 4f57008f346ea127f632f07ffb4a88d9eafff6c3
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 25 12:23:19 2026 -0400
[FIX] Preserve offsets when sharing iterator padding (#65)
---
src/sym/iter_affine_map.cc | 86 +++++++++++++++++++-------------
tests/python/sym/test_iter_affine_map.py | 50 ++++++++++++-------
2 files changed, 81 insertions(+), 55 deletions(-)
diff --git a/src/sym/iter_affine_map.cc b/src/sym/iter_affine_map.cc
index d9a26e2fe..61b5a21dd 100644
--- a/src/sym/iter_affine_map.cc
+++ b/src/sym/iter_affine_map.cc
@@ -352,19 +352,12 @@ class IterMapRewriter : public tvm::ExprMutator {
*/
ffi::Optional<IterSumExpr> PreprocessDividend(IterMapExpr dividend, PrimExpr
original_dividend);
- // Create an iterator that represents the expression (split+base), with
- // padding such that the iterator's extents are evenly divisible by
- // `divisor`.
- //
- // If iterators can have padding added through UpdatePadding, pad a
- // dividend out to be evenly divisible. Otherwise, validate that the
- // padding previously defined for the split using UpdatePadding can be
- // used. If no such previous padding exists, return an empty
- // IterMark.
- //
- // Returns a pair of IterSplit that represents (split+base) in a
- // form that can be dividied by divisors, and PrimExpr that
- // represents the left padding applied to split.
+ // First pass: collect source-padding requirements; provisional splits are
+ // discarded and rewritten if any padding is required.
+ // Second pass: return a split whose unscaled value is the input's unscaled
+ // value plus the returned left shift, with the residual base offset
divisible
+ // by divisor. Reject incompatible or unproven shared padding instead of
+ // changing the mapping.
std::optional<std::pair<IterSplitExpr, PrimExpr>>
PadDividendToDivisor(IterSplitExpr split,
PrimExpr base,
PrimExpr divisor);
@@ -1875,14 +1868,19 @@ std::optional<std::pair<IterSplitExpr, PrimExpr>>
IterMapRewriter::PadDividendTo
// In the second pass, update iteration mark's to padded form
auto it = padded_iter_map_.find(mark);
- if (it == padded_iter_map_.end()) {
- return std::make_pair(split, left_pad);
+ if (it == padded_iter_map_.end() ||
+ (IsZero(it->second.left_pad) && CanProveDivisible(mark->extent,
it->second.padding_factor))) {
+ // No shift is applied to this mark. In particular, a nested split of an
+ // already padded mark cannot silently absorb a new nonzero offset:
+ // with P = f + 7, (P % 7 + 2) % 7 still needs the separate +2.
+ if (!IsZero(left_pad)) {
+ ErrorLogger(this) << "Cannot absorb left padding " << left_pad << " into
"
+ << NormalizeIterMapToExpr(split);
+ return std::nullopt;
+ }
+ return std::make_pair(split, PrimExpr(0));
}
auto& info = it->second;
- if (IsZero(info.left_pad) && CanProveDivisible(mark->extent,
info.padding_factor)) {
- // the iter mark requires no padding
- return std::make_pair(split, left_pad);
- }
// check that padding factor is compatible with current split and divisor
TVM_FFI_ICHECK(CanProveDivisible(info.padding_factor, split->lower_factor))
@@ -1892,24 +1890,40 @@ std::optional<std::pair<IterSplitExpr, PrimExpr>>
IterMapRewriter::PadDividendTo
<< "The padding factor " << info.padding_factor << " is not divisible by
" << divisor
<< " for the split " << split;
- if (!info.padded.has_value()) {
- // the first time encounter the iter mark to pad, update the padded mark.
- PrimExpr mark_left_pad = info.left_pad;
- if (CanProveDivisible(mark_left_pad, split->lower_factor)) {
- // correct current split's left padding
- // (mark_left_pad + iter) // lower_factor % extent =>
- // (left_pad * lower_factor + mark) // lower_factor % extent =>
- // (left_pad + mark // lower_factor) % extent =>
- // left_pad + (mark // lower_factor % extent) =>
- // left_pad + split
- // since the extent covers the full padding range.
- left_pad = floordiv(mark_left_pad, split->lower_factor);
- } else {
- ErrorLogger(this) << "Detect incompatible left padding on " <<
NormalizeIterMapToExpr(split)
- << ", the iter mark is left padded with " <<
mark_left_pad;
- return std::nullopt;
- }
+ // Let l = split.lower_factor and E = split.extent. Widening to the whole
+ // padded mark must not remove an existing wrap: for 0 <= s < mark.extent,
+ // prove (s // l) % E = s // l first.
+ if (!analyzer_->CanProve(mark->extent <= split->lower_factor *
split->extent)) {
+ ErrorLogger(this) << "Cannot remove inner modulo while padding "
+ << NormalizeIterMapToExpr(split);
+ return std::nullopt;
+ }
+
+ // Every use must establish its actual source shift L. When L % l = 0,
+ // (s + L) // l = s // l + L // l. The enlarged extent covers the whole
+ // shifted mark, so its modulo cannot wrap either. The new unscaled split
+ // equals the original unscaled split plus L // l. The split's scale is
+ // preserved separately. Check this even when reusing the padded mark.
+ PrimExpr mark_left_pad = info.left_pad;
+ if (!CanProveDivisible(mark_left_pad, split->lower_factor)) {
+ ErrorLogger(this) << "Detect incompatible left padding on " <<
NormalizeIterMapToExpr(split)
+ << ", the iter mark is left padded with " <<
mark_left_pad;
+ return std::nullopt;
+ }
+ PrimExpr actual_left_pad = floordiv(mark_left_pad, split->lower_factor);
+ // FloorDiv can extract the residual offset, and FloorMod can discard it,
+ // only when it is a multiple of the divisor. Compare against base % divisor
+ // to retain the already-simplified remainder for symbolic offsets.
+ if (!CanProveDivisible(actual_left_pad - left_pad, divisor)) {
+ ErrorLogger(this) << "Detect incompatible left padding on " <<
NormalizeIterMapToExpr(split)
+ << ", the shared padding leaves offset " << base -
actual_left_pad
+ << " for divisor " << divisor;
+ return std::nullopt;
+ }
+ left_pad = actual_left_pad;
+
+ if (!info.padded.has_value()) {
PrimExpr right_edge = mark->extent + mark_left_pad;
PrimExpr mark_right_pad = CanProveDivisible(right_edge,
info.padding_factor)
? PrimExpr(0)
diff --git a/tests/python/sym/test_iter_affine_map.py
b/tests/python/sym/test_iter_affine_map.py
index e7170c58f..8ae2d1a2c 100644
--- a/tests/python/sym/test_iter_affine_map.py
+++ b/tests/python/sym/test_iter_affine_map.py
@@ -1133,25 +1133,6 @@ class TestPadding:
# padding for free iteration part
({y: 360}, {fld(x * 360 + y, 16): (23, fld(x * 360 - flm(x, 2) * 8,
16), 1)}),
({y: 360}, {flm(x * 360 + y, 16): (16, 0, 1)}),
- # multiple split with same mark offset, could
- # be surjective on missing (padded // LCM)
- (
- {x: 240},
- {
- flm(x + 10, 3): (3, 0),
- flm(fld(x + 10, 3), 4): (4, 0),
- flm(fld(fld(x + 10, 3), 4), 5): (5, 0),
- },
- ),
- # different offsets on splits
- (
- {x: 240},
- {
- flm(x + 1, 3): (3, 0),
- flm(fld(x + 10, 3) + 2, 4): (4, 0),
- flm(fld(fld(x + 10, 3), 4) + 3, 5): (5, 0),
- },
- ),
)
@pytest.fixture(params=_positive_test_case_params, scope="session")
@@ -1175,6 +1156,26 @@ class TestPadding:
fld(fld(fld(x + 10, 3), 4), 5),
},
),
+ # The collected maximum source padding is 9, but the low digit
+ # requires remainder 1 modulo 3. Sharing that padding is unsound.
+ (
+ {x: 240},
+ {
+ flm(x + 10, 3),
+ flm(fld(x + 10, 3), 4),
+ flm(fld(fld(x + 10, 3), 4), 5),
+ },
+ ),
+ # Different nested offsets collect padding 36, which also fails to
+ # preserve the low digit's remainder 1 modulo 3.
+ (
+ {x: 240},
+ {
+ flm(x + 1, 3),
+ flm(fld(x + 10, 3) + 2, 4),
+ flm(fld(fld(x + 10, 3), 4) + 3, 5),
+ },
+ ),
# original extent is smaller than the divident
# it is not surjective wrt to the region [0, 16)
({x: 3}, {flm(x, 16)}),
@@ -1310,6 +1311,17 @@ def test_iter_map_simplify_symbolic_predicate():
)
+def test_iter_map_simplify_cyclic_shift():
+ fused = tvm_tirx.tirx.Var("fused", "int32")
+ index = (fused // 7 + 1) % 6 * 7 + (fused % 7 + 2) % 7
+ simplified = tvm_tirx.sym.iter_map_simplify([index], var_dom([(fused,
42)]))[0]
+ for value in range(42):
+ analyzer = tvm_tirx.sym.Analyzer()
+ analyzer.bind(fused, tvm_tirx.tirx.const(value, "int32"))
+ expected = ((value // 7 + 1) % 6) * 7 + (value % 7 + 2) % 7
+ assert int(analyzer.simplify(simplified)) == expected
+
+
def test_iter_map_simplify_predicate_fallback_requires_no_padding():
fused = tvm_tirx.tirx.Var("fused", "int64")
predicate = fused % 2 == 0