Tensor Algebra for VJP Proofs #
Vectors, matrices, and operations over ℝ, using Mathlib's Finset.sum.
Partial derivatives (pdiv) and their composition rules (chain rule,
linearity, product rule) are now defined and proved from Mathlib's
Fréchet derivative fderiv. The post-foundation-flip definition is
pdiv f x i j := fderiv ℝ f x (basisVec i) j
and every former axiom (pdiv_id, pdiv_const, pdiv_reindex,
pdiv_add, pdiv_comp, pdiv_mul) is now a theorem proved against
Mathlib's API. The bilinear rules carry Differentiable hypotheses
that propagate through every downstream chapter.
The post-flip path: every claim downstream of this file is either a
definition Lean unfolds or a theorem typechecked against Mathlib —
no project axioms remain. #print axioms vit_full_has_vjp lists only
Lean core (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
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.
A scalar function of ONE coordinate, lifted to Vec K → Vec 1, and its pdiv. pdiv_sigmoid's
proof at one coordinate and a general f; the shape every summand of a per-class loss has.
Finset-sum rule — derived from pdiv_add and pdiv_const by
induction on the Finset. Linearity of the derivative extended to
arbitrary finite sums. Requires each f s to be differentiable
at x.
Chain rule for VJPs — proved, no sorry. Requires f and g
to be differentiable everywhere.
Equations
- Proofs.vjp_comp 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
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.biPath_has_vjp 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.identity_has_vjp 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.
Identity pointwise VJP — trivial.
Equations
Instances For
Chain rule for pointwise VJPs. Same shape as vjp_comp, 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.vjp_comp_at f g x hf_diff hg_diff hf hg = { backward := fun (dy : Proofs.Vec p) => hf.backward (hg.backward dy), correct := ⋯ }
Instances For
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 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
Matrix partial derivative, defined in terms of pdiv on the
row-major flattened Vec form. No longer an axiom — the rank-2
structural rules (chain/sum/id) now 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 — now a theorem, 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.
Chain rule for matrix VJPs — proved, no sorry.
Direct transcription of vjp_comp to rank-2 indices.
Equations
- Proofs.vjpMat_comp 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.identityMat_has_vjp 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 (vit_body, 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
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) — theorem, derived from
pdiv_finset_sum + pdiv_mul + pdiv_const + pdiv_reindex.
Row-wise Jacobian decomposition — proved (planning/archive/VJP.md follow-up D).
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.
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 (Phase 8, 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 rowSoftmax_has_vjp_mat: any per-token
operation (LayerNorm, GELU, dense, activation) lifts to a per-sequence
matrix operation via this one helper.
Equations
- Proofs.rowwise_has_vjp_mat 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
Per-head / per-slab column independence #
Multi-head attention applies the same per-head function to each of heads
column slabs of width d_in from a Mat n (heads * d_in) input. The
column-slab analog of rowwise_has_vjp_mat factors that vmap-over-heads
structure: each head's output depends only on its own slab of the input,
so the matrix Jacobian is block-diagonal across the head axis.
Apply g : Mat n d_in → Mat n d_out to each of the heads column
slabs of width d_in in a Mat n (heads * d_in) input, producing
a Mat n (heads * d_out) output. Output column (h, j_out) is
column j_out of g (slab h M), where slab h M extracts the
d_in-wide column block at head index h.
Equations
- Proofs.colSlabApply g M r hj = g (fun (r' : Fin n) (j_in : Fin d_in) => M r' (finProdFinEquiv ((finProdFinEquiv.symm hj).1, j_in))) r (finProdFinEquiv.symm hj).2
Instances For
Column-slab independence Jacobian — column-axis analog of
pdivMat_rowIndep. For a slab-applied function colSlabApply g,
the Jacobian is block-diagonal across the heads axis: zero unless
the input slab h_j matches the output slab h_l, otherwise equal
to pdivMat g on that slab.
Requires Differentiable ℝ (flat g) for the same reason as
pdivMat_rowIndep: the Pi-valued flat form must be differentiable
everywhere so fderiv doesn't fall back to junk-default 0.
Lift HasVJPMat g to column-slab vmap — column-axis analog of
rowwise_has_vjp_mat. Given g : Mat n d_in → Mat n d_out with a
matrix VJP, applying g independently to each of heads-many column
slabs gives a HasVJPMat for colSlabApply g. The backward applies
g.backward per slab.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Ternary matrix VJP #
For ternary-input functions like SDPA (Q, K, V) ↦ out, package the
three per-input VJPs as a single structure analogous to HasVJPMat.
The backward returns the triple of per-input gradients; correctness
holds independently for each input (with the others fixed).
VJP structure for Mat × Mat × Mat → Mat functions where all
three inputs share the same shape Mat n d_in and the output is
Mat n d_out. Backward returns the triple of per-input gradients;
correct_{1,2,3} ensure each gradient matches the partial derivative
treating the other two inputs as constants.
Instances For
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.matmul_left_const_has_vjp 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.matmul_right_const_has_vjp 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.scalarScale_has_vjp 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.transpose_has_vjp = { 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. differentiable_pi
reduces to per-coordinate differentiability, which fun_prop
discharges via the eval/projection rule.
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)). Three nested differentiable_pi
peel the Fin c → Fin h → Fin w → ℝ pi structure; fun_prop
discharges the innermost projection.
3D partial derivative — now a definition via the triple-nested
flatten bijection, no longer an axiom. The four structural rules
(comp / add / id) follow as theorems. Operator-specific VJPs at
rank 3 (conv2d_has_vjp3, maxPool2_has_vjp3, depthwise_has_vjp3)
are bundled HasVJP3 defs in their respective files — those state
specific Jacobian values, not framework.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Chain rule for 3D partial derivatives — theorem, via pdiv_comp
and two applications of Fintype.sum_equiv + sum_prod_type. Requires
the flattened forms of f and g to be differentiable at the
relevant points.
VJP for 3D→3D functions.
Instances For
Chain rule for 3D VJPs — proved, no sorry. Requires the
flattened forms of f and g to be differentiable everywhere.
Equations
- Proofs.vjp3_comp f g hf_diff hg_diff hf hg = { backward := fun (x : Proofs.Tensor3 c₁ h₁ w₁) (dy : Proofs.Tensor3 c₃ h₃ w₃) => hf.backward x (hg.backward (f x) dy), correct := ⋯ }
Instances For
Tensor3 analogue of HasVJPAt: the same pdiv3-sum contract, but
only required at the chosen smooth point x. The natural home for
maxPool2_has_vjp_at3 and any other kinked Tensor3 operator.
Instances For
Trivial lift: a global HasVJP3 gives a HasVJPAt3 at any point.
Equations
- hf.toHasVJPAt3 x = { backward := fun (dy : Proofs.Tensor3 c₂ h₂ w₂) => hf.backward x dy, correct := ⋯ }
Instances For
Chain rule for pointwise Tensor3 VJPs. Tensor3 analogue of
vjp_comp_at. Requires DifferentiableAt only at the relevant
points (on the flattened forms), which is what vjp3_comp_at
consumers built from _at instances of kinked operators can
actually supply.
Equations
- Proofs.vjp3_comp_at f g x hf_diff hg_diff hf hg = { backward := fun (dy : Proofs.Tensor3 c₃ h₃ w₃) => hf.backward (hg.backward dy), correct := ⋯ }
Instances For
Bridge: HasVJP3 → HasVJP via the Tensor3.flatten bijection.
Rank-3 analogue of hasVJPMat_to_hasVJP. 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 two Fintype.sum_prod_type +
Fintype.sum_equiv finProdFinEquiv reindexes.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Bridge: HasVJPAt3 → HasVJPAt via the Tensor3.flatten bijection.
Smooth-point analogue of hasVJP3_to_hasVJP, 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.
Instances For
Equations
- Proofs.identity3_has_vjp c h w = { backward := fun (_x dy : Proofs.Tensor3 c h w) => dy, correct := ⋯ }
Instances For
Sum rule for Tensor3 partial derivatives — theorem, via pdiv_add.
Equations
- Proofs.biPath3 f g x c h w = f x c h w + g x c h w
Instances For
Equations
- One or more equations did not get rendered due to their size.