Canonical GraphSpec DAG #
This file defines the canonical general GraphSpec representation: typed SSA/DAG terms.
NN.GraphSpec.Core gives a sequential authoring language (Graph with >>>) for chain-like
architectures. That syntax lowers into this DAG language. Many modern architectures are not purely
sequential:
- skip connections (ResNets),
- shared subcomputations (reusing the same feature tensor multiple times),
- multi-input ops (e.g.
add, concatenation, attention blocks, …).
GraphSpec.DAG is the general version: a small SSA/A-normal-form term language whose terms denote
DAG-shaped computation graphs.
Core idea: typed environments #
We track an environment Γ : List Shape at the type level. A term Term Γ τ means:
- “if you provide values for every shape in
Γ(in order),” - “this term computes a tensor of shape
τ.”
This is similar in spirit to a simply-typed lambda calculus with de Bruijn variables, except:
- there are no lambdas (only
let-bindings), so the graph is acyclic by construction, - primitive ops have arbitrary arity
ins : List Shape, - and
Args Γ insis a typed list of input subterms for an op.
The constructors are:
var: read a variable from the environment (Fin Γ.lengthindex),cast/castEnv: explicit casts to handle non-definitional equalities inShape/Γ,op: apply an n-ary primitive to n arguments,let1: bind an intermediate result and extend the environment (Γ ++ [σ]).
Mathematical semantics (intended) #
For a fixed scalar type α with [Context α], we interpret an environment as a typed list of
tensors TList α Γ. Then:
Term.eval : TList α Γ → Term Γ τ → Tensor α τis the pure reference semantics.Term.compileproduces a backend-generic TorchLean program that computes the same graph, but in the executable (monadic, reference-based) runtime world.
Small example (residual add) #
The residual pattern “main path + skip path” is the canonical example that needs DAG syntax:
y = linear(W,b,x)
out = relu(y + x)
Here x is used twice, so a pure chain representation would need duplication. With let1,
sharing is explicit: compute y once, then reuse it.
See NN/GraphSpec/Models/ResidualLinear.lean for a complete, well-typed instance.
References / citations #
Conceptual background (stable, standard references):
- SSA form: Cytron et al. (1991), “Efficiently Computing Static Single Assignment Form…”.
- A-normal form / let-normal form (used to enforce DAG structure).
- Automatic differentiation survey: Baydin et al. (2018), “Automatic Differentiation in Machine Learning: a Survey”.
- Residual networks: He et al. (2016), “Deep Residual Learning for Image Recognition”.
Primitives (arbitrary arity) #
An n-ary primitive operation.
Compared to the sequential GraphSpec.Primitive, a PrimOp here is parameter-free: parameters are
just ordinary inputs in the environment. This is what makes the DAG language flexible: a “layer”
is expressed by let1-binding its parameters and then using them as inputs to ops.
Type indices:
ins : List Shapeis the ordered list of input tensor shapes the op expects.τ : Shapeis the output tensor shape.
- name : String
Debug name for error messages / inspection.
Pure reference semantics (
insarguments packed as a typed list).- torchProgram {α : Type} [Context α] [DecidableEq Shape] : Runtime.Autograd.TorchLean.Program α ins τ
Executable TorchLean program with arguments of shapes
ins.
Instances For
DAG terms + arguments (mutual) #
A well-typed DAG term.
Read this as: “under environment Γ, this term produces a tensor of shape τ”.
- var
{Γ : List Shape}
(i : Fin Γ.length)
: Term Γ (Γ.get i)
Variable read (de Bruijn index into the environment).
- cast
{Γ : List Shape}
{σ τ : Shape}
: Term Γ σ → σ = τ → Term Γ τ
Cast a term’s output shape along a propositional equality.
This is an internal hygiene tool: when we build terms programmatically (e.g. by lowering a higher-level syntax into DAG form), we often end up with goals like “
Γ.get i = τ” that are true but not definitional.Using a
castnode keeps the term in constructor form (so evaluators/compilers can still pattern match), and pushes the non-definitional equality into the semantics where it can be handled bycases h. - castEnv
{Γ Γ' : List Shape}
{τ : Shape}
: Term Γ τ → Γ = Γ' → Term Γ' τ
Cast a term’s environment along a propositional equality.
This is useful when normalizing list-association/parenthesization choices in
Γwithout changing meaning. - op
{Γ ins : List Shape}
{τ : Shape}
: PrimOp ins τ → Args Γ ins → Term Γ τ
Apply an n-ary primitive op to n arguments.
- let1
{Γ : List Shape}
{σ τ : Shape}
: Term Γ σ → Term (Γ ++ [σ]) τ → Term Γ τ
Let-bind a single intermediate value, extending the environment.
Instances For
Typed environment lookup for pure tensors.
This is the underlying “variable semantics” for Term.eval.
Instances For
Typed environment lookup for backend references.
This is the underlying “variable semantics” for Term.compile.
Instances For
Spec interpreter #
Evaluate a typed argument list by evaluating each component term under the same environment.
Instances For
Pure evaluation of a DAG term.
This is the “math-first” semantics: we interpret a term as a pure function on tensors. No monads, no mutation, no autograd tape — just the Spec definitions of primitives.
The key runtime discipline is the environment discipline:
varreads fromenv,opevaluates its arguments and feeds them to the primitive’sspecFwd,let1evaluates the bound term once and extendsenvfor the body.
Instances For
TorchLean compiler #
RefT is the backend’s reference type for tensors of a given shape.
In the executable runtime, primitives operate on references (allocated tensors) inside a monad. This matches how typical deep-learning runtimes model device placement, mutation, and autograd.
Instances For
Compile a typed argument list by compiling each component term under the same environment.
Instances For
Compile a typed Term Γ τ into the backend monad m, producing a reference to a tensor of shape
τ.
This is the "executable" counterpart of Term.eval: instead of returning a pure Tensor, we
emit backend ops (Runtime.Autograd.Torch.Ops) that allocate tensors and apply primitives.
Instances For
Model wrapper #
A small “model” wrapper around DAG terms.
This mirrors the sequential Graph surface:
psare parameter tensor shapes (tracked at the type level),insare the shapes of non-parameter inputs (e.g. data tensors),τis the output shape.
The model body is a Term (ps ++ ins) τ, i.e. it expects an environment that starts with
parameters and then contains the actual inputs.
- initParams : Runtime.Autograd.Torch.TList Float ps
init Params.
body.
Instances For
Pure forward semantics of a DAG model.
We build the full environment Γ = ps ++ ins by appending the parameter list and the input list,
then evaluate the body using Term.eval.
Instances For
Compile a DAG model to a backend-generic TorchLean program.
The resulting program expects arguments in the order ps ++ ins (parameters first, then inputs),
matching the environment discipline used by specFwd.