The autograd tape #
One tape serves every device. A Tape V is a grow-only array of nodes whose forward values have
type V: the CPU stores Spec.SomeTensor α, CUDA stores LibTorch.SomeBuffer. Each node keeps its
parents and a local VJP, and the reverse sweeps below walk node ids from last to first, summing the
contributions that several children send to one parent.
The dense sweeps are shared between devices. GradValue supplies device-specific gradient checks,
addition, and buffer ownership; the CPU hooks are pure tensor operations. CUDA training also uses
the sparse LibTorch.Tape.backwardSparse traversal in
NN.Runtime.Autograd.Engine.LibTorch.Tape, which releases unretained gradients in IO.
Two traversals live here, and it matters which one a caller runs:
backwardDense(and its totalized formbackwardDenseAll) keeps anOptionper node and runs a node's VJP only when that node has received a cotangent, so disconnected nodes are never visited. Skipping is deliberate: onFloat, feeding a synthetic zero cotangent through the VJP of a singular value can produceNaNvia0 * (1/0).backwardDenseFromstarts from a total gradient array and runs every node's VJP. It is the variant the proof layer reasons about directly (Proofs.Autograd.Algebra.Graph.backwardDenseFrom_lowerGraphToTape_eq_backpropAllCtx).
The two agree whenever every VJP on the tape sends a zero cotangent to zero contributions of the
parents' shapes; that is Proofs.Autograd.Algebra.Graph.ZeroPreserving in
NN.Proofs.Autograd.Runtime.Link.BackwardDense.
Runtime error monad for the autograd tape.
We use plain Except String (instead of IO exceptions) so the tape constructors remain pure and
easy to test. Front-ends that prefer exceptions can use Lean's IO.ofExcept.
Instances For
What the reverse sweep needs from the values a tape stores.
A gradient for a node has the node's shape. retag re-expresses a gradient whose shape has been
checked against a value, check performs any further device check (CUDA compares dtypes and
buffer lengths), and add sums two gradients of one node. Backward rules return fresh
contributions, so a node keeps its first contribution as it is; drop retires one that no node
keeps, which on CUDA releases the device buffer at once.
- shape : V → Spec.Shape
Runtime shape of a value.
g, given the shape ofv, once the two shapes are known to agree.Device checks of a gradient
gagainst the valuevit belongs to.- add : V → V → Result V
Sum two gradients of one node.
- drop {β : Type} : V → β → β
Retire a contribution that no node keeps, then continue with
rest.
Instances
A zero gradient of the same shape as a value, for nodes the sweep never reached.
- zerosLike : V → V
A zero gradient shaped like
v.
Instances
One recorded computation step.
value is the forward value, parents the ids of its inputs, and backward its local VJP: given
the upstream cotangent of value, it returns (parentId, contribution) pairs. When several
children contribute to one parent, the sweep sums the contributions with GradValue.add.
PyTorch comparison: an autograd Function instance with its saved tensors, storing the backward
closure directly.
Optional node name used for debugging and pretty-printing.
- value : V
Forward value computed at this node.
- ownsValue : Bool
Whether this node owns its forward value. On CUDA, shape views and detached references borrow a parent's buffer, and releasing a borrowed value would also invalidate the parent.
- requiresGrad : Bool
Whether reverse-mode propagation should visit this node.
If
false, reverse-mode traversal skips this node and does not accumulate gradients into it. Operations inherit the disjunction of their differentiable parents' flags; leaves retain the caller's explicit flag. Parent node ids (dependencies) in the tape.
- cleanup : Array V
Forward workspace kept only because
backwardmay need it. CUDA releases these buffers when the tape is reset, so long training loops do not wait on finalizers for large intermediates. Local VJP rule for this node.
Instances For
Autograd tape: a grow-only array of nodes.
Node ids are array indices (Nat). All operations append exactly one node and return its id, so
reverse mode traverses ids in reverse order.
Tape nodes in evaluation order (id = index).
Instances For
Number of nodes stored in the tape.
Instances For
Reverse sweeps #
Add one parent contribution into the optional gradient array.
The entry is none until the sweep first reaches the node. A first contribution is kept as it is;
later ones are summed in, in the order the sweep produces them.
Instances For
Reverse-mode backpropagation producing a dense array of optional gradients.
- The result array has length
t.nodes.size. - Entry
idissome gif the node was reached fromoutIdduring reverse traversal, otherwisenone. - When multiple paths contribute to the same node, the contributions are summed.
- A node's VJP runs only if the node was reached; see the module docstring for why.
It is loosely analogous to PyTorch's autograd engine walking the dynamic graph and accumulating
.grad, but it keeps gradients for every node id rather than for leaves alone. That makes the
runtime easier to debug and gives proof-bridge code direct access to intermediate cotangents.
Instances For
One reverse-mode step at a single node id, updating a total dense gradient array.
Precondition by convention: acc has one entry per tape node, and every entry has the matching
node shape. The function checks those conditions dynamically and returns an error if a caller
violates them, which makes it the small proof-friendly step used by backwardDenseFromLoop.
Instances For
Reverse-mode accumulation over the first n nodes in reverse order.
The recursion visits node ids n-1, n-2, ..., 0. Passing n = t.nodes.size therefore traverses the
entire tape.
Instances For
Reverse-mode accumulation starting from an explicit dense gradient array.
This always runs every node's VJP (in reverse order) and keeps a gradient for every node id. The
eager runtime does not call it; it calls backwardDenseAll, which agrees with this function on
zero-preserving tapes (Proofs.Autograd.Algebra.Graph.backwardDenseAll_eq_backwardDenseFrom).
Instances For
Reverse-mode accumulation that returns a gradient for every node id.
Propagation uses backwardDense, so local VJP closures run only for nodes reached from outId.
Unreached nodes then receive explicit zeros. This is necessary at singular forward values:
applying a disconnected VJP to a synthetic zero cotangent can manufacture NaN through
expressions such as 0 * (1 / 0), even though the mathematical gradient of the selected output
with respect to that node is zero.
On zero-preserving tapes (in particular every tape produced by lowerGraphToTape) it returns
exactly what backwardDenseFrom returns from the one-hot seed array; see
NN.Proofs.Autograd.Runtime.Link.BackwardDense.