§B: the ConvNeXt block-body backward chain IS the certified VJP #
cnxBlockBodyBack (ConvNeXtBackChains.lean) is the hand-composed reverse of the ConvNeXt block
body, written in the per-op backward maps of BackwardMaps.lean. This file closes §B for the net
the repo ships, whose LayerNorm is the CHANNEL LN chanLNTensor3: the chain IS the certified
input-gradient VJP, in the SAME non-batched vocabulary.
The block body is layerScale ∘ project ∘ GELU ∘ expand ∘ LN ∘ depthwise, whose certified VJP
backward applies the reverses in order LS.back → PR.back → GE.back → EX.back → LN.back → D.back.
cnxBlockBodyBack is the exact peer chain
depthwiseFlatBack ∘ lnB ∘ convFlatBack Wex ∘ geluB ∘ convFlatBack Wpr ∘ lsB. The tie pins the
layer-scale and GELU backs (lsB/geluB) to the certified backwards at the exact saved
activations, fills the LN slot with the concrete chanLNTensor3Back (tied by
chanLNTensor3Back_eq_chanLN_vjp), and ties the two 1×1 convs + the depthwise to their certified
input-VJPs via the leaf gates (convFlatBack_eq_vjp_backward, depthwiseFlatBack_eq_vjp_backward).
b1-free: the per-example body is the non-batched object the chain reverses, so there is no
batchMap reconciliation. 3-axiom-clean.
chanLNTensor3Back (ChannelLNBack.lean) is not an abstract slot — it is a concrete
five-factor chain, the row map conjugated by the forward's four layout permutations. So it owes a
tie of its own: that the chain IS chanLNTensor3_has_vjp's backward. That is what this section
proves.
The proof is piecewise, and every piece is already in the repo:
- the two re-associations collapse by
reassoc{Fwd,Back}_has_vjp_backward_eq(a permutation's scatter has exactly one surviving delta); - the transpose collapses by
rfl—transpose_has_vjp's backward isfun i j => dY j i, which throughhasVJPMat_to_hasVJPis the flat transpose back; - the row map is ViT's vector-LN, whose VJP is
(+β)(identity backward) afterlayerScale γ(diagBack γ) afterLN(1,0)— and the LN backward meets the concrete three-termbn_grad_inputthrough the canonical∑ pdivform, NOT byrfl(thebn_has_vjpwitness is built through arw [bnForward_eq_compose]cast — the trapbnBack_faithful_fndocuments).
The tie is β-free: the certified backward does not depend on the LN bias, and neither does
the chain — the +β translation's VJP is the identity, which is why chanLNTensor3Back never
took a β in the first place.
The concrete three-term BN/LN input gradient IS the certified VJP backward. bn_grad_input
is not rfl-equal to (bn_has_vjp …).backward — the witness is built through a
rw [bnForward_eq_compose] cast — but both reduce to the canonical ∑ pdiv form
(bn_input_grad_correct and .correct). The function-level peer of bnBack_faithful_fn.
The flat transpose's VJP backward is the flat transpose back — transpose_has_vjp's
backward is fun i j => dY j i, and hasVJPMat_to_hasVJP reads it at the row-major split, so
this is definitional. The permutation adjoint the channel-LN conjugation needs, alongside
reassoc{Fwd,Back}_has_vjp_backward_eq.
The vector-LN row backward is bn_grad_input after the γ scale. layerNormVec is
(+β) ∘ layerScale γ ∘ LN(1,0), so its VJP applies: the bias translation's identity backward,
then diagBack γ, then the LN input gradient at γ = 1. The +β drops out — this is where
the whole channel-LN backward story becomes β-free.
The rowwise vector-LN backward is rowLNVecFlatBack. rowLNVecFlat_has_vjp is the
rowwise_has_vjp_mat lift of the row VJP through hasVJPMat_to_hasVJP, and
rowLNVecFlatBack is perRowFlatPR of the row's closed form — the same per-row map at the
same row of the saved input, so this is the row lemma read at each (row, col).
THE §2n §B TIE: the channel-LN backward chain IS the certified VJP. chanLNTensor3Back —
the hand-composed reverse of chanLNTensor3 — equals (chanLNTensor3_has_vjp …).backward at
every saved input and cotangent, so the chain is the certified gradient.
Proof: the witness is a term-mode vjp_comp chain, so its backward unfolds to the nested
chain; rewrite its five factors (two reassoc collapses, two transposes by rfl, the row map
through bn_grad_input). 3-axiom-clean.
The §B channel-LN BODY tie: the block-body backward chain = the certified VJP, for the net
the repo ships. cnxBlockBodyBack with
its LayerNorm slot filled by the CONCRETE chanLNTensor3Back (at the saved post-depthwise
activation) and its
layer-scale / GELU slots pinned to the certified backwards equals
(cnxBodyWith_has_vjp (chanLNTensor3 …) …).backward.
Note what fills the LN slot: not a certified object but the concrete five-factor chain, which
has to go through chanLNTensor3Back_eq_chanLN_vjp to earn its place. The proof rewrites the
two 1×1 conv leaves and the depthwise leaf through their gates, rewrites the LN chain through
its tie, and the rest matches definitionally.
3-axiom-clean.
The §B channel-LN BLOCK tie (residual-wrapped). cnxBlockChW is residual of the body, so
the block backward chain is residual (cnxBlockBodyBack …) and equals
(cnxBlockChW_has_vjp …).backward — the additive skip's backward being dy. Immediate from the
body tie. With this, the channel-LN net's §B coverage matches the scalar net's: body, block, and
(new, because the LN slot is no longer abstract) the LayerNorm op itself.