TileLang Layout System
Core types (src/layout/layout.h, Python tilelang/layout/)
tl::Layout maps logical coordinates to physical coordinates:
input_size_ (logical shape) + forward_index_ (one PrimExpr per output
axis). Expressions are written in process-wide placeholder Vars
InputPlaceholder(i) (printed _i, _j, ...). Key APIs:
InputShape/OutputShape/GetForwardIndex/Forward(vars)/Inverse/ InverseWithLevel/Reshape/DetectInjective/DebugOutput. OutputShape() is
DERIVED from the expressions' ranges by the Analyzer, not stored.
tl::Fragment : Layout adds a thread dimension for register buffers:
forward_thread_ (PrimExpr, may reference ReplicationPlaceholder(),
printed _rep) and replicate_size_. ThreadExtent() =
max(forward_thread)+1 under bound domains — also derived, not stored.
thread_range_ (via BindThreadRange) offsets the thread space for warp
specialization; the forward map itself stays in normalized [0, T).
Replication semantics: each logical point is held by replicate_size
threads; Replicate/DeReplicate/CondenseReplicateVar manipulate the rep
axis.
Gotchas (verified, easy to trip on):
FragmentNode::GetForwardVars() PREPENDS ReplicationPlaceholder()
when replicate_size > 1 (src/layout/layout.cc:1103). The input
placeholders are always the TRAILING InputDim() entries. Python code
that zips get_forward_vars() against the shape positionally is wrong
for replicated fragments.
- The canonical (thread, slot, rep) packing lives in
FragmentNode::InverseWithLevel (src/layout/layout.cc:1133): rep
becomes a trailing ORDINARY input dim of extent ReplicateExtent()
(substituting the placeholder), thread a trailing output. The inverse
then maps (slot..., thread) -> (coords..., rep) with rep last;
loop_partition.cc consumes exactly this ordering and guards
replica-zero for stores. Mirror this packing when you need a fragment
as a plain multi-output layout.
- Layouts print via
DebugOutput() / Python repr:
Fragment((2,) -> (2,), replicate: 256, thread: _rep, index: (_i,), thread_range: I.Range(0, 256)). Structured fields are directly
accessible from Python: replicate_size, get_thread_size(),
forward_thread, forward_index, thread_range, get_input_shape().
Swizzles (tilelang/layout/swizzle.py, src/layout/swizzle_mode.*)
are XOR-based shared-memory layouts. They are NOT expressible as strided
layouts; the CuTe side models them as a separate Swizzle functor
(ComposedLayout), never as (shape, stride) modes.
CuTe layout algebra (src/layout/cute_layout.{h,cc}, tilelang/layout/cute.py)
A full CuTe implementation maintained for the MMA/TMA backends, exposed to
C++ (namespace tvm::tl::cute) and Python (tilelang.layout.cute):
- Layouts are
(shape, stride) IntTuple trees, COLUMN-major (first mode
fastest) — opposite of TileLang's row-major intuition. Multi-output
codomains use ScaledBasis strides (v@axis, E<i>).
- Algebra:
Coalesce, RightInverse, LeftInverse, Composition,
Complement, Cosize, LogicalDivide, Filter, Restrict,
Layout::WithShape, Parse/Print (exact CuTe spelling).
- Converters:
LayoutFromTileLang (single flat output; multi-output
tl::Layouts are serialized ROW-major, so outputs [thread, slot] yield
thread * slots + slot), LayoutFromTileLangHierarchical (per-axis
recovery onto basis axes), ComposedLayoutFromTileLang (swizzle
recovery). All use probe-then-prove: numerically probe strides at
one-hot points, then symbolically PROVE equivalence — a wrong recovery
cannot slip through; failure returns None/nullopt.
Failure conventions (must respect): only the three *FromTileLang
converters return Optional. Every other algebra op ICHECK-crashes on its
preconditions (composition divisibility, non-constant extents in the
probe, complement non-injectivity, restrict rank...). Wrap calls when the
input is untrusted. RightInverse is PARTIAL: it silently drops stride-0
and non-const-stride modes and inverts only the maximal contiguous chain —
check bijectivity via size(right_inverse(F)) == size(F).
Conversion caveats: the converters read only GetForwardIndex(); a
Fragment's forward_thread and replication are silently ignored unless
you pack them into a plain multi-output Layout first (see the canonical
packing above; substitute ReplicationPlaceholder with a trailing input
var — if it leaks into the probe, conversion gracefully returns None).
Layout inference (src/transform/layout_inference/)
tl.LayoutInference assigns a layout to every fragment buffer and
parallel loop nest. Three levels: strict (annotations, MMA-imposed) →
common (BFS propagation through shared buffers) → free (per
connected component, try every member as inference root, keep the
cheapest attempt). Results are stored as IR annotations: layout_map
(Buffer -> Layout, on the SBlock) and parallel_loop_layout (Fragment,
on the outermost parallel For); ParallelLoopLayoutValidator enforces the
annotation contract.
"Cheapest" is a pluggable policy (layout_cost_model.{h,cc}):
tl.layout_cost_model="io-aware" (opt-in): every
fragment<->global copy and global-touching parallel loop is charged
max(bandwidth bytes, issue bytes) under the attempt's layouts, scored
symbolically on the CuTe algebra (pack -> LayoutFromTileLang ->
RightInverse -> Composition; vector width read off coalesced modes;
segments counted at warp/step granularity). Registers are the tiebreak.
Statements outside the model are charged a conservative worst case — an
attempt must never profit from opacity.
tl.layout_cost_model="register-count" (default): register-slots-only
ordering.
- The scoring formulas are guarded by the Python parity check
maint/layout_inference/run.py --cute (symbolic scorer vs an
independent NumPy enumeration oracle); keep cute_model.py in lockstep
when changing layout_cost_model.cc.
Hardware geometry is parameterized: lane width via MaxVectorLoadBits
(src/transform/loop_vectorize.h, SHARED with the vectorizer so the
model's width beliefs match codegen), warp size from the target's
thread_warp_size, segment granularity 128B (see BindMemoryGeometry).
Downstream consumers
loop_partition.cc: partitions parallel loops per-thread via the
fragment inverse; emits replica-zero guards for replicated stores.
loop_vectorize.cc: plans vector widths (GetVectorizeSize,
IndicesCanVectorize); the cost model mirrors its judgment.
- TMA/MMA lowering:
src/cuda/op/tma_layout.cc and
producer_consumer_ws.cc recover shared-buffer swizzles via
ComposedLayoutFromTileLang; tcgen05/wgmma macro generators
(tilelang/cuda/intrinsics/macro/) round-trip TMEM layouts through
to_tilelang/from_tilelang_hierarchical.
Tools
maint/layout_inference/ — the layout verification harness (see its
README): golden layout snapshots per constructed case (run.py),
lowered vector-width anchors (--anchor), and symbolic-vs-oracle parity
(--cute, backed by cute_model.py + the numpy oracle.py). Extend it
with a new case whenever you change inference, the cost model, or the
converters; goldens are recorded then human-reviewed (--record).
- Diagnostics: the inference and cost-model passes log decisions via
DLOG (debug builds);
tl.layout_visualization_enable renders layouts.
- Python inspection: run
tl.transform.LayoutInference() on a module
and read the annotations (see maint/layout_inference/common.py for
the extraction idiom), or cute.Layout.from_tilelang(...) to see a
layout's (shape, stride) normal form.
File map
| area |
files |
| core types |
src/layout/layout.{h,cc}, tilelang/layout/{layout,fragment}.py |
| swizzle |
src/layout/swizzle_mode.*, tilelang/layout/swizzle*.py |
| CuTe algebra |
src/layout/cute_layout.{h,cc}, tilelang/layout/cute.py |
| inference |
src/transform/layout_inference/layout_inference.cc |
| cost model |
src/transform/layout_inference/layout_cost_model.{h,cc} |
| MMA layouts |
src/layout/gemm_layouts.cc, src/layout/tcgen05_layout.* |
| verification |
maint/layout_inference/ |
1---2name: tilelang-layout3description: Use when working with TileLang layouts — the Layout/Fragment classes, layout inference and its cost model, the CuTe layout algebra and TileLang-to-CuTe conversion, swizzles, or when debugging layout-related passes and their annotations.4---56# TileLang Layout System78## Core types (`src/layout/layout.h`, Python `tilelang/layout/`)910**`tl::Layout`** maps logical coordinates to physical coordinates:11`input_size_` (logical shape) + `forward_index_` (one PrimExpr per output12axis). Expressions are written in process-wide placeholder Vars13`InputPlaceholder(i)` (printed `_i`, `_j`, ...). Key APIs:14`InputShape/OutputShape/GetForwardIndex/Forward(vars)/Inverse/15InverseWithLevel/Reshape/DetectInjective/DebugOutput`. `OutputShape()` is16DERIVED from the expressions' ranges by the Analyzer, not stored.1718**`tl::Fragment : Layout`** adds a thread dimension for register buffers:19`forward_thread_` (PrimExpr, may reference `ReplicationPlaceholder()`,20printed `_rep`) and `replicate_size_`. `ThreadExtent()` =21`max(forward_thread)+1` under bound domains — also derived, not stored.22`thread_range_` (via `BindThreadRange`) offsets the thread space for warp23specialization; the forward map itself stays in normalized `[0, T)`.24Replication semantics: each logical point is held by `replicate_size`25threads; `Replicate/DeReplicate/CondenseReplicateVar` manipulate the rep26axis.2728**Gotchas (verified, easy to trip on):**2930- `FragmentNode::GetForwardVars()` PREPENDS `ReplicationPlaceholder()`31 when `replicate_size > 1` (`src/layout/layout.cc:1103`). The input32 placeholders are always the TRAILING `InputDim()` entries. Python code33 that zips `get_forward_vars()` against the shape positionally is wrong34 for replicated fragments.35- The canonical (thread, slot, rep) packing lives in36 `FragmentNode::InverseWithLevel` (`src/layout/layout.cc:1133`): rep37 becomes a trailing ORDINARY input dim of extent `ReplicateExtent()`38 (substituting the placeholder), thread a trailing output. The inverse39 then maps `(slot..., thread) -> (coords..., rep)` with rep last;40 `loop_partition.cc` consumes exactly this ordering and guards41 replica-zero for stores. Mirror this packing when you need a fragment42 as a plain multi-output layout.43- Layouts print via `DebugOutput()` / Python `repr`:44 `Fragment((2,) -> (2,), replicate: 256, thread: _rep, index: (_i,),45 thread_range: I.Range(0, 256))`. Structured fields are directly46 accessible from Python: `replicate_size`, `get_thread_size()`,47 `forward_thread`, `forward_index`, `thread_range`, `get_input_shape()`.4849**Swizzles** (`tilelang/layout/swizzle.py`, `src/layout/swizzle_mode.*`)50are XOR-based shared-memory layouts. They are NOT expressible as strided51layouts; the CuTe side models them as a separate `Swizzle` functor52(`ComposedLayout`), never as (shape, stride) modes.5354## CuTe layout algebra (`src/layout/cute_layout.{h,cc}`, `tilelang/layout/cute.py`)5556A full CuTe implementation maintained for the MMA/TMA backends, exposed to57C++ (`namespace tvm::tl::cute`) and Python (`tilelang.layout.cute`):5859- Layouts are `(shape, stride)` IntTuple trees, **COLUMN-major (first mode60 fastest)** — opposite of TileLang's row-major intuition. Multi-output61 codomains use `ScaledBasis` strides (`v@axis`, `E<i>`).62- Algebra: `Coalesce`, `RightInverse`, `LeftInverse`, `Composition`,63 `Complement`, `Cosize`, `LogicalDivide`, `Filter`, `Restrict`,64 `Layout::WithShape`, `Parse`/`Print` (exact CuTe spelling).65- Converters: `LayoutFromTileLang` (single flat output; multi-output66 tl::Layouts are serialized ROW-major, so outputs `[thread, slot]` yield67 `thread * slots + slot`), `LayoutFromTileLangHierarchical` (per-axis68 recovery onto basis axes), `ComposedLayoutFromTileLang` (swizzle69 recovery). All use probe-then-prove: numerically probe strides at70 one-hot points, then symbolically PROVE equivalence — a wrong recovery71 cannot slip through; failure returns `None`/nullopt.7273**Failure conventions (must respect):** only the three `*FromTileLang`74converters return Optional. Every other algebra op ICHECK-crashes on its75preconditions (composition divisibility, non-constant extents in the76probe, complement non-injectivity, restrict rank...). Wrap calls when the77input is untrusted. `RightInverse` is PARTIAL: it silently drops stride-078and non-const-stride modes and inverts only the maximal contiguous chain —79check bijectivity via `size(right_inverse(F)) == size(F)`.8081**Conversion caveats:** the converters read only `GetForwardIndex()`; a82Fragment's `forward_thread` and replication are silently ignored unless83you pack them into a plain multi-output Layout first (see the canonical84packing above; substitute `ReplicationPlaceholder` with a trailing input85var — if it leaks into the probe, conversion gracefully returns None).8687## Layout inference (`src/transform/layout_inference/`)8889`tl.LayoutInference` assigns a layout to every fragment buffer and90parallel loop nest. Three levels: **strict** (annotations, MMA-imposed) →91**common** (BFS propagation through shared buffers) → **free** (per92connected component, try every member as inference root, keep the93cheapest attempt). Results are stored as IR annotations: `layout_map`94(Buffer -> Layout, on the SBlock) and `parallel_loop_layout` (Fragment,95on the outermost parallel For); `ParallelLoopLayoutValidator` enforces the96annotation contract.9798"Cheapest" is a pluggable policy (`layout_cost_model.{h,cc}`):99100- `tl.layout_cost_model="io-aware"` (opt-in): every101 fragment<->global copy and global-touching parallel loop is charged102 `max(bandwidth bytes, issue bytes)` under the attempt's layouts, scored103 symbolically on the CuTe algebra (pack -> `LayoutFromTileLang` ->104 `RightInverse` -> `Composition`; vector width read off coalesced modes;105 segments counted at warp/step granularity). Registers are the tiebreak.106 Statements outside the model are charged a conservative worst case — an107 attempt must never profit from opacity.108- `tl.layout_cost_model="register-count"` (default): register-slots-only109 ordering.110- The scoring formulas are guarded by the Python parity check111 `maint/layout_inference/run.py --cute` (symbolic scorer vs an112 independent NumPy enumeration oracle); keep `cute_model.py` in lockstep113 when changing `layout_cost_model.cc`.114115Hardware geometry is parameterized: lane width via `MaxVectorLoadBits`116(`src/transform/loop_vectorize.h`, SHARED with the vectorizer so the117model's width beliefs match codegen), warp size from the target's118`thread_warp_size`, segment granularity 128B (see `BindMemoryGeometry`).119120## Downstream consumers121122- `loop_partition.cc`: partitions parallel loops per-thread via the123 fragment inverse; emits replica-zero guards for replicated stores.124- `loop_vectorize.cc`: plans vector widths (`GetVectorizeSize`,125 `IndicesCanVectorize`); the cost model mirrors its judgment.126- TMA/MMA lowering: `src/cuda/op/tma_layout.cc` and127 `producer_consumer_ws.cc` recover shared-buffer swizzles via128 `ComposedLayoutFromTileLang`; tcgen05/wgmma macro generators129 (`tilelang/cuda/intrinsics/macro/`) round-trip TMEM layouts through130 `to_tilelang`/`from_tilelang_hierarchical`.131132## Tools133134- **`maint/layout_inference/`** — the layout verification harness (see its135 README): golden layout snapshots per constructed case (`run.py`),136 lowered vector-width anchors (`--anchor`), and symbolic-vs-oracle parity137 (`--cute`, backed by `cute_model.py` + the numpy `oracle.py`). Extend it138 with a new case whenever you change inference, the cost model, or the139 converters; goldens are recorded then human-reviewed (`--record`).140- **Diagnostics**: the inference and cost-model passes log decisions via141 DLOG (debug builds); `tl.layout_visualization_enable` renders layouts.142- **Python inspection**: run `tl.transform.LayoutInference()` on a module143 and read the annotations (see `maint/layout_inference/common.py` for144 the extraction idiom), or `cute.Layout.from_tilelang(...)` to see a145 layout's (shape, stride) normal form.146147## File map148149| area | files |150|---|---|151| core types | `src/layout/layout.{h,cc}`, `tilelang/layout/{layout,fragment}.py` |152| swizzle | `src/layout/swizzle_mode.*`, `tilelang/layout/swizzle*.py` |153| CuTe algebra | `src/layout/cute_layout.{h,cc}`, `tilelang/layout/cute.py` |154| inference | `src/transform/layout_inference/layout_inference.cc` |155| cost model | `src/transform/layout_inference/layout_cost_model.{h,cc}` |156| MMA layouts | `src/layout/gemm_layouts.cc`, `src/layout/tcgen05_layout.*` |157| verification | `maint/layout_inference/` |