TileFoundry Spec — tir (@prim_func imperative IR)¶
TIR is the imperative target IR. A @tilefoundry.prim_func body parses
into TIR; the lowering pass HirToTirPass (passes)
also produces TIR. TIR has no value return; effect-form Ops carry
the work, structural Stmts carry control flow.
- Container:
tir.PrimFunction(name, params, body, output_count).bodyis aSequential; the function returns no value. - Stmt tree: function bodies are nested Stmts only. Exprs appear
inside Stmt fields (e.g.
LetStmt.value,For.start). - Effect Ops (
Copy,Fill,Mma,ReLU,RMSNorm,Reduce) are value-class Ops registered with@register_op; in Stmt position they are invoked asEvaluate(op, args)(§1.4). - Value Ops (
AllocTensor,MemorySpan,PtrOf,TensorView) are anchored byLetStmtso their resultVarhas stable identity. - No HIR Ops reach TIR; HIR-to-TIR rewriting is owned by the pass layer.
1. TIR Stmt hierarchy¶
1.1 Stmt¶
- constraints:
- the abstract base of every TIR Stmt subclass; HIR has no
Stmt.
Stmt is the abstract base of every TIR Stmt subclass. HIR has no
Stmt. loc is a debug surface; it is not load-bearing for
semantics.
flowchart TB
Stmt["<b>Stmt</b>"]
Sequential["<b>Sequential</b> (Stmt)"]
CtrlStmts["<b>control-flow stmts</b><br/>For / While / If / MeshScope / LetStmt / Return"]
PrimFunction["<b>PrimFunction</b> (Stmt)"]
EvaluateStmt["<b>Evaluate</b> (Stmt)<br/>invokes an Op or function symbol"]
Stmt --> Sequential
Stmt --> CtrlStmts
Stmt --> PrimFunction
Stmt --> EvaluateStmt
1.2 Structural Stmts (tir.stmts)¶
class Sequential(Stmt):
body: tuple[Stmt, ...] # a Stmt sequence (__iter__ / __len__ / __getitem__ provided)
class LetStmt(Stmt):
var: Var # binds var to value; let chains nest body
value: Expr
body: Sequential
class For(Stmt):
induction_var: Var # counted loop
start: Expr
stop: Expr
step: Expr
body: Sequential
class While(Stmt): # control flow
cond: Expr
body: Sequential
class If(Stmt): # control flow
cond: Expr
then_body: Sequential
else_body: Sequential
class MeshScope(Stmt):
mesh: Mesh # scopes a mesh binding over body
binding: Var
body: Sequential
class Return(Stmt):
... # @prim_func has no value return
- constraints:
-
the structural (control-flow / binding) Stmt family; bodies are
Sequential. -
MeshScope.meshcarries theMeshobject; thebindingVarscopes the mesh insidebody.
1.3 PrimFunction¶
class PrimFunction(Stmt):
name: str # the function name
params: tuple[Var, ...] # parameter Vars (the trailing output_count are outputs)
body: Sequential # the function body
output_count: int = 1 # number of trailing output params
- constraints:
- itself a
Stmt, not a separate top-level node; returns no value.verify_prim_functionenforces the rules below.
PrimFunction is itself a Stmt, not a separate top-level node;
it sits inside the TIR Stmt tree along with everything else.
tir.verify.verify_prim_function(fn, *, module_fns=()) enforces:
- Param homogeneity. All parameters' layouts MUST be uniformly
ShardLayoutor uniformly non-ShardLayout; mixing is rejected. - Fresh
Varidentity. The sameVarobject MUST NOT be bound by more than oneLetStmt/For/MeshScopeacross the function. Parameters seed the bound set. LetStmttyping.LetStmt.var.typeMUST equal the typeinfer ofLetStmt.value.AllocTensorplacement.Call(AllocTensor, ...)MAY only appear directly asLetStmt.value. Nesting it inside any other Expr is rejected.MeshScopemesh in scope. Any embeddedShardLayoutMUST reference a mesh on the activeMeshScopestack or a parameter'sShardLayout.mesh.Evaluate.callable. Whencallableis aSymbolRef(§2.1), module-level resolution MUST find exactly onePrimFunctionof that name in the enclosingModule,argslength MUST match the resolved callee'sparams, and theSymbolRef.typeMUST equal the resolved callee'sCallableType. Whencallableis anOp, the per-Op verifier registered via@register_verify_stmt(Op)runs.
1.4 Evaluate¶
class Evaluate(Stmt):
callable: Op | SymbolRef # an effect-form Op or a SymbolRef callee
args: tuple[Expr, ...] # the callable's operands in ParamDef / parameter order
- constraints:
- TIR's single Stmt-position wrapper for a no-result invocation; verify and
lowering dispatch on
type(callable).
Evaluate is TIR's single Stmt-position wrapper for a callable
invocation that has no result value. The callable is one of:
- an effect-form
Op(e.g.tir.memory.Copy,tir.cuda.nn.Mma,tir.tensor.Reduce,tir.Launch§2.3).argsare the Op's operands inParamDeforder; the per-Op verifier registered via@register_verify_stmt(Op)runs. - a
SymbolRef(§2.1) — a reference to a calleePrimFunctionin the enclosingModule.argsfollow the callee's parameter order, the finaloutput_countpositions binding output buffers; the callee is resolved uniquely at module level (§1.3).
Verify and lowering MUST dispatch on type(callable). The per-Op
verify / codegen handlers are keyed by Op type and receive the Op
together with args; an Op callable carries no result, so its
Call form is unit-typed.
The value-producing counterpart is the Call(Op, args) Expr
(core-ir.md §2.1): it has a non-Unit result
type and is anchored by LetStmt. Evaluate is the unit-typed,
Stmt-position form and the only Stmt-position invocation wrapper.
Effect Op vs. control Stmt. A callable that is a single
unconditional invocation — an effect Op or a function SymbolRef —
is expressed as Evaluate(callable, args). A construct that carries
its own control flow stays a first-class Stmt, not an Evaluate
callable: DispatchCall (§1.6) is a first-match
if/else over patterns with a fallback, and Abort
(§1.7) is a terminator. Their nested function invocations
are themselves Evaluate(SymbolRef, args).
1.5 Sync¶
Sync is a mesh-scoped barrier. It is an effect-form op (tir.sync.Sync),
authored T.sync(m), and appears in Stmt position wrapped by Evaluate
(§1.4) like any other effect op. The surface is only
T.sync(m) / T.sync(m[slice]) — there is no m.sync() receiver form.
class Sync(Op):
"""Effect form; mesh-scoped barrier op ``tir.sync.Sync``, authored ``T.sync(m)``.
Attributes:
mesh: attribute; the (possibly sliced) mesh the barrier synchronizes.
"""
mesh: Mesh
- constraints:
- a mesh-scoped barrier; in Stmt position it is wrapped by
Evaluate. The participant set, barrier mapping, and named-barrier id rules are below.
mesh — the participating threads¶
meshis the (possibly sliced) meshSyncsynchronizes.T.sync(m)synchronizes the whole mesh;T.sync(m[1:3, :])synchronizes the constant sub-mesh selected by the slice.- A mesh slice is a compile-time descriptor.
m[...]is evaluated at parse time viaMesh.__getitem__into a sub-Meshwhoselayoutis aComposedLayoutrecording the participating sub-box (the affine "mesh scope" caseimage(c) = offset + outer(c)): the selected per-axis extents over the parent strides inouter, the slice origin (linear thread index of the first participant) inoffset, identityinner. An un-sliced mesh'slayoutis a plainLayout. A sliced mesh is still aMesh; the slice never becomes an IR/SSA value. - The participant set is derived through the existing layout algebra
(
shard.md): the participating linear thread indices areoffset + outer(coord)overouter's domain (the plainlayoutatoffset 0for an un-sliced mesh);baseis the minimum,count = size(outer), and the block domain is the product of the topology extents.classify/participationare the single source of truth shared by verify and codegen. - Legal-slice verification. A sliced mesh is accepted only if its
ComposedLayoutlayoutreconstructs as a constant slice of an enclosing full meshe: same strides, per-axis sub-extents bounded bye's shape, an offset that decomposes into in-range per-axis starts, and the full topology tuple + names equal — the proof rebuildse[key]and compares, so a forged slice cannot pass. A full mesh (plain-Layoutlayout) is accepted only by equality with an enclosing mesh.
Supported slices and the barrier mapping¶
The participant set MUST be a single contiguous thread interval [base,
base+count). Verify MUST reject (never broaden or split):
- a non-contiguous slice (e.g. a lane subset spanning warps);
- a cross-warp range that is not warp-aligned (
baseandcountnot both multiples of 32); - a dynamic / inconsistent / unsupported-topology mesh.
A valid participant set maps to exactly one hardware barrier:
| participant set | barrier |
|---|---|
whole block, more than one warp (base==0, count==domain) |
__syncthreads() |
| whole block that is one warp | __syncwarp() |
| a contiguous lane subset within one warp | __syncwarp(mask) under a participant predicate |
| a warp-aligned contiguous multi-warp subset | a named bar.sync <id>, <count> under a participant predicate |
the full mesh over the cta topology (all CTAs of the grid) |
the grid-wide software barrier (runtime §3) |
Codegen MUST guard the __syncwarp(mask) and bar.sync cases with the
participant predicate base <= tid < base+count (tid =
program_id<thread>()): a non-participant thread MUST NOT execute the barrier,
and every participant MUST execute the same id and count.
The first four rows synchronize threads within one block; their participant
set is the contiguous thread interval above. A mesh whose topologies are all the
cta topology instead synchronizes CTAs across the grid — program_id<cta>
ranges over the launch's blocks — and maps to the grid-wide software barrier.
Only the full cta mesh participates: a cta slice (a subset of CTAs) has no
supported barrier and MUST be rejected at verify. The grid barrier's correctness
requires every CTA of the launch to be co-resident; that co-residency is the
launch's occupancy contract, not something the barrier can enforce. The
grid-barrier device helper and its counter protocol are specified in
runtime §3.
Named-barrier id allocation¶
A sub-CTA bar.sync MUST carry a named-barrier id, allocated implicitly during
codegen, per kernel. Id 0 is reserved for the whole-CTA barrier; sub-CTA syncs
draw ids from 1..15. Each emitted bar.sync MUST take the next free id; a
sync op node emits once, so a loop body reuses its id. A kernel requiring more
than 15 distinct named barriers MUST error; an id MUST NOT be reused across
distinct sync sites.
Design rationale¶
A barrier's scope is a compile-time constant — which threads take part — so the mesh, and any slice of it, is a compile-time descriptor rather than an SSA value, and the barrier kind is derived from that set by one shared routine so verify and codegen cannot disagree.
1.6 DispatchCall¶
tir.DispatchCall is a first-class TIR Stmt that implements
pattern-based first-match dispatch over a tuple of Expr subjects.
It is the lowered form of an HIR dispatch prototype
(hir.md §1.1) and of any sub-call
to a dispatch-prototype callee.
class DispatchCall(Stmt):
callee_name: str # unmangled dispatcher name (debug / printer)
subjects: tuple[Expr, ...] # one Expr per dispatch axis
case_patterns: tuple[tuple[Pattern, ...], ...] # parallel case table: patterns
case_calls: tuple[Evaluate, ...] # parallel case table: Evaluate(SymbolRef, args)
fallback: Sequential # the Sequential taken when no case matches
- constraints:
- a control Stmt (not an
Evaluatecallable) implementing first-match dispatch; source order is part of the IR contract. Verifier rules below.
DispatchCall is a control Stmt, not an Evaluate callable
(§1.4); each case_calls[i] is an
Evaluate(SymbolRef, args) invoking that case's specialized callee.
Semantics: the i-th case_patterns matches against subjects by
position; the first i whose every pattern matches runs
case_calls[i] and the op completes. If no case matches, fallback
runs. Source order is part of the IR contract — printers and viewers
MUST preserve it.
Verifier rules¶
The verifier requires:
len(subjects) == 1.subjects[0]is atir.ShapeOf(param, axis).len(case_patterns) == len(case_calls).- Each
case_patterns[i]has length== len(subjects) == 1. - Each
case_patterns[i][0]is aDimVarRangePat(core-ir.md §3.1). fallbackis exactlySequential((Abort(),))— a length-1 body containing oneAbort.
subjects carries a canonical ordering so the IR is deterministic
across compiles: ordered by axis kind, then by canonical name of the
matched key. A single dispatch axis makes this ordering trivial.
1.7 Abort¶
- constraints:
-
a terminating Stmt on believed-unreachable paths (notably
DispatchCall.fallback). -
Abortis terminating. It exists in code paths the compiler believes are unreachable (notablyDispatchCall.fallback). - The CUDA emitter renders
Abortas__trap();in device contexts andassert(false);in host contexts so a runtime hit is loud rather than silent. messageis a debug surface; it does not carry semantics.
1.8 @intrinsic — user-defined effect Stmts¶
# example
@intrinsic
def <name>(<param>: Expr, ...) -> None: ... # decorated function's signature defines the synthesized Stmt subclass; its body becomes the verifier
- constraints:
- synthesises a Stmt subclass, registers the body as its verifier, and wires
parser dispatch under the snake-case name; parameters are annotated
Exprand the return annotation isNone.
tilefoundry.ir.tir.intrinsic.intrinsic synthesises a Stmt subclass
from the decorated function's signature, registers the function
body as the Stmt's verifier, and wires the parser dispatch entry
under the function's snake-case name. Parameters MUST be annotated
Expr; the return annotation MUST be None.
2. TIR Expr and callable constructs¶
2.1 SymbolRef¶
class SymbolRef(Expr):
name: str # canonical name of the callee PrimFunction (may be a mangled specialization name)
nested: tuple[str, ...] = () # empty — the Module holds only top-level functions
# type: CallableType — the resolved callee's CallableType, set at construction
- constraints:
- a leaf
Exprnaming a callee as anEvaluate/Launchtarget; resolution is module-level and unique. Per-field rules below.
SymbolRef is a leaf Expr naming a callee PrimFunction as a call
target: the callable of an Evaluate(SymbolRef, args)
(§1.4) function invocation and args[0] of a Launch
(§2.3).
name¶
- MUST be the canonical name of a
PrimFunctionin the enclosingModule(core-ir.md §1), exactly as stored inPrimFunction.name. It MAY be a generated / mangled specialization name.
nested¶
- MUST be empty: the
Moduleholds only top-level functions, so a non-emptynestedis rejected.
type¶
- MUST be the resolved callee's IR-level
CallableType(types §7):parametersare the calleeparamstypes in order;return_typeisUnitType(types §6) — a TIRPrimFunctionreturns no value, its outputs are trailing params (§1.4). - MUST be set at construction from the callee in hand; a
SymbolRefwith a deferred or unresolved type MUST NOT enter constructed IR.Expris frozen and verify MUST NOT mutate IR, so verify only checkstypeagainst the resolved callee (§1.3) — it never back-fills.
Resolution is module level: a unique lookup over the Module
(core-ir.md §1) MUST map name to exactly
one PrimFunction; zero or more than one match is an error.
Specialization variants each carry a distinct canonical
PrimFunction.name, so a SymbolRef to a variant resolves
unambiguously; the unmangled dispatcher name lives on
DispatchCall.callee_name (§1.6), not on a
SymbolRef. Local typeinfer does not resolve a SymbolRef; it
carries its type directly.
2.2 ShapeOf¶
class ShapeOf(Expr):
param: Var # a parameter Var of the enclosing PrimFunction
axis: int # a valid axis index of param.type
- constraints:
-
typeis a rank-0i32TensorType(scalar); it is the runtime-extent ABI for a dynamic tensor dimension. Per-field and ABI rules below. -
ShapeOf.typeis rank-0TensorTypeof dtypei32(a scalar). paramMUST resolve to a parameterVarof the enclosingPrimFunction;axisMUST be a valid axis index ofparam.type.- The CUDA emitter lowers
ShapeOf(param, axis)to a kernel scalar parameter namedf"{param.name}_shape_{axis}". The host wrapper reads the value from the runtime tensor's shape and forwards it to the kernel.
The <param>_shape_<axis> i32 scalar is the runtime-extent ABI for a
dynamic tensor dimension, independent of how the PrimFunction was
produced:
- A device (CUDA)
PrimFunctionwhose body references a dynamic tensor dimension (aDimVaraxis of a tensor parameter) MUST carry the corresponding hidden<param>_shape_<axis>i32 scalar parameter, in addition to the tensor parameter. The dimension maps to the first tensor parameter / axis in which it occurs. - A CPU host entry MUST NOT expose such a scalar at its user-facing surface — it reads the extent from its tensor argument's runtime shape and forwards it (§2.3).
2.3 TIR Ops¶
Value Ops MUST be anchored by LetStmt.value — their result Var is the only
handle. Effect Ops appear in Stmt position as Evaluate(op, args)
(§1.4). Each Op's full contract lives here, in its catalog entry
below; code carries only a one-line purpose docstring
(SPEC-RULES).
TensorType.storageis aStorageKind(gmem/smem/rmem/host/tmem) orNone(types §2).storage=Noneis rank-0-only, reserved for shape-element tensors. A memory-resident TIR tensor MUST carry a concrete level; the unmaterializedumat(types §2) is an HIR-only value and MUST already be materialized to a concrete level by the timeHirToTirPassproduces TIR — it never appears in TIR.Resharddoes not appear in TIR; HIR-sideReshardis lowered intoLetStmt(AllocTensor)+Evaluate(Copy, ...)chains duringHirToTirPass(passes).
Memory Ops (tir.memory.*)¶
AllocTensor¶
class AllocTensor(Op):
"""Value form; allocate a tensor, anchored by ``LetStmt.value``.
Attributes:
tensor_type: attribute; the allocated tensor's result type.
"""
tensor_type: TensorType
LetStmt.value.
MemorySpan¶
class MemorySpan(Op):
"""Value form; re-interpret a memory region as a typed tensor.
Attributes:
x: input; the memory region being re-interpreted.
"""
x: Tensor
PtrOf¶
class PtrOf(Op):
"""Value form; take the device address of a tensor.
Attributes:
x: input; the tensor whose device address is taken.
"""
x: Tensor
TensorView¶
class TensorView(Op):
"""Value form; derive a sub-view of a tensor.
Attributes:
memory: input; the base tensor (may be a ``PtrOf`` result).
layout: attribute; the sub-view descriptor — a plain ``Layout`` or a
``ShardLayout`` placed over ``memory``.
shape: attribute; optional logical-shape override (reshape).
"""
memory: Tensor
layout: object
shape: tuple | None = None
Copy¶
class Copy(Op):
"""Effect form; byte-equivalent copy between two tensors.
Attributes:
source: input; copy source.
destination: input; copy destination.
"""
source: Tensor
destination: Tensor
Fill¶
class Fill(Op):
"""Effect form; broadcast a scalar value into a tensor.
Attributes:
tensor: input; destination tensor.
value: input; rank-0 scalar broadcast into ``tensor``.
"""
tensor: Tensor
value: Tensor
NN Ops (tir.nn.*)¶
Mma¶
class Mma(Op):
"""Effect form; matrix-multiply-accumulate ``acc += lhs @ rhs``.
Attributes:
acc: input; accumulator fragment.
lhs: input; left-hand operand fragment.
rhs: input; right-hand operand fragment.
atom: attribute; optional compile-time ``MmaAtom``, absent ⇒ bare-Mma
per-target path.
"""
acc: Tensor
lhs: Tensor
rhs: Tensor
atom: MmaAtom | None = None
acc += lhs @ rhs; per-target PTX lowering lives in
target, the atom calling convention in
§2.3.
ReLU¶
class ReLU(Op):
"""Effect form; pointwise ``max(src, 0)`` written into ``dst``.
Attributes:
src: input; input tensor.
dst: input; destination tensor.
"""
src: Tensor
dst: Tensor
max(x, 0).
RMSNorm¶
class RMSNorm(Op):
"""Effect form; fused RMS normalisation written into ``dst``.
Attributes:
src: input; input tensor, reduced over its last axis.
dst: input; normalised-output tensor.
weight: input; 1-D scale multiplied onto the normalised output.
eps: attribute; epsilon applied with rsqrt.
"""
src: Tensor
dst: Tensor
weight: Tensor
eps: float
Tensor Ops (tir.tensor.*)¶
Reduce¶
class Reduce(Op):
"""Effect form; generic axis reduction dispatched by the ``kind`` tag.
Attributes:
src: input; reduction source.
dst: input; reduction destination.
workspace: input; optional staging buffer sized by lowering.
axes: attribute; reduced-axis tuple.
kind: attribute; ``ReduceKind`` tag.
"""
src: Tensor
dst: Tensor
workspace: Tensor | None = None
axes: tuple
kind: ReduceKind
Reduce carries no dispatch parameter; runtime selects the strategy.
- workspace is present only when lowering sizes cross-warp staging.
- All forms lower to the single public runtime entry
tilefoundry::ops::reduce<Op, Axes>(src, dst[, workspace]).
- Plain and sharded runtime extents/tiers are derived inside the runtime.
Generic kind-tagged effect Ops (tir.arith)¶
Binary / Unary are effect-form Ops that dispatch on a kind enum rather than
per-op classes; they appear as Evaluate(op, args). BinaryKind /
UnaryKind / ReduceKind are compiler-wide tag enums shared across HIR and
TIR; lowering preserves the kind value without re-mapping.
Binary¶
class Binary(Op):
"""Effect form; pointwise binary operation ``dst = lhs <kind> rhs``.
Attributes:
lhs: input; left-hand operand.
rhs: input; right-hand operand.
dst: input; destination operand.
kind: attribute; ``BinaryKind`` tag.
"""
lhs: Tensor
rhs: Tensor
dst: Tensor
kind: BinaryKind
Unary¶
class Unary(Op):
"""Effect form; pointwise unary operation ``dst = <kind>(src)``.
Attributes:
src: input; input operand.
dst: input; destination operand.
kind: attribute; ``UnaryKind`` tag, including rsqrt.
"""
src: Tensor
dst: Tensor
kind: UnaryKind
Launch¶
Effect Op for a host-side launch of a device kernel (CPU entry only, no value);
the callee SymbolRef and grid/block extents flow through the Evaluate args,
the non-grid/block launch config through the Op attributes.
The authored launch-attribute descriptors are owned by
tilefoundry.ir.tir.launch:
class CudaLaunchAttr(IntEnum):
"""Authored selector for a CUDA launch attribute."""
...
class LaunchAttrs:
"""Authored launch attribute selector/value pairs."""
entries: tuple[tuple[CudaLaunchAttr, object], ...] = ()
CudaLaunchAttr identifies the CUDA launch-attribute values carried by
LaunchAttrs.entries; CUDA target lowering interprets them and rejects
unsupported values. These are authored-IR selectors, not a target registration
API. Launch geometry is derived inside codegen and emitted into the generated
host entry; it is not part of the Launch schema and is not carried as runtime
metadata.
class Launch(Op):
"""Effect form; host launch of a device kernel, producing no value.
Attributes:
cluster: attribute; optional cluster extents.
dynamic_smem: attribute; dynamic shared-memory byte count.
stream: attribute; optional stream handle.
attrs: attribute; remaining ``LaunchAttrs`` launch configuration.
"""
cluster: tuple | None = None
dynamic_smem: int = 0
stream: object | None = None
attrs: LaunchAttrs = LaunchAttrs()
# Evaluate(Launch(...), (SymbolRef(callee), grid_x, grid_y, grid_z, block_x, block_y, block_z, *forwarded_args))
- constraints:
- appears only in a CPU (host) entry body; grid/block extents are launch config, not kernel parameters. Per-arg / per-attribute rules below.
Launch appears only in a CPU (host) entry body, as Evaluate(Launch(...),
args) with args = (SymbolRef(callee), grid_x, grid_y, grid_z, block_x,
block_y, block_z, *forwarded_args):
- callee:
args[0]MUST be aSymbolRef(§2.1) resolving to a devicePrimFunctionwith a CUDA target. - grid / block:
args[1:7]are the grid then block extents in the fixed ordergrid_x, grid_y, grid_z, block_x, block_y, block_z. Each is anExpr— aConstantfor a static extent, aShapeOf(§2.2) for a launch-provided (dynamic) one, or a dim-arithmeticCallover those. They are launch configuration, not kernel parameters: the device observes geometry throughgridDim/blockIdx(the codegenprogram_dim/program_shapeaccessors), never as arguments. - forwarded args: the remaining
argsbind the callee's host-visible parameters in declaration order. They MUST NOT include the hidden<param>_shape_<axis>scalar parameters (§2.2) — the host fills those from a tensor argument's runtime shape. - attributes:
cluster,dynamic_smem,stream, andattrscarry the non-grid/block launch configuration. Acluster/stream/attrsvalue the active CUDA target does not support MUST be rejected in target lowering.
MMA atom and the hand-written calling convention¶
A hand-written kernel issues an MMA through an explicit atom — a
realized instruction descriptor — instead of the bare Mma op whose
fragment layouts the per-target lowering chooses
(hir §1.3, passes). An MMA atom fixes a
concrete hardware instruction, so the whole MMA surface is target-owned:
the Mma op and the MmaOpSpec / MmaAtom descriptors
(tilefoundry.ir.tir.cuda.nn, mirroring the CuTe MMA_Op → MMA_Atom
layering), the concrete instructions, and their fragment layouts all live
under tilefoundry.ir.tir.cuda.nn.mma / mma_atom, following IR's dialect-first
layout ir/{dialect}/{target}/{category}.
MmaOpSpec¶
A named, fully-specified MMA instruction (the CuTe MMA_Op analog).
class MmaOpSpec:
name: str # uniquely identifies the instruction; the other fields mirror it
shape_mnk: tuple[int, int, int] # the instruction's static (M, N, K)
dtype_a: DType # lhs operand element type
dtype_b: DType # rhs operand element type
dtype_c: DType # accumulator element type
operand_layout: str # source operand order string (e.g. "TN")
- constraints:
- a fully-specified MMA instruction descriptor carrying no fragment-layout knowledge. Per-field rules below.
name¶
- MUST uniquely identify the instruction. dtype / shape / source layout are fixed by it; the remaining fields mirror the name so verify and codegen do not re-parse the string.
shape_mnk¶
- MUST be the instruction's
(M, N, K)tuple; every entry MUST be a static int.
dtype_a¶
- MUST be the
lhsoperand element type (DType).
dtype_b¶
- MUST be the
rhsoperand element type (DType); it MAY differ fromdtype_a.
dtype_c¶
- MUST be the accumulator element type (
DType); it MAY differ from the operand types (e.g.f32accumulation overbf16operands).
operand_layout¶
- MUST encode the source operand order as a string, e.g.
"TN"(A row-major, B col-major). AnMmaOpSpecMUST NOT carry fragment-layout knowledge.
MmaAtom¶
The realized atom for an op (the CuTe MMA_Atom analog), built by
T.cuda.mma.atom(op=...)
(parser §2.6).
class MmaAtom:
op: MmaOpSpec # the MmaOpSpec this atom realizes
A: ShardLayout # lhs fragment ShardLayout contract
B: ShardLayout # rhs fragment ShardLayout contract
C: ShardLayout # accumulator fragment ShardLayout contract
required_scope: Mesh # the thread-participation contract, carried as its own Mesh
- constraints:
- the realized atom for an
op; fragment layouts are returned as-is and not rebound onto the caller's mesh. Per-field rules below.
op¶
- MUST be the
MmaOpSpecthis atom realizes.
A¶
- MUST be the
lhsoperand fragmentShardLayoutcontract — the lane→value layout the instruction reads. It MUST be returned as-is at a use site and MUST NOT be rebound onto the caller's mesh.
B¶
- MUST be the
rhsoperand fragmentShardLayoutcontract; the same as-is / no-rebind rule asAapplies.
C¶
- MUST be the accumulator fragment
ShardLayoutcontract; the same as-is / no-rebind rule asAapplies.
required_scope¶
- MUST be the thread-participation contract the atom needs, carried as
its own
Mesh(for the SM8016x8x16instruction, 32 lanes arranged as a(4, 8)thread mesh). It MUST NOT be the caller's mesh; the caller's enclosing scope MUST be checked against it at verify (below).
Calling convention¶
Load, compute, and store are three separate effect statements under
an enclosing MeshScope (§1.2); T.mma is
verify-only and MUST NOT fuse the loads or the store.
atom = T.cuda.mma.atom(op=T.cuda.mma.SM80_16x8x16_F32BF16BF16F32_TN)
with Mesh(Topology("thread", 32), Layout(shape=(4, 8), strides=(1, 4))) as warp:
a_frag = T.alloc_tensor(TensorType(..., layout=atom.A, storage=rmem))
acc = T.alloc_tensor(TensorType(..., layout=atom.C, storage=rmem))
T.copy(T.tensor_view(a, layout=atom.A), a_frag) # load
T.fill(acc, 0.0)
T.mma(acc, a_frag, b_frag, atom=atom) # compute
T.copy(acc, T.tensor_view(c, layout=atom.C)) # store
- The author allocates each register fragment with the matching
atom.A/B/Clayout and fills it with its ownT.copy. The accumulator is initialised withFilland then read-modify-written. atomis a compile-time attribute on theMmaOp (parser §2.6), not a runtime operand. When absent, lowering takes the bare-Mmaper-target path.
Verify¶
A T.mma carrying an atom MUST satisfy:
- operand contracts:
acc.layout == atom.C,lhs.layout == atom.A,rhs.layout == atom.B. - scope: some mesh on the active
MeshScopestack provides the atom's required thread scope —mesh_scope_matches_required_scope(mesh, atom.required_scope). The match is identity- and name-independent (mesh object identity, the binding-var name, and axis names are not compared); it holds iff: - the two meshes share the same program topology level — a
ctascope is never athread/ warp scope, even when its layout carries the same shape; - both topology domains (the product of the topology extents) are statically known;
- each mesh is self-consistent (
topology domain == layout extent), and the enclosing mesh is inverse-projectable; - the thread-value decomposition matches exactly — same layout
shape and strides. A flat lane layout cannot host the atom's
multi-axis fragment
Splitand is rejected.
Per-target PTX emission dispatches on the atom (target).
Async copy Ops (tir.async.*)¶
Non-blocking cp.async gmem→smem staging for warp-specialized pipelines: a
producer issues copies, groups them, and a consumer waits on the group queue.
CopyAsync¶
class CopyAsync(Op):
"""Effect form; async gmem→smem copy, non-blocking.
Attributes:
source: input; gmem staging source.
destination: input; smem staging destination.
"""
source: Tensor
destination: Tensor
tilefoundry::ops::copy_async(src, dst).
- A later read of dst is ordered by CpAsyncCommit followed by
CpAsyncWait.
CpAsyncCommit¶
- constraints: - LaterCpAsyncWait counts committed groups.
CpAsyncWait¶
class CpAsyncWait(Op):
"""Effect form; wait until at most ``n`` committed groups remain in flight.
Attributes:
n: attribute; most-recent committed groups allowed to remain in flight.
"""
n: int = 0
n is a non-negative compile-time count.
- n = 0 drains every outstanding committed group.