TileFoundry Spec — semantic-analysis (per-Op semantic derivation)¶
This spec owns TileFoundry's concrete static analysis services — the semantic contracts that derive types, access relations, and shard layouts over the IR. Each service is a registry-backed derived visitor: the common registration and dispatch mechanism is owned by visitor-registry; this file owns each service's requirements, handler shape, required context, and the semantic rules it enforces. The access relation's own carrier is owned by visitor-registry §4.1; this file links to it rather than redefining it.
1. Type propagation¶
Type inference is registered per Op through
@register_typeinfer(<OpClass>) and enforces its constraints via ctx.error(...)
(visitor-registry §4). A handler receives the op and a
typeinfer context, derives the output Type, and reports violations through
ctx.error. A hir.Function call composes these per-op rules under
elaboration (hir §1.1): the callee body is
reconstructed and each of its nodes re-derives through the same per-op
rules under the call's actual argument types, so a relation-derived rule
never needs its own function-boundary case.
- constraints:
- A function boundary MUST NOT complete or reject a legal
Partial. - A function return MAY carry a
Partial(reduction)in aTensorType, and a tuple return MAY carry it in any nested tensor field. - Call elaboration MUST preserve the
ShardLayoutmesh-axis position and reduction on the actual value; completion remains the responsibility of an explicitReshardor allreduce.
1.1 Relation-derived type behavior¶
An op's typeinfer MAY derive the output type from the Op's access relations (visitor-registry §4.1) rather than from a hand-written rule. Every boundary states an access map from the Op's own iteration space to that tensor's index space, and what the Op walks is the union of those domains. The relations carry no tensor shape: the output shape is typeinfer-side data, derived from the op's shape rule or from the extents the output relation reaches.
Within the relation:
- A domain dim that appears in an input access map but not in the output access map is a reduction dim (it is eliminated in the output).
- A tensor axis whose access maps to a constant (rather than a domain dim) is a broadcast axis.
- A symbolic size is an isl parameter of the domain; the relation's rank is fixed and is read from the input types.
The shard consequences of these structural facts (how Split /
Broadcast / Partial propagate, and the reduction effect) are
defined in §3.2.
1.2 Domain construction and output shape derivation¶
Every boundary's image rank is held to the Type of the value it describes by the relation service (visitor-registry §4.1).
2. Access relation analysis¶
The access relation is the boundary model shared by relation-derived type
behavior, shard propagation, dependence and movement: per boundary, an affine
access map from the Op's own iteration space to a tensor's index space. Its
carrier AccessRelation, the tuple of them one Op states, and the registry
that produces it are all defined in
visitor-registry §4.1.
The rule reads only the access maps' affine structure (which domain dim each
axis uses), never the domain bounds, so it is size-agnostic and identical for
static and dynamic shapes.
An effect-form TIR instruction uses the same tuple even though its call is unit-typed:
- One relation per input
ParamDef, in parameter order, comes first, whether that operand is read, written or both. - Its relations after those are its Unit result's, as for any Op.
- Which operands are read and which are written is each parameter's
effect, not its position: the argument prefix is not the set of reads.
An HIR consumer such as tf.schedule may use the relations
of written operands to derive its own value result. Instructions selected by
such a consumer MUST register their relation explicitly; there is no fallback
relation.
2.1 IR to isl conversion¶
Every analysis service converts IR values to isl through the functions that
types §11 owns, and every
boundary carries what its parameters stand for in the values of its
AccessRelation (visitor-registry §4.1).
Both use the one IslParamValues dictionary, from isl parameter name to IR value.
- constraints:
- A service MUST compose converted values as isl objects. It MUST NOT render an isl object back to text and splice that text into a new relation.
- A loop domain MUST treat each enclosing induction variable, and each capture of one, as a dimension of the space, not as a parameter. Each other leaf of a loop bound MUST be a parameter with a stated range; a loop bound with an unbounded parameter is refused.
- A parameter of an access relation that an enclosing scope already names MUST keep that name, and a capture of that value MUST be the same parameter, so one value is one parameter across the scope.
- The parameter name has no meaning. A reader MUST find what a parameter
stands for in
values, not in its name. Boundaries of one Op MUST share a parameter only when they bind the same object, whichiteratingmakes so before the Op's boundaries are combined.
3. Shard propagation¶
3.1 Logical shape to layout domain¶
TensorType.shapeis the logical shape.layouthas its own domain shape.- The current interpretation is canonical regroup: linearize first along the logical shape's row-major order, then reinterpret along the layout domain's row-major order. Strides describe physical positions only and MUST NOT participate in logical-coordinate correspondence. Nested domains associate their top-level modes with the corresponding logical axes only when the number of top-level modes equals the logical rank and each mode's size equals its axis extent; otherwise, the flattened modes regroup.
layout_to_isl_map(types §11) implements this regroup for aShardLayout. A logical axis whose extent is the product of consecutive flattened layout positions maps onto them, so a symbolic extent stays representable. Any other regroup, including a mode merging logical axes, MUST be rejected where the type is stated. Type inference MUST check function parameters and every Call result with the same axis-group rule and report the declaring source location. CanonicalTensorType.umat_tensormetadata has no physical positions and is exempt.- A reader with no topology level addresses logical coordinates. A reader at a named topology level addresses the positions one unit of that level holds: a position cut by mesh axes at that level or coarser is split into digits, the cutting axes outermost first in mesh-axis order and then the residual, and each such digit is that unit's mesh coordinate.
- A mesh coordinate is an isl parameter while the placement is built. The access relation service fixes it to the requested unit, 0 on every mesh axis unless stated, and removes it before any boundary is counted.
3.2 Relation-driven shard propagation¶
When an op's output ShardLayout is derived from a forward access
relation (§1.1), the
output ShardAttrs are determined from the input shards and the
relation's access maps by a single rule, shared across ops.
def partial_reductions_by_axis(layout: object) -> tuple[str | None, ...]: ...
def derive_output_shard_layout(
input_types: tuple,
relation,
output_shape: tuple,
*,
partial_reduction_dims: frozenset[int] = frozenset(),
complete_reduction_dims: frozenset[int] = frozenset(),
fresh_strides: bool = False,
): ...
- constraints:
derive_output_shard_layoutreads only input types, forward-relation maps, the requested output shape, and the explicit reduction/stride controls. It MUST NOT read relation bounds or an already-derived output type.partial_reductions_by_axisreturns one entry per mesh axis: the carried reduction name forPartial,Nonefor any other attr, and an empty tuple for a non-sharded layout.- With no real input sharding it returns
None. Inputs with real sharding on different meshes MUST fail. partial_reduction_dimsturns a reduced split intoPartial("sum");complete_reduction_dimsturns it intoBroadcast. The sets MUST NOT overlap.fresh_strides=Truerequests fresh canonical strides; otherwise a compatible propagated physical layout is preserved when possible.ShardLayout.attrsis indexed by mesh axis. APartial(reduction)is a value state at that exact index, not an unordered collection of reductions and not a layout position. Every propagation decision MUST retain that index and its reduction independently of every other mesh axis.- A reduction dim (a domain dim absent from the output access map) carries
one of two effects declared by the op/relation:
partialmeans the per-shard result still needs a cross-shard reduction;completemeans the reduction is already complete within each shard. - Propagation applies per input mesh axis:
Split(k)maps layout axiskto the input's logical tensor axis and then to a domain dim through the input access map.- If that domain dim appears in the output access map, the output carries
Spliton the output layout axis to which the domain dim maps. - If that domain dim is reduced, the output mesh axis becomes
Partial(reduction)for apartialeffect orBroadcastfor acompleteeffect. The resultingPartialis a value state on that mesh axis and carries no layout axis. - A
Partial(reduction)input propagates on the same mesh axis only when the op's math is proven to commute withreduction:op(reduction(x0..xn)) == reduction(op(x0)..op(xn)). This service MUST NOT make that mathematical judgment; each op's typeinfer rule owns it. On each mesh axis, two Partial inputs MUST be compatible with that op's rule. States on different mesh axes MUST NOT be compared as an unordered set. Only an explicit reduction/allreduce resolvesPartialtoBroadcast. - A
Broadcastinput axis contributes noSplit. - Two inputs binding the same domain dim to incompatible mesh axes is an error.
- A
PartialMUST NOT be silently eliminated or carried through a non-commuting ordinary op; only an explicitReshard/allreduce fromPartialtoBroadcastcompletes it. - An ordinary multi-input op MUST inspect every tensor input for a
Partial. When its result type cannot represent a secondary input's axis-preserving state, typeinfer MUST reject that input and name theReshardremedy. - A fully-
BroadcastinputShardLayoutis replicated: it carries no real sharding, contributes noSplit/Partial, and does not pin a mesh. When no input carries real sharding, the output carries none. - Only a zero-offset, unit-coefficient access of one domain dim is a
projection for shared ownership propagation. An input
Splitthat accesses any other form, including a nonzero affine translation, or an output-surviving dim reachable only through such an access, MUST fail closed. The shared service reads only the maps' affine structure, never the domain bounds, so it has no owner-boundary alignment proof for a translated access. Split(axis)indexes an output layout axis, not a logical tensor axis. A reduction-inducedPartialattaches to no layout axis; it remains a value state on the mesh axis that was reduced.
3.3 Output storage and mesh/layout compatibility¶
- constraints:
- A symmetric multi-input op (
Binary,MatMul,Concat,Stack) resolves output storage by anchoring on the concrete residency among its operands (types §2); the rule is independent of operand order. - An unmaterialized operand (
storage=umat), including host shape metadata, abstains and does not constrain the output. - One concrete operand storage is the anchor; the output takes that storage.
- Several concrete operands that agree on a storage produce that storage.
- Several concrete operands that disagree on storage cause typeinfer to
ctx.error, unless the op defines its own destination/mixed-storage resolution. There is no operand-order tie-break. - If all operands are unmaterialized, the output is unmaterialized (
umat).
This resolution uses no memory-level lattice; output residency is a function
of the concrete anchor(s) alone. (The rmem < smem < gmem hierarchy is
a Reshard-direction notion and is unrelated to output-storage anchoring.)
A tensor value's mesh / layout is carried by its TensorType.layout
(ShardLayout.mesh names the mesh instance) — that type is the source of
truth, and the IR places no scope-based restriction on values from
different meshes coexisting. Each op's registered typeinfer owns the
operand layout / mesh compatibility it requires and its result layout;
there is no uniform cross-op rule imposed from outside typeinfer.
Reshard is the explicit op that changes a value's layout / mesh.
For example, IndexSelect owns whether an indexed access is a foldable
one-element slice or a materializing data-dependent selection.