Documentation

LeanMlir.Proofs.Nets.ResNet.ResNet34BackCertifiedTie

§B: the r34 identity-block backward chain IS the certified VJP #

r34IdBlockBack (ResNetBackChains.lean) is the hand-composed reverse of the r34 identity block, written in the per-op backward maps of BackwardMaps.lean. This file closes the §B integrity question for that block: the chain IS the certified input-gradient VJP.

The key design choice that makes this b1-free (no batched↔non-batched batchMap reconciliation): r34IdBlockBack is the reverse of rblkPC — the per-channel-BN, non-batched identity block (ResNet34RenderPC). So the right certified target is a VJP of rblkPC in the same vocabulary, NOT the batched true-BN r34BasicBlockB_has_vjp_at (ResNet34BackB0). That certified object did not exist, so we build it here (rblkPC_has_vjp_at), then tie.

Three pieces:

  1. convFlatBack_eq_vjp_backward — the conv leaf tie: the backward map convFlatBack (reversed-kernel conv) IS the certified conv input-VJP, via the general odd-kernel IR.convBackDenote_eq_input_grad_formula.
  2. rblkPC_has_vjp_at — the certified per-channel-BN identity-block VJP, assembled from the per-op VJPs (convBnReluPC_has_vjp_at + bnPerChannelTensor3_has_vjp + residual_has_vjp_at), mirroring the scalar-BN resblock_has_vjp_at.
  3. r34IdBlockBack_eq_rblkPC_vjpthe tie: r34IdBlockBack with its abstract BN-backs pinned to the certified per-channel-BN backwards and its ReLU masks pinned to the actual pre-activation signs equals (rblkPC_has_vjp_at …).backward. Closes by rewriting the two conv leaves; everything else (residual fan-in, the -reversal, the relu masks, the pinned BN-backs) matches definitionally.

What the tie buys: the hand-composed chain — the spelling a reader can check against the emitted backward — IS the certified gradient, not merely a map that looks like one.

The downsample block is closed the same way (§ The DOWNSAMPLE block below): a strided-conv leaf tie (flatConvStride2Back_eq_vjp_backward = conv leaf + the decimateBack rfl), the certified strided block VJP rblkPStridedPC_has_vjp_at (mirrors resblockProj_has_vjp_at, with the residualProj two-branch fan-in), and the tie r34DownBlockBack_eq_rblkPStridedPC_vjp. So both r34 block types (identity + downsample) now target the certified gradient, b1-free.

The endpoint leaf ties (§ The ENDPOINT leaf ties below) close the rest of the per-op set: dense_transpose_eq_vjp_backward (the dense head, Wᵀ·dy = certified Mat.mulVec W), gapBack_eq_vjp_backward (GAP broadcast-÷, rfl), maxPoolFlatBack_eq_vjp_backward (the smooth-point arg-max scatter). With these + the conv/strided-conv leaves above, every per-op backward of the r34 whole-net r34InputGrad is now individually tied to its certified VJP.

⭐⭐ THE WHOLE-NET FOLD IS CLOSED (2026-09-03): r34InputGrad_eq_resnet34_vjp. The last section of this file assembles the per-op ties into r34InputGrad = (resnet34_has_vjp_at … ).backward at the full 3×224² dims — so the claim is no longer "every piece of the chain is the certified gradient" but "the chain IS the certified whole-net gradient".

⛔ The blocker recorded here previously — "resnet34_has_vjp_at is parametric / only concretely instantiated at toy resnet34Concrete dims" — was a misreading. That theorem is dimension- generic and parametric in its component maps, so instantiating it at ImageNet dims needs no new apex: it needs the components' HasVJPAt/DifferentiableAt witnesses, and every one already existed except the stem's (cbrStridedPC_has_vjp_at, added below — the strided peer of convBnReluPC_has_vjp_at). ⭐ The blocks stay OPAQUE, entering as the ChainData/PProd bundles resnet34_has_vjp_at already takes, so the whole-net isDefEq runs between variables.

⛔⛔ What the tie actually found was a DRIFT, and it moved the r34 backward number. r34InputGrad pooled with maxPoolFlatBack — the 2×2 pool's backward — while the committed forward resnet34Forward_full_pc pools with maxPool3s2Flat, He et al.'s 3×3/s2 stem pool. MaxPool3s2.lean's header warns the two share a TYPE and are different functions; nothing forced the two statements to unify until a theorem needed them to be about ONE net. The missing leaf is maxPool3s2FlatBack (BackwardMaps.lean — the ACCUMULATING scatter: 3×3/s2 windows overlap, so an input can be the argmax of up to four outputs, window 4A not A), and the whole-net number then stated on this chain moved 4× (that budget was deleted 2026-09-08). ⚠ This is imagenet_specs_drift_from_twins for the fourth time and ConvNeXt's stale head-LayerNorm slot for the second: "the same net as the tie" is an unchecked claim until something forces the two statements to unify, and what forces it is needing the tie.

