Semantics #
Denotational semantics for NN.IR.Graph.
This file defines an evaluator for the current IR fragment:
- it evaluates nodes in SSA/topological order,
- each node applies the corresponding spec-layer tensor operation to its parents,
- parameter payloads for
const,linear, andconv2dare supplied by an explicitPayload.
The evaluator is total on well-formed, well-shaped graphs and returns Except String on malformed
graphs or missing payloads.
Softmax and layer norm:
softmax axisis interpreted as softmax along the givenaxis, but the spec primitive we have is last-axis softmax (Activation.softmax_spec). We therefore interpret non-last-axis softmax by permuting the requested axis to the last position, applying last-axis softmax, then permuting back. This matches the meaning oftorch.softmax(x, dim=axis)in PyTorch.layernorm axismatches PyTorch'sF.layer_norm(x, normalized_shape=x.shape[axis:])convention:axisselects the start of the normalized suffix. We implement this by reshaping the tensor into a 2D view(seqLen, embedDim), applying the spec 2D LayerNorm (Spec.layerNorm), then reshaping back.
How this relates to PyTorch:
Graph.nodesis analogous to a topologically-sorted IR like FX/TorchScript.Payloadis analogous to “parameters / buffers / constants” that live outside the pure graph structure.- The evaluator is a pure, denotational model of running the graph. It is designed for clarity and for connecting to proofs and verification passes (not for performance).
References / related systems:
- PyTorch FX: https://pytorch.org/docs/stable/fx.html
- TorchScript: https://pytorch.org/docs/stable/jit.html
- ONNX (graph + initializers): https://onnx.ai/
Dynamic (shape-tagged) values #
During evaluation we keep values in a dependent pair Σ s, Tensor α s so we can store a
heterogenous
table of intermediate tensors while still recovering precise shapes when we need them.
Dynamic (shape-tagged) tensor value used by the IR evaluator.
This is a dependent pair Σ s, Tensor α s, which lets us store heterogeneously-shaped intermediate
values in one table while still recovering exact shapes when needed.
Instances For
The shape tag carried by a dynamic value.
Instances For
The underlying tensor, with its shape recovered from the dependent pair.
Instances For
Construct a dynamic value from a shape and a tensor of that shape.
Instances For
Permutation lowering #
Compute a sequence of adjacent swaps that realizes a target permutation.
This is used to implement .permute by repeatedly applying swapAdjacentAtDepth, which is already
available in the spec tensor library. If the permutation is ill-formed, this returns an error
explaining what went wrong.
Instances For
Permute a dynamic tensor value according to perm.
This checks that perm is a valid permutation for the input shape (using Shape.permute?), then
lowers it to a sequence of adjacent swaps and applies them to the tensor.
Instances For
Evaluation helpers #
The evaluator itself (evalAt / denoteAll) is a fold over nodes. These helpers keep the fold
readable:
expectShapeenforces “dynamic shape agrees with declaredoutShape” at each step.evalConst/evalLinear/evalConv2Dfetch and apply external payloads keyed by node id.
Check a dynamic value has the expected shape and return it as a statically-typed tensor.
Instances For
Denominator for totalized mean reductions over a dynamic IR shape.
For nonempty shapes this is the real element count. For empty shapes, the mathematical mean is
undefined; the IR is total, so it uses denominator 1 and the empty sum contributes 0.
Instances For
Evaluate MSE loss on two dynamic values, checking that their runtime shapes agree.
Instances For
Transport a Tensor α (dim n scalar) across an equality n = n' (helper for payload casts).
Instances For
Evaluate a const node from the external payload.
Constants are stored “flat” (1D) for convenience, so we check the flattened length matches
Spec.Shape.size s and then unflatten to the requested shape.
Instances For
Evaluate a linear node from the external payload.
We enforce:
- the input dynamic value has shape
(inDim), and - the node's declared outShape matches
(outDim).
The actual math is the usual affine map: $y=Wx+b$.
Instances For
Evaluate a conv2d node from the external payload.
The output shape is computed with the standard (no dilation) formula $$ \mathrm{out}=\left\lfloor \frac{\mathrm{in}+2\,\mathrm{pad}-k}{\mathrm{stride}} \right\rfloor+1 $$ for each spatial dimension.
Instances For
Apply fixed-statistics BatchNorm2d to a batched channel-first tensor.
The input shape records the batch, channel, height, and width axes. Naming the shared tensor operation independently of that layout notation keeps compiler and evaluator code concise while the type retains the exact contract.
Instances For
Evaluate eval-mode BatchNorm2d over a batched channel-first tensor.
Instances For
Deterministic LayerNorm used by the IR evaluator ($\gamma=1$, $\beta=0$).
Instances For
Decode a dynamic concat parent as a tensor with an existential leading dimension and the requested tail shape. This is the checked boundary shared by list-indexed concat evaluation and its proofs.
Instances For
Evaluate a concat node from already evaluated parent values.
The IR concat operation accepts any valid axis. The tensor primitive concatenates along axis 0,
so the evaluator implements the generic case by moving the requested axis to the front, folding
Tensor.concatLeadingAxisSpec over the permuted parents, and moving the result back.
Instances For
Normalize a node result to the node's declared shape, rejecting inconsistent implementations.
Instances For
Evaluate a known node from its already computed parent values.
Keeping operator dispatch separate from graph lookup lets local correctness proofs reduce only the
selected OpKind branch. The caller remains responsible for the graph's topological invariant.
Instances For
Evaluate node i after checking the graph's id discipline and retrieving the corresponding node.
denoteAll checks the full graph structure before repeatedly calling this one-step evaluator.
Instances For
Evaluate nodes i, i+1, ... given already computed prefix values vals.
This is written as a structurally recursive function so it is easy to reason about in proofs (evaluation is “a simple loop over node ids”).
Instances For
Evaluate a graph to a table of node values.
This returns an array vals of length g.size where vals[i] is the value of node i.
We do a structural well-formedness check once up front (ids/arity/topology). For compiler-produced
graphs, the boolean Graph.wellFormed check is a fast path; if it fails we fall back to the
exception-producing Graph.checkWellFormed so callers get a readable error message.
The evaluator is total in the sense that it always returns either:
.ok vals(all nodes evaluated successfully), or.error msgdescribing the first failure (malformed IR, missing payload, or a local shape error).
Instances For
Scoped notation #
Scoped notation for evaluating a graph to all node values.
Use with:
open scoped IR
g⟦payload, input⟧
Instances For
ASCII alternative to g⟦payload, input⟧.
Instances For
Evaluate the graph and return the value at outputId.