Tensor algebra and the VJP framework — the root of the proof suite #
Vec n := Fin n → ℝ, Mat m n, Tensor3 c h w, and the backward-pass calculus over them,
grounded in Mathlib's Fréchet derivative:
pdiv f x i j := fderiv ℝ f x (basisVec i) j
Every structural rule is a theorem against Mathlib's API; the bilinear ones carry
Differentiable hypotheses that propagate through every downstream chapter.
Contents, in file order:
| section | what |
|---|---|
| Types, Matrix Operations | Vec, Mat, Mat.mulVec/mul/transpose/outer, basisVec |
| Differentiation | pdiv and its rules (pdiv_comp, pdiv_add, pdiv_mul, pdiv_reindex, …); the linear-map kits pdiv_of_affine / pdiv_of_linear, pdiv_elementwise, pdiv_finset_sum, pdiv_const_smul |
| VJP Framework, Pointwise VJP | HasVJP (global) and HasVJPAt (at a point), each @[ext] and a Subsingleton, canonical, backward_unique, vjpComp / vjpCompAt / vjpCompDiffAt and their _backward peels, biPath |
| flattening | Mat.flatten / unflatten with _apply and @[simp] round-trips, sum_finProdFinEquiv |
| Matrix-level | pdivMat, HasVJPMat, vjpMatComp, row-independence (pdivMat_rowIndep*, rowwiseHasVJPMat), and the matmul / scale / transpose Jacobians and VJPs. The multi-head column-slab kit and HasVJPMat3 live in Attention.lean |
| 3D tensors | Tensor3, pdiv3, HasVJP3 / HasVJPAt3 and HasVJP3.toHasVJP — nets compose at Vec through it |
Zero project axioms: everything closes under propext, Classical.choice, Quot.sound.
Equations
- Proofs.Mat.outer u v i j = u i * v j
Instances For
The reindex map y ↦ (k ↦ y (σ k)) packaged as a continuous linear
map. Used to discharge pdiv_reindex and to provide
DifferentiableAt evidence for reindex-shaped subexpressions.
Equations
- Proofs.reindexCLM σ = { toFun := fun (y : Proofs.Vec a) (k : Fin b) => y (σ k), map_add' := ⋯, map_smul' := ⋯, cont := ⋯ }
Instances For
A pdiv entry is the derivative of one output coordinate — pdiv f x i j is
fderiv f x (basisVec i) j, and at a differentiable point that is the i-th directional
derivative of y ↦ f y j.
Product rule for pdiv. Vec n is a normed algebra over ℝ
via Pi.normedAlgebra, so fderiv_mul applies directly to the
pointwise product f * g. Requires both factors to be
DifferentiableAt x.
Sum rule for pdiv. Requires both summands to be
DifferentiableAt x.
Chain rule for pdiv. Requires f differentiable at x and
g differentiable at f x.
Elementwise rule — a scalar function applied to every coordinate has a diagonal
Jacobian, the scalar derivative on the diagonal: each coordinate is φ ∘ proj k
(HasDerivAt.comp_hasFDerivAt), assembled by hasFDerivAt_pi. GELU, swish and sigmoid are
this with φ their scalar function.
A scalar function of ONE coordinate, lifted to Vec K → Vec 1, and its pdiv. The
elementwise rule at one coordinate and a general f; the shape every summand of a per-class
loss has.
Finset-sum rule — linearity of the derivative extended to
arbitrary finite sums (fderiv_fun_sum). Requires each f s to be
differentiable at x.
pdiv_finset_sum at a scalar loss: the gradient of a sum of scalar losses (lifted to
Vec 1) is the sum of their gradients. The form every summed-loss cotangent takes.
Affine rule — if f is additive and homogeneous it is a linear map on the
finite-dimensional Vec m, hence continuous (LinearMap.continuous_on_pi), and the Jacobian
of v ↦ f v + c at every point is f read on the basis vector. Every conv / depthwise /
dense / patch-embed / pooling / BN-affine Jacobian in the suite is this with c the bias:
two one-line linearity side goals instead of distributing pdiv through the sum.
A vector–Jacobian product for f: a backward map with the proof that at every x it
contracts f's Jacobian (pdiv) against the cotangent. correct is the whole content.
HasVJP.canonical inhabits it for every f (its backward is the contraction itself), so
a witness says something only when its backward is a formula proved equal to that
contraction.
Instances For
The canonical witness — the backward IS the pdiv contraction, so correct is rfl.
Exists for every f; a hand-written backward is tied to it by HasVJP.backward_unique.
Equations
- Proofs.HasVJP.canonical f = { backward := fun (x : Proofs.Vec m) (dy : Proofs.Vec n) (i : Fin m) => ∑ j : Fin n, Proofs.pdiv f x i j * dy j, correct := ⋯ }
Instances For
Transport a witness along an equality of maps. The backward is carried over verbatim, so
unlike h ▸ hf it is never hidden behind a cast (congr_backward is rfl).
Equations
- Proofs.HasVJP.congr h hf = { backward := hf.backward, correct := ⋯ }
Instances For
Two VJP witnesses for EQUAL maps have the same backward. Both .correct to the same
∑ pdiv f x i j * dy j. Going through .correct rather than hfg ▸ · avoids an
Eq.mpr-blocked backward when the witnesses have different types (a respelling of f).
Any two VJP witnesses for the same map have the same backward — the backward is a
property of f, not of how the witness was assembled. Lets a hand-written chain be tied to a
tactic-built witness without unfolding it.
f scales with its argument: f (s • v) = s • f v, spelled pointwise. The statement of
every cotangent-chain _smul lemma; an abbrev, so rw [h] and h s v see the equation.
Equations
- Proofs.IsHomog f = ∀ (s : ℝ) (v : Proofs.Vec a), (f fun (i : Fin a) => s * v i) = fun (i : Fin b) => s * f v i
Instances For
A VJP backward is linear in its cotangent — read off HasVJP.correct: it scales.
The VJP of a coordinate reindex y ↦ y ∘ σ (a gather, reindexCLM σ): the backward
scatters each output cotangent back to the input cell it was read from — pdiv_reindex's
indicator, contracted. The one witness behind every reindex-shaped layer: the BN layout
bridges (reassocFwd/Back, bnchwFwd/Back), the StridedConv decimations, and
reindexHasVJP's correct. (broadcastFlatHasVJP keeps its own witness: its backward
is spelled the way the IR's broadcastBack denotes, and the SE-gate graph ties match it by
rfl.)
Equations
- Proofs.reindexVJP σ = { backward := fun (_v : Proofs.Vec a) (dy : Proofs.Vec b) (idx : Fin a) => ∑ k : Fin b, (if idx = σ k then 1 else 0) * dy k, correct := ⋯ }
Instances For
Along a bijection the scatter is the inverse gather: when τ inverts σ, exactly one
delta survives, so reindexVJP σ's backward is the reindex along τ.
Additive fan-in — proved, no sorry. Requires f and g to be
differentiable everywhere.
Equations
- Proofs.biPath f g x i = f x i + g x i
Instances For
Equations
- Proofs.biPathHasVJP f g hf_diff hg_diff hf hg = { backward := fun (x : Proofs.Vec m) (dy : Proofs.Vec n) (i : Fin m) => hf.backward x dy i + hg.backward x dy i, correct := ⋯ }
Instances For
Multiplicative fan-in — proved, no sorry. Requires f and g
to be differentiable everywhere.
Equations
- Proofs.elemwiseProduct f g x i = f x i * g x i
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Identity VJP — proved, no sorry.
Equations
- Proofs.identityHasVJP n = { backward := fun (_x dy : Proofs.Vec n) => dy, correct := ⋯ }
Instances For
Why a separate HasVJPAt. The global HasVJP framework
delivers a single backward function that's correct at every input.
For non-smooth operators (relu, maxPool2, …) the only honest
correct witness is the canonical pdiv-derived sum, which gives a
trivially-rfl-true contract that doesn't pin down behavior at the
kinks. HasVJPAt f x carries the same contract but only at a chosen
smooth point x — exactly enough to discharge the chain rule under
DifferentiableAt and to plug in real per-operator Jacobian formulas
(pdiv_relu, pdiv3_maxPool2_smooth, …) instead of correct := rfl.
Smooth operators (dense, add, mul, softmax, batchNorm, …)
keep their global HasVJP instances; we trivially lift to HasVJPAt
at any point via HasVJP.toHasVJPAt when composing.
A map has at most one VJP witness at a point.
The canonical witness at a point: the backward is the pdiv contraction itself.
Equations
- Proofs.HasVJPAt.canonical f x = { backward := fun (dy : Proofs.Vec n) (i : Fin m) => ∑ j : Fin n, Proofs.pdiv f x i j * dy j, correct := ⋯ }
Instances For
Transport a pointwise witness along an equality of maps; the backward is carried over
verbatim (HasVJP.congr's pointwise peer).
Equations
- Proofs.HasVJPAt.congr h hf = { backward := hf.backward, correct := ⋯ }
Instances For
Two HasVJPAt witnesses for EQUAL maps at one point have the same backward — the pointwise
peer of HasVJP.backward_unique_of_eq, through .correct rather than a transport.
A pointwise VJP backward scales with its cotangent (HasVJP.backward_smul's peer).
Identity pointwise VJP — trivial.
Equations
Instances For
Chain rule for pointwise VJPs. Same shape as vjpComp, but
only requires DifferentiableAt at the relevant points (not
everywhere). The pointwise analogue is what lets us compose
through relu at smooth inputs.
Equations
- Proofs.vjpCompAt f g x hf_diff hg_diff hf hg = { backward := fun (dy : Proofs.Vec p) => hf.backward (hg.backward dy), correct := ⋯ }
Instances For
Chain rule for VJPs — vjpCompAt at every point. Requires f and g
to be differentiable everywhere.
Equations
- Proofs.vjpComp f g hf_diff hg_diff hf hg = { backward := fun (x : Proofs.Vec m) (dy : Proofs.Vec p) => hf.backward x (hg.backward (f x) dy), correct := ⋯ }
Instances For
vjpCompAt's backward: g's, then f's. Definitional; stated so a chain peels by rw.
Note: Not by simp only in a whole-net tie: simp uses an rfl lemma as a dsimp step and the
kernel re-derives the unfolding.
vjpComp's backward: g's at f x, then f's. Definitional, for rw.
A VJP witness at x together with differentiability there — one stage of a whole-net
chain. A PProd, so the DifferentiableAt Prop can ride along with the data.
Equations
- Proofs.HasVJPDiffAt f x = (Proofs.HasVJPAt f x ×' DifferentiableAt ℝ f x)
Instances For
Compose two HasVJPDiffAt stages. The fold step of every whole-net apex.
Equations
- Proofs.vjpCompDiffAt f g x hf hg = ⟨Proofs.vjpCompAt f g x ⋯ ⋯ hf.fst hg.fst, ⋯⟩
Instances For
One vjpCompDiffAt level's backward, unfolded: the composite runs g's backward, then
f's. Definitional, stated so that a chain of levels peels by rw rather than by a
rfl that has to find the same unfolding through concrete witnesses. Note: Not by
simp only: simp would use it as a dsimp step and record nothing for the kernel to replay.
Mat m n and Vec (m * n) are in bijection by row-major flattening.
This bijection lets us define pdivMat in terms of pdiv rather
than introducing parallel axioms, and so derive the rank-2 chain,
sum, and identity rules as theorems. The 5 local Jacobian theorems
(matmul, scalarScale, transpose, rowIndep) are likewise derived from
foundation rules — they state genuine calculus facts about specific
operations, not structural framework.
Row-major reindexing of a flat sum — a sum over Fin (m * n) is the double sum over
(i, j) read through finProdFinEquiv. Mathlib has the Fin m × Fin n form
(Fintype.sum_prod_type) but no Fin (m * n) one; every flatten/unflatten sum in the suite
reduces to this split.
The rank-3 case: a Fin (a·b·c) sum is the row-major triple sum — the layout Tensor3.flatten
and every c·h·w activation index use.
Row-major flatten: Mat m n → Vec (m * n). Uses Mathlib's
finProdFinEquiv : Fin m × Fin n ≃ Fin (m * n).
Equations
- A.flatten k = A (finProdFinEquiv.symm k).1 (finProdFinEquiv.symm k).2
Instances For
Row-major unflatten: Vec (m * n) → Mat m n.
Equations
- Proofs.Mat.unflatten v i j = v (finProdFinEquiv (i, j))
Instances For
Mat.flatten is a coordinate reindexing, hence differentiable.
Mat.unflatten is a coordinate reindexing, hence differentiable.
Mat.transpose is a coordinate reindexing, hence differentiable.
Right-multiplying by a constant matrix is differentiable.
Left-multiplying by a constant matrix is differentiable.
Matrix partial derivative, defined in terms of pdiv on the
row-major flattened Vec form. The rank-2 structural rules
(chain/sum/id) follow as theorems.
Equations
- Proofs.pdivMat f A i j k l = Proofs.pdiv (fun (v : Proofs.Vec (a * b)) => (f (Proofs.Mat.unflatten v)).flatten) A.flatten (finProdFinEquiv (i, j)) (finProdFinEquiv (k, l))
Instances For
Chain rule for pdivMat — derived from pdiv_comp
via the row-major flatten bijection.
Sum rule for pdivMat — theorem, via pdiv_add. Requires both
flattened summands to be differentiable at flatten A.
A matrix map has at most one VJP witness.
Two HasVJPMat witnesses for EQUAL maps have the same backward — the matrix peer of
HasVJP.backward_unique_of_eq, through .correct rather than a transport.
Chain rule for matrix VJPs — proved, no sorry.
Direct transcription of vjpComp to rank-2 indices.
Equations
- Proofs.vjpMatComp F G hF_diff hG_diff hF hG = { backward := fun (A : Proofs.Mat a b) (dY : Proofs.Mat e f) => hF.backward A (hG.backward (F A) dY), correct := ⋯ }
Instances For
Additive fan-in for matrices — proved, no sorry.
Equations
- Proofs.biPathMat F G M r s = F M r s + G M r s
Instances For
Equations
- One or more equations did not get rendered due to their size.
Instances For
Identity VJP for matrices — proved, no sorry.
Equations
- Proofs.identityMatHasVJP a b = { backward := fun (_A dY : Proofs.Mat a b) => dY, correct := ⋯ }
Instances For
Bridge: HasVJPMat → HasVJP via the Mat.flatten bijection.
Given a matrix-level VJP for f : Mat a b → Mat c d, produce a
vector-level VJP for the flattened version
fun v : Vec (a*b) => Mat.flatten (f (Mat.unflatten v)). The backward
reshapes the input/output flat vectors to matrices, applies the
matrix backward, and flattens the result.
Lets us compose HasVJPMat pieces (vitBody, transformer blocks)
with rank-crossing pieces (patch embed, classifier head) that live
natively as Vec → Vec by first bridging everything to HasVJP.
Equations
- One or more equations did not get rendered due to their size.
Instances For
HasVJPMat.toHasVJP's backward: decode the flat index, run the matrix backward.
The three theorems here are local Jacobians for the operations that appear in scaled dot-product attention's backward pass:
pdivMat_matmul_left_const— right-factor varies, left factor fixed:∂(C · B')_{kl} / ∂B'_{ij} = C_{ki} · [l = j].pdivMat_matmul_right_const— left factor varies, right factor fixed:∂(A' · D)_{kl} / ∂A'_{ij} = D_{jl} · [i = k].pdivMat_rowIndep— functions that act row-wise have block-diagonal Jacobians, with the per-row block equal to the vector Jacobian of the row functiong.
Each is a direct transcription of an elementary calculus fact. They are
numerically gradient-checked in check_jacobians.py.
Matmul Jacobian (left-const) — B' ↦ C·B' is linear, so by pdiv_of_linear the entry
is C·E_{ij} read at (k, l): C k i when l = j.
Block-diagonal Jacobian of a per-row family, at a point. Applying g r to each row r
keeps the matrix Jacobian block-diagonal across rows: the (i, j, k, l) entry is
pdiv (g k) (A k) j l when i = k and 0 otherwise. Each g r need only be differentiable
at its own row A r: the flat map's coordinate (r, l) is g r's coordinate l after the
row projection, and hasFDerivAt_pi assembles the rows' derivatives.
Row-wise Jacobian decomposition.
For a row-independent function M ↦ (r ↦ g (M r)), the (i,j,k,l)
Jacobian entry is pdiv g (A i) j l when i = k and 0 otherwise:
pdivMat_rowIndep_perRow_at with one g for every row.
Requires Differentiable ℝ g: without it, the flattened Pi-valued
function may be non-differentiable at Mat.flatten A (per
differentiable_pi's coordinate-wise condition), making fderiv = 0
junk and breaking the per-row decomposition.
Row-wise lifting of a HasVJP (Tensor-level).
Given any g : Vec n → Vec p with a proved HasVJP, applying g
independently to each row of a matrix A : Mat m n gives a
HasVJPMat on Mat m n → Mat m p. The backward is just g.backward
applied per row. Generalizes rowSoftmaxHasVJPMat: any per-token
operation (LayerNorm, GELU, dense, activation) lifts to a per-sequence
matrix operation via this one helper.
Equations
- Proofs.rowwiseHasVJPMat hg hg_diff = { backward := fun (A : Proofs.Mat m n) (dY : Proofs.Mat m p) (r : Fin m) (c : Fin n) => hg.backward (A r) (dY r) c, correct := ⋯ }
Instances For
rowwiseHasVJPMat's backward: the row map's backward, row by row.
Scalar-scale Jacobian — theorem, derived from pdiv_const_smul +
pdiv_id via the flatten bijection.
∂(s · A')_{kl} / ∂A'_{ij} = s · δ_{ik,jl}.
Transpose Jacobian — theorem, derived from pdiv_reindex via
the flatten bijection. ∂A^T_{kl} / ∂A_{ij} = δ_{l=i, k=j}.
Matmul with right factor varying, left factor fixed — proved.
f : Mat p q → Mat m q, f B' = C · B'.
Backward: dB' = C^T · dY.
Equations
- Proofs.matmulLeftConstHasVJP C = { backward := fun (_B : Proofs.Mat p q) (dY : Proofs.Mat m q) (i : Fin p) (j : Fin q) => ∑ k : Fin m, C k i * dY k j, correct := ⋯ }
Instances For
Matmul with left factor varying, right factor fixed — proved.
f : Mat m p → Mat m q, f A' = A' · D.
Backward: dA' = dY · D^T.
Equations
- Proofs.matmulRightConstHasVJP D = { backward := fun (_A : Proofs.Mat m p) (dY : Proofs.Mat m q) (i : Fin m) (j : Fin p) => ∑ l : Fin q, dY i l * D j l, correct := ⋯ }
Instances For
Scalar-scale VJP — proved. Backward: dA = s · dY.
Equations
- Proofs.scalarScaleHasVJP s = { backward := fun (_A dY : Proofs.Mat m n) (i : Fin m) (j : Fin n) => s * dY i j, correct := ⋯ }
Instances For
Transpose VJP — proved. Backward: dA = (dY)^T.
Equations
- Proofs.transposeHasVJP = { backward := fun (_A : Proofs.Mat m n) (dY : Proofs.Mat n m) (i : Fin m) (j : Fin n) => dY j i, correct := ⋯ }
Instances For
A 3D feature map: channels × height × width (single sample).
Instances For
Row-major flatten: Tensor3 c h w → Vec (c * h * w). Two nested
finProdFinEquiv calls: first bundle (ci, hi) into Fin (c*h),
then bundle with wi into Fin ((c*h)*w) = Fin (c*h*w).
Equations
- T.flatten k = T (finProdFinEquiv.symm (finProdFinEquiv.symm k).1).1 (finProdFinEquiv.symm (finProdFinEquiv.symm k).1).2 (finProdFinEquiv.symm k).2
Instances For
Row-major unflatten: inverse of flatten.
Equations
- Proofs.Tensor3.unflatten v ci hi wi = v (finProdFinEquiv (finProdFinEquiv (ci, hi), wi))
Instances For
Tensor3.flatten is differentiable. It is a coordinate
reindexing: each output coordinate flatten x k is the single input
coordinate x (decode k), hence a projection.
Tensor3.unflatten is differentiable. The inverse reindexing:
each output coordinate unflatten v ci hi wi is the single input
coordinate v (encode (ci,hi,wi)), hence a projection.
3D partial derivative — pdiv of the flattened map, both indices read through the
triple-nested flatten bijection. Operator-specific VJPs at rank 3 (conv2dHasVJP3,
maxPool2HasVJP3, depthwiseHasVJP3) are bundled HasVJP3 defs in their own files;
nets compose them at Vec, through HasVJP3.toHasVJP / HasVJPAt3.toHasVJPAt.
Equations
- One or more equations did not get rendered due to their size.
Instances For
VJP for 3D→3D functions.
Instances For
A rank-3 map has at most one VJP witness.
A rank-3 VJP backward scales with its cotangent (HasVJP.backward_smul's peer).
Tensor3 analogue of HasVJPAt: the same pdiv3-sum contract, but
only required at the chosen smooth point x. The natural home for
maxPool2HasVJPAt3 and any other kinked Tensor3 operator.
Instances For
A rank-3 map has at most one VJP witness at a point.
The canonical rank-3 witness at a point: the backward is the pdiv3 contraction itself.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Bridge: HasVJP3 → HasVJP via the Tensor3.flatten bijection.
Rank-3 analogue of HasVJPMat.toHasVJP. Given a Tensor3-level VJP
for f : Tensor3 c₁ h₁ w₁ → Tensor3 c₂ h₂ w₂, produce a vector-level
VJP for the flattened fun v => Tensor3.flatten (f (Tensor3.unflatten v)).
The backward decodes the flat index in two finProdFinEquiv.symm
levels (matching pdiv3's row-major encode), applies the Tensor3
backward, and the closing collapse folds the triple co/ho/wo sum
back to the single flat sum via sum_finProdFinEquiv twice.
Equations
- One or more equations did not get rendered due to their size.
Instances For
HasVJP3.toHasVJP's backward: decode the flat index, run the rank-3 backward.
A rank-3 backward, flattened, is the flat pdiv contraction — HasVJP3.toHasVJP's
correct, read at a tensor activation.
Bridge: HasVJPAt3 → HasVJPAt via the Tensor3.flatten bijection.
Smooth-point analogue of HasVJP3.toHasVJP, with x fixed. Needed
for kinked operators (e.g. maxPool2) that only carry HasVJPAt3.
Same two-level index decode and triple→flat reindex collapse.
Equations
- One or more equations did not get rendered due to their size.