Problem
A stick-layout annotation on a descriptor feeding tl.inter_tile produces IR that
fails the ktdp.inter_tile_reduce verifier. The op's future and result are
physicalized; its identities operand is not.
error: 'ktdp.inter_tile_reduce' op failed to verify that identity types
must match result types
This is the reason the inter_tile_reduce fixture carries no
tt.spyre_tensor_layout variant.
Reproduction
inter_tile_add_kernel (fixtures/inter_tile_reduce/kernel.py:25) with a
stick-on-N annotation on both descriptors, BLOCK_M=16, BLOCK_N=64, f32
(stick = 32), grid (8,):
tl.spyre_tensor_layout(x_desc, [(1, "floordiv", 32), 0, (1, "mod", 32)])
tl.spyre_tensor_layout(out_desc, [(1, "floordiv", 32), 0, (1, "mod", 32)])
partial = x_desc.load([pid_m * BLOCK_M, pid_n * BLOCK_N])
result = tl.inter_tile(partial, axis="n", combiner="add",
mode="all_reduce", work_slices=WORK_SLICES)
The emitted op, with the three operand/result types called out:
%108 = "ktdp.inter_tile_reduce"(%107, %106) ... : (
!ktdp.tile_future<(tensor<2x16x32xf32>), groups = ...>, // future: physical
tensor<16x64xf32> // identity: LOGICAL
) -> tensor<2x16x32xf32> // result: physical
[16, 64] stick-on-N at stick 32 physicalizes to [64/32, 16, 32] =
[2, 16, 32]. Future and result made that transition; the identity did not.
Root cause
Two independent facts combine.
1. retypeChain cannot reach a sibling operand.
RewriteDescriptorLayout.cpp:596-635 walks forward over getUsers(), retyping
each single-result op's result from operand 0's shape (:624-631). For
tt.inter_tile that is the partial — correct, so the result is physicalized. But
the identity is operand 1: a sibling input to the same op, not a successor in the
use-def chain. The walk enqueues only op->getResult(0).getUsers() (:632-633),
so it never visits the identity's defining op. The pass has no notion of
inter-tile at all — grep -n 'inter_tile\|InterTile' over
RewriteDescriptorLayout.cpp and RewriteDescriptorLayout/ returns one hit, a
comment about pass ordering (:5).
2. LowerInterTile derives result types from the partials but forwards the
identity verbatim. LowerInterTile.cpp:413 and :417:
// Result types == partial types.
SmallVector<Type> resultTypes(partialTypes.begin(), partialTypes.end());
// Identities are always provided on the tt op (semantic.py materializes
// them for shorthand combiners at TTIR construction time).
SmallVector<Value> identityValues(identities.begin(), identities.end());
resultTypes tracks whatever shape the partials now carry, so it follows
physicalization automatically. identityValues is passed straight through from
the tt op, where semantic.py materialized it as a splat at logical rank
before any layout pass ran. Nothing in between reconciles the two.
The verifier that catches this is a TypesMatchWith constraint on the op itself
(ktir-mlir-frontend/include/Ktdp/KtdpOps.td:183-185):
TypesMatchWith<"identity types must match result types",
"results", "identity", "$_self">
So this fails loudly, unlike the dispatch-boundary hole in the
silent-miscompile issue. The op's own contract catches it. That is the good case.
Scope
Affects every layout-annotated inter-tile kernel regardless of mode — the
identity/result equality is required for both all_reduce and reduce_to_one,
and neither path retypes the identity. Reproduced above on all_reduce.
Independent of the softmax broadcast blocker
(fixtures/softmax/meta.py:68-72, arith.subf operand-type mismatch): that one
is a rank-mismatch between two values in the chain, this one is a sibling
operand the chain never visits. Same underlying theme — retypeChain only
propagates forward along operand 0 — but different fix sites.
Proposed fix
Two candidate layers; worth deciding explicitly rather than by whichever is
closer to hand.
(a) Rebuild the identity in LowerInterTile. At :417, re-materialize the
splat against resultTypes[i] instead of forwarding the tt operand. The
identity is a splat constant, so this is shape-agnostic by construction and needs
no layout knowledge — LowerInterTile already has the identity attribute logic
(:271). Localized, and correct for any future physicalization scheme.
(b) Retype the identity in RewriteDescriptorLayout. Teach the pass that
tt.inter_tile's identity operands must be retyped in lockstep with its result.
This keeps IR well-typed at every step (nicer for --verify-each), but adds an
op-specific special case to a pass that currently knows nothing about inter-tile,
and generalizes poorly to the next op with a shape-coupled sibling operand.
(a) looks right: the identity is derivable rather than something to be propagated.
Splat-of-identity at the result type is the definition, so recomputing it is
strictly more robust than keeping a copy in sync.
Test coverage gap
- No lit case covers a layout-annotated inter-tile kernel. The existing
lower-inter-tile.mlir (375 lines) and lower-inter-tile-invalid.mlir (114)
predate layouts and use logical shapes only.
fixtures/inter_tile_reduce/meta.py has no spyre_stick variant, unlike
matmul, gather, reduce, and vector_add. The blocker is now documented in
its module docstring.
- With the fix, the natural additions are a positive lit case pinning the
physicalized identity, and a numerical spyre_stick variant on the fixture.
Related
- The silent-miscompile issue — same pass, opposite failure mode (that one is
silent because no verifier constrains it; this one is caught by the op's own
TypesMatchWith).
- The tracking issue — coverage gap list.
fixtures/softmax/meta.py:68-72 — the other documented "no layout variant"
blocker, also rooted in retypeChain's forward-only propagation.
Problem
A stick-layout annotation on a descriptor feeding
tl.inter_tileproduces IR thatfails the
ktdp.inter_tile_reduceverifier. The op's future and result arephysicalized; its
identitiesoperand is not.This is the reason the
inter_tile_reducefixture carries nott.spyre_tensor_layoutvariant.Reproduction
inter_tile_add_kernel(fixtures/inter_tile_reduce/kernel.py:25) with astick-on-N annotation on both descriptors,
BLOCK_M=16,BLOCK_N=64, f32(stick = 32), grid
(8,):The emitted op, with the three operand/result types called out:
[16, 64]stick-on-N at stick 32 physicalizes to[64/32, 16, 32]=[2, 16, 32]. Future and result made that transition; the identity did not.Root cause
Two independent facts combine.
1.
retypeChaincannot reach a sibling operand.RewriteDescriptorLayout.cpp:596-635walks forward overgetUsers(), retypingeach single-result op's result from operand 0's shape (
:624-631). Fortt.inter_tilethat is the partial — correct, so the result is physicalized. Butthe identity is operand 1: a sibling input to the same op, not a successor in the
use-def chain. The walk enqueues only
op->getResult(0).getUsers()(:632-633),so it never visits the identity's defining op. The pass has no notion of
inter-tile at all —
grep -n 'inter_tile\|InterTile'overRewriteDescriptorLayout.cppandRewriteDescriptorLayout/returns one hit, acomment about pass ordering (
:5).2.
LowerInterTilederives result types from the partials but forwards theidentity verbatim.
LowerInterTile.cpp:413and:417:resultTypestracks whatever shape the partials now carry, so it followsphysicalization automatically.
identityValuesis passed straight through fromthe
ttop, wheresemantic.pymaterialized it as a splat at logical rankbefore any layout pass ran. Nothing in between reconciles the two.
The verifier that catches this is a
TypesMatchWithconstraint on the op itself(
ktir-mlir-frontend/include/Ktdp/KtdpOps.td:183-185):So this fails loudly, unlike the dispatch-boundary hole in the
silent-miscompile issue. The op's own contract catches it. That is the good case.
Scope
Affects every layout-annotated inter-tile kernel regardless of mode — the
identity/result equality is required for both
all_reduceandreduce_to_one,and neither path retypes the identity. Reproduced above on
all_reduce.Independent of the softmax broadcast blocker
(
fixtures/softmax/meta.py:68-72,arith.subfoperand-type mismatch): that oneis a rank-mismatch between two values in the chain, this one is a sibling
operand the chain never visits. Same underlying theme —
retypeChainonlypropagates forward along operand 0 — but different fix sites.
Proposed fix
Two candidate layers; worth deciding explicitly rather than by whichever is
closer to hand.
(a) Rebuild the identity in
LowerInterTile. At:417, re-materialize thesplat against
resultTypes[i]instead of forwarding thettoperand. Theidentity is a splat constant, so this is shape-agnostic by construction and needs
no layout knowledge —
LowerInterTilealready has the identity attribute logic(
:271). Localized, and correct for any future physicalization scheme.(b) Retype the identity in
RewriteDescriptorLayout. Teach the pass thattt.inter_tile's identity operands must be retyped in lockstep with its result.This keeps IR well-typed at every step (nicer for
--verify-each), but adds anop-specific special case to a pass that currently knows nothing about inter-tile,
and generalizes poorly to the next op with a shape-coupled sibling operand.
(a) looks right: the identity is derivable rather than something to be propagated.
Splat-of-identity at the result type is the definition, so recomputing it is
strictly more robust than keeping a copy in sync.
Test coverage gap
lower-inter-tile.mlir(375 lines) andlower-inter-tile-invalid.mlir(114)predate layouts and use logical shapes only.
fixtures/inter_tile_reduce/meta.pyhas nospyre_stickvariant, unlikematmul,gather,reduce, andvector_add. The blocker is now documented inits module docstring.
physicalized identity, and a numerical
spyre_stickvariant on the fixture.Related
silent because no verifier constrains it; this one is caught by the op's own
TypesMatchWith).fixtures/softmax/meta.py:68-72— the other documented "no layout variant"blocker, also rooted in
retypeChain's forward-only propagation.