TileFoundry Spec — evaluator (HIR reference interpreter)¶
The evaluator executes a HIR Function's SSA-DAG on a tensor backend
and returns concrete values. It is a codegen-independent reference
oracle for parser output, type inference, and op value semantics; it
does not lower to TIR or invoke codegen / runtime.
flowchart TB
evaluate["<b>evaluate()</b><br/>entry"]
Evaluator["<b>Evaluator</b><br/>ExprVisitor[Value]"]
registry["<b>eval_registry</b><br/>register_eval(Op)"]
handler["per-op handler<br/>(EvalContext → Value)"]
Value["<b>Value</b>"]
TensorValue["<b>TensorValue</b><br/>(data, type)"]
TupleValue["<b>TupleValue</b><br/>(elements)"]
evaluate --> Evaluator
Evaluator -. "Call(target=Op)" .-> registry
registry --> handler
handler -. returns .-> Value
Evaluator -. produces .-> Value
Value --> TensorValue
Value --> TupleValue
def evaluate(
fn_or_call: "Function | Call",
*inputs: "torch.Tensor",
backend: str = "torch",
) -> "torch.Tensor | tuple[torch.Tensor, ...]":
...
evaluate binds inputs to the entry Function's parameters in
order, walks the body, and returns the logical tensor for a single
output or a tuple for a TupleType result. backend selects the
tensor engine; "torch" is the defined backend.
1. Value¶
The values that flow through evaluation form a small hierarchy: a
single-output node produces a TensorValue; a multi-output node (a
Tuple, a TupleType Call, or a multi-carry GridRegionExpr)
produces a TupleValue.
- constraints: none — abstract base; concrete values are
TensorValue/TupleValue
TensorValue¶
class TensorValue:
data: torch.Tensor # the logical tensor value
type: TensorType # the HIR type of this value (carries layout)
- constraints:
dataholds the value in its logical shape — the shape oftype(types §2), not a layout-domain shape.type.layout, when aShardLayoutorLayout, drives the layout-domain projection of §6.- A scalar value is a rank-0
datawith a rank-0TensorType.
TupleValue¶
- constraints:
elementsare the per-fieldValues;tuple_get_itemprojects one by static index.
2. Parameters and inputs¶
evaluate binds each entry-Function parameter Var to the
corresponding positional input:
- An input MUST be convertible to a backend tensor; it is cast to the
parameter
TensorType's dtype. - Weights and activations are bound identically — a weight is an
ordinary
Functionparameter, not a distinct constant carrier. - Each
DimVar(types §3) appearing in a parameter'sTensorType.shapeis bound from the corresponding axis of the input tensor's concrete shape. A later occurrence of the sameDimVarMUST agree with the first binding.
3. register_eval and the eval context¶
Each op's value semantics are a handler registered against the op
class. The registry is local to the evaluator (it reuses the
AnalysisRegistry container of
visitor-registry §2 but is not one of that
spec's module-level instances).
A handler receives an EvalContext and returns a Value:
class EvalContext:
args: tuple[Value, ...] # already-evaluated operands in `Call.args` order (TensorValue / TupleValue)
op: Op # the op instance; attributes read as fields (e.g. `ctx.op.kind`, `ctx.op.index`)
result_type: Type # the `Call`'s result type
device: torch.device # backend device; a handler materialising a new tensor (e.g. `Zeros`) creates it there
def handler(ctx: EvalContext) -> Value: ... # a registered per-op value handler
A Call whose op class has no registered handler raises an error that
names the op class. Backend dtype promotion follows the backend's own
rules; a handler MUST NOT depend on type inference having run.
4. Node evaluation¶
Evaluation is an ExprVisitor[Value]
(visitor-mutator §1) memoized on id(expr), so
a shared sub-DAG (hir §1.1) is evaluated once:
class Evaluator(ExprVisitor[Value]): # memoized on id(expr): a shared sub-DAG is evaluated once
def visit(self, expr: Expr) -> Value: ... # dispatch by expr kind: Var / Constant / Tuple / Call(Op) / Call(Function) / GridRegionExpr
- A
Varresolves to its binding in the current environment; aConstant(core-ir §2) materialises to a backend tensor of itsTensorType(a scalar becomes a rank-0 tensor). - A
Callwhosetargetis anOpevaluates its operands, then dispatches througheval_registry(§3). - A
Callwhosetargetis aFunction(hir §1.1) binds the evaluated arguments to the callee's parameters in a fresh environment and evaluates the calleebody— the same value semantics a call site has under type inference.
5. GridRegionExpr¶
A GridRegionExpr (hir §1.2) is a loop over its iteration
domain whose carry chain starts from init_args:
def eval_grid(region: GridRegionExpr) -> Value: ... # loop over the iteration domain; carry chain starts from init_args
- The first iteration binds each
carried_argsphi to the matchinginit_argsvalue; each later iteration binds it to the previous iteration'syield_values. induction_varis bound to the current index (a rank-0 tensor) for every iteration.- The result is the final carried value (single carry) or a
TupleValueof them (multi-carry), matching the node'stype. - A no-carry loop (
init_args/carried_args/yield_valuesall empty) yields the finalbodyvalue.
6. Layout domain¶
Evaluation models a single mesh participant and operates on logical values:
- An axis-bearing op (
Reduce,rms_norm, …) addresses itsaxis/axesin the operand's logicalTensorType.shape, regardless oftype.layout. A computation that must group a logical axis differently (e.g. per-head normalisation) is expressed by a logicalReshape(hir §1.3) to the target logical shape before the op; the op's axis then indexes that reshaped logical shape.Reshardonly changes distribution / layout and never changes which values an op reduces or indexes. as_layout_view(value: TensorValue) -> torch.Tensorreshapesdatafrom its logical shape to the element organisation oftype.layout.shape(for aShardLayout, itslayout.shape) under default-contiguous ordering;from_layout_view(data, type)is the inverse. These are provided for an op explicitly defined to compute in the layout domain; no op in the current set is layout-domain, so none of them call these helpers.Reshard(hir §1.3) preserves the logical value and MAY reshape it into the target layout's shape; it performs no cross-participant data movement.Local(hir §1.3) returns its operand's value for the single modelled participant.