Link #
Link the executable runtime tape (Runtime.Autograd.Tape) to the shape-indexed SSA/DAG models in
Proofs.Autograd.Algebra.
GraphData stores executable forward, JVP, and VJP functions without derivative laws. Graph
extends that representation with the local adjointness law used by its global backpropagation
theorem. Lowering places the stored VJP into each runtime node's backward closure.
What is proved here #
- Forward-pass correspondence:
lowerGraphToTape{,Data}produces the same values asGraph{,Data}.eval, and the runtime tape stores those values in the same order (lowerGraphToTape{,Data}_ctx_eq_eval,lowerGraphToTape{,Data}_values_eq). - Backward-pass correspondence: running the runtime dense reverse loop
Tape.backwardDenseFromon a lowered tape matches the graph's stored reverse programbackpropAllCtx(backwardDenseFrom_lowerGraphToTape_eq_backpropAllCtxand itsGraphDatavariant). ForGraphData, this is an implementation-equivalence result. ForGraph, it can be combined withGraph.backprop_correctto obtain derivative correctness.
The core invariant making the runtime reverse loop well-founded is that lowered nodes only emit
contributions to earlier node ids (pid < id).
PyTorch correspondence / citations #
This is analogous to lowering a graph representation to an executable autograd tape whose nodes carry backward closures (PyTorch does this internally for the eager autograd engine). https://pytorch.org/docs/stable/autograd.html
Extend a tape with leaf nodes for every tensor in the input context Γ.
Each leaf has requiresGrad = true and an empty backward contribution array, so the runtime loop
treats them as gradient accumulation slots but never produces parent contributions from them.
Instances For
Turn a shape-erased tensor into a runtime leaf node.
This is the node-level counterpart of addLeaves: it has no parents and contributes nothing in
backward.
Instances For
Result equations #
Result is Except String. The runtime backward functions are written with do notation, so
proofs about them repeatedly need the unfolding equations for bind, pure, throw and
Except.map. They are stated once here and used with simp only instead of unfolding the
Monad instances by hand.
Binding a successful Result applies the continuation.
Binding a failed Result propagates the error.
throw in Result is Except.error.
Except.map on a successful Result.
Except.map on a failed Result.
Backward closures of t only point backwards: every contribution emitted by the node stored at
id targets a node id strictly smaller than id. This is the invariant that makes the dense
reverse loop well-founded; lowerGraphToTape_backward_pids_lt_id and its GraphData counterpart
establish it for lowered tapes.
Instances For
Each entry of toShapeErasedArray carries the shape recorded at its position of ss.
addLeaves grows the tape by exactly Γ.length nodes.
addLeaves appends leafNodeOfSomeTensor nodes for each input tensor, in order.
Value projection of nodes_addLeaves: node.value agrees with toShapeErasedArray for added
leaves.
Runtime node produced by lowering one graph node.
The node stores the forward value and wraps the stored vjp in a runtime backward closure
that checks the shape of the upstream cotangent and emits one contribution per entry of the
node's input context, indexed from 0. The name only serves debugging output.
Instances For
The lowered node stores the node's forward value.
Lowered nodes always take part in gradient accumulation.
On an upstream cotangent of the right shape, the lowered backward runs the stored vjp.
On an upstream cotangent of the wrong shape, the lowered backward fails.
Lower an executable graph (GraphData) to a runtime tape by evaluating forward nodes and storing
each node's vjp program in its runtime backward closure.
PyTorch analogy: this corresponds to building a tape of autograd nodes during the forward pass, where each node stores enough information to compute parent contributions when given an upstream cotangent.
Instances For
Forward-pass correspondence #
The next lemmas show that lowerGraphDataToTape preserves executable forward semantics, and that
the resulting runtime tape contains exactly the evaluated context as shape-erased tensors in order.
The context returned by lowerGraphDataToTape agrees with GraphData.eval.
The lowered tape's .value array is GraphData.eval with shapes erased in the same order.
Size bookkeeping: the lowered tape contains one runtime node for each element of Γ ++ ss.
Lower a proved graph (Graph) to a runtime tape by evaluating forward nodes and storing each
node’s proved vjp.
Compared to lowerGraphDataToTape, this uses the pure graph interface (no explicit GraphData
payload).
Instances For
The context returned by lowerGraphToTape agrees with the proved Graph.eval.
The lowered tape's .value array is Graph.eval with shapes erased in the same order.
Size bookkeeping: lowerGraphToTape produces Γ.length + ss.length runtime nodes.
Full backpropagation (dense) for proofs and runtime #
The runtime engine computes a dense gradient array, accumulating cotangents for every node in the tape (inputs and intermediates). The following definition and theorems connect that behavior to the proved backpropagation semantics.
A "full" backpropagation that returns gradients for every value in Γ ++ ss.
Instances For
“Full” backpropagation for GraphData that returns gradients for every value in Γ ++ ss,
including inputs.
This is the GraphData-analogue of backpropAllCtx above. We keep both definitions because:
Graphuses[CommSemiring α](so it can express dot products and semiring-based accumulation), whileGraphDataonly needs[Add α]here (it just adds contributions).
Both follow the same reverse-mode accumulation structure: peel off the last node, apply its VJP to the seed on that node, add into the previous seed, and recurse.