Conversation
Proposal: restructure
|
| Op | combine | placement | producers/grp | consumers/grp | dim attrs | region | identity |
|---|---|---|---|---|---|---|---|
consume |
none | replicate | 1 | free | — | — | — |
reduce |
fold | replicate | all | free | — | combiner | yes |
reduce_scatter |
fold | split | all | free | scatter_dimension |
combiner | yes |
gather |
none | concat | all | free | gather_dimension |
— | — |
all_to_all |
none | permute | all | all | split_dimension, concat_dimension |
— | — |
scatter |
none | split | 1 | free | scatter_dimension |
— | — |
all_to_all is promoted before scatter because it shares the all-producers
cardinality cell with gather and reduce_scatter; its relationship to
scatter is structural (permute = split + concat in one step) and should be
visible in the table rather than deferred to a section.
Three things this matrix makes visible:
placementtakes only four values. The per-op "type rules" subsections are
four formulas wearing six hats.- The empty cells are principled.
none×replicatewith all producers is
undefined (which producer's value wins?), andfold×concat/fold×
permuteis meaningless (folding then shuffling what?). all_to_allis the fourth placement value, not a special case:gather+scatterin one step.
1.1 Type rules, one per placement value
| placement | result type derived from T_p |
|---|---|
replicate |
tile axes collapsed (fold) / unchanged (none) |
concat |
extent along gather_dimension multiplied by K |
permute |
split_dimension extent divided by M; concat_dimension extent multiplied by K |
split |
extent along scatter_dimension divided by M |
For the square all_to_all case (M == K == |group|) the permute result type
equals T_p — total tensor size is conserved. This is the uniform one-to-one
shuffle that the SDSC backend emits today.
1.2 Normative definition of within-group local index
l is the rank of a tile, by ascending tile id, among the relevant set
(producers for concat and permute's concat axis; consumers for split and
permute's split axis) within its group.
Without this definition, concatenation and split orders are pinned down only by
contiguous-tile-id coincidence and silently break under non-monotone tile
assignments (see §8.4 of the backend findings).
For all_to_all specifically: producer rank determines which split_dimension
slice each producer contributes; consumer rank determines which
concat_dimension position each consumer receives.
2. Verification matrix
Principle: each rule has exactly one owner and one statement; applicability
is a column, not a restatement.
| Rule | Owner | consume | reduce | red_scat | gather | all_to_all | scatter |
|---|---|---|---|---|---|---|---|
| R1 group disjointness | produce | y | y | y | y | y | y |
| R2 single-use future | produce | y | y | y | y | y | y |
| R3 dep set subset of producers | delivery | y | y | y | y | y | n/a |
| R4 every producer covered by some consumer | delivery | y | y | y | y | y | n/a |
| R5 dep sets pairwise disjoint | delivery | — | — | — | y | y | n/a |
| R6 uniform dep-set cardinality | delivery | — | — | — | y | y | n/a |
| R7 uniform producer cardinality across groups | delivery | — | — | — | y | y | n/a |
| R8 producers per group = 1 | delivery | y | — | — | — | — | y |
R9 scatter_dimension divisible by consumer count |
delivery | — | — | y | — | — | y |
R9b split_dimension divisible by group size |
delivery | — | — | — | — | y | — |
| R10 combiner purity | delivery | — | y | y | — | — | — |
R11 identity shape matches T_p |
delivery | — | y | y | — | — | — |
R12 concat_dimension extent × K well-defined |
delivery | — | — | — | y | y | — |
New rules introduced by all_to_all:
- R9b —
split_dimensionextent must be divisible byM(the group size).
Analogous to R9 forreduce_scatter/scatter. For the square caseM == K
so R9b and R12 reduce to the same divisibility. - R12 — The result extent along
concat_dimensionisK × T_p[concat_dim].
This requires that all producers contribute the same post-split size (follows
from R7 + R9b for the square case, but should be stated independently for the
non-square case).
Rules implemented in KTIRCheckLegality.cpp:
| Rule | Op | Check | Location |
|---|---|---|---|
| R2 (single-use future) | inter_tile_produce |
future.hasOneUse() |
KTIRCheckLegality.cpp:80–85 |
| R3 (dep set ⊆ producers) | inter_tile_reduce |
C⊆P check per group | KTIRCheckLegality.cpp:107–117 |
| R4 (every producer covered) | inter_tile_reduce |
coverage check per group | KTIRCheckLegality.cpp:163–174 |
| R3 on dep attr | inter_tile_reduce |
dep tile ∈ P(g) | KTIRCheckLegality.cpp:151–160 |
| R4 on dep attr | inter_tile_reduce |
every p covered by dep | KTIRCheckLegality.cpp:163–174 |
Not yet in KTIRCheckLegality.cpp — R1, R5, R6, R7, R8, R9, R9b, R10,
R11, R12, and R3/R4 for gather, scatter, consume, reduce_scatter, all_to_all.
The current legality pass only walks InterTileProduceOp and InterTileReduceOp.
R7 and R5 are enforced in the Torch-Spyre SDSC planner (_compatible_partitions)
but absent from the KTIR verifier entirely — the gap exists at both the spec and
implementation level.
2.1 Rules that do not exist yet (the actual defects)
R7 — uniform producer cardinality across groups. producer_tiles_per_group
is a parameterized affine set over g, and nothing requires equal cardinality
per group. The op result is a single static tensor type, so unequal groups yield
no expressible result type. Applies to both gather and all_to_all.
R5 — dependency sets pairwise disjoint. §6.1's coverage check requires only
that each producer be claimed by at least one consumer — overlap is permitted.
Uniform cardinality combined with at-least-one coverage admits declared sets
that double-count producers. Either add R5, or retract the uniform-result claim.
R9b — stated above, not yet in the document.
2.2 An asymmetry the matrix forces into the open
R8 is verifier-enforced for scatter but merely conventional for consume.
Whether that difference is intentional should be decided rather than inherited.
3. All-gather and all-to-all
3.1 All-gather is gather with wider consumer set
All-gather is ktdp.inter_tile_gather with
consumer_tiles_per_group = <all tiles in group>. It is not a new op — the
existing attribute already accepts this value and §6.4 already states the case.
What all-gather does need (inherited from gather):
- R7 (uniform producer cardinality, §2.1). Without it
Kin the result type
is undefined when groups differ. - §1.2 (normative local index). Without it concatenation order depends on
contiguous-tile-id coincidence.
3.2 All-to-all is not decomposable into existing ops
All-to-all requires every tile to be simultaneously a producer of M distinct
slices and a consumer of K distinct slices:
tile 0: A[0][0..3] tile 0: A[0][0] A[1][0] A[2][0] A[3][0]
tile 1: A[1][0..3] tile 1: A[0][1] A[1][1] A[2][1] A[3][1]
tile 2: A[2][0..3] --> tile 2: A[0][2] A[1][2] A[2][2] A[3][2]
tile 3: A[3][0..3] tile 3: A[0][3] A[1][3] A[2][3] A[3][3]
Neither existing op admits this:
gatherdelivers the same assembled tensor to every consumer (§6.4). It
cannot give consumers different content.scatterpermits exactly one producer per group. It cannot have every tile
contribute.
Composing them materializes the full concatenation on every tile — wrong data
volume and wrong communication pattern.
3.3 all_to_all — op definition
One new op, ktdp.inter_tile_all_to_all, with placement permute.
Attributes: split_dimension, concat_dimension.
Type rule: T_p with split_dimension extent divided by M and
concat_dimension extent multiplied by K. For the square case
(M == K == |group|) the result type equals T_p.
Verification rules: reuses R3, R4, R5, R6, R7 unchanged from gather; adds
R9b (split_dimension divisible by M) and R12 (well-defined concat extent).
MLIR sketch:
// all-to-all — 4 tiles, each splits along dim 0 and collects along dim 1
%r = ktdp.inter_tile_all_to_all(%future)
producer_tiles_per_group = <all tiles>,
consumer_tiles_per_group = <all tiles>,
split_dimension = 0,
concat_dimension = 13.4 Relationship to the SDSC backend shuffle
The SDSC backend emits a single opfunc = "shuffle" for all relayout patterns.
The entire payload is two coreIdToWkSlice_ tables — one per tensor in
coordinates_ — describing the per-core ownership before and after the movement.
all_to_all with explicit split_dimension/concat_dimension attributes lowers
directly to this table pair.
The axis-transpose case ([4,8] → [8,4], same dimensions swapped) is the one
pattern the current KTIR dim-attribute design cannot express — the ownership
tables differ but no single split_dimension or concat_dimension captures the
transformation. An explicit source→destination affine map (Option B below) would
handle it; for now, mark it as out of scope for all_to_all and add a verifier
that rejects non-decomposable transpositions.
3.5 Option B — explicit coordinate map (recorded, not recommended now)
Replace the split_dimension/concat_dimension attribute pair with a single
source-to-destination affine map, subsuming all four placement values. This is
the shape that interface-specs PR 14 already uses (SHUFFLE as source/destination
coordinate sets). The backend already speaks per-core partitionings
(coreIdToWkSlice_ tables), not dim-attributes, so Option B is arguably closer
to the existing contract.
Recommendation: implement all_to_all with dim-attributes first (additive,
reviewable on its own), and record Option B as the long-term direction. If none
of the five delivery ops is built yet, the cost argument for Option A over B is
weaker than usual.
3.6 Fused relayout — deferred
Relayout is a separate preceding op; fusion is a lowering concern. The backend
structurally cannot fuse them: restickify is a separate pass (runs before LX
planning), and restickified weights are explicitly barred as shuffle sources.
4. All- naming convention
Ops whose consumers/grp is free already subsume the all-tiles case by
widening the consumer set. The "all-" prefix names the pattern, not a new op:
| Pattern name | KTIR op | Consumer set | Notes |
|---|---|---|---|
| all-gather | inter_tile_gather |
all tiles in group | existing op, wider consumer set |
| all-to-all | inter_tile_all_to_all |
all tiles in group | new op, §3.3 |
| all-reduce | inter_tile_reduce |
all tiles in group | existing op |
| all-reduce-scatter | inter_tile_reduce_scatter |
all tiles in group | existing op |
inter_tile_scatter and inter_tile_consume do not have natural "all-" variants
under the current cardinality constraints (R8 limits them to 1 producer per group).
5. Proposed outline
1 Motivation + three-axis decomposition ← matrix from §1 (six ops, four placements)
2 produce (unchanged)
3 Shared delivery semantics ← NEW. consumer sets,
consumer_tiles_per_group producer_dependency_per_consumer,
producer_dependency_per_consumer local-index definition (§1.2), and
within-group local index sync model stated ONCE.
4 Placement algebra + type rules ← four formulas (§1.1), not five
5 Verification rules R1-R12 ← matrix from §2
6 The delivery ops ← per op: only its own cells;
all_to_all placed before scatter
7 Pattern instantiation / examples ← current §10, annotated per §1.2
8 Backend pattern catalogue ← new, §7 of this proposal
9 Open questions and extensions
6. Smaller corrections to fold in
- §9 coverage table contradicts §6.1. The table lists gather as "all tiles
per group" forinter_tile_produceand omits the consumer-set axis. Add
all_to_allrow; show consumer set as a column since that distinguishes
gather from all-gather and all_to_all from scatter. - §1 line 51 cross-reference omits §7.1.
- §12 renumbering dropped a still-open question. Q1 (must a consumer also be
a producer?) is resolved for scatter only; mark open for consume/reduce/reduce_scatter. - §10.6.1 example style diverges. Align with other full-IR examples or note
why it differs. all_to_allbelongs in the §9 coverage table as a new row with
placement = permute,producers/grp = all,consumers/grp = all.
7. Backend pattern catalogue (Torch-Spyre)
The following table records patterns observed in the Torch-Spyre backend
(scratchpad/lx_relayout.py) where all classification fields are known. Rows
with unknown split axes are omitted. Patterns are classified using the derivation
in this proposal: gathered_dims = src_syms − dst_syms,
scattered_dims = dst_syms − src_syms, factor = num_cores // prod(dst splits).
All implemented patterns emit a single SDSC opfunc = "shuffle" whose payload
is two coreIdToWkSlice_ tables on the source and destination tensors.
| ID | SenDNN op | Src work_div |
Dst view | gathered | scattered | factor | Proposed KTIR op | Backend status |
|---|---|---|---|---|---|---|---|---|
| P01 | all-gather | {H:8, Lk:4} |
replicated (all cores) | H, Lk | — | N/A | inter_tile_gather (all consumers) |
missing — replication not supported |
| P03 | grouped all-gather | {H:8, Lk:4} |
{H:8} |
Lk | — | 4 | inter_tile_gather |
#3440 (open PR) |
| P04 | grouped all-gather | {Lk:32} |
{H:8} |
Lk | H | 4 | inter_tile_gather |
#3440 (open PR) |
| P06 | all-to-all | {H:8, Lk:4} |
{Lk:32} |
H | — | 1 | inter_tile_all_to_all |
main (#3439) |
| P08 | all-to-all (axis transpose) | {A:4, B:8} |
{A:8, B:4} |
— | — | 1 | inter_tile_all_to_all + explicit coord map |
missing — axis swap inexpressible |
| P14 | all-to-all | {H:8, Lq:4} |
{Lq:32} |
H | — | 1 | inter_tile_all_to_all |
main (#3439) |
Notes:
- P03 / P04 demonstrate that
gathered_dims ≠ ∅withfactor > 1maps to
inter_tile_gather. For P04,scattered_dims = {H}(H is introduced at the
destination) — this is a combined gather+scatter in one shuffle step; the KTIR
op must express both axes. - P06 / P14 demonstrate
inter_tile_all_to_allat factor=1:gathered_dims = {H},
scattered_dims = ∅. H is contracted into the 1D destination axis. - P08 is the case
all_to_allwith dim-attributes cannot express: both sides
split the same two dims with swapped counts ([4,8] → [8,4]). The
coreIdToWkSlice_tables differ because the mixed-radix odometer ordering
changes, but no singlesplit_dimension/concat_dimensioncaptures the
transformation. This is the §8.4 miscompile class — making the coordinate map
explicit in KTIR would catch it statically. - P01 (replication / full all-gather) is blocked because
_compatible_partitionsrequires distinct slices per destination core. Maps to
inter_tile_gatherwithconsumer_tiles_per_group = all, but requires new
backend support for non-bijective shuffle.
Classification decision rule (derived from TensorArg.work_division on a
relayout identity OpSpec):
gathered_dims |
scattered_dims |
factor |
slot_exprs_differ |
KTIR op |
|---|---|---|---|---|
| ∅ | ∅ | 1 | false | no-op |
| ∅ | ∅ | 1 | true | inter_tile_all_to_all + explicit coord map |
| non-∅ | ∅ or non-∅ | > 1 | — | inter_tile_gather |
| non-∅ | ∅ | 1 | — | inter_tile_all_to_all |
| ∅ | non-∅ | 1 | — | inter_tile_scatter |
| ∅ | ∅ | 1 | — (replication) | inter_tile_gather (all consumers) |
Restructures the RFC per the review of torch-spyre#53 so that the machinery shared by the delivery ops is stated once instead of repeated per op. - Reorganization: §1 gives the three-property decomposition (combine × placement × cardinality) and a semantics matrix; §3 states the shared delivery semantics (operand, consumer set, local index, dependency attribute, combiner, synchronization, result); §4 the placement algebra and type rules; §5 the verification rules R1-R14. §6 then reduces each op to what is only true of it. - Adds ktdp.inter_tile_all_to_all as a first-class delivery op (§6.5, worked example §7.6). It is the permute placement -- split and concat in one step -- and is not decomposable into gather + scatter, since gather delivers the same tensor to every consumer and scatter permits only one producer. - Renames gather_dimension/scatter_dimension to gather_dim/scatter_dim, consistent across all delivery ops. Naming the split axis uniformly makes the separate all_to_all divisibility rule redundant: it is now covered by R9. - Moves the verifier's current state into its own non-normative section (§8) so §5 is purely normative, and adds the two rules the pass already enforces for reduce: R13 (consumer set subset of producer set) and R14 (reduce mode gate, C == P or |C| == 1). - Reserves "axis" for tensor and tile axes, and "rank" for a tensor's number of dimensions; the within-group local index is a tile's "position". Co-authored-by: Yu Chin Fabian Lim <fabianlim@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
ff61eeb to
73cf87f
Compare
|
@fabianlim Thank you so much for your review. I reflected your comments. |
|
@ani300 @AdnanHoque would appreciate if you guys can review the Section 7 in the comment above regarding the use cases in torch-spyre |
|
The overall direction looks right: KTIR should describe the communication meaning, while torch-spyre can lower several of these forms to the same SDSC shuffle. Some notes on Section 7:
I suggest classifying from:
With that adjustment, the proposed mappings are roughly: P01/P03/P04 → gather, P06 → all-to-all, P08 → one-to-one consume/permutation, and P14 → selection followed by permutation. |
|
Thanks @AdnanHoque I did one more round to update Section 7 and came up now with a pretty detailed design. cc: @tnakaike 7. Backend pattern catalogueEvery relayout in the backend compiles to one SDSC entry with Everything that classifies factorizes onto axes. A region is a multi-dimensional 7.1 The inputA table maps Axis versus region. An axis is a direction that gets cut; a region is what cutting
Axes must be aligned by physical axis, not by label. Labels differ between the two Shorthand and its limits. A work division so Precondition. Per-axis reasoning assumes each side's region set is the full product 7.2 The axis tableOne row per axis, with fanin(a) = max(ceil((kd+1)*Ns(a)/Nd(a)) - floor(kd*Ns(a)/Nd(a)) for kd in range(Nd(a)))
fanout(a) = max(ceil((ks+1)*Nd(a)/Ns(a)) - floor(ks*Nd(a)/Ns(a)) for ks in range(Ns(a)))
Everything the decision needs is a product or conjunction down the rows: max_fanin = prod(fanin(a)) uniform_fanin = all(ufi(a))
max_fanout = prod(fanout(a)) uniform_fanout = all(ufo(a))
len(src_regions) = prod(Ns(a)) len(dst_regions) = prod(Nd(a))
components = prod(gcd(Ns(a), Nd(a)))
C = {a : Ns(a) > Nd(a)} # coarsened
R = {a : Nd(a) > Ns(a)} # refinedA component is a maximal set of source and destination regions exchanging only M = len(dst_regions) / components # contributed = consumers per group
K = len(src_regions) / components # received = producers per groupNote the sides: What the axis table cannot say. 7.3 The decision table
The dimension attributes are the axis sets themselves, in the order §1.2 fixes, so Two things worth stating plainly. Row 3's 7.4 Worked exampleA real all-to-all —
Two shapes are instructive by contrast. Source and destination cutting different 7.5 Measured patterns51 measured relayouts, each
Divisions are the measured ones, in What the measurements establish.
Where the records and the measurements disagree. In each case the measurement is
7.6 What would still need measuring
|
Add ktdp.inter_tile_gather as a full first-class delivery-op section (new §6), alongside consume / reduce / reduce_scatter. Gather assembles the producers' partials into one tensor by ordered concatenation along gather_dimension — no fold. Design choices: - General consumer set: one tile per group is a plain gather, all tiles is an all-gather. - Optional producer_dependency_per_consumer for a partial/segmented gather over a declared producer subset. - No combiner region and no identity operand (pure positional assembly, ordered by within-group local index). Also: add a full-IR multi-group example (§9.5.1, 128x8x12x64), update the scope/motivation/coverage tables and count-prose (four ops -> five, three patterns -> four), extend the relationship note, and resolve the gather open question (removed from the open-questions list). Renumber the sections after gather down by one to make room for §6, fixing internal cross-references. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
Add ktdp.inter_tile_scatter as a full first-class delivery-op section (new §7). Scatter splits a single producer's tensor into ordered slices, one per consumer, along scatter_dimension — the dual of gather. Design choices: - Single producer per group (verifier rejects multi-producer selection). - No producer_dependency_per_consumer: with one producer the full-barrier and per-tile modes collapse, so the attribute is omitted. - No combiner region and no identity operand (pure positional partition). - Slice type is the honest T_p with one axis divided (<128x1x64> -> <32x1x64>). Also: add a full-IR multi-group example (§10.6.1, 128x8x64), update the scope/motivation/coverage tables and count-prose (five ops -> six, four patterns -> five), extend the relationship note, and resolve the consumer-need-not-be-producer open question for scatter (removed from the open-questions list). Flip the coverage-summary note to "all rows are first-class ops (§2–§7)". Renumber the Synchronization, Coverage, Pattern instantiation, Relationship, Open questions, and Extensions sections down by one to make room for §7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
Restructures the RFC per the review of torch-spyre#53 so that the machinery shared by the delivery ops is stated once instead of repeated per op. - Reorganization: §1 gives the three-property decomposition (combine × placement × cardinality) and a semantics matrix; §3 states the shared delivery semantics (operand, consumer set, local index, dependency attribute, combiner, synchronization, result); §4 the placement algebra and type rules; §5 the verification rules R1-R14. §6 then reduces each op to what is only true of it. - Adds ktdp.inter_tile_all_to_all as a first-class delivery op (§6.5, worked example §7.6). It is the permute placement -- split and concat in one step -- and is not decomposable into gather + scatter, since gather delivers the same tensor to every consumer and scatter permits only one producer. - Renames gather_dimension/scatter_dimension to gather_dim/scatter_dim, consistent across all delivery ops. Naming the split axis uniformly makes the separate all_to_all divisibility rule redundant: it is now covered by R9. - Moves the verifier's current state into its own non-normative section (§8) so §5 is purely normative, and adds the two rules the pass already enforces for reduce: R13 (consumer set subset of producer set) and R14 (reduce mode gate, C == P or |C| == 1). - Reserves "axis" for tensor and tile axes, and "rank" for a tensor's number of dimensions; the within-group local index is a tile's "position". Co-authored-by: Yu Chin Fabian Lim <fabianlim@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
|
@tnakaike incorporated the torch-spyre relayout analysis from @AdnanHoque in e196353 |
Signed-off-by: Yu Chin Fabian Lim <flim@sg.ibm.com>
Two small follow-ups on top of the measured-data rework. Table names. Every table gets an explicit "**Bold name.** One row per ..." line so prose can refer to it by name instead of "the table": semantics matrix (§1.1), coverage table (§1.2), symbol table (§3.1), sharing table (§3.7), type-rule table (§4), verification matrix (§5), the two dependency tables (§7.4.1, §7.4.2), implemented-rule table (§8) and migration table (Appendix A). §8's two references to "the §5 matrix" follow the new name. §4 conservation case. The equal-type all-to-all — the distributed transpose, where split_dimensions == concat_dimensions — was described as "the uniform one-to-one shuffle the SDSC backend emits today". The measured data contradicts that: every measured all-to-all splits and concats *different* axes (§9.3), so P == C conserves the element count while the type still changes, and the equal-type case is recorded but unmeasured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
e196353 to
2d8dc1a
Compare
…ge clause §9 already decides the one measured file that is a selection rather than a partition: the guard row's verdict is "not a work-division pair — enumerate regions instead". Nothing here changes that. What a reader could not do was reach that verdict from the stated clauses, for two reasons. First, the preamble defined `Ns(a)`/`Nd(a)` as "the number of slices axis `a` is cut into", which admits two readings — count the distinct slices an ownership table contains, or divide the axis extent by one slice's extent. They agree on 50 of the 51 measured files; on the selection the destination gives `Nd(mb) = 1` counted, since every core names `mb[511:512]`, and `512` divided. Counting is the reading intended: dividing counts 511 pieces no core owns, and would report `mb` as refined when the movement assembles along it. So pin the definition to counting, and add that file's two ownership tables to the preamble so the rule can be read off real data rather than asserted. §9.2 works a pattern forward from slice counts already given; this pair shows where the counts come from. Second, under that rule both region-count clauses of the guard row pass — `prod(Ns(a)) = 8 × 4 = 32` and `prod(Nd(a)) = 1 × 32 = 32`, matching the region counts on either side. What disqualifies the file is the coverage §9.3 already reports: `32 × 64 = 2048` elements against `512 × 32 × 64 = 1048576`, one 512th of it. No count clause can see that, since a selection is a perfectly consistent set of regions that happens not to add up to the tensor. So write the condition the verdict already implies as a third clause on the same row, and explain its two qualifications: it sums a side's *distinct* regions, or a per-core sum overshoots by design, and it measures against the value being delivered, or the select-then-deliver that repairs a selection would trip the guard it was meant to satisfy. Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
2d8dc1a to
bbcc830
Compare
|
@AdnanHoque @fabianlim Thank you for your reviews and updates. I made a few additional minor changes to improve the readability of Section 9. Pushed Rebase note first. The branch was force-pushed. Your What (1) A strict definition for (2) The row-0 condition, with a description and an example. Its verdict is right for a selection — "not a work-division pair — enumerate regions instead". The following is an example of a case where the classification fails: both region-count clauses match, so the row lets it through.
What rejects it is coverage — the 1/512 §9.3 already reports: 32 distinct destination regions of one stick each, One thing I did not change, flagging in case you want to: the preamble still says "Everything else the classification needs is a product down the axes." With the coverage condition spelled out that is now slightly narrow — it reads slice sizes and the tensor shape, the only test in §9 that does. For completeness, the other new commit on the branch, |
R8 read "exactly one producer tile per group", which contradicted the
document's own `consume` examples: the per-tile pairing example declares
two producers per group and the butterfly exchange declares four. §6.1
also claimed the dependency attribute never changes what a consumer
receives, while §3.4 says that for `replicate` placement it selects which
producer a consumer reads. Both cannot hold.
What `replicate` and `split` actually require is that each *consumer
tile* have a single source -- their results have room for one
contribution and no combiner to fold a second -- so state R8 that way:
forall g, forall c in consumer_tiles_per_group(g) : |dep(c, g)| == 1
One producer per group is then the common case that satisfies it without
an attribute (broadcast). A multi-producer group stays legal for
`consume` when the attribute pairs each consumer tile with exactly one
producer: that is a routing pattern, several point-to-point deliveries
sharing one produce op, not an all-producers delivery. Absent the
attribute, a multi-producer group is rejected -- receiving from every
producer is §1.1's undefined cell. A producer may serve several consumer
tiles; it may not serve none, which R4 already requires. `scatter` takes
no dependency attribute, so for it R8 reduces to its original form.
Also records that the one-symbol dependency spelling is now implemented,
and drops a duplicated sentence in R9.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Yu Chin Fabian Lim <flim@sg.ibm.com>
73969ca to
e4d3f0f
Compare
§9 says three times that a selection is not a delivery and that coverage is measured against the value delivered, but never says where a bounded extent lives. The ownership tables raise it directly: the destination owns mb[511:512], and nothing in the delivery ops can say that. The tile sets name which tiles participate, the dimension attributes name which axes split or concatenate, and §4's type rules are extent arithmetic with no base coordinate -- none of the six ops carries an offset. So a bounded extent is a property of T_p, which gets it from the access tile the partial was loaded through. The snippet shows that chain -- access tile, load, yield_partial, delivery -- in §6's placeholder style rather than with one measured file's coordinates, since what generalizes is that the extent and offset are arguments to construct_access_tile and appear nowhere downstream: selecting a different sub-tensor changes only T_p. Whether a delivery is needed at all is then the ordinary classification question, which for this pair §9.1 row 1 answers with `no op needed`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Yu Chin Fabian Lim <flim@sg.ibm.com>
e4d3f0f to
1f9797d
Compare
|
@tnakaike @fabianlim Thanks for incorporating the earlier feedback. I would like us to use the real Torch-Spyre relayout edges as the acceptance contract for this design, rather than deciding coverage from operation names alone. I put the full workload inventory in torch-spyre #4300. It covers all 55 prefill and 75 decode records in the pinned Granite catalog, the Gemma E0–E10 expert-body boundaries, and the known paged-attention cache/K/V/state edges. Missing current application captures are listed as gaps, so this does not claim that KTIR already passes them. For each edge, I propose that we record the original source owners, the destination owners, the local value order, and whether the inputs are independent pieces or an already-completed reduction. Then we require three separate checks: the KTIR form verifies, the lowering moves the right values without an intermediate HBM spill, and the real consumer produces the right result. The four-way paged-attention V case is a useful first example. Cores 0–3 each start with a different piece; cores 0, 8, 16, and 24 each need all four pieces in order. That is gather + broadcast. Loading a complete V page from HBM on one leader is not equivalent because it changes the starting ownership. I suggest we settle these cases first in this PR:
Once we agree on the smallest KTIR examples for those cases, we can turn them into verifier and execution tests and then run the full inventory. Any equivalent on-chip lowering is fine; the requirement is the ownership relation and the observed result, not one prescribed spelling. |
Coverage was being read off operation names, so the two tile sets a delivery relates were only recoverable by tracing R4-R8 and R13 across sections. State the relation directly and settle what was left open. - §1.2: a relation table, one row per delivery op, answering whether a consumer must also be a producer, whether one producer may serve several consumers, and whether the two sets coincide. - §3.3: pin, normatively, that when producer_dependency_per_consumer is present an assembling consumer's positions come from its own declared set, not from the group's producer set. §3.4 already implies this by making P follow from the subset: a P-chunk result needs P consecutive positions, which only the declared subset supplies. - §5 R13: y for reduce, n for the other five. §6.6's argument turns on combine = none, which all four copy-only ops share, not on scatter's single source; and §9.3's non-square all_to_all measures K = 2 producers against M = 4 consumers per group, so a receiving-only tile is a measured shape there. - §5 R5: scope the disjointness obligation to a partitioning use. Well-definedness comes from §3.3, so identical or overlapping dependency sets - an all-gather, a multicast source - must not be rejected as such. - §6.5: "all tiles produce, all tiles consume" means the whole declared set on each side, not that the two sets are the same tiles. - §8, §10.1: record the above; what remains open is reduce alone, where dropping R13 also means revisiting R14's mode gate. Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
4b69bad to
1b1520f
Compare
|
@AdnanHoque Thank you so much for summarizing real use cases. I created KTIR examples that cover the use cases which you suggested under
Some notes that are not in the README: The spec commit settles what your earlier notes touched. §10.1 is resolved as Two files reach a verdict today. KT-06 turned out to be a clean 4x8 transpose, and its five records are one KTIR form. GR-PF-052 ( KT-04 needs no new op — it is KT-03 needs no new capability, but its correctness is not something a verifier can reach. §3.3 orders an assembly by ascending tile id and no dependency set can change that, so the reorder belongs at the store: two stores at swapped base coordinates carry it, with identity order inside each and no extra data movement. A single access tile cannot, being a base plus a region relative to that base. Coverage §5 allows exactly this form — "local selection/reordering is acceptable if fully expressed and stays on-chip" — so the requirement is satisfied as written. What it costs is verifiability: no rule relates the store bases to §3.3's assembly order, so a verifier sees two well-formed stores that between them cover the region exactly once, while storing the assembly verbatim has the same shape, the same element count, the same coverage and the wrong answer. The correctness of the reorder therefore falls outside inter-tile verification, and the numerical check is its only guarantee. I did consider an ordering attribute on the delivery op redefining §3.3's Two things about reading the catalog, in case they save anyone else the detour. Still to write, for the record: KT-08 as above, KT-09 (reuse and lifetime, which your text notes does not require multiple delivery users of one future, so it does not need R2 relaxed), and KT-10's deliberate bad variants. Of those, only negatives against |
d06d022 to
f52f65c
Compare
f52f65c to
9825db1
Compare
torch-spyre#4300 §7 names KT-01/02 (shared sources, receiving-only cores), KT-05 (selection) and KT-07 (completed sums) as the cases that decide semantics rather than implementation polish. This adds the smallest KTIR form for each, plus KT-03 (logical order against tile-id order), KT-04 (multi-axis assembly), KT-06 (permuted core ownership) and KT-09 (many readers of one delivery, and scratch reuse) — 8 of the 10 KT cases. Each file carries its fixture, its operation sequence, its expected values, the rules it exercises, and its verification status stated rather than assumed. Fixtures come from the pinned ownership catalog, not from the inventory's summary rows: those rows cannot separate divisions that give the same piece counts, and their shape column is alphabetically sorted rather than in layoutDimOrder_. Two further traps in reading it are recorded in kt06. source_pieces is ordered lexicographically by its key string, so piece index is not owner order, and source_core_patterns is not a per-piece record - the owners field on each piece is the authoritative one, and deriving the map from index position yields a bijection that is not the transpose. And the byte fields are logical: prod(extents) * word_length equals logical_tensor_bytes even for the record whose SDSC run shows an extent-1 y carried as 64 padded lanes, so padding is invisible in the catalog for every record and no record can settle a layout question alone. Only produce and reduce exist in KTDP.td, so kt07a - an all-reduce - is the only delivery that parses and passes --ktir-check-legality end to end. The others parse up to their delivery op and stop there; each says which op it waits on. kt06's variant A also passes, for the opposite reason: it is the branch §9.1 row 1 resolves to "no op needed", so there is no delivery op in it to be missing. Every file states its operation sequence, collected as a table in the README. Side by side that view carries what no single file shows: kt05 is the only load inside a produce region (§2.2, since only one tile per group produces); kt06 variant A is one op long and the absent delivery is its claim; kt06 variant B loads and stores at the same address, so its permutation is entirely in the dependency attribute; kt09 half 1 is the only case whose wrong version has an identical shape and identical values, differing only in emitted transfer count; and 8 of the 10 sequences rely on a landing store the IR never names. What the examples establish, read off the catalog or the verifier rather than argued: - Receiving-only cores are the norm. 64 of 130 records have destination owners that are not source owners, so R13 = n for the copy-only ops is required by measurement. - The catalog holds no reduction at all: 130 of 130 are copy-only. So KT-07's halves come from different places, and the risk it guards against is one-sided - reading a copy as a fold. - A fold op cannot stand in for a copy op. Writing KT-07 (b) as a degenerate reduce over one producer is rejected by the shipped legality pass on R13, which is evidence rather than argument. - Overlapping-but-differing dependency sets occur in 0 of 130 records, so R5's scoping is forced by measurement while §3.3's "which set" clause is not. kt01 carries a synthetic second function for the latter, which also discriminates §3.1's P derivation; the measured form is blind to both. - Where a required order disagrees with the order the IR supplies, whether a verifier can see it depends only on where the ordering is allowed to live. kt06's permutation lives in a dependency set, where R3 through R8 reach it - and the grouping choice is what gives them anything to compare, since R1, R4 and R5 are obligations within a group: 8 groups of 4 make R1 and R3 bite, one group of 32 leaves both nearly trivial, and 32 singleton groups make R4 and R5 vacuous. kt03's reorder has to live in store bases, where no rule reaches it at all. - KT-04 needs no new op - it is gather with two entries in gather_dimensions - and its verdict splits. §4's normative flattening plus R9's ascending rule and R12's per-axis check close both spelling errors, a reversed list and a single axis where two are needed. They do not close the case where the producers' tile order and the required cell order are a transpose, which is the requirement's own toy and is kt03's finding in multi-axis form. - One delivery may have many readers, and R2 does not stand in the way: it constrains the tile_future, not the delivery's result. kt09's fixture makes the cost measured rather than argued - mean-LayerNormNorm_out is the source of relayouts[1, 8, 16], the Q/K/V projections off one layer norm, and the three records are identical at 12 MiB each, so a delivery per reader moves 36 MiB where 12 suffices. The catalog invites it by being indexed on (consumer, input); 8 of its 120 distinct tensors are shared this way. Measurement never forces the ordering problem. Sources contribute in ascending tile-id order in 130 of 130 records, GR-PF-052 and its four siblings agree with the transpose, and GR-PF-121 agrees with §4's odometer for all 16 producers. So kt03 and kt04's toy are synthetic, and the measured functions beside them need nothing beyond a plain delivery. KT-03 is satisfied as written rather than blocked: coverage §5 permits "local selection/reordering ... if fully expressed and stays on-chip", and two stores at swapped bases are exactly that. Nor do they cost anything - a copy delivery lands every received tile in LX before a compute unit can read it, so P landing stores are mandatory whatever the order is, and the two stores here are those landings aimed at swapped bases. What the case does cost is verifiability, since no rule relates the store bases to §3.3's assembly order. An ordering attribute on the delivery op was considered and rejected on that basis: an attribute can be checked for well-formedness but never for intent, so it would add a second statement of the ordering without moving the case into the verifier - and, given the mandatory landings, without saving a write either. The same landing rule is why kt09's two halves are a real choice rather than a good form and a bad one. Keep the delivered value a tensor and no store appears in the IR, so its readers hold no buffer and its lifetime is the backend's; store it into LX and the live range becomes ordinary MLIR memory effects, at no extra cost because the landing happens either way. Reduction is exempt from the rule - a compute unit can send over a different ring - which is why kt07a can leave its result unstored. kt05 also shows the §9.1 guard doing what it is for: it rejects the pre-select reading of GR-PF-055 on coverage, and after the select the post-select pair classifies as row 2 and yields the scatter in the file. The op is derived, not chosen. Group structure is stated the same way in every file - a table keyed on g, producers before consumers, the formula in the column head - because the affine sets all bind g, so the h and q two of the files used disagreed with the IR they described. One aside, independent of any case: RFC 0682 defines access_tile_order both as a dimension nesting order and as a sort key over an intermediate variable space, and only the second could reorder within a dimension. kt03 deliberately does not depend on the generous reading, and KTDP.td's op description carries neither sentence. Not written: KT-08 (stick indivisibility) and KT-10 (deliberate bad variants). KT-08 has 19 measured records - y 32->64 in relayouts[39..50], a sub-stick y 4->2 in [57, 64, 73] - but needs an SDSC run first, because the catalog's logical byte fields cannot say whether y is sticked for those shapes or at what size. This line of work is suspended here: the plan has moved to expressing these relayouts with ktdp.construct_distributed_memory_view instead of the produce/delivery pair. The examples are kept because the fixtures, the measured counts and the catalog-reading notes are independent of which form is chosen, and because they record what each case demands of any answer. Signed-off-by: Takuya Nakaike <nakaike@jp.ibm.com>
9825db1 to
56e1194
Compare
This PR adds
ktdp.inter_tile_gatherandktdp.inter_tile_scatteras delivery ops indocs/inter-tile-communication.md, bringing the specification to six ops covering all five inter-tile communication patterns: broadcast, all-reduce, reduce-scatter, gather, and scatter.Closes #52.
What this PR does
Two commits, one per op:
ktdp.inter_tile_gather(§6) — assembles the producers' partials into one tensor by ordered concatenation alonggather_dimension(no fold). General consumer set (one tile per group is a plain gather, all tiles is an all-gather), optionalproducer_dependency_per_consumerfor a partial / segmented gather over a declared producer subset, and a full-IR multi-group example (§10.5.1, 128×8×12×64).ktdp.inter_tile_scatter(§7) — splits a single producer tile's tensor into ordered slices, one per consumer, alongscatter_dimension(the dual of gather). Single producer tile per group, noproducer_dependency_per_consumer(with one producer the full-barrier and per-tile modes collapse), and a full-IR multi-group example (§10.6.1, 128×8×64).Both ops carry no combiner region and no
identityoperand: unlikereduce/reduce_scatterthey place data by position rather than folding it, so once the axis is known the placement is deterministic. On this point they align withconsume.The rest of each op — the
!ktdp.tile_future<..., #groups>operand,producer_tiles_per_group/consumer_tiles_per_group, groups carried in the future type, and the def-use synchronization model — reuses the existing delivery-op scaffolding unchanged.Supporting edits
Scope
Documentation only — this PR updates the specification. No dialect / verifier / lowering implementation is included.