Soundness #
Tape-style (SSA/DAG) reverse-mode soundness for the proved-correct layer.
We model a dynamic graph as a sequence of nodes that may reference any previously computed values (so sharing/fan-out is allowed). For each node we assume a local JVP/VJP adjointness law, then prove the global reverse-mode accumulation algorithm is sound.
This is a proof-only layer; the runtime engine in NN.Runtime.Autograd.Engine is an
executable implementation of the same idea.
PyTorch correspondence / citations #
- This file is the proof analogue of PyTorch’s dynamic autograd engine building a tape of nodes during the forward pass and running a reverse pass that accumulates gradients at shared inputs. https://pytorch.org/docs/stable/autograd.html
References (background):
- Reverse-mode AD as backpropagation on a computation graph is standard; see e.g. Baydin et al. (JMLR 2018) for an overview and terminology (JVP/VJP, duality, etc.).
Dot product over contexts: sum of per-entry tensor dot products.
Informally: dotList xs ys is the “context inner product” used to state global adjointness for
tape evaluation and backprop.
Instances For
A context cast can be moved from one side of the context dot product to the other.
Casts appear whenever two tapes are composed and their context shape lists are only propositionally
equal; without this lemma every adjointness proof would have to cases the equality by hand.
dotList is linear in its right argument with respect to TorchLean.TensorPack.add.
Informally: ⟪x, y + z⟫ = ⟪x, y⟫ + ⟪x, z⟫ for contexts.
dotList respects appending: dot of two snoced contexts splits into prefix + last entry.
Informally: ⟪(x,a), (y,b)⟫ = ⟪x,y⟫ + ⟪a,b⟫.
Dotting any tensor with a zero-filled tensor gives 0.
This is the tensor-level fact used to show that “one-hot” cotangents behave as expected.
dotList x 0 = 0 for the all-zero context.
Build a sparse context with a single nonzero entry at idx and zeros elsewhere.
This is used to express “one-hot” cotangents when proving local-to-global backprop correctness.
Instances For
single idx v is the “one-hot” context with value v at idx, and zeros elsewhere.
This lemma says the context dot product against single idx v picks out the corresponding entry
of dx:
⟪dx, single idx v⟫ = ⟪dx[idx], v⟫.
A node with local JVP/VJP and an adjointness proof against the tensor dot product.
- forward : TorchLean.TensorPack ℝ Γ → TorchLean.Tensor ℝ τ
The node's forward pass, reading the whole context and producing one output tensor.
- jvp : TorchLean.TensorPack ℝ Γ → TorchLean.TensorPack ℝ Γ → TorchLean.Tensor ℝ τ
Forward-mode derivative: a basepoint and a tangent context give an output tangent.
- vjp : TorchLean.TensorPack ℝ Γ → TorchLean.Tensor ℝ τ → TorchLean.TensorPack ℝ Γ
Reverse-mode derivative: a basepoint and an output cotangent give a context cotangent.
- correct (x dx : TorchLean.TensorPack ℝ Γ) (δ : TorchLean.Tensor ℝ τ) : Spec.dot (self.jvp x dx) δ = TensorPack.dotList dx (self.vjp x δ)
- validate : TorchLean.TensorPack ℝ Γ → Except String Unit
Runtime precondition metadata, preserved by the algebraic bridge; pure semantics ignore it.
Instances For
A tape/SSA graph: nodes are appended in topological order and may reference any previous value.
- nil {Γ : List Spec.Shape} : Graph Γ []
- snoc {Γ ss : List Spec.Shape} {τ : Spec.Shape} : Graph Γ ss → Node (Γ ++ ss) τ → Graph Γ (ss ++ [τ])
Instances For
Evaluate a tape/graph, returning the full context (inputs ++ intermediates).
Instances For
Evaluate the JVP (“forward-mode tangent”) of a graph, producing tangents for all values in the
extended context Γ ++ ss.
Instances For
Reverse-mode backpropagation on a tape/graph, returning gradients for the inputs Γ.
This is the proof model of what PyTorch calls “running backward” starting from an output seed cotangent and accumulating gradients at shared parents.
Instances For
Global tape soundness: if each node satisfies a local JVP/VJP adjointness law, then the global
reverse-mode accumulation algorithm (backpropCtx) is correct.
Informally: for any input perturbation dx and any output seed cotangent seed,
⟪JVP(g, x, dx), seed⟫ = ⟪dx, backprop(g, x, seed)⟫.
This is the formal analogue of PyTorch’s guarantee that backward() computes vector–Jacobian
products and accumulates them through a dynamic DAG/tape.