Skip to content

[Bug] tt.inter_tile identity operand is not physicalized, so any layout-annotated inter-tile kernel fails to verify #92

Description

@fabianlim

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    wontfixThis will not be worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions