Skip to content

Commit 2e9ee1f

Browse files
committed
Restore §3.3.3 truncation cases in the divisibility check
The strong divisibility check from the previous commit fixes the wrong-answer composition from #3177, but rejects the paper's §3.3.3 "apparent violation" cases that produce well-defined results, e.g. A = (4,2,8):(3,12,97), B = 3:3 -> 3:9 After the public composition() coalesces A to (8,8):(3,97), the strong check sees `8 % 3 != 0` and refuses to compile, even though A(0)=0, A(3)=9, A(6)=18 is well-defined. Add a third disjunct that accepts the safe-truncation pattern: when B's entire image fits inside the current LHS mode, higher modes are unreachable and cannot perturb the result. This is the §3.3.3 distinction between "apparent" and "real" divisibility violations. Predicate now accepts iff at least one of: (a) (rest_stride % curr_shape) == 0 -- skip mode entirely (b) (curr_shape % rest_stride) == 0 -- partial traversal (c) (rest_shape - 1) * rest_stride < curr_shape -- safe truncation: B's image stays within the current mode Verification matrix: Case Pre-coalesce LHS Decision ---------------------------------- --------------------- -------- paper §3.3.3 ok (returns 3:9) (8,8):(3,97) o 3:3 accept paper §3.3.3 fail-left (8,8):(3,97) o 4:3 reject paper §3.3.3 fail-right (4,2,8):(3,15,97) o 3:3 reject wrong-answer bug #3177 (4,6,8):(2,3,5) o 6:3 reject CuTe test (8,8):(8,1) o 2:3 (8,8):(8,1) o 2:3 accept CuTe test (8,8):(8,1) o 3:3 (8,8):(8,1) o 3:3 accept CuTe test (8,8):(8,1) o 4:3 (8,8):(8,1) o 4:3 reject Reference: arXiv:2603.02298 §3.3.3.
1 parent 433fd67 commit 2e9ee1f

2 files changed

Lines changed: 59 additions & 14 deletions

File tree