⚠ Still supplied, and unchanged by this: the SMOOTHNESS side. The tie takes the pool's MaxPool3s2Smooth, the stem's post-BN no-zero, and the per-block ChainData bundles as hypotheses — a smooth-point statement, as every HasVJPAt in this cone is.

theorem Proofs.convFlatBack_eq_vjp_backward {ic oc h w kH kW : } (hkH : 2 * ((kH - 1) / 2) + 1 = kH) (hkW : 2 * ((kW - 1) / 2) + 1 = kW) (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Vec (ic * h * w)) :

Conv input-VJP leaf tie. The backward map convFlatBack W (= reversed-kernel forward conv) IS the certified conv input-VJP (flatConv_has_vjp W b).backward x (conv is linear, so the saved activation x is ignored), for odd kernels. Routes through the general IR.convBackDenote_eq_input_grad_formula; the leaf the §B block tie reuses (×2).

noncomputable def Proofs.rblkPC_has_vjp_at {c h w : } (W₁ : Kernel4 c c 3 3) (b₁ : Vec c) (ε₁ : ) (γ₁ β₁ : Vec c) (W₂ : Kernel4 c c 3 3) (b₂ : Vec c) (ε₂ : ) (γ₂ β₂ : Vec c) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (v : Vec (c * h * w)) (h_smooth₁ : ∀ (k : Fin (c * h * w)), bnPerChannelTensor3 c h w ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k 0) (h_smooth_out : ∀ (k : Fin (c * h * w)), ((bnPerChannelTensor3 c h w ε₂ γ₂ β₂ flatConv W₂ b₂) relu (c * h * w) bnPerChannelTensor3 c h w ε₁ γ₁ β₁ flatConv W₁ b₁) v k + v k 0) :
HasVJPAt (rblkPC W₁ b₁ ε₁ γ₁ β₁ W₂ b₂ ε₂ γ₂ β₂) v

Certified VJP of the per-channel-BN identity basic block rblkPC (non-batched). relu ∘ residual(F) with F = (bnPC₂∘conv₂) ∘ (relu∘bnPC₁∘conv₁). The same-vocabulary certified target for r34IdBlockBack — no batched/batchMap reconciliation. Mirrors resblock_has_vjp_at (scalar BN) with bnPerChannelTensor3 for bnForward, reusing convBnReluPC_has_vjp_at for stage 1.

Equations
  • One or more equations did not get rendered due to their size.
