TileFoundry Spec — Shard / Layout System¶
flowchart TB
LayoutBase["<b>LayoutBase</b><br/>(abstract)"]
Layout["<b>Layout</b>"]
ComposedLayout["<b>ComposedLayout</b>"]
ShardLayout["<b>ShardLayout</b>"]
IntTuple["<b>IntTuple</b><br/>(shape / stride primitive)"]
Topology["<b>Topology</b>"]
Mesh["<b>Mesh</b>"]
ShardAttr["<b>ShardAttr</b>"]
Split["<b>Split</b>"]
Broadcast["<b>Broadcast</b>"]
Dynamic["<b>Dynamic</b>"]
Partial["<b>Partial</b>"]
LayoutBase --> Layout
LayoutBase --> ComposedLayout
LayoutBase --> ShardLayout
IntTuple -. shape and stride .-> Layout
Topology --> Mesh
Layout -. layout .-> Mesh
ComposedLayout -. layout .-> Mesh
ShardAttr --> Split
ShardAttr --> Broadcast
ShardAttr --> Dynamic
ShardAttr --> Partial
Layout -. layout .-> ShardLayout
ComposedLayout -. layout .-> ShardLayout
ShardAttr -. attrs .-> ShardLayout
Mesh -. mesh .-> ShardLayout
TensorType.layout (types §2) carries
any LayoutBase. Pure-layout invariants and shard-binding
invariants live with the construct they constrain. Enforcement is
dispatched by visitor-registry; concrete
checks live in tir §1.3 (TIR side) and
hir §1.3 (HIR side).
1. IntTuple¶
The primitive building block, taken from pycute's shape / stride
convention. It is not just a flat tuple — nested tuples are allowed:
IntTuple = ShapeDim | tuple[IntTuple, ...] # entry: a static or symbolic extent, or a nested `IntTuple`
- constraints:
- admits both a unique flat
int-tuple view and a unique nested-structure view; theshape/stridesofLayoutandShardLayoutareIntTuple.
A shape / stride entry is not restricted to a static int: it may be a
symbolic / dynamic dim (a DimVar or dim Expr — a ShapeDim). None is not
a shape extent; only the complete Layout.strides field may be None while
strides are not materialized. Consumers that need a concrete integer —
Mesh.__getitem__ and T.sync participation (tir §1.5) —
require static int entries and fail closed on a symbolic / dynamic one
rather than guessing.
Dimension substitution traverses a tensor's logical shape and layout together.
For a ShardLayout it also traverses the bound Mesh: the Mesh layout and each
Topology.size are geometry of the same program and MUST receive the same
binding. A consumer that needs fixed geometry MUST reject any residual DimVar
or dimension expression before asking size or product for a position count.
2. Layout hierarchy¶
TensorType.layout is not a free-form slot; it accepts only objects in
the Layout hierarchy:
class LayoutBase:
"""Provide the abstract base for legal tensor layouts."""
class Layout(LayoutBase):
"""Provide a primitive coordinate-to-offset layout."""
class ComposedLayout(LayoutBase):
"""Provide a composition of two layout mappings."""
class ShardLayout(LayoutBase):
"""Bind a layout domain to a device mesh."""
- constraints:
TensorType.layoutaccepts only objects in this hierarchy; the common contract below applies to every legal layout object.
Common contract — every legal layout object MUST satisfy:
- a stable domain shape exposed as
shape, - a coord-to-physical mapping (
layout(coord) → physical index / offset), - a stable domain-axis numbering exposed as
domain_rank, wheredomain_rank == rank(flatten(shape)), - injective mapping — overlapping / aliasing layouts are not permitted; distinct domain coords MUST NOT map to the same physical element. Extending to non-injective cases (padding / broadcast layouts) requires a separate RFC.
An object that does not satisfy these conditions cannot enter
TensorType.layout.
3. Layout (pure, primitive)¶
Mirrors pycute.layout.Layout:
class Layout(LayoutBase):
"""Describe a primitive coordinate-to-offset mapping.
Attributes:
shape: attribute; Layout-domain shape.
strides: attribute; Per-domain-axis step rule, or None when unmaterialized.
"""
shape: IntTuple
strides: IntTuple | None = None
- constraints:
- the pure primitive layout; it has no
offsetfield. Field meanings and semantics below.
Field meanings:
shape— the layout-domain shape (flat or nested). When aLayoutsits insideShardLayout.layout, this describes the global, unsharded layout shape — i.e., the shape before mesh dividing. The per-thread local layout shape is derived fromlayout.shape÷ mesh extents perSplit(see §7).strides— the step rule from layout domain to physical index. When not explicitly given, it defaults toprefix_product(shape)in the pycute style. When aLayoutsits insideShardLayout.layout,strides[k]is the storage-physical step on the engine attached to thatShardTensor(see §7).
Semantics:
The primitive Layout has no offset field. offset belongs to
ComposedLayout and MUST NOT leak down into the primitive.
4. ComposedLayout¶
Mirrors CuTeDSL make_composed_layout(inner, offset, outer):
class ComposedLayout(LayoutBase):
"""Describe a composition of two layout mappings.
Attributes:
inner: attribute; Output-side layout applied last, or identity.
offset: attribute; Intermediate scalar offset.
outer: attribute; Domain-side layout applied first, or identity.
"""
inner: LayoutBase | Swizzle | None
offset: int
outer: LayoutBase | None
- constraints:
idx = apply(inner, offset + apply(outer, coord)), whereapply(None, value) == value.shapeanddomain_rankcome fromouterwhen it is present, otherwise frominner; a composition with no explicit domain hasshape == (). ASwizzlestates no domain, soinner=Swizzlewithouter=Nonehasshape == ()and is not a complete tensor layout.outer, and aninnerthat is aLayoutBase, MAY be anyLayoutBase, including aShardLayoutthat preserves an earlier distribution.innerMAY instead be aSwizzle, which is the only non-affine mapping this IR states.outerMUST NOT be one: aSwizzlehas no domain to be the domain-side component of.
Field meanings:
outer— applied first (the input / domain-side layout);Noneapplies the identity mappingoffset— the intermediate scalar offset (a property of the composition object, not of the primitiveLayout)inner— applied last (the output-side layout);Noneapplies the identity mapping
A ComposedLayout normally inherits its domain shape and axis numbering
from outer. When outer=None, the identity mapping contributes no explicit
domain, so the composition inherits them from inner. Therefore, when an
outer ShardLayout binds a ComposedLayout, a Split(k) attr still
references the composition's stable shape / domain_rank contract.
4.1 Swizzle¶
Mirrors CuTe Swizzle<B,M,S>: the XOR offset functor a swizzled
shared-memory layout applies to its index.
class Swizzle:
"""Describe the XOR permutation CuTe `Swizzle<B,M,S>` applies to an index.
Attributes:
bits: attribute; CuTe `BBits`, how many bits are XORed.
base: attribute; CuTe `MBase`, how many low bits are left alone.
shift: attribute; CuTe `SShift`, the signed source-to-target distance.
"""
bits: int
base: int
shift: int
def __call__(self, offset: int) -> int: ...
def get_swizzle_portion(layout: Layout | ComposedLayout) -> Swizzle | None:
"""Return the final swizzle of a composed layout, when present."""
def make_swizzle(active_y: int, active_z: int) -> Swizzle:
"""Build the representable CuTe swizzle that maps Y bits onto Z bits."""
Terms. The Y bits are the ones read out of the index
(yyy_msk = ((1 << bits) - 1) << (base + max(0, shift))); the Z bits are
the ones they are XORed onto
(zzz_msk = ((1 << bits) - 1) << (base - min(0, shift))).
- constraints:
bits >= 0,base >= 0, andabs(shift) >= bits, which is what keeps the Y and Z ranges from overlapping.swizzle(offset) == offset ^ shiftr(offset & yyy_msk, shift), the CuTe formula unchanged.- Because the two ranges do not overlap, a
Swizzleis an involution:swizzle(swizzle(offset)) == offset, so it is its own left and right inverse.bits == 0is the identity. - Swizzle-specialized
composition,left_inverse, andright_inverseMUST follow the corresponding CuTeswizzle_layout.hppoverloads.get_swizzle_portionreturnsNonefor a layout with no final swizzle;make_swizzleMUST reject mask pairs that are not equal-width contiguous runs rather than inventing a representation. - A
Swizzleis a mapping on an index, not a layout. It is not aLayoutBase, it states noshape, and it MUST NOT be aTensorType.layoutor aShardLayout.layouton its own. It reaches a tensor only asComposedLayout.inner. - A
Swizzlepermutes bits inside the codomainouteralready spans, socosize(ComposedLayout(Swizzle, offset, outer)) == cosize(outer)and the backing allocation is unchanged by it. - A
Swizzlestates where an element lives, never which element a logical coordinate names.shape,domain_rank, the axis numbering aSplitreferences, and the access relation an op states over a value are all read offouterand MUST be the same as the unswizzled layout's. This is why an op that only relabels the domain --Transpose,Reshape,Slice-- carries the sameSwizzlethrough: a window's start is a constant shift of the index, whichoffsetalready states. - A swizzled
ComposedLayoutMUST NOT serve as aMeshexecution scope: §9 admits an identityinneronly.
5. Mesh¶
class Topology:
"""Describe one parallel-resource level.
Attributes:
name: attribute; Stable topology-level name.
size: attribute; Explicit static or symbolic extent.
"""
name: str
size: ShapeDim
class Mesh:
"""Record a parallel device domain and its logical positions.
Attributes:
topologies: attribute; Ordered topology sequence.
layout: attribute; One nested arrangement, mode i being level i.
names: attribute; Human-readable layout-axis names.
"""
topologies: tuple[Topology, ...]
layout: Layout | ComposedLayout
names: tuple[str, ...] = ()
def __getitem__(self, key) -> Mesh: ...
def levels(mesh: Mesh) -> tuple[Layout, ...]:
"""Return each topology level's arrangement in its own numbering."""
...
def starts(mesh: Mesh) -> tuple[int, ...]:
"""Decode each topology level's start from device numbering."""
...
def within_scope(mesh: Mesh, current: Mesh) -> bool:
"""Return whether each continuous position set is contained in current."""
...
- constraints:
- Scope comparison MUST reduce each level's arrangement with
filter(arrangement, major="row"), retaining the level's start. Filtering removes shape-one and stride-zero modes and coalesces adjacent modes after affine dimension normalization. For continuous selections,covered_by_scopeMUST compare starts and normalized sizes, independent of mode order. Otherwise it MUST conservatively compare the filtered arrangements and starts for equality. within_scopeMUST require both filtered selections to be continuous and prove containment of their half-open intervals[start, start + size). Static continuity issize == cosize; symbolic continuity requires either coalescing direction to leave one mode with stride one. Symbolic interval bounds MUST use the conservative dimension range of their difference; an unproved bound MUST return False.- a compile-time constant that does not enter the IR graph; describes the device domain, not a tensor layout object. A slice never becomes an IR/SSA value.
Field meanings:
topologies— the ordered device-domain descriptions (name+size)layout— a nestedLayoutwhose modeiis leveli's own arrangement; a constant slice (m[...]) replaces it with aComposedLayoutrecording the sub-box (tir §1.5)names— optional human-readable names (cta.x,cta.y, …)
In a kernel, with Mesh(selection, layout=shape, names=names) rearranges a lexical,
single-level selection.
layout=(shape, strides) states selection-local numbering (row-major by default); it MUST
cover every selected position once, with one fixed physical stride per axis.
Composition preserves topology and offset; slicing the resulting composed mesh is rejected.
Every mesh MUST state one arrangement per level it names, each in that level's
own numbering, as one nested Layout whose mode i is level i -- a mesh
naming one level included. Mesh normalizes what was written into that form at
construction: one arrangement over all the levels' axes is cut at the level
boundaries, each level taking axes left to right until their extents multiply to
its own size and its steps divided by what the levels under it hold. An axis of
extent one adds no positions and joins the level being filled. A boundary no
prefix of axes lands on MUST be refused.
Because the layout is a nested Layout, the layout operations read it: get(mesh.layout, i)
is level i's own arrangement, flatten(mesh.layout) is every level's axes end
to end, and repeat_like against mesh.layout.shape states per axis whatever is
stated per level. device_layout(mesh) is those axes as the device numbers its
positions, each stepping by what its level states times what the levels under it
hold, and a slice's ComposedLayout.offset is where the whole mesh starts in
that numbering.
Mesh composition uses the following rules:
make_mesh(*meshes)MUST append a mesh whose level names are disjoint from those in force, MUST replace them entirely when the inner mesh names every one, and MUST replace the trailing levels when the inner mesh names a suffix of them, keeping the levels above and their names unchanged. Level names overlapping in any other way MUST be rejected rather than decomposed.- Append concatenates the per-level arrangements. When an appended mesh is
sliced, its per-level start is re-encoded in the combined device numbering
and retained in the result's
ComposedLayout.offset. - Replacing a suffix with a sliced mesh MUST preserve each retained upper
level's start and take each replaced level's start and arrangement from the
inner mesh. The combined
ComposedLayout.offsetMUST then be re-encoded in device numbering from those per-level starts. Replacing an unsliced suffix and replacing the whole mesh retain their existing behavior. Every replaced level MUST select a continuous position set contained in the enclosing level's continuous position set. Continuity and containment MUST hold throughout the declared dimension envelopes. A replacement or enclosing selection with holes MUST be rejected rather than approximated as an interval. make_mesh(*meshes)invokescheck_topologyon its result. For each named level with a concrete declared extent, its position count MUST NOT exceed that extent; symbolic extents are deferred until dimensions are bound. A level stating a run is already bounded byMesh.__getitem__and is not checked again.separate(mesh)is the inverse construction: it returns one single-levelMeshper topology, with that level's arrangement, slice start, and share of the axis names. It is imported fromtilefoundry.ir.types.mesh.
HIR MeshRegion applies this composition only at its body boundary. Its args
are evaluated in the enclosing scope and are not recomposed merely because the
value is consumed by a region.
- constraints:
Topologyconstruction rejects aNonesize.Meshconstruction rejects aNoneentry in its layout shape and normalizes the layout into one mode per level; beyond that it performs no position-consistency check. Itstopologiesfield is atuple[Topology | str, ...]: a single-level mesh may temporarily name its topology by string until the parser resolves it from the enclosing module declaration, while a multi-level mesh MUST carryTopologyvalues. Topology names in one mesh MUST be unique.- The author surface is
with Mesh(("cta",), layout=(128,)) as cta:. The parser resolves the non-empty tuple of declared topology names to theTopologytuple before it constructs the record. A bare string and thetopology=keyword are not Mesh author forms. states_consistent_positions(mesh)is true when the product of topology sizes equalssize(mesh.layout). Mesh-scope verification asserts this predicate; Mesh construction and slicing do not, so a derived slice remains a record of its parent scope.- A Mesh naming several levels segments its layout axes between them, left to
right in the order it names them: a level takes axes until their extents
multiply to exactly its own
Topology.size. An axis that straddles a boundary MUST be refused rather than divided, because a level boundary cannot assign the positions of an axis it runs through. Every axis MUST belong to one level. - The layout of the positions one level has is the axes up to and including that level's segment, with their strides divided by the product of the sizes of the levels below it; a stride that division does not divide exactly MUST be refused. A Mesh naming one level takes every axis into its single mode, which needs no division and therefore no declared extent. A Mesh naming several may be sliced: the key addresses all level axes in order, every level retains its own arrangement, and the slice offset is the sum of each selected start times that axis's step in device numbering.
- Nested single-level Mesh scopes compose to exactly that shape: the axes join outermost first and each outer stride is scaled by the positions below it. A value distributed at two levels at once may therefore be written either way, and both state one placement.
- A reader that asks for a position count by topology name reads
size(mesh.layout). It accepts a Mesh with one topology only; a multi-topology Mesh is rejected rather than projecting its layout onto a guessed level. - A reader that asks for an exact participant set at a selected topology level
MUST take that level's projected layout as above, and MUST refuse a Mesh
that names no such level. For both a plain
Layoutand a slicedComposedLayout, the set is{apply(layout, c) | 0 <= c < size(layout)}; composition applies its retained offset and inner mapping. - That participant set MUST be static, positive, inverse-projectable,
duplicate-free,
and contained in
[0, selected_topology.size). A plain Mesh MUST cover the complete selected domain. A strict subdomain MUST use a sliced Mesh so its offset is retained rather than collapsed to an extent. Meshexposes neither a primarytopologynortopology_domain(), and it has no attribute-style axis API. In layout sugar,m.axisis parser syntax resolved to the pair(mesh, layout_axis_index), not a Mesh attribute.
The placed-layout constructor has one additional guard: a single layout may
split a named level only once. If two distinct meshes used by one placed layout
name the same topology level, parsing MUST reject that layout at its source
node. This is a layout-construction rule, independent of make_mesh()'s
scope-composition rules.
5.1 Placement¶
A Mesh states which positions exist; it does not state which of them the
process reading a checkpoint occupies, because that is a property of the run
rather than of the model. Placement states exactly that and nothing else.
class Placement:
ids: Mapping[str, int]
def program_ids(self, topologies: tuple[Topology, ...]) -> tuple[int | None, ...]: ...
@classmethod
def from_env(cls, topology_level: str = "gpu") -> "Placement": ...
- constraints:
- a
PlacementMUST be data, not a callable. Which program a loaded module speaks for is settled when it is loaded, so an answer computed at each call would let one module change identity while holding weights narrowed for the old one, and a weight cache keyed on it would then be describing a property of the process rather than of the data. idsis keyed by level name, soprogram_ids(topologies)answers one id per orderedTopologyby lookup. A level the mapping does not name is unfixed, which is what makes the answer's length the caller's business rather than the caller's arithmetic.- an unfixed level MUST be left undivided rather than read as
0: the device chooses its ownctaandthread, and a host that guessed would hand one thread's data back as the whole card's. - a
Placementis host-side execution context. It MUST NOT appear in aTensorType, a checkpoint, aCallableSignature, a kernel parameter, or aforwardargument. - one loaded module speaks for one program. A host serving several MUST build one module per program, each told which it is; the current device is thread-local and is set once per thread, so a module MUST NOT read it to decide who it is.
6. ShardAttr¶
class ShardAttr:
"""Provide the base for per-mesh-axis sharding attributes."""
class Split(ShardAttr):
"""Bind a mesh axis to a layout-domain axis.
Attributes:
axis: attribute; Layout-domain axis index.
"""
axis: int
class Broadcast(ShardAttr):
"""Mark the value replicated across this mesh axis."""
class Dynamic(ShardAttr):
"""Mark the distribution policy unresolved on this mesh axis."""
class Partial(ShardAttr):
"""Mark an unreduced partial value.
Attributes:
reduction: attribute; Reduction required to obtain the full value.
"""
reduction: str = "sum"
def S(axis: int) -> Split:
"""Build a split attribute.
Args:
axis: Layout-domain axis index.
Returns:
The split attribute.
"""
...
def P(reduction: str = "sum") -> Partial:
"""Build a partial attribute.
Args:
reduction: Required reduction.
Returns:
The partial attribute.
"""
...
def B() -> Broadcast:
"""Build a broadcast attribute.
Returns:
The broadcast attribute.
"""
...
- constraints:
- each entry of
ShardLayout.attrsdescribes one mesh axis by its tuple position; per-attr semantics and surface sugar below.
Each entry of ShardLayout.attrs describes one mesh axis (by its
position in the tuple); the attr says what that mesh axis does. Split
binds a mesh axis to a layout axis (placement); Broadcast and Partial
are value states on a mesh axis and carry no layout axis.
Field meanings:
Split(axis)— the current mesh axis is bound to the underlying layout'saxis-th layout domain axis (i.e.,layout.shape[axis]).axisMUST satisfy0 <= axis < rank(flatten(domain(layout))). Multiple mesh axes MAY bind the same layout axis. Each static mesh extent must divide the remaining extent in mesh-axis order, and the local extent islayout.shape[axis] // product(mesh extents bound to axis). The canonical constructor normally factorizes such bindings into distinct layout positions, butshard_layout_local_shapealso accepts and composes the unfactorized form.Broadcast— the current mesh axis does not participate in splitting; the value is replicated across this mesh axis.Dynamic— the distribution policy of the current mesh axis is not yet resolved.DynamicMAY appear during analysis / intermediate inference but MUST be resolved toSplitorBroadcastbefore final lowering.Partial(reduction)— the current mesh axis carries an un-reduced partial value: the full result over this mesh axis is thereduction(sum/max/min) of the per-shard values, and a subsequent allreduce over this mesh axis is required to obtain it. APartialcarries no layout axis — it is a value state on the mesh axis given by its position inattrs. How aPartialpropagates, resolves, and must not be silently lost is part of relation-driven propagation (semantic-analysis §3.2).
Comparing or combining shard layouts and consuming their mesh use one projection that keeps mesh axes aligned with their attributes without rewriting stored types. It omits all-Broadcast levels except an instruction's required participant levels; levels containing Split or Partial remain part of the distribution.
Surface syntax sugar¶
| Written form | Meaning |
|---|---|
(8, 16) |
Plain row-major Layout |
((8, 16), (1, 8)) |
Plain Layout with explicit strides |
(8 @ t.w, 16) |
ShardLayout splitting a tensor dimension over t.w |
(8, 16) @ {B()} |
All-Broadcast ShardLayout |
((8, 16) + 4 \| Swizzle(2, 4, 3)) @ {B()} |
All-Broadcast ShardLayout based on a ComposedLayout |
(8 @ t.w, 16) @ {t.l @ P("sum")} |
Split placement with Partial values on t.l |
((8 @ t.w, 16), (16, 1)) @ {t.l @ P("sum")} |
Placement with explicit strides and Partial values |
where(layout=(_, _, dim) @ {cta @ B()}) |
Broadcast constraint on the named topology cta |
Placement covers the current composed mesh scope, or the function mesh in a
signature; unstated axes are Broadcast. None, plain Layout, and an
all-Broadcast ShardLayout are compatible forms without a split; when both
layouts are stated, their written shape and stride constraints remain.
Shared-memory distribution belongs to CTA or coarser levels; author-written tiles
in thread scope use plain layouts. A where clause constrains named topologies;
acceptance rules are generated in parser §2.
7. ShardLayout¶
ShardLayout is the distributed binding layer; it does not introduce
a new layout-algebra primitive, it binds an underlying layout's domain
axes to mesh axes.
class ShardLayout(LayoutBase):
"""Bind a layout domain to a device mesh.
Attributes:
layout: attribute; Underlying layout-family member.
attrs: attribute; Shard attributes in mesh-axis order.
mesh: attribute; Device-domain mesh.
"""
layout: LayoutBase
attrs: tuple[ShardAttr, ...]
mesh: Mesh
- constraints:
- the distributed binding layer; binds an underlying layout's domain axes to mesh axes without a new layout-algebra primitive. Sub-field contracts in §7.1–§7.5.
The distribution-changing transformation (redistribute / sharding) is
itself an IR op (hir.sharding.Reshard, see hir §1.3).
7.1 layout¶
The underlying LayoutBase. Layout / ComposedLayout (§3, §4) own
the layout algebra; ShardLayout binds their stable shape and
domain_rank to a mesh without redefining that algebra. Nested stage
layouts remain represented through the LayoutBase hierarchy.
When a Layout sits inside ShardLayout.layout, its shape and
stride carry additional, narrower semantics beyond the plain
§3 meaning — they describe the distributed form of the tensor, not a
free-standing primitive layout. These narrower meanings are defined in
§7.1.1 and §7.1.2 and refine (not replace) the §3 contract.
7.1.1 layout.shape¶
Let sl: ShardLayout, T: TensorType, and G = sl.layout.shape.
Gis the canonical / unsharded layout-domain shape; it MUST NOT encode per-instance extents.rank(G)MAY differ fromrank(T.shape).size(G) == size(T.shape)MUST hold.- For any
Split(k)insl.attrs,0 <= k < rank(G)MUST hold.Partial/Broadcastcarry no layout axis. Split(k)indexes intoG; it MUST NOT refer toT.shapeaxes or mesh axes.- For every mesh axis
awithsl.attrs[a] = Split(k),G[k] == sl.mesh.layout.shape[a]MUST hold. Equivalently,local_shape(sl)[k] == 1on everySplit-bound layout dim. Surface sugarN @ m.awithN > mesh_extent(a)is canonicalized at parse time into a factorised form ((mesh_extent(a) @ m.a, N // mesh_extent(a))); the factorised residual axis enters the IR as a non-Splitlayout dim. See parser §2.1. A symbolic logical axis split across multiple mesh axes MUST be factored in mesh-axis order by exact successive quotients: equal extents yield one, and aDimMulcontaining the mesh extent as either factor yields its other factor. Integer quotients MUST be exact; undecidable divisions MUST be rejected. ThusNC * 128split over(NC, 64)becomes(NC, 64, 2), with the residual2unbound. Symbolic factored shapes keepstrides=Noneuntil specialization. local_shape(sl)[k] = G[k] / sl.mesh.layout.shape[a] = 1iff some mesh axisahassl.attrs[a] = Split(k).local_shape(sl)[k] = G[k]otherwise.- The canonical regroup rule (§8) defines how
T.shapealigns withG.
7.1.2 layout.strides¶
Let sl: ShardLayout, S = sl.layout.strides, and let engine(i)
denote the ShardTensor.engine seen by instance index i along the
relevant mesh axis.
SMAY beNone.S is None⇒ "un-materialized": the layout strides have not yet been fixed; the layout'sshape/ partition is determined but the per-axis stride form is deferred toReshardtypeinfer (hir.md §1.3). Surface sugar(N @ m.a, ...)always emitsS is None; verbose((shape), (strides))always emits a concreteS. AfterReshardtypeinfer has run on a value,Sreachable from that value MUST be a concrete tuple (the un-materialized form is an intermediate-only signal).S == ()is a concrete rank-0 layout (shape == ()), not theNonesentinel; the empty tuple is never overloaded to mean un-materialized.- When
Sis a concrete tuple,S[k]is the element step along layout dimkon the physical storage held byShardTensor.engine; it MUST NOT be an abstract global stride. - For every mesh axis
awithsl.attrs[a] = Split(k),S[k] ∈ {0} ∪ ℤ_{>0}MUST hold. S[k] == 0⇒ mesh axisacontributes0to intra-engine offset on dimk. Typical: allocator gives each instance a distinctengine(i).S[k] > 0⇒ mesh axisacontributesi · S[k]elements to intra-engine offset on dimk. Typical:engine(i)shares one base ptr across instances.- For layout dims
kwith noSplitbinding,S[k]follows §3 semantics, evaluated on the engine's physical storage. - Layouts requiring more than one stride per
Splitaxis (cyclic / interleaved) MUST be rejected byShardLayoutconstruction.
7.2 attrs¶
Per-mesh-axis attributes (Split | Broadcast | Dynamic | Partial),
ordered by mesh axis. len(attrs) MUST equal rank(mesh).
A Split(k) substitutes the current mesh_coord into layout dim k of
layout, producing the local projection on the current device. See
§6 for individual ShardAttr semantics.
7.3 mesh¶
The device-domain Mesh (§5).
7.4 Reshard preserves logical shape¶
Reshard is the IR op that swaps a tensor's layout / storage and
preserves TensorType.shape. A (1, 1536) logical tensor MAY
reshard to a ShardLayout with layout.shape=(1, 8, 192); the output
TensorType.shape is still (1, 1536). Logical-shape rewrites
(transpose / flatten / true reshape) go through hir.tensor.Reshape,
not Reshard.
7.5 Example¶
Logical tensor (2, 1536) reshards via surface sugar
(2 @ m.x, 12 @ m.y, 128 @ m.t) with mesh=(x=2, y=4, t=32). Parser
canonicalization (§7.1.1, parser §2.1) expands 12 @ m.y into
(4 @ m.y, 3) and 128 @ m.t into (32 @ m.t, 4) and emits
Layout(shape=(2, 4, 3, 32, 4), strides=None) — un-materialized
because the user wrote sugar (§7.1.2). Reshard typeinfer
then materializes strides via the direction rule; the resulting
form depends on the (src.storage, dst.storage) pair.
For reshard(a:gmem, ..., storage='rmem') (high → low, per-instance
default):
# example
layout = Layout(shape=(2, 4, 3, 32, 4), strides=(0, 0, 4, 0, 1))
attrs = (Split(0), Split(1), Broadcast, Split(3), Broadcast)
mesh = Mesh((Topology("thread", 256),), Layout(shape=(2, 4, 32), ...))
shard_layout_local_shape(sl) yields (1, 1, 3, 1, 4). Each Split
axis has local_shape = 1 by construction, so
runtime §2.4.1's offset sum
reduces to 0 and every mesh instance receives its own engine
holding 3 × 4 = 12 elements laid out in C-order.
For reshard(a:rmem, ..., storage='gmem') (low → high, shared
default), the same sugar materializes to C-order strides over the
canonical global shape — strides = (1536, 384, 128, 4, 1) — so the
8 warps write to disjoint offsets of a single underlying gmem
buffer.
A reshard whose user-provided cute strides are non-default (e.g. an
SM80 mma fragment) bypasses this materialization step: the layout
already has a concrete strides tuple, so the Reshard typeinfer
rule (hir.md §1.3) preserves
it verbatim.
7.6 local_layout¶
Which part of a sharded tensor one mesh instance holds, answered once and read by both sides of a launch.
def local_layout(shard, tensor_shape, ids) -> Layout: ...
def local_layout_and_offset(shard, tensor_shape, ids) -> tuple[Layout, int]: ...
- constraints:
local_layoutMUST require canonical modes for every logical tensor axis: split modes come first in mesh-axis order, followed by at most one residual mode. Zero-stride Split modes are outside the compactness chain; all remaining non-unit modes MUST have compact adjacent strides (stride[i] == stride[i+1] * extent[i+1]). A noncanonical axis MUST raiseValueErrorstatingnot canonical (split outer, residual inner); not supported, rather than return an incorrect local view.- a tensor axis is divided by every mesh axis that cuts it, and their extents multiply: two axes of one level cut it into a grid, and so do two levels, one taking a block of what the other left.
- the axes cutting one tensor axis are ordered outermost first, so one step of one of them MUST clear everything the axes inside it hold, whether or not the level owning them was given an id.
- an axis nothing cuts is held whole, and so is one cut only by a level whose id is unfixed.
- a static extent its mesh axis does not divide MUST be refused by name: a
shard would then not be one slice. A non-static axis extent MUST pass through
local_layoutunchanged; a symbolic remainder cannot prove nondivisibility. local_layout_and_offsetMUST reject unbound tensor sizes withValueErrorbefore projecting, because its offset must be concrete. It MUST reuse thelocal_layoutprojection and its canonical check.local_layoutMUST keep the whole tensor's strides: what one instance holds is the same rows the same distance apart, begun further in.local_layout_and_offsetcounts its offset in exactly those strides, so a host tensor laid out otherwise MUST be refused by name rather than read at the offset's word:as_stridedwould silently read elsewhere.- the host states
tensor_shapeandids, which the device reads off its own layout and its hardware: §7.1.1 factors a split axis, so the layout's rank is not the tensor's, and aPlacementmay leavecta/threadfor the device to divide. - both halves MUST answer under the same two names
(runtime §2.3.3). Naming the host's
answer a window rather than a layout and an offset stated one stopped-short
arithmetic as though it were a second algorithm: both sides compute
coord x local_extent x inner, and only the host's last multiplication by a stride was missing. Two implementations of "which part is mine" that can disagree is what a per-call shape assertion used to be guarding against; one rule stated once is what removes the need.
8. Layout propagation¶
ShardLayout here is the data model that the analysis services read and
produce. Logical-shape-to-layout-domain interpretation and relation-driven
shard propagation are owned by semantic-analysis §3:
logical shape → layout domain in
semantic-analysis §3.1, and
relation-driven propagation in
semantic-analysis §3.2.
A static-offset tensor view is represented as
ComposedLayout(inner=None, outer=ShardLayout(...)). shard_layout_of(layout)
MUST return the direct ShardLayout or that outer carrier and MUST return
None for other compositions. Services that inspect distribution rather than
addressing use this projection. The ComposedLayout.offset remains a property
of the input view; propagation derives a fresh result ShardLayout and does not
copy that displacement to a materialized consumer.
9. Layout construction and algebra¶
Making the steps of a compact arrangement and reading an index back into the
coordinate that reaches it are not operations on layouts, so they are filed
apart from the algebra, as CuTe files stride.hpp apart from layout.hpp.
class NotProjectable(ValueError):
"""Report that a layout cannot serve as a mesh execution scope."""
def apply(layout: Layout | ComposedLayout, coord: int) -> int:
"""Apply a layout to one linear domain coordinate."""
...
def prefix_product(shape: tuple[int, ...]) -> tuple[int, ...]:
"""Return exclusive prefix-product strides.
Args:
shape: Static flat shape.
Returns:
Column-major natural strides.
"""
...
def c_order_strides(shape: tuple, *, mul=None) -> tuple:
"""Return row-major contiguous strides.
Args:
shape: Flat shape.
mul: Optional multiplication operation for symbolic entries.
Returns:
C-order strides.
"""
...
def try_c_order_strides(shape: tuple) -> tuple[int, ...] | None:
"""Return static C-order strides when possible.
Args:
shape: Candidate flat shape.
Returns:
Static strides, or None for a symbolic or dynamic shape.
"""
...
def canonical_shard_layout(
logical_shape: tuple, mesh: Mesh, attrs: tuple[ShardAttr, ...]
) -> ShardLayout:
"""Build the canonical factorized sharding layout.
Args:
logical_shape: Logical tensor shape.
mesh: Device mesh.
attrs: Shard attributes naming logical axes.
Returns:
The canonical sharding layout.
"""
...
def shard_layout_local_shape(
sl: ShardLayout, *, require_static: bool = True
) -> tuple:
"""Project a global sharding layout to its local shape.
Args:
sl: Sharding layout to project.
require_static: Whether unresolved local extents are rejected.
Returns:
The per-shard shape. In strict mode every extent is static.
"""
...
def coalesce(layout: Layout | ComposedLayout, trg_profile=..., *, major: str = "col"):
"""Merge contiguous modes, optionally within profile-selected groups.
Args:
layout: Layout to simplify.
major: "col" (mode zero fastest) or "row" (last mode fastest).
trg_profile: Optional nesting whose terminals select groups to merge.
Returns:
The equivalent layout with contiguous modes merged.
"""
...
def composition(left, right, offset: int = 0, *, major: str = "col"):
"""Compose CuTe layouts; offset belongs to the swizzle overload."""
...
def complement(layout: Layout, max_idx: int = 1, *, major: str = "col") -> Layout:
"""Return the modes filling the layout's gaps below max_idx."""
...
def logical_divide(layout: Layout, tile, *, major: str = "col") -> Layout:
"""Compose a tile and its complement into the source layout."""
...
def zipped_divide(layout: Layout, tile, *, major: str = "col") -> Layout:
"""Gather hierarchical tile and remainder modes into two modes."""
...
def is_inverse_projectable(layout: Layout) -> bool:
"""Return whether a layout admits the supported inverse construction.
Args:
layout: Primitive layout to inspect.
Returns:
Whether it is injective and compact-ordered.
"""
...
def left_inverse(layout: Layout | ComposedLayout):
"""Build the supported CuTe left inverse.
Args:
layout: Layout to invert.
Returns:
Its left-inverse layout.
"""
...
def right_inverse(layout: Layout | ComposedLayout):
"""Build the supported CuTe right inverse.
Args:
layout: Layout to invert.
Returns:
Its right-inverse layout.
"""
...
- constraints:
canonical_shard_layoutMUST factor each logical axis split by one or more mesh axes in mesh-axis order, remap eachSplitto its factor, and append a non-unit residual factor. Symbolic multi-axis splits MUST use successive exact integer or structural product quotients as specified in shard §7.1.1. It MUST reject indivisible or undecidable dynamic multi-axis splits.shard_layout_local_shapeMUST multiply the divisors of multipleSplitattributes that name the same layout axis. Equal symbolic split and mesh extents produce local extent one. ADimMulwith the mesh extent as either factor MUST yield its other factor as the local extent; other symbolic split relations MUST be rejected as undecidable. An unconsumed symbolic extent MAY pass through only whenrequire_static=False; strict mode MUST reject it.try_c_order_stridesMUST returnNoneunless every shape entry is a non-boolean integer.coalesce(layout)MUST flatten and merge contiguous modes under CuTe's mode-zero-fast convention. Withtrg_profile, it MUST apply that rule at each profile terminal, preserve unmatched trailing modes, and reject a profile that asks for more modes at any nesting level with that level in the diagnostic.major="row"MUST process modes from last to first and return the reduced modes in their stated order. Merge comparisons MUST normalize dimension expressions on both sides.filter(layout, profile=..., major="col")MUST remove shape-one and stride-zero modes before coalescing, with the same profile and major-order conventions.is_contiguous(layout, major="col")takes the already filtered result with the samemajorand MUST test it without repeating filtering, usingsize == cosizefor static extents and one unit-stride mode after coalescing in either direction for symbolic extents.- Inverting a composed mesh layout MUST accept only an identity inner mapping
and an inverse-projectable primitive outer layout; other layouts MUST raise
NotProjectablerather than guess an inverse. - General
compositionMUST support aLayouton the left and aLayout, integer tile, tuple of per-mode tiles, or identityNoneon the right, preserving unmatched trailing modes in a tuple dispatch. The existing swizzle overload MUST retain its offset semantics. logical_divideMUST compose the tile with its complement;zipped_divideMUST gather the hierarchical result into tile and remainder modes. Tiles MUST have integer extents and strides.Nonedenotes an unsplit mode. All three operations andcomplementMUST interpret traversal order viamajor="col"ormajor="row", without changing their per-mode tile dispatch. Overlapping modes incomplementMUST remain rejected asNotProjectable.tile_view_layoutandtile_inner_typeMUST receive operand-axis tilecountsprojected from the caller's validated instruction repeat, rather than infer them from whole and tile shape ratios. Prefix extraction MUST retain the existing grouped and flat mode boundaries and remapSplitaxes to the retained modes. An absent prefix or a removed sharded axis MUST retain its existing diagnostic. Count-one axes MUST retain symbolic extents without requiring division or a static extent.