include/cute/layout.hpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,12 +1075,26 @@ composition_impl(LShape const& lhs_shape, [[maybe_unused]] LStride const& lhs_st
10751075
// (b) a divisor of the mode's shape ((curr_shape % rest_stride) == 0), so the mode is partially traversed by an integral number of strides.
10761076
//CUTE_STATIC_ASSERT_V(((rest_stride % curr_shape) == Int<0>{}) or ((curr_shape % rest_stride) == Int<0>{}), "Stride Divisibility Condition");
10771077

1078-
// Weak divisibility condition -- verify the divisibility condition whenever possible
1079-
if constexpr (is_static<decltype(curr_shape)>::value and is_static<decltype(rest_stride)>::value) {
1080-
CUTE_STATIC_ASSERT_V(((rest_stride % curr_shape) == Int<0>{}) or ((curr_shape % rest_stride) == Int<0>{}), "Stride Divisibility Condition");
1078+
// Composition C = A o B is well defined for the current mode iff at least one of:
1079+
// (a) rest_stride is a multiple of curr_shape ((rest_stride % curr_shape) == 0):
1080+
// the mode is skipped entirely.
1081+
// (b) curr_shape is a multiple of rest_stride ((curr_shape % rest_stride) == 0):
1082+
// the mode is partially traversed by an integral number of strides.
1083+
// (c) every point in B's image stays inside the current mode:
1084+
// (rest_shape - 1) * rest_stride < curr_shape.
1085+
// Higher modes are unreachable and cannot perturb the result;
1086+
// this is the §3.3.3 "apparent violation" pattern resolved by truncation.
1087+
if constexpr (is_static<decltype(curr_shape)>::value and
1088+
is_static<decltype(rest_stride)>::value and
1089+
is_static<decltype(rest_shape)>::value) {
1090+
CUTE_STATIC_ASSERT_V(((rest_stride % curr_shape) == Int<0>{}) or
1091+
((curr_shape % rest_stride) == Int<0>{}) or
1092+
(((rest_shape - Int<1>{}) * rest_stride) < curr_shape),
1093+
"Stride Divisibility Condition");
10811094
} else {
10821095
// DEBUG assert can cause extra registers and inappropriate compile-time/run-time failure
1083-
//assert((((rest_stride % curr_shape) == 0) or ((curr_shape % rest_stride) == 0)) && "Stride Divisibility Condition");
1096+
//assert((((rest_stride % curr_shape) == 0) or ((curr_shape % rest_stride) == 0) or
1097+
// (((rest_shape - 1) * rest_stride) < curr_shape)) and "Stride Divisibility Condition");
10841098
}
10851099

10861100
// next_shape: ceil(exclusive_prefix_product<r>(lhs_shape) / rhs_stride)

test/unit/cute/core/composition.cpp

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -456,25 +456,56 @@ TEST(CuTe_core, Composition)
456456
test_composition(a, b);
457457
}
458458

459-
// Should fail the strong stride-divisibility condition: rhs stride 3 neither
460-
// divides nor is divided by lhs mode-0 shape 8. The previous "weak" check
461-
// (rest_stride < curr_shape) accepted these cases; for these particular
462-
// rhs sizes they happen to produce the right answer because the rhs
463-
// coordinates never reach the mode boundary, but the same algorithm produces
464-
// wrong answers for compositions that do cross it (see NVIDIA/cutlass#3177).
459+
// Safe truncation: rhs stride 3 neither divides nor is divided by lhs
460+
// mode-0 shape 8, but the rhs image stays inside that mode so higher modes
461+
// are unreachable. Accepted via the safe-truncation disjunct
462+
// ((rest_shape - 1) * rest_stride < curr_shape).
463+
{
464+
auto a = make_layout(Shape<_8,_8>{}, Stride<_8,_1>{});
465+
auto b = make_layout(_2{}, _3{});
466+
test_composition(a, b);
467+
}
468+
{
469+
auto a = make_layout(Shape<_8,_8>{}, Stride<_8,_1>{});
470+
auto b = make_layout(_3{}, _3{});
471+
test_composition(a, b);
472+
}
473+
474+
// Paper §3.3.3 "apparent violation": (4,2,8):(3,12,97) coalesces to
475+
// (8,8):(3,97), then o 3:3 truncates safely to 3:9.
476+
// arXiv:2603.02298 §3.3.3.
477+
{
478+
auto a = make_layout(Shape<_4,_2,_8>{}, Stride<_3,_12,Int<97>>{});
479+
auto b = make_layout(_3{}, _3{});
480+
test_composition(a, b);
481+
}
482+
483+
// Should fail the divisibility condition: rhs image crosses the mode
484+
// boundary (every disjunct of the predicate fails).
465485
// {
466486
// auto a = make_layout(Shape<_8,_8>{}, Stride<_8,_1>{});
467-
// auto b = make_layout(_2{}, _3{});
487+
// auto b = make_layout(_4{}, _3{});
468488
// test_composition(a, b);
469489
// }
490+
// Paper §3.3.3 fail-left: same A as above but B=4:3 doesn't truncate
491+
// (after coalesce, (4-1)*3 = 9 >= 8).
470492
// {
471-
// auto a = make_layout(Shape<_8,_8>{}, Stride<_8,_1>{});
493+
// auto a = make_layout(Shape<_4,_2,_8>{}, Stride<_3,_12,Int<97>>{});
494+
// auto b = make_layout(_4{}, _3{});
495+
// test_composition(a, b);
496+
// }
497+
// Paper §3.3.3 fail-right: A's middle stride 15 prevents the coalesce
498+
// that would otherwise expose a safe truncation.
499+
// {
500+
// auto a = make_layout(Shape<_4,_2,_8>{}, Stride<_3,Int<15>,Int<97>>{});
472501
// auto b = make_layout(_3{}, _3{});
473502
// test_composition(a, b);
474503
// }
504+
// Wrong-answer case from #3177: the previous weak check accepted this and
505+
// returned (_2,_3):(_6,_3), but C(2) = 3 != A(B(2)) = 7.
475506
// {
476-
// auto a = make_layout(Shape<_8,_8>{}, Stride<_8,_1>{});
477-
// auto b = make_layout(_4{}, _3{});
507+
// auto a = make_layout(Shape<_4,_6,_8>{}, Stride<_2,_3,_5>{});
508+
// auto b = make_layout(_6{}, _3{});
478509
// test_composition(a, b);
479510
// }
480511

0 commit comments

Comments
 (0)