Instances For
    theorem Proofs.r34IdBlockBack_eq_rblkPC_vjp {c h w : } (W₁ : Kernel4 c c 3 3) (b₁ : Vec c) (ε₁ : ) (γ₁ β₁ : Vec c) (W₂ : Kernel4 c c 3 3) (b₂ : Vec c) (ε₂ : ) (γ₂ β₂ : Vec c) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (v : Vec (c * h * w)) (h_smooth₁ : ∀ (k : Fin (c * h * w)), bnPerChannelTensor3 c h w ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k 0) (h_smooth_out : ∀ (k : Fin (c * h * w)), ((bnPerChannelTensor3 c h w ε₂ γ₂ β₂ flatConv W₂ b₂) relu (c * h * w) bnPerChannelTensor3 c h w ε₁ γ₁ β₁ flatConv W₁ b₁) v k + v k 0) :
    (r34IdBlockBack W₁ W₂ ((bnPerChannelTensor3_has_vjp c h w ε₁ hε₁ γ₁ β₁).backward (flatConv W₁ b₁ v)) ((bnPerChannelTensor3_has_vjp c h w ε₂ hε₂ γ₂ β₂).backward (flatConv W₂ b₂ ((relu (c * h * w) bnPerChannelTensor3 c h w ε₁ γ₁ β₁ flatConv W₁ b₁) v))) (fun (i : Fin (c * h * w)) => residual ((bnPerChannelTensor3 c h w ε₂ γ₂ β₂ flatConv W₂ b₂) relu (c * h * w) bnPerChannelTensor3 c h w ε₁ γ₁ β₁ flatConv W₁ b₁) v i > 0) fun (i : Fin (c * h * w)) => bnPerChannelTensor3 c h w ε₁ γ₁ β₁ (flatConv W₁ b₁ v) i > 0) = (rblkPC_has_vjp_at W₁ b₁ ε₁ γ₁ β₁ W₂ b₂ ε₂ γ₂ β₂ hε₁ hε₂ v h_smooth₁ h_smooth_out).backward

    The §B identity-block tie: hand-composed backward = certified VJP. r34IdBlockBack, with its abstract BN-backs pinned to the certified per-channel-BN backwards (bnPerChannelTensor3_has_vjp.backward at the respective conv outputs) and its ReLU masks pinned to the actual pre-activation signs, equals (rblkPC_has_vjp_at …).backward.

    Both sides are fun dy ↦ bodyBack(mask dy) + mask dy (residual fan-in over the outer-relu mask). The bodies match because: the two conv leaves tie via convFlatBack_eq_vjp_backward (3×3 is odd), the BN-backs are pinned to the exact certified terms, and the inner ReLU mask is the mid pre-activation sign — so after rewriting the conv leaves everything is definitional. Closes under [propext, Classical.choice, Quot.sound].

    theorem Proofs.flatConvStride2Back_eq_vjp_backward {ic oc h w kH kW : } (hkH : 2 * ((kH - 1) / 2) + 1 = kH) (hkW : 2 * ((kW - 1) / 2) + 1 = kW) (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Vec (ic * (2 * h) * (2 * w))) :

    Strided conv input-VJP leaf tie. flatConvStride2Back W (= convFlatBack ∘ decimateBack) IS the certified strided conv input-VJP (flatConvStride2_has_vjp W b).backward x, for odd kernels. Decomposes into the conv leaf tie (convFlatBack_eq_vjp_backward) and the decimate leaf (decimateBack_eq_vjp, rfl), matching flatConvStride2 = decimateFlat ∘ flatConv.

    theorem Proofs.flatConvStride2XlaBack_eq_vjp_backward {ic oc h w kH kW : } (hkH : 2 * ((kH - 1) / 2) + 1 = kH) (hkW : 2 * ((kW - 1) / 2) + 1 = kW) (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Vec (ic * (2 * h) * (2 * w))) :

    XLA-SAME strided conv input-VJP leaf tie. flatConvStride2XlaBack W (= convFlatBack ∘ decimateOddBack) IS the certified (flatConvStride2Xla_has_vjp W b).backward x, for odd kernels: the conv leaf tie and the odd-scatter leaf (decimateOddBack_eq_vjp, rfl), matching flatConvStride2Xla = decimateOddFlat ∘ flatConv. The TF-origin stems' (B0, MobileNetV2) leaf. ⚠ This is the theorem that fixes the odd-phase backward's DIRECTION: the emitted transposed-conv pad [p+1, p-1] (opposite to the weight grads' [p-1, p+1]) denotes this map through depthwiseStridedXlaBack_faithful's conv peer, so a backward derived "by symmetry" with the weight grads cannot be tied here.

    noncomputable def Proofs.rblkPStridedPC_has_vjp_at {ic oc h w kHp kWp : } (W₁ : Kernel4 oc ic 3 3) (b₁ : Vec oc) (ε₁ : ) (γ₁ β₁ : Vec oc) (W₂ : Kernel4 oc oc 3 3) (b₂ : Vec oc) (ε₂ : ) (γ₂ β₂ : Vec oc) (Wp : Kernel4 oc ic kHp kWp) (bp : Vec oc) (εp : ) (γp βp : Vec oc) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (hεp : 0 < εp) (v : Vec (ic * (2 * h) * (2 * w))) (h_smooth₁ : ∀ (k : Fin (oc * h * w)), bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ (flatConvStride2 W₁ b₁ v) k 0) (h_smooth_out : ∀ (k : Fin (oc * h * w)), (bnPerChannelTensor3 oc h w εp γp βp flatConvStride2 Wp bp) v k + ((bnPerChannelTensor3 oc h w ε₂ γ₂ β₂ flatConv W₂ b₂) relu (oc * h * w) bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ flatConvStride2 W₁ b₁) v k 0) :
    HasVJPAt (rblkPStridedPC W₁ b₁ ε₁ γ₁ β₁ W₂ b₂ ε₂ γ₂ β₂ Wp bp εp γp βp) v

    Certified VJP of the per-channel-BN downsample block rblkPStridedPC (non-batched). relu ∘ residualProj(proj, F_s)proj = bnPC∘convStride2(Wp) (the kHp×kWp-stride-2 skip), F_s = (bnPC₂∘conv₂) ∘ (relu∘bnPC₁∘convStride2(W₁)) (first conv strided). The same-vocabulary certified target for r34DownBlockBack. Mirrors the scalar-BN resblockProj_has_vjp_at with bnPerChannelTensor3 + flatConvStride2; per-op VJPs assembled by vjp_comp_at.

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For
      theorem Proofs.r34DownBlockBack_eq_rblkPStridedPC_vjp {ic oc h w kHp kWp : } (hkHp : 2 * ((kHp - 1) / 2) + 1 = kHp) (hkWp : 2 * ((kWp - 1) / 2) + 1 = kWp) (W₁ : Kernel4 oc ic 3 3) (b₁ : Vec oc) (ε₁ : ) (γ₁ β₁ : Vec oc) (W₂ : Kernel4 oc oc 3 3) (b₂ : Vec oc) (ε₂ : ) (γ₂ β₂ : Vec oc) (Wp : Kernel4 oc ic kHp kWp) (bp : Vec oc) (εp : ) (γp βp : Vec oc) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (hεp : 0 < εp) (v : Vec (ic * (2 * h) * (2 * w))) (h_smooth₁ : ∀ (k : Fin (oc * h * w)), bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ (flatConvStride2 W₁ b₁ v) k 0) (h_smooth_out : ∀ (k : Fin (oc * h * w)), (bnPerChannelTensor3 oc h w εp γp βp flatConvStride2 Wp bp) v k + ((bnPerChannelTensor3 oc h w ε₂ γ₂ β₂ flatConv W₂ b₂) relu (oc * h * w) bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ flatConvStride2 W₁ b₁) v k 0) :
      (r34DownBlockBack W₁ W₂ Wp ((bnPerChannelTensor3_has_vjp oc h w ε₁ hε₁ γ₁ β₁).backward (flatConvStride2 W₁ b₁ v)) ((bnPerChannelTensor3_has_vjp oc h w ε₂ hε₂ γ₂ β₂).backward (flatConv W₂ b₂ ((relu (oc * h * w) bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ flatConvStride2 W₁ b₁) v))) ((bnPerChannelTensor3_has_vjp oc h w εp hεp γp βp).backward (flatConvStride2 Wp bp v)) (fun (i : Fin (oc * h * w)) => residualProj (bnPerChannelTensor3 oc h w εp γp βp flatConvStride2 Wp bp) ((bnPerChannelTensor3 oc h w ε₂ γ₂ β₂ flatConv W₂ b₂) relu (oc * h * w) bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ flatConvStride2 W₁ b₁) v i > 0) fun (i : Fin (oc * h * w)) => bnPerChannelTensor3 oc h w ε₁ γ₁ β₁ (flatConvStride2 W₁ b₁ v) i > 0) = (rblkPStridedPC_has_vjp_at W₁ b₁ ε₁ γ₁ β₁ W₂ b₂ ε₂ γ₂ β₂ Wp bp εp γp βp hε₁ hε₂ hεp v h_smooth₁ h_smooth_out).backward

      The §B downsample-block tie: hand-composed backward = certified VJP. r34DownBlockBack, with BN-backs pinned to the certified per-channel backwards and ReLU masks pinned to the pre- activation signs, equals (rblkPStridedPC_has_vjp_at …).backward. Both sides are fun dy ↦ projBack(mask dy) + bodyBack(mask dy) (the residualProj two-branch fan-in over the outer-relu mask). Closes by rewriting the two strided-conv leaves (flatConvStride2Back_eq_vjp_backward) and the one non-strided conv leaf (convFlatBack_eq_vjp_backward); the rest is definitional. Completes the r34 block set (identity + downsample). 3-axiom-clean.

      theorem Proofs.dense_transpose_eq_vjp_backward {m n : } (W : Mat m n) (b : Vec n) (x : Vec m) :

      Dense head input-VJP leaf tie. The chain's dense backward dense (Wᵀ) 0 (= Wᵀ·dy) IS the certified dense input-VJP (dense_has_vjp W b).backward x (= Mat.mulVec W dy), conv is linear so the activation x is ignored. One mul_comm per term.

      theorem Proofs.gapBack_eq_vjp_backward (c h w : ) (x : Vec (c * h * w)) :

      GAP input-VJP leaf tie. The backward map gapBack c h w (broadcast dy(channel)/(h·w)) IS the certified GAP input-VJP (globalAvgPoolFlat_has_vjp c h w).backward x — definitionally the same broadcast-÷ map (the VJP ignores its primal argument).

      theorem Proofs.maxPoolFlatBack_eq_vjp_backward {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) :

      Maxpool input-VJP leaf tie (smooth point). The backward map maxPoolFlatBack x (scatter dy to the arg-max cell, 0 elsewhere) IS the certified maxpool input-VJP (maxPoolFlat_has_vjp_at x h_smooth).backward at a smooth point (unique arg-max per window). Both denote if MaxPool2IsArgmax then dy(winRow,winCol) else 0 (IR.maxPoolBackDenote = maxPool2_has_vjp_at3.backward).

      noncomputable def Proofs.cbrStridedPC_has_vjp_at {ic oc h w kH kW : } (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε : ) (γ β : Vec oc) ( : 0 < ε) (v : Vec (ic * (2 * h) * (2 * w))) (h_smooth : ∀ (k : Fin (oc * h * w)), bnPerChannelTensor3 oc h w ε γ β (flatConvStride2 W b v) k 0) :
      HasVJPAt (cbrStridedPC W b ε γ β) v

      conv(stride-2) → per-channel-BN → relu VJP at a smooth point — the strided peer of convBnReluPC_has_vjp_at, i.e. the certified VJP of cbrStridedPC, which is r34's stem. Two vjp_comp_ats: the strided conv, the per-channel BN (differentiable everywhere at ε > 0), then the ReLU at its smooth point.

      Equations
      • One or more equations did not get rendered due to their size.
      Instances For
        theorem Proofs.cbrStridedPC_differentiableAt {ic oc h w kH kW : } (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε : ) (γ β : Vec oc) ( : 0 < ε) (v : Vec (ic * (2 * h) * (2 * w))) (h_smooth : ∀ (k : Fin (oc * h * w)), bnPerChannelTensor3 oc h w ε γ β (flatConvStride2 W b v) k 0) :

        cbrStridedPC is differentiable at a smooth point (the companion resnet34_has_vjp_at threads alongside every HasVJPAt).

        theorem Proofs.cbrStridedPCBack_eq_vjp_backward {ic oc h w kH kW : } (hkH : 2 * ((kH - 1) / 2) + 1 = kH) (hkW : 2 * ((kW - 1) / 2) + 1 = kW) (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε : ) (γ β : Vec oc) ( : 0 < ε) (v : Vec (ic * (2 * h) * (2 * w))) (h_smooth : ∀ (k : Fin (oc * h * w)), bnPerChannelTensor3 oc h w ε γ β (flatConvStride2 W b v) k 0) :
        (flatConvStride2Back W (bnPerChannelTensor3_has_vjp oc h w ε γ β).backward (flatConvStride2 W b v) reluMaskBack fun (i : Fin (oc * h * w)) => bnPerChannelTensor3 oc h w ε γ β (flatConvStride2 W b v) i > 0) = (cbrStridedPC_has_vjp_at W b ε γ β v h_smooth).backward

        The stem tie. r34InputGrad's stem slot — flatConvStride2Back Ws ∘ bnB ∘ reluMaskBack, with the BN-back pinned to the certified per-channel backward and the mask to the actual post-BN sign — IS (cbrStridedPC_has_vjp_at …).backward. Closes by rewriting the one strided conv leaf; the BN-back and the ReLU mask are definitionally the certified terms (relu_has_vjp_at's backward is fun dy i => if x i > 0 then dy i else 0, which is reluMaskBack (· > 0)).

        theorem Proofs.r34InputGrad_eq_resnet34_vjp (Ws : Kernel4 64 3 7 7) (bs : Vec 64) (ε : ) (γs βs : Vec 64) ( : 0 < ε) (Wd : Mat 512 10) (bd : Vec 10) (a2 a1 a0 : Vec (64 * 56 * 56)Vec (64 * 56 * 56)) (down2 : Vec (64 * 56 * 56)Vec (128 * 28 * 28)) (b2 b1 b0 : Vec (128 * 28 * 28)Vec (128 * 28 * 28)) (down3 : Vec (128 * 28 * 28)Vec (256 * 14 * 14)) (c4 c3 c2 c1 c0 : Vec (256 * 14 * 14)Vec (256 * 14 * 14)) (down4 : Vec (256 * 14 * 14)Vec (512 * 7 * 7)) (e1 e0 : Vec (512 * 7 * 7)Vec (512 * 7 * 7)) (x : Vec (3 * 224 * 224)) (hstem_smooth : ∀ (k : Fin (64 * 112 * 112)), bnPerChannelTensor3 64 112 112 ε γs βs (flatConvStride2 Ws bs x) k 0) (hmp_smooth : MaxPool3s2Smooth (Tensor3.unflatten (cbrStridedPC Ws bs ε γs βs x))) (hids1 : ChainData (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)) [a2, a1, a0]) (hdown2 : HasVJPAt down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x))) ×' DifferentiableAt down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))) (hids2 : ChainData (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))) [b2, b1, b0]) (hdown3 : HasVJPAt down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x))))) ×' DifferentiableAt down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))))) (hids3 : ChainData (down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))))) [c4, c3, c2, c1, c0]) (hdown4 : HasVJPAt down4 (chainComp [c4, c3, c2, c1, c0] (down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x))))))) ×' DifferentiableAt down4 (chainComp [c4, c3, c2, c1, c0] (down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))))))) (hids4 : ChainData (down4 (chainComp [c4, c3, c2, c1, c0] (down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))))))) [e1, e0]) :
        (r34InputGrad Ws Wd ((bnPerChannelTensor3_has_vjp 64 112 112 ε γs βs).backward (flatConvStride2 Ws bs x)) hids4.snd.fst.backward hids4.snd.snd.snd.fst.backward hdown4.fst.backward hids3.snd.fst.backward hids3.snd.snd.snd.fst.backward hids3.snd.snd.snd.snd.snd.fst.backward hids3.snd.snd.snd.snd.snd.snd.snd.fst.backward hids3.snd.snd.snd.snd.snd.snd.snd.snd.snd.fst.backward hdown3.fst.backward hids2.snd.fst.backward hids2.snd.snd.snd.fst.backward hids2.snd.snd.snd.snd.snd.fst.backward hdown2.fst.backward hids1.snd.fst.backward hids1.snd.snd.snd.fst.backward hids1.snd.snd.snd.snd.snd.fst.backward (Tensor3.unflatten (cbrStridedPC Ws bs ε γs βs x)) fun (i : Fin (64 * 112 * 112)) => bnPerChannelTensor3 64 112 112 ε γs βs (flatConvStride2 Ws bs x) i > 0) = (resnet34_has_vjp_at (cbrStridedPC Ws bs ε γs βs) (maxPool3s2Flat 64 56 56) [a2, a1, a0] down2 [b2, b1, b0] down3 [c4, c3, c2, c1, c0] down4 [e1, e0] (globalAvgPoolFlat 512 7 7) (dense Wd bd) x cbrStridedPC_has_vjp_at Ws bs ε γs βs x hstem_smooth, maxPool3s2Flat_has_vjp_at_vec (cbrStridedPC Ws bs ε γs βs x) hmp_smooth, hids1 hdown2 hids2 hdown3 hids3 hdown4 hids4 (globalAvgPoolFlat_has_vjp 512 7 7).toHasVJPAt (chainComp [e1, e0] (down4 (chainComp [c4, c3, c2, c1, c0] (down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x))))))))), (dense_has_vjp Wd bd).toHasVJPAt (globalAvgPoolFlat 512 7 7 (chainComp [e1, e0] (down4 (chainComp [c4, c3, c2, c1, c0] (down3 (chainComp [b2, b1, b0] (down2 (chainComp [a2, a1, a0] (maxPool3s2Flat 64 56 56 (cbrStridedPC Ws bs ε γs βs x)))))))))), ).backward

        The whole-net certified tie. r34InputGrad, with every one of its slots pinned to the certified per-op backward, IS (resnet34_has_vjp_at …).backward — the certified input-gradient VJP of dense ∘ GAP ∘ [3,4,6,3] ∘ maxPool3s2 ∘ stem at x.

        ⭐ This is the statement the per-op ties above could not make: with it the chain IS the certified whole-net gradient, where before it only every piece of it was. (The whole-net float budget once stated on this chain was deleted 2026-09-08 as vacuous; the tie stays.)

        ⛔⛔ And closing it is what found the drift. r34InputGrad used maxPoolFlatBack — the 2×2 pool's backward — while the committed forward resnet34Forward_full_pc pools with maxPool3s2Flat, He et al.'s 3×3/s2 stem pool. MaxPool3s2.lean warns that the two share a TYPE and are different functions; nothing forced them to unify until this theorem needed the two statements to be about one net. The fix is maxPool3s2FlatBack (BackwardMaps.lean, the accumulating scatter, window 4A not A), and the r34 backward number moved 2.188·10²⁴⁵ → 8.857·10²⁴⁵.

        ⭐ The blocks stay OPAQUE — they enter as the ChainData/PProd witnesses resnet34_has_vjp_at already takes, and r34InputGrad's block slots are pinned to their backwards — so the composition is checked between variables and costs nothing (§3.7(a)'s lesson, in the direction that works). Only the four concrete endpoints are rewritten: the dense head, GAP, the 3×3/s2 pool and the stem.

        theorem Proofs.resnet34Forward_full_pc_eq_chain (ε : ) (Ws : Kernel4 64 3 7 7) (bs γs βs : Vec 64) (a0W1 : Kernel4 64 64 3 3) (a0b1 a0g1 a0t1 : Vec 64) (a0W2 : Kernel4 64 64 3 3) (a0b2 a0g2 a0t2 : Vec 64) (a1W1 : Kernel4 64 64 3 3) (a1b1 a1g1 a1t1 : Vec 64) (a1W2 : Kernel4 64 64 3 3) (a1b2 a1g2 a1t2 : Vec 64) (a2W1 : Kernel4 64 64 3 3) (a2b1 a2g1 a2t1 : Vec 64) (a2W2 : Kernel4 64 64 3 3) (a2b2 a2g2 a2t2 : Vec 64) (d2W1 : Kernel4 128 64 3 3) (d2b1 d2g1 d2t1 : Vec 128) (d2W2 : Kernel4 128 128 3 3) (d2b2 d2g2 d2t2 : Vec 128) (d2Wp : Kernel4 128 64 1 1) (d2bp d2gp d2tp : Vec 128) (b0W1 : Kernel4 128 128 3 3) (b0b1 b0g1 b0t1 : Vec 128) (b0W2 : Kernel4 128 128 3 3) (b0b2 b0g2 b0t2 : Vec 128) (b1W1 : Kernel4 128 128 3 3) (b1b1 b1g1 b1t1 : Vec 128) (b1W2 : Kernel4 128 128 3 3) (b1b2 b1g2 b1t2 : Vec 128) (b2W1 : Kernel4 128 128 3 3) (b2b1 b2g1 b2t1 : Vec 128) (b2W2 : Kernel4 128 128 3 3) (b2b2 b2g2 b2t2 : Vec 128) (d3W1 : Kernel4 256 128 3 3) (d3b1 d3g1 d3t1 : Vec 256) (d3W2 : Kernel4 256 256 3 3) (d3b2 d3g2 d3t2 : Vec 256) (d3Wp : Kernel4 256 128 1 1) (d3bp d3gp d3tp : Vec 256) (c0W1 : Kernel4 256 256 3 3) (c0b1 c0g1 c0t1 : Vec 256) (c0W2 : Kernel4 256 256 3 3) (c0b2 c0g2 c0t2 : Vec 256) (c1W1 : Kernel4 256 256 3 3) (c1b1 c1g1 c1t1 : Vec 256) (c1W2 : Kernel4 256 256 3 3) (c1b2 c1g2 c1t2 : Vec 256) (c2W1 : Kernel4 256 256 3 3) (c2b1 c2g1 c2t1 : Vec 256) (c2W2 : Kernel4 256 256 3 3) (c2b2 c2g2 c2t2 : Vec 256) (c3W1 : Kernel4 256 256 3 3) (c3b1 c3g1 c3t1 : Vec 256) (c3W2 : Kernel4 256 256 3 3) (c3b2 c3g2 c3t2 : Vec 256) (c4W1 : Kernel4 256 256 3 3) (c4b1 c4g1 c4t1 : Vec 256) (c4W2 : Kernel4 256 256 3 3) (c4b2 c4g2 c4t2 : Vec 256) (d4W1 : Kernel4 512 256 3 3) (d4b1 d4g1 d4t1 : Vec 512) (d4W2 : Kernel4 512 512 3 3) (d4b2 d4g2 d4t2 : Vec 512) (d4Wp : Kernel4 512 256 1 1) (d4bp d4gp d4tp : Vec 512) (e0W1 : Kernel4 512 512 3 3) (e0b1 e0g1 e0t1 : Vec 512) (e0W2 : Kernel4 512 512 3 3) (e0b2 e0g2 e0t2 : Vec 512) (e1W1 : Kernel4 512 512 3 3) (e1b1 e1g1 e1t1 : Vec 512) (e1W2 : Kernel4 512 512 3 3) (e1b2 e1g2 e1t2 : Vec 512) (Wd : Mat 512 10) (bd : Vec 10) :
        resnet34Forward_full_pc ε Ws bs γs βs a0W1 a0b1 a0g1 a0t1 a0W2 a0b2 a0g2 a0t2 a1W1 a1b1 a1g1 a1t1 a1W2 a1b2 a1g2 a1t2 a2W1 a2b1 a2g1 a2t1 a2W2 a2b2 a2g2 a2t2 d2W1 d2b1 d2g1 d2t1 d2W2 d2b2 d2g2 d2t2 d2Wp d2bp d2gp d2tp b0W1 b0b1 b0g1 b0t1 b0W2 b0b2 b0g2 b0t2 b1W1 b1b1 b1g1 b1t1 b1W2 b1b2 b1g2 b1t2 b2W1 b2b1 b2g1 b2t1 b2W2 b2b2 b2g2 b2t2 d3W1 d3b1 d3g1 d3t1 d3W2 d3b2 d3g2 d3t2 d3Wp d3bp d3gp d3tp c0W1 c0b1 c0g1 c0t1 c0W2 c0b2 c0g2 c0t2 c1W1 c1b1 c1g1 c1t1 c1W2 c1b2 c1g2 c1t2 c2W1 c2b1 c2g1 c2t1 c2W2 c2b2 c2g2 c2t2 c3W1 c3b1 c3g1 c3t1 c3W2 c3b2 c3g2 c3t2 c4W1 c4b1 c4g1 c4t1 c4W2 c4b2 c4g2 c4t2 d4W1 d4b1 d4g1 d4t1 d4W2 d4b2 d4g2 d4t2 d4Wp d4bp d4gp d4tp e0W1 e0b1 e0g1 e0t1 e0W2 e0b2 e0g2 e0t2 e1W1 e1b1 e1g1 e1t1 e1W2 e1b2 e1g2 e1t2 Wd bd = dense Wd bd globalAvgPoolFlat 512 7 7 chainComp [idFwd ε e1W1 e1b1 e1g1 e1t1 e1W2 e1b2 e1g2 e1t2, idFwd ε e0W1 e0b1 e0g1 e0t1 e0W2 e0b2 e0g2 e0t2] downFwd ε d4W1 d4b1 d4g1 d4t1 d4W2 d4b2 d4g2 d4t2 d4Wp d4bp d4gp d4tp chainComp [idFwd ε c4W1 c4b1 c4g1 c4t1 c4W2 c4b2 c4g2 c4t2, idFwd ε c3W1 c3b1 c3g1 c3t1 c3W2 c3b2 c3g2 c3t2, idFwd ε c2W1 c2b1 c2g1 c2t1 c2W2 c2b2 c2g2 c2t2, idFwd ε c1W1 c1b1 c1g1 c1t1 c1W2 c1b2 c1g2 c1t2, idFwd ε c0W1 c0b1 c0g1 c0t1 c0W2 c0b2 c0g2 c0t2] downFwd ε d3W1 d3b1 d3g1 d3t1 d3W2 d3b2 d3g2 d3t2 d3Wp d3bp d3gp d3tp chainComp [idFwd ε b2W1 b2b1 b2g1 b2t1 b2W2 b2b2 b2g2 b2t2, idFwd ε b1W1 b1b1 b1g1 b1t1 b1W2 b1b2 b1g2 b1t2, idFwd ε b0W1 b0b1 b0g1 b0t1 b0W2 b0b2 b0g2 b0t2] downFwd ε d2W1 d2b1 d2g1 d2t1 d2W2 d2b2 d2g2 d2t2 d2Wp d2bp d2gp d2tp chainComp [idFwd ε a2W1 a2b1 a2g1 a2t1 a2W2 a2b2 a2g2 a2t2, idFwd ε a1W1 a1b1 a1g1 a1t1 a1W2 a1b2 a1g2 a1t2, idFwd ε a0W1 a0b1 a0g1 a0t1 a0W2 a0b2 a0g2 a0t2] maxPool3s2Flat 64 56 56 cbrStridedPC Ws bs ε γs βs

        ⭐⭐ THE SHAPE CHECK — the eleven-slot chain the whole-net tie is about IS the committed forward. resnet34Forward_full_pc, regrouped into exactly the eleven arguments resnet34_has_vjp_at takes: the stem cbrStridedPC, He et al.'s 3×3/s2 pool, the four chainComp stages [a2,a1,a0] / [b2,b1,b0] / [c4,c3,c2,c1,c0] / [e1,e0], the three downFwd downsamples, GAP and the dense head.

        This is the theorem that would have caught the 2×2 pool. r34InputGrad_eq_resnet34_vjp keeps its blocks OPAQUE — they enter as the ChainData/PProd witnesses, so the apex's subject is a chain of VARIABLES and nothing in that theorem says which net they are. The drift it eventually found (maxPoolFlatBack, the 2×2 pool's backward, against a forward that pools 3×3/s2) lived a month for exactly that reason: "the same net as the tie" was prose in a docstring. Here the pool appears on both sides of one statement the kernel checks.

        The MobileNetV2 peer is mobilenetv2Forward_full_pc_eq_chain (MobileNetV2WholeBackCertifiedTie.lean), which had this from the day it was written; the ConvNeXt peer is convNextForwardTCh_eq_chain (Nets/ConvNeXt/ConvNeXtFullT.lean). ⚠ Both of those are a bare rfl and this one CANNOT be: their apexes chain their blocks one slot each, where resnet34_has_vjp_at groups its [3,4,6,3] runs under chainComp, and a chainComp node has to be reduced away BEFORE the defeq — see chainComp₂_comp above.