Documentation

LeanMlir.Proofs.Architectures.CNN

CNN VJP Proofs #

VJP correctness for the convolutional and pooling layers used in historical/mlir_poc/hand_cnn_train_step.mlir. The architecture there is:

x(1,28,28) → Conv(1→32) → ReLU → Conv(32→32) → ReLU → MaxPool
          → Flatten → Dense(6272→512) → ReLU → Dense(512→512)
          → ReLU → Dense(512→10) → logits

The dense and ReLU layers are inherited from MLP.lean. This file adds the new operations: conv2d, max-pool, and flatten.

The big idea: conv backward IS conv #

The most pedagogically valuable result is that the VJP of a convolution is itself expressible as convolutions — with appropriate kernel reversal and axis transposition. This is why conv layers train efficiently: there's no special backward operator. The same primitive runs in both directions.

Two specific tricks appear in the MLIR:

  1. Input-gradient via reversed kernel: dx = conv(dy, reverse(Wᵀ))
  2. Weight-gradient via the transpose trick: dW = conv(xᵀ, dyᵀ), where the spatial dims of the gradient become the "kernel".

We bundle the VJP formulas as HasVJP3 / HasVJP defs whose .correct fields are proved (the proofs are standard matrix calculus on cross-correlations), and the commentary explains why each formula has the form it does.

@[reducible, inline]
abbrev Proofs.Kernel4 (oc ic kh kw : ℕ) :

A conv kernel: out_channels × in_channels × kH × kW. This is the OIHW layout used by StableHLO and IREE.

Equations
Instances For

    Kernel4 oc ic kH kW and Vec (oc * ic * kH * kW) are in bijection by row-major flattening — mirrors Mat.flatten / Tensor3.flatten. We need this so that the weight-gradient VJP can be stated as a plain HasVJP (Vec → Vec) on the flattened kernel, reusing the existing framework instead of introducing a parallel 4D machinery.

    Nat multiplication associates left, so `oc * ic * kH * kW` parses as
    `((oc * ic) * kH) * kW` — three nested `finProdFinEquiv` calls. 
    
    noncomputable def Proofs.Kernel4.flatten {oc ic kH kW : ℕ} (W : Kernel4 oc ic kH kW) :
    Vec (oc * ic * kH * kW)

    Row-major flatten: Kernel4 oc ic kH kW → Vec (oc * ic * kH * kW).

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For
      noncomputable def Proofs.Kernel4.unflatten {oc ic kH kW : ℕ} (v : Vec (oc * ic * kH * kW)) :
      Kernel4 oc ic kH kW

      Row-major unflatten: inverse of flatten.

      Equations
      Instances For
        theorem Proofs.Kernel4.unflatten_flatten {oc ic kH kW : ℕ} (W : Kernel4 oc ic kH kW) :
        theorem Proofs.Kernel4.flatten_unflatten {oc ic kH kW : ℕ} (v : Vec (oc * ic * kH * kW)) :
        noncomputable def Proofs.conv2d {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Tensor3 ic h w) :
        Tensor3 oc h w

        Conv2d forward (SAME padding, stride 1).

        y[o, h, w] = (Σ_{c, kh, kw} x[c, h+kh−p, w+kw−p] · W[o, c, kh, kw]) + b[o]

        where p = (kH−1)/2 is the padding offset and out-of-bounds reads return 0 (zero padding). The output spatial size equals the input.

        Note: this is technically cross-correlation, not convolution in the strict signal-processing sense. ML literature uses "convolution" loosely; the difference (kernel flipping) only matters when comparing against classical signal-processing references.

        MLIR (hand_cnn_train_step.mlir): %cv0 = "stablehlo.convolution"(%x, %W0) { padding = dense<[[1, 1], [1, 1]]>, ... } %h0pre = stablehlo.add %cv0, broadcast(%b0)

        Equations
        • One or more equations did not get rendered due to their size.
        Instances For
          theorem Proofs.differentiable_dite_zero {E : Type u_1} [NormedAddCommGroup E] [NormedSpace ℝ E] (P : Prop) [Decidable P] (f : P → E → ℝ) (hf : ∀ (hP : P), Differentiable ℝ (f hP)) :
          Differentiable ℝ fun (x : E) => if hP : P then f hP x else 0

          A pad-guarded read is differentiable. The guard P does not depend on x, so if hP : P then f hP x else 0 is one branch or the constant 0. Tagged for fun_prop, which has no rule for a dependent if; every conv / depthwise pad-eval goes through it.

          theorem Proofs.conv2d_differentiable {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) :

          Conv2d is differentiable everywhere. Each output coordinate conv2d W b x o hi wi is the affine map b o + ∑_{c,kh,kw} W o c kh kw · (pad-eval x): a constant bias plus a finite ℝ-linear combination of pad-guarded input reads (differentiable_dite_zero).

          theorem Proofs.differentiableAt_pad_eval {n : ℕ} (P : Prop) [Decidable P] (σ : P → Fin n) (v : Vec n) :
          DifferentiableAt ℝ (fun (y : Vec n) => if h : P then y (σ h) else 0) v

          Differentiability of an if hpad : P then v(σ hpad) else 0 term — the pointwise, Vec-indexed form of differentiable_dite_zero.

          noncomputable def Proofs.conv2dInputGradFormula {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (dy : Tensor3 oc h w) :
          Tensor3 ic h w

          Closed-form input gradient for conv2d — direct formula, written as a sum over output positions (co, ho, wo) with reconstructed kernel offsets kh_nat = hi + pH − ho, kw_nat = wi + pW − wo. The body is nonzero only when the reconstructed (kh_nat, kw_nat) lies in [0, kH) × [0, kW) — i.e., when the input position (hi, wi) is actually reachable from output (ho, wo) via some valid kernel offset. Equivalent (under the (ho, wo) ↔ (kh, kw) partial bijection ho = hi+pH-kh) to the MLIR-aligned "reversed-kernel" formula dx[c, h, w] = Σ_{o, kh, kw} W[o, c, kH−1−kh, kW−1−kw] · dy[o, h+kh−p, w+kw−p].

          Equations
          • One or more equations did not get rendered due to their size.
          Instances For
            theorem Proofs.sum_fin_ite_add_eq {K : ℕ} (f : Fin K → ℝ) (a b : ℕ) :
            (∑ k : Fin K, if ↑k + a = b then f k else 0) = if h : a ≤ b ∧ b - a < K then f ⟨b - a, ⋯⟩ else 0
            theorem Proofs.padTap_indicator {C h w kH kW : ℕ} (c ci : Fin C) (hi ho : Fin h) (wi wo : Fin w) (kh : Fin kH) (kw : Fin kW) :
            (if hpad : (kH - 1) / 2 ≤ ↑kh + ↑ho ∧ ↑kh + ↑ho - (kH - 1) / 2 < h ∧ (kW - 1) / 2 ≤ ↑kw + ↑wo ∧ ↑kw + ↑wo - (kW - 1) / 2 < w then if finProdFinEquiv (finProdFinEquiv (ci, hi), wi) = finProdFinEquiv (finProdFinEquiv (c, ⟨↑kh + ↑ho - (kH - 1) / 2, ⋯⟩), ⟨↑kw + ↑wo - (kW - 1) / 2, ⋯⟩) then 1 else 0 else 0) = if c = ci ∧ ↑kh + ↑ho = ↑hi + (kH - 1) / 2 ∧ ↑kw + ↑wo = ↑wi + (kW - 1) / 2 then 1 else 0

            The padded-tap indicator of a flat input index (ci, hi, wi): output (ho, wo) reads it through tap (kh, kw) of channel c exactly when the channel matches and the tap offsets land on it — the padding condition is then automatic.

            noncomputable def Proofs.conv2dHasVJP3 {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) :

            Conv2d input-VJP — proved from foundation rules.

            The function v ↦ flatten (conv2d W b (unflatten v)) is affine in v: the bias-free conv (linear in v) plus the broadcast bias, so pdiv_of_affine reads each per-(idx_in, idx_out) entry off the bias-free conv of the basis vector — a sum over (c, kh, kw) of W o(idx_out) c kh kw times the pad-guarded Kronecker. Reindex Fin (oc*h*w) ↔ Fin oc × Fin h × Fin w on the sum-over-idx_out; padTap_indicator turns each Kronecker into "c = ci and the tap lands on (hi, wi)", and sum_fin_ite_add_eq collapses (c, kh, kw) to the closed-form input gradient conv2dInputGradFormula.

            The backward function (accessed as (conv2dHasVJP3 W b).backward, or via the conv2dInputGrad abbrev below) implements conv2dInputGradFormula W dy ci hi wi — a direct sum over (co, kh, kw) of W co ci kh kw * dy co ho_nat wo_nat for valid (ho_nat, wo_nat). Equivalent (under kh ↔ kH−1−kh) to the MLIR-aligned reversed-kernel formula dx[c, h, w] = Σ_{o, kh, kw} W[o, c, kH−1−kh, kW−1−kw] · dy[o, h+kh−p, w+kw−p].

            MLIR emits the reversed-kernel form directly: %W1_t = stablehlo.transpose %W1, dims = [1, 0, 2, 3] -- swap oc↔ic %W1_rev = stablehlo.reverse %W1_t, dims = [2, 3] -- flip spatial %d_h0 = "stablehlo.convolution"(%d_h1pre, %W1_rev) ...

            Equations
            Instances For
              @[reducible, inline]
              noncomputable abbrev Proofs.conv2dInputGrad {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Tensor3 ic h w) (dy : Tensor3 oc h w) :
              Tensor3 ic h w

              Named accessor for the conv2d input backward — aligns with MLIR codegen (stablehlo.convolution in the backward pass).

              Equations
              Instances For
                theorem Proofs.conv2dHasVJP3_correct {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Tensor3 ic h w) (dy : Tensor3 oc h w) (ci : Fin ic) (hi : Fin h) (wi : Fin w) :
                (conv2dHasVJP3 W b).backward x dy ci hi wi = ∑ co : Fin oc, ∑ ho : Fin h, ∑ wo : Fin w, pdiv3 (conv2d W b) x ci hi wi co ho wo * dy co ho wo

                Uniform VJP-correctness wrapper for conv2d — a citable _correct matching the convention of every other layer (just unfolds the HasVJP3.correct field of conv2dHasVJP3).

                noncomputable def Proofs.flatConv {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) :
                Vec (ic * h * w) → Vec (oc * h * w)

                Flat conv — conv2d bridged into flattened Vec → Vec space: flatConv W b = flatten ∘ conv2d W b ∘ unflatten. Spatial dims are preserved (ic h w → oc h w), so this is Vec (ic*h*w) → Vec (oc*h*w). This is the form the ResNet/CNN VJP composition actually uses (everything lives in flat Vec space).

                Equations
                Instances For
                  theorem Proofs.flatConv_differentiable {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) :

                  flatConv is differentiable everywhere. Composition of the three differentiable maps unflatten, conv2d, flatten. This is the differentiability witness vjpCompAt needs to chain conv into the block.

                  theorem Proofs.flatConv_continuous {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) :
                  theorem Proofs.flatConv_eq_zero {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (hW : ∀ (o : Fin oc) (c : Fin ic) (kh : Fin kH) (kw : Fin kW), W o c kh kw = 0) (hb : ∀ (o : Fin oc), b o = 0) (v : Vec (ic * h * w)) :
                  flatConv W b v = fun (x : Fin (oc * h * w)) => 0

                  A conv with everywhere-zero kernel and bias maps anything to 0.

                  noncomputable def Proofs.stageHasVJPAt {a b : ℕ} (lin : Vec a → Vec b) (norm act : Vec b → Vec b) (v : Vec a) (hlin : Differentiable ℝ lin) (hlinV : HasVJP lin) (hnorm : Differentiable ℝ norm) (hnormV : HasVJP norm) (hact : DifferentiableAt ℝ act (norm (lin v))) (hactV : HasVJPAt act (norm (lin v))) :
                  HasVJPAt (act ∘ norm ∘ lin) v

                  An act ∘ norm ∘ lin stage's VJP at a point — a conv (or depthwise, or batched) op, a normalisation, then an activation. lin and norm are differentiable everywhere, so their global VJPs lift through .toHasVJPAt; act needs a derivative and a VJP only at the stage's pre-activation norm (lin v), which is what relu and relu6 have off their kinks. Two vjpCompAts: norm ∘ lin, then act. Every conv-bn-act stage VJP in the repo is this at a particular lin, norm and act.

                  Equations
                  • One or more equations did not get rendered due to their size.
                  Instances For
                    noncomputable def Proofs.convBnReluHasVJPAt {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε γ β : ℝ) (hε : 0 < ε) (v : Vec (ic * h * w)) (h_smooth : ∀ (k : Fin (oc * h * w)), bnForward (oc * h * w) ε γ β (flatConv W b v) k ≠ 0) :
                    HasVJPAt (relu (oc * h * w) ∘ bnForward (oc * h * w) ε γ β ∘ flatConv W b) v

                    conv → bn → relu block VJP at a smooth point.

                    The workhorse for composing a ResNet VJP. In flattened Vec space, the block is relu ∘ bnForward ∘ flatConv : Vec (ic*h*w) → Vec (oc*h*w) (BatchNorm runs over the oc*h*w flattened activations with scalar ε, γ, β). It is stageHasVJPAt at a point v:

                    • inner = bnForward ∘ flatConv — both differentiable everywhere (flatConv_differentiable, bnForward_differentiable), so their bundled VJPs lift through .toHasVJPAt. The conv witness is the HasVJP3-bridged HasVJP3.toHasVJP (conv2dHasVJP3 W b).
                    • outer = relu — needs the smoothness hypothesis h_smooth (no post-BN activation hits the ReLU kink) for both relu_differentiableAt_of_smooth and reluHasVJPAt.

                    Mirrors mlpHasVJPAt (dense→relu→dense), with flatConv/bn in place of the dense layers.

                    Equations
                    • One or more equations did not get rendered due to their size.
                    Instances For
                      noncomputable def Proofs.convBnHasVJP {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε γ β : ℝ) (hε : 0 < ε) :
                      HasVJP (bnForward (oc * h * w) ε γ β ∘ flatConv W b)

                      conv → bn block VJP (no ReLU), everywhere. Just flatConv then bnForward, both differentiable everywhere, so this is a global HasVJP (no smoothness needed). This is the building block for the second conv→bn of a residual body and for the 1×1 projection skip.

                      Equations
                      Instances For
                        theorem Proofs.convBn_differentiable {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε γ β : ℝ) (hε : 0 < ε) :
                        Differentiable ℝ (bnForward (oc * h * w) ε γ β ∘ flatConv W b)

                        convBn is differentiable everywhere.

                        noncomputable def Proofs.resblockBodyHasVJPAt {ic mid oc h w kH₁ kW₁ kH₂ kW₂ : ℕ} (W₁ : Kernel4 mid ic kH₁ kW₁) (b₁ : Vec mid) (W₂ : Kernel4 oc mid kH₂ kW₂) (b₂ : Vec oc) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ : ℝ) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (v : Vec (ic * h * w)) (h_smooth₁ : ∀ (k : Fin (mid * h * w)), bnForward (mid * h * w) ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k ≠ 0) :
                        HasVJPAt ((bnForward (oc * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (mid * h * w) ∘ bnForward (mid * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁) v

                        Basic-block body VJP at a smooth point.

                        F := convBn₂ ∘ convBnRelu₁ = bn₂ ∘ conv₂ ∘ relu ∘ bn₁ ∘ conv₁, the body of a post-activation ResNet basic block (the outer ReLU and skip-add are applied later). Channels go ic → mid → oc (generic; set ic = mid = oc = c for the identity-skip block, ic ≠ oc for the downsample/projection block). Spatial dims h w preserved.

                        Inner convBnRelu₁ needs the smoothness hyp h_smooth₁ (no post-bn₁ activation hits the ReLU kink); outer convBn₂ is everywhere differentiable, lifted via .toHasVJPAt. Two vjpCompAt chain.

                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For
                          theorem Proofs.resblock_body_differentiableAt {ic mid oc h w kH₁ kW₁ kH₂ kW₂ : ℕ} (W₁ : Kernel4 mid ic kH₁ kW₁) (b₁ : Vec mid) (W₂ : Kernel4 oc mid kH₂ kW₂) (b₂ : Vec oc) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ : ℝ) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (v : Vec (ic * h * w)) (h_smooth₁ : ∀ (k : Fin (mid * h * w)), bnForward (mid * h * w) ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k ≠ 0) :
                          DifferentiableAt ℝ ((bnForward (oc * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (mid * h * w) ∘ bnForward (mid * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁) v

                          Basic-block body is DifferentiableAt at a smooth point. Needed as the diff witness when feeding the body into the residual/projection fan-in and the post-add ReLU.

                          noncomputable def Proofs.resblockHasVJPAt {c h w kH₁ kW₁ kH₂ kW₂ : ℕ} (W₁ : Kernel4 c c kH₁ kW₁) (b₁ : Vec c) (W₂ : Kernel4 c c kH₂ kW₂) (b₂ : Vec c) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ : ℝ) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (v : Vec (c * h * w)) (h_smooth₁ : ∀ (k : Fin (c * h * w)), bnForward (c * h * w) ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k ≠ 0) (h_smooth_out : ∀ (k : Fin (c * h * w)), ((bnForward (c * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (c * h * w) ∘ bnForward (c * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁) v k + v k ≠ 0) :
                          HasVJPAt (relu (c * h * w) ∘ residual ((bnForward (c * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (c * h * w) ∘ bnForward (c * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁)) v

                          Full basic residual block VJP (identity skip).

                          relu(x + F(x)) with F the conv→bn→relu→conv→bn body and an identity skip (so ic = mid = oc = c, spatial preserved). Two smoothness hyps: h_smooth₁ for the inner block ReLU, and h_smooth_out for the post-add outer ReLU (F v + v avoids the kink). Built as relu ∘ residual F via residualHasVJPAt then a final vjpCompAt with relu.

                          Equations
                          • One or more equations did not get rendered due to their size.
                          Instances For
                            noncomputable def Proofs.resblockProjHasVJPAt {ic oc h w kH₁ kW₁ kH₂ kW₂ kHp kWp : ℕ} (W₁ : Kernel4 oc ic kH₁ kW₁) (b₁ : Vec oc) (W₂ : Kernel4 oc oc kH₂ kW₂) (b₂ : Vec oc) (Wp : Kernel4 oc ic kHp kWp) (bp : Vec oc) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ εp γp βp : ℝ) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (hεp : 0 < εp) (v : Vec (ic * h * w)) (h_smooth₁ : ∀ (k : Fin (oc * h * w)), bnForward (oc * h * w) ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k ≠ 0) (h_smooth_out : ∀ (k : Fin (oc * h * w)), (bnForward (oc * h * w) εp γp βp ∘ flatConv Wp bp) v k + ((bnForward (oc * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (oc * h * w) ∘ bnForward (oc * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁) v k ≠ 0) :
                            HasVJPAt (relu (oc * h * w) ∘ residualProj (bnForward (oc * h * w) εp γp βp ∘ flatConv Wp bp) ((bnForward (oc * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (oc * h * w) ∘ bnForward (oc * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁)) v

                            Downsample/projection basic residual block VJP.

                            relu(proj(x) + F(x)) where the channel/stride change is folded into the conv dims: body F maps ic → oc (first conv ic → oc, second oc → oc), and the skip is a 1×1 convBn projection proj : ic → oc (everywhere differentiable — no ReLU, so its diffAt is immediate). Built with residualProjHasVJPAt, then vjpCompAt with the post-add relu under h_smooth_out.

                            Equations
                            • One or more equations did not get rendered due to their size.
                            Instances For

                              Weight gradient (now proved from foundation via unfold + fun_prop) #

                              The conv weight gradient implements the transpose trick:

                              `dW[o, c, kh, kw] = Σ_{h, w} x[c, h+kh−p, w+kw−p] · dy[o, h, w]`
                              

                              Here's the slick observation: this is a convolution, with the input and gradient playing the roles of "input" and "kernel" respectively.

                              This avoids needing a separate "convolution-with-funny-dimension-numbers" op; we use the same forward conv operator, just with shapes reinterpreted. Critical for backends like IREE that don't accept non-standard dimension_numbers (see iree-org/iree#21955).

                              MLIR (Conv 1 backward — exactly this trick): %x_t = stablehlo.transpose %x, dims = [1, 0, 2, 3] -- (1,128,28,28) %dh0p_t = stablehlo.transpose %d_h0pre, dims = [1, 0, 2, 3] -- (32,128,28,28) %d_W0_raw = "stablehlo.convolution"(%x_t, %dh0p_t) ... -- (1,32,3,3) %d_W0 = stablehlo.transpose %d_W0_raw, dims = [1, 0, 2, 3] -- (32,1,3,3)

                              Framework. HasVJP3 covered only input→output VJPs. For the weight gradient we reuse the plain HasVJP on Vec by flattening both the kernel (Kernel4.flatten : Kernel4 → Vec (oc*ic*kH*kW)) and the output (Tensor3.flatten : Tensor3 → Vec (oc*h*w)). The bundled HasVJP def packages a correct backward for the flattened function together with its proof; the user-facing conv2dWeightGrad wrapper does the flatten / unflatten housekeeping so callers see the natural Kernel4 type.

                              Numerical validation: check_jacobians.py:test_conv2dWeightGrad gradient-checks the transpose-trick formula against finite differences.

                              noncomputable def Proofs.conv2dWeightGradHasVJP {ic oc h w kH kW : ℕ} (b : Vec oc) (x : Tensor3 ic h w) :
                              HasVJP fun (v : Vec (oc * ic * kH * kW)) => (conv2d (Kernel4.unflatten v) b x).flatten

                              Conv2d weight-VJP — proved from foundation rules.

                              The function v ↦ flatten (conv2d (unflatten v) b x) is affine in v: the broadcast bias b o(idx_out) plus the bias-free conv, which is linear in v. pdiv_of_affine reads each Jacobian entry off the bias-free conv of a basis vector — a Kronecker at (o', c', kh', kw') against the padded input — and the collapsed sum is the transpose-trick backward dW[o', c', kh', kw'] = Σ_{hi, wi} x_pad_term(...) · dy(flat(o', hi, wi)).

                              Equations
                              • One or more equations did not get rendered due to their size.
                              Instances For
                                noncomputable def Proofs.conv2dWeightGrad {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Tensor3 ic h w) (dy : Tensor3 oc h w) :
                                Kernel4 oc ic kH kW

                                Named accessor for the conv2d weight backward — aligns with MLIR codegen (the "transpose trick" stablehlo.convolution in the backward pass). Unwraps the flattening so callers see Kernel4 → Kernel4.

                                Equations
                                Instances For
                                  noncomputable def Proofs.conv2dBiasGradHasVJP {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (x : Tensor3 ic h w) :
                                  HasVJP fun (b : Vec oc) => (conv2d W b x).flatten

                                  Conv2d bias-VJP — proved from foundation rules. The function b ↦ flatten (conv2d W b x) decomposes as (channel broadcast of b) + (bias-free conv, constant in b), so pdiv_of_affine gives the channel Kronecker, collapsed over the (c, hi, wi) decomposition of Fin (oc*h*w). The backward is db[o] = Σ_{hi, wi} dy[o, hi, wi] (the same sum as conv2dBiasGradFormula below, written on the flattened dy).

                                  Equations
                                  • One or more equations did not get rendered due to their size.
                                  Instances For
                                    noncomputable def Proofs.conv2dBiasGrad {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (x : Tensor3 ic h w) (dy : Tensor3 oc h w) :
                                    Vec oc

                                    Named accessor for the conv2d bias backward via the VJP framework.

                                    Equations
                                    Instances For
                                      noncomputable def Proofs.conv2dBiasGradFormula {oc h w : ℕ} (dy : Tensor3 oc h w) :
                                      Vec oc

                                      Conv2d bias gradient — closed-form formula.

                                      db[o] = Σ_{h, w} dy[o, h, w]

                                      This is the sum conv2dBiasGradHasVJP's backward is defined as, on Tensor3 instead of the flattened dy. No theorem in this file states the equation between the two.

                                      Each output cell adds the same b[o], so its gradient accumulates the contributions from every spatial position. MLIR emits this as a stablehlo.reduce across the spatial (and batch) dims.

                                      Equations
                                      Instances For
                                        noncomputable def Proofs.maxPool2 {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) :
                                        Tensor3 c h w

                                        MaxPool 2×2 stride 2 forward — concrete definition.

                                        Each output cell is the maximum of a 2×2 window of input cells: y[c, h, w] = max{ x[c, 2h+a, 2w+b] : a, b ∈ {0,1} }, written as an explicit four-way max.

                                        MLIR: %pool = "stablehlo.reduce_window"(%h1, %neginf) ({ ^bb0(%a, %b): stablehlo.return (stablehlo.maximum %a, %b) }) {window_dimensions = [1, 1, 2, 2], window_strides = [1, 1, 2, 2]}

                                        Equations
                                        Instances For
                                          noncomputable def Proofs.maxPool2HasVJP3 {c h w : ℕ} :

                                          MaxPool2 input-VJP — gradient routes only to the argmax positions.

                                          The backward function implements:

                                          dx[c, 2h+a, 2w+b] = dy[c, h, w] · 𝟙[(a,b) is the argmax of the window]

                                          Conceptually, max-pool is a piecewise selection: each output is one specific input. So the Jacobian is a sparse 0/1 matrix and the VJP just routes the gradient to the chosen input.

                                          The verified renders (the maxPoolBack node, printed by Proofs.StableHLO.pretty) emit stablehlo.select_and_scatter over the 2×2 stride-2 windows:

                                          // select: keep the current cell unless a later one is strictly larger %sge = stablehlo.compare GE, %sa, %sb // scatter: add the routed cotangent into the chosen cell %ss = stablehlo.add %sc, %sd

                                          The GE select keeps the first window cell attaining the max, so each window's cotangent lands in ONE cell, as PyTorch's and JAX's max-pool backwards do.

                                          Canonical (junk-at-tie) witness. HasVJP3.correct is satisfied by the canonical pdiv3-derived backward via rfl. At argmax-tie boundaries maxPool2 is not differentiable, so pdiv3 agrees with fderiv's junk default of 0 and the canonical witness is also 0 there, where the render routes the cotangent to the first tied cell. Off ties the two agree: see maxPool2_codegen_matches_canonical below. The generic MlirCodegen walk still emits the older tile-compare-select (compare EQ of the input against the tiled window max, then select), which routes the cotangent to every tied cell. See LeanMlir/Proofs/README.md's trust-boundary section.

                                          Equations
                                          • One or more equations did not get rendered due to their size.
                                          Instances For
                                            @[reducible, inline]
                                            noncomputable abbrev Proofs.maxPool2InputGrad {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (dy : Tensor3 c h w) :
                                            Tensor3 c (2 * h) (2 * w)

                                            Named accessor for the maxPool2 input backward (the canonical witness of maxPool2HasVJP3).

                                            Equations
                                            Instances For

                                              Smooth-point codegen bridge for MaxPool2 #

                                              Closes the smooth-point half of the codegen trust boundary at MaxPool2. At points where every 2×2 window has a unique strict argmax, the canonical pdiv-derived backward in maxPool2HasVJP3 collapses to "route dy to the argmax position, zero elsewhere" — what the rendered select_and_scatter computes when every window has one argmax. Mirrors relu_codegen_matches_canonical in MLP.lean, but the local linearization is per-2×2-window rather than per-coordinate.

                                              def Proofs.winRow {h : ℕ} (hi_in : Fin (2 * h)) :
                                              Fin h

                                              Row of the 2×2 window that contains input row hi_in.

                                              Equations
                                              Instances For
                                                def Proofs.winRowMod {h : ℕ} (hi_in : Fin (2 * h)) :
                                                Fin 2

                                                Position within the window's row (0 = top, 1 = bottom).

                                                Equations
                                                Instances For
                                                  def Proofs.winCol {w : ℕ} (wi_in : Fin (2 * w)) :
                                                  Fin w
                                                  Equations
                                                  Instances For
                                                    def Proofs.winColMod {w : ℕ} (wi_in : Fin (2 * w)) :
                                                    Fin 2
                                                    Equations
                                                    Instances For
                                                      def Proofs.winRowInv {h : ℕ} (hi_out : Fin h) (a : Fin 2) :
                                                      Fin (2 * h)

                                                      Row index of position a ∈ Fin 2 inside output window hi_out.

                                                      Equations
                                                      Instances For
                                                        def Proofs.winColInv {w : ℕ} (wi_out : Fin w) (b : Fin 2) :
                                                        Fin (2 * w)
                                                        Equations
                                                        Instances For
                                                          theorem Proofs.winRowInv_winRow {h : ℕ} (hi_in : Fin (2 * h)) :
                                                          winRowInv (winRow hi_in) (winRowMod hi_in) = hi_in
                                                          theorem Proofs.winColInv_winCol {w : ℕ} (wi_in : Fin (2 * w)) :
                                                          winColInv (winCol wi_in) (winColMod wi_in) = wi_in
                                                          theorem Proofs.winRow_winRowInv {h : ℕ} (ho : Fin h) (a : Fin 2) :
                                                          winRow (winRowInv ho a) = ho
                                                          theorem Proofs.winRowMod_winRowInv {h : ℕ} (ho : Fin h) (a : Fin 2) :
                                                          theorem Proofs.winCol_winColInv {w : ℕ} (wo : Fin w) (b : Fin 2) :
                                                          winCol (winColInv wo b) = wo
                                                          theorem Proofs.winColMod_winColInv {w : ℕ} (wo : Fin w) (b : Fin 2) :
                                                          theorem Proofs.winRowInv_zero {h : ℕ} (ho : Fin h) :
                                                          winRowInv ho 0 = ⟨2 * ↑ho, ⋯⟩
                                                          theorem Proofs.winRowInv_one {h : ℕ} (ho : Fin h) :
                                                          winRowInv ho 1 = ⟨2 * ↑ho + 1, ⋯⟩
                                                          theorem Proofs.winColInv_zero {w : ℕ} (wo : Fin w) :
                                                          winColInv wo 0 = ⟨2 * ↑wo, ⋯⟩
                                                          theorem Proofs.winColInv_one {w : ℕ} (wo : Fin w) :
                                                          winColInv wo 1 = ⟨2 * ↑wo + 1, ⋯⟩
                                                          def Proofs.MaxPool2Smooth {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) :

                                                          Smoothness: every 2×2 window of x attains its max at exactly one cell — a cell that dominates its window is strictly above every other cell of it. The other cells may tie with each other (a post-ReLU window [5, 0, 0, 0] qualifies); a window whose max is attained twice (an all-zero post-ReLU window) does not, and there maxPool2 has no derivative.

                                                          Equations
                                                          • One or more equations did not get rendered due to their size.
                                                          Instances For
                                                            theorem Proofs.maxPool2Smooth_of_pairwise {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (hd : ∀ (ci : Fin c) (hi_out : Fin h) (wi_out : Fin w) (ab ab' : Fin 2 × Fin 2), ab ≠ ab' → x ci (winRowInv hi_out ab.1) (winColInv wi_out ab.2) ≠ x ci (winRowInv hi_out ab'.1) (winColInv wi_out ab'.2)) :

                                                            Pairwise-distinct windows are smooth: a dominating cell is ≥ every other cell and differs from it.

                                                            def Proofs.MaxPool2IsArgmax {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (ci : Fin c) (hi_in : Fin (2 * h)) (wi_in : Fin (2 * w)) :

                                                            Input position (ci, hi_in, wi_in) attains the max of its window.

                                                            Equations
                                                            Instances For
                                                              @[instance_reducible]
                                                              noncomputable instance Proofs.MaxPool2IsArgmax.decidable {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (ci : Fin c) (hi_in : Fin (2 * h)) (wi_in : Fin (2 * w)) :
                                                              Decidable (MaxPool2IsArgmax x ci hi_in wi_in)

                                                              A finite ∀ over the window of real ≤: decidable (classically, as every ℝ order is), so an if MaxPool2IsArgmax … elaborates without open Classical.

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

                                                              Offsets and positions coincide for 2×2 windows, so MaxPool2Smooth (over offsets) gives the generic WindowSmooth (over positions).

                                                              theorem Proofs.maxPool2_eq_windowMax {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) :

                                                              maxPool2 is the 2×2 windowMax. The four-way max spelling stays the definition, since the graph ties and the generated certificates read it; this is the bridge to the generic window-max lemmas.

                                                              @[reducible, inline]
                                                              noncomputable abbrev Proofs.maxPool2Argmax {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) :
                                                              Fin 2 × Fin 2

                                                              A (not necessarily unique) argmax of the 2×2 window at output position (co, ho, wo). Unique under MaxPool2Smooth.

                                                              Equations
                                                              Instances For
                                                                theorem Proofs.maxPool2Argmax_max {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) (ab : Fin 2 × Fin 2) :
                                                                x co (winRowInv ho ab.1) (winColInv wo ab.2) ≤ x co (winRowInv ho (maxPool2Argmax x co ho wo).1) (winColInv wo (maxPool2Argmax x co ho wo).2)
                                                                @[reducible, inline]
                                                                noncomputable abbrev Proofs.maxPool2LocalReindex {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (k_out : Fin (c * h * w)) :
                                                                Fin (c * (2 * h) * (2 * w))

                                                                For each output flat index k_out (decoded to (co, ho, wo)), the flat index of the argmax's input position in Vec (c * (2*h) * (2*w)). Used as the carrier of the local-linearization reindexCLM.

                                                                Equations
                                                                Instances For
                                                                  theorem Proofs.maxPool2_flat_hasFDerivAt {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) :

                                                                  Smooth-point local-linearization for max-pool: near flatten x the flattened max-pool is the reindex y ↦ y ∘ σ, σ routing each output to its argmax's input position (windowMax_flat_hasFDerivAt through maxPool2_eq_windowMax).

                                                                  theorem Proofs.pdiv3_maxPool2_smooth {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) (ci : Fin c) (hi_in : Fin (2 * h)) (wi_in : Fin (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) :
                                                                  pdiv3 maxPool2 x ci hi_in wi_in co ho wo = if co = ci ∧ ho = winRow hi_in ∧ wo = winCol wi_in ∧ MaxPool2IsArgmax x ci hi_in wi_in then 1 else 0

                                                                  MaxPool2 smooth-point Jacobian. At a smooth point, pdiv3 of maxPool2 is a sparse 0/1 indicator: 1 exactly when the output (co, ho, wo) is the window of the input (ci, hi_in, wi_in) AND that input is the argmax of its window.

                                                                  theorem Proofs.maxPool2_codegen_matches_canonical {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) (dy : Tensor3 c h w) (ci : Fin c) (hi_in : Fin (2 * h)) (wi_in : Fin (2 * w)) :
                                                                  maxPool2HasVJP3.backward x dy ci hi_in wi_in = if MaxPool2IsArgmax x ci hi_in wi_in then dy ci (winRow hi_in) (winCol wi_in) else 0

                                                                  Bridge: maxPool2HasVJP3's canonical backward matches the codegen formula at smooth points.

                                                                  At points where every 2×2 window has a unique strict argmax, the canonical pdiv3-derived backward collapses to "dy at the window's output position, but only at the argmax input cell" — what the rendered select_and_scatter computes there. Closes the smooth-point half of the codegen trust boundary; what remains is the kink convention at argmax ties (the render routes the cotangent to the first tied cell, the canonical witness gives 0).

                                                                  noncomputable def Proofs.maxPool2HasVJPAt3 {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) :

                                                                  MaxPool2 pointwise VJP — no canonical-witness escape.

                                                                  HasVJPAt3 maxPool2 x under MaxPool2Smooth x. The backward is the argmax routing directly (route dy to the argmax cell, zero elsewhere); the correct field is maxPool2_codegen_matches_canonical flipped, not rfl. Companion of reluHasVJPAt in MLP.lean: mlpHasVJPAt chains through reluHasVJPAt, and cnnHasVJPAt chains through both (this one via maxPoolFlatHasVJPAt), so neither uses the global canonical witness at a kinked operator.

                                                                  Equations
                                                                  • One or more equations did not get rendered due to their size.
                                                                  Instances For

                                                                    Reshape (flatten / unflatten) #

                                                                    Flatten is a permutation of indices, so its VJP is just the inverse permutation. No gradient computation needed.

                                                                    MLIR: %flat = stablehlo.reshape %pool : (tensor<128x32x14x14xf32>) -> tensor<128x6272xf32>

                                                                    The flatten / unflatten bijection is already defined in Tensor.lean as Tensor3.flatten / Tensor3.unflatten (used by the pdiv3 derivation there). We reuse those here rather than duplicating — see Tensor3.flatten_unflatten / unflatten_flatten for the mutual-inverse proofs.

                                                                    Summary of derivations in this file #

                                                                    Derived (not axioms):

                                                                    theorem Proofs.maxPool2HasVJP3_correct {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (dy : Tensor3 c h w) (ci : Fin c) (hi : Fin (2 * h)) (wi : Fin (2 * w)) :
                                                                    maxPool2HasVJP3.backward x dy ci hi wi = ∑ co : Fin c, ∑ ho : Fin h, ∑ wo : Fin w, pdiv3 maxPool2 x ci hi wi co ho wo * dy co ho wo

                                                                    Public correctness theorem for maxPool2HasVJP3: the canonical-witness backward equals the pdiv3-contracted Jacobian by definition. The codegen substitutes the standard argmax-routing convention at non-smooth tiebreaks (see LeanMlir/Proofs/README.md's Codegen Trust Boundary).

                                                                    theorem Proofs.maxPool2HasVJPAt3_correct {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) (dy : Tensor3 c h w) (ci : Fin c) (hi : Fin (2 * h)) (wi : Fin (2 * w)) :
                                                                    (maxPool2HasVJPAt3 x h_smooth).backward dy ci hi wi = ∑ co : Fin c, ∑ ho : Fin h, ∑ wo : Fin w, pdiv3 maxPool2 x ci hi wi co ho wo * dy co ho wo

                                                                    Public correctness theorem for maxPool2HasVJPAt3 — the pointwise variant under MaxPool2Smooth. The underlying .correct field is maxPool2_codegen_matches_canonical flipped (a real proof), not rfl; this wrapper exposes it for comparator re-verification.

                                                                    The capstone: a whole-network ResNet-style CNN VJP #

                                                                    cnnHasVJPAt is the CNN analogue of vitFullHasVJP — a single HasVJPAt for an end-to-end forward pass, chained entirely in flattened Vec space via vjpCompAt. It first needs global average pooling, which we define here: globalAvgPool x ci = (∑ hi ∑ wi x ci hi wi) / (h*w) (mean over spatial per channel), bridge it to flat Vec space (globalAvgPoolFlat), and prove its linear VJP (globalAvgPoolFlatHasVJP, backward broadcasts dy ci / (h*w) to every spatial cell of channel ci) and differentiability.

                                                                    Fixed structural choices (a concrete-but-representative pipeline, in the spirit of hand_cnn_train_step.mlir; prioritising a complete axiom-clean end-to-end witness over maximal generality):

                                                                    input  : Vec (ic * (2h) * (2w))
                                                                    stem   : convBnRelu  ic → c   (spatial 2h×2w preserved)
                                                                    pool   : maxPool2    c, 2h×2w → c, h×w
                                                                    block1 : resblockHasVJPAt        (identity skip,  c → c,  h×w)
                                                                    block2 : resblockProjHasVJPAt    (projection skip, c → oc, h×w)
                                                                    gap    : globalAvgPool   oc, h×w → Vec oc
                                                                    head   : dense           oc → nClasses
                                                                    

                                                                    So: 1 stem conv, 1 max-pool, exactly two residual blocks (one of EACH skip type — identity and 1×1 projection — to exercise both code paths), global-average pool, one dense classifier. Channel/spatial dims stay implicit Nat params; the block/stage counts are fixed. The bundled smoothness hypotheses (h_stem, h_mp, h_rb1/h_rb1o, h_rb2/ h_rb2o) are the family of every ReLU + max-pool site's smooth-point condition, exactly like mlpHasVJPAt's multiple h_smooth_*.

                                                                    The differentiability obstacle (max-pool is non-smooth globally, so vjpCompAt cannot get DifferentiableAt of a max-pool-containing prefix from a global lemma) is discharged at the smooth point by maxPool2_flat_hasFDerivAt (the local linearization already proved for the max-pool Jacobian) via .differentiableAt.

                                                                    noncomputable def Proofs.globalAvgPool {c h w : ℕ} (x : Tensor3 c h w) :
                                                                    Vec c

                                                                    Global average pool: mean over spatial dims per channel.

                                                                    Equations
                                                                    Instances For
                                                                      noncomputable def Proofs.globalAvgPoolFlat (c h w : ℕ) :
                                                                      Vec (c * h * w) → Vec c

                                                                      Flat GAP: Vec (c*h*w) → Vec c.

                                                                      Equations
                                                                      Instances For

                                                                        The pool is continuous (the fun_prop form of globalAvgPoolFlat_differentiable).

                                                                        noncomputable def Proofs.flatChannel (c h w : ℕ) (idx : Fin (c * h * w)) :
                                                                        Fin c

                                                                        The channel of a flat index idx : Fin (c*h*w).

                                                                        Equations
                                                                        Instances For
                                                                          theorem Proofs.pdiv_globalAvgPoolFlat (c h w : ℕ) (v : Vec (c * h * w)) (idx : Fin (c * h * w)) (ci : Fin c) :
                                                                          pdiv (globalAvgPoolFlat c h w) v idx ci = (if flatChannel c h w idx = ci then 1 else 0) / (↑h * ↑w)
                                                                          noncomputable def Proofs.globalAvgPoolFlatHasVJP (c h w : ℕ) :

                                                                          Global average pool VJP (flattened). Linear map; backward broadcasts dy ci / (h*w) to every spatial cell of channel ci.

                                                                          Equations
                                                                          • One or more equations did not get rendered due to their size.
                                                                          Instances For
                                                                            theorem Proofs.globalAvgPoolFlatHasVJP_correct (c h w : ℕ) (x : Vec (c * h * w)) (dy : Vec c) (i : Fin (c * h * w)) :
                                                                            (globalAvgPoolFlatHasVJP c h w).backward x dy i = ∑ j : Fin c, pdiv (globalAvgPoolFlat c h w) x i j * dy j

                                                                            Uniform VJP-correctness wrapper for globalAvgPoolFlat — a citable _correct matching the convention of every other layer (just unfolds the HasVJP.correct field of globalAvgPoolFlatHasVJP).

                                                                            noncomputable def Proofs.maxPoolFlat (c h w : ℕ) :
                                                                            Vec (c * (2 * h) * (2 * w)) → Vec (c * h * w)

                                                                            The 2×2/s2 max pool maxPool2 on the flattened Vec form (unflatten, pool, flatten).

                                                                            Equations
                                                                            Instances For
                                                                              theorem Proofs.maxPoolFlat_differentiableAt {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) :
                                                                              noncomputable def Proofs.maxPoolFlatHasVJPAt {c h w : ℕ} (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool2Smooth x) :

                                                                              The VJP of maxPoolFlat at a flattened input whose 2×2 windows each have a unique max (MaxPool2Smooth): maxPool2HasVJPAt3 moved to Vec form.

                                                                              Equations
                                                                              Instances For
                                                                                theorem Proofs.maxPoolFlat_close {c h w : ℕ} (vt va : Vec (c * (2 * h) * (2 * w))) {e : ℝ} (hv : ∀ (k : Fin (c * (2 * h) * (2 * w))), |vt k - va k| ≤ e) (k : Fin (c * h * w)) :
                                                                                |maxPoolFlat c h w vt k - maxPoolFlat c h w va k| ≤ e

                                                                                Flattened maxPoolFlat closeness — the form the Vec-space MNIST-CNN forward (mnistCnnNoBnForward) composes.

                                                                                theorem Proofs.maxPoolFlat_abs_le {c h w : ℕ} {v : Vec (c * (2 * h) * (2 * w))} {A : ℝ} (hv : ∀ (k : Fin (c * (2 * h) * (2 * w))), |v k| ≤ A) (k : Fin (c * h * w)) :
                                                                                |maxPoolFlat c h w v k| ≤ A

                                                                                Flattened maxPoolFlat magnitude bound — the form the CNN forward threads.

                                                                                theorem Proofs.resblock_differentiableAt {c h w kH₁ kW₁ kH₂ kW₂ : ℕ} (W₁ : Kernel4 c c kH₁ kW₁) (b₁ : Vec c) (W₂ : Kernel4 c c kH₂ kW₂) (b₂ : Vec c) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ : ℝ) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (v : Vec (c * h * w)) (h_smooth₁ : ∀ (k : Fin (c * h * w)), bnForward (c * h * w) ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k ≠ 0) (h_smooth_out : ∀ (k : Fin (c * h * w)), ((bnForward (c * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (c * h * w) ∘ bnForward (c * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁) v k + v k ≠ 0) :
                                                                                DifferentiableAt ℝ (relu (c * h * w) ∘ residual ((bnForward (c * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (c * h * w) ∘ bnForward (c * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁)) v
                                                                                theorem Proofs.resblockProj_differentiableAt {ic oc h w kH₁ kW₁ kH₂ kW₂ kHp kWp : ℕ} (W₁ : Kernel4 oc ic kH₁ kW₁) (b₁ : Vec oc) (W₂ : Kernel4 oc oc kH₂ kW₂) (b₂ : Vec oc) (Wp : Kernel4 oc ic kHp kWp) (bp : Vec oc) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ εp γp βp : ℝ) (hε₁ : 0 < ε₁) (hε₂ : 0 < ε₂) (hεp : 0 < εp) (v : Vec (ic * h * w)) (h_smooth₁ : ∀ (k : Fin (oc * h * w)), bnForward (oc * h * w) ε₁ γ₁ β₁ (flatConv W₁ b₁ v) k ≠ 0) (h_smooth_out : ∀ (k : Fin (oc * h * w)), (bnForward (oc * h * w) εp γp βp ∘ flatConv Wp bp) v k + ((bnForward (oc * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (oc * h * w) ∘ bnForward (oc * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁) v k ≠ 0) :
                                                                                DifferentiableAt ℝ (relu (oc * h * w) ∘ residualProj (bnForward (oc * h * w) εp γp βp ∘ flatConv Wp bp) ((bnForward (oc * h * w) ε₂ γ₂ β₂ ∘ flatConv W₂ b₂) ∘ relu (oc * h * w) ∘ bnForward (oc * h * w) ε₁ γ₁ β₁ ∘ flatConv W₁ b₁)) v
                                                                                theorem Proofs.convBnRelu_differentiableAt {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε γ β : ℝ) (hε : 0 < ε) (v : Vec (ic * h * w)) (h_smooth : ∀ (k : Fin (oc * h * w)), bnForward (oc * h * w) ε γ β (flatConv W b v) k ≠ 0) :
                                                                                DifferentiableAt ℝ (relu (oc * h * w) ∘ bnForward (oc * h * w) ε γ β ∘ flatConv W b) v
                                                                                @[reducible, inline]
                                                                                noncomputable abbrev Proofs.cbr {ic oc h w kH kW : ℕ} (W : Kernel4 oc ic kH kW) (b : Vec oc) (ε γ β : ℝ) :
                                                                                Vec (ic * h * w) → Vec (oc * h * w)
                                                                                Equations
                                                                                Instances For
                                                                                  @[reducible, inline]
                                                                                  noncomputable abbrev Proofs.rblk {c h w kH₁ kW₁ kH₂ kW₂ : ℕ} (W₁ : Kernel4 c c kH₁ kW₁) (b₁ : Vec c) (W₂ : Kernel4 c c kH₂ kW₂) (b₂ : Vec c) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ : ℝ) :
                                                                                  Vec (c * h * w) → Vec (c * h * w)
                                                                                  Equations
                                                                                  • One or more equations did not get rendered due to their size.
                                                                                  Instances For
                                                                                    @[reducible, inline]
                                                                                    noncomputable abbrev Proofs.rblkP {ic oc h w kH₁ kW₁ kH₂ kW₂ kHp kWp : ℕ} (W₁ : Kernel4 oc ic kH₁ kW₁) (b₁ : Vec oc) (W₂ : Kernel4 oc oc kH₂ kW₂) (b₂ : Vec oc) (Wp : Kernel4 oc ic kHp kWp) (bp : Vec oc) (ε₁ γ₁ β₁ ε₂ γ₂ β₂ εp γp βp : ℝ) :
                                                                                    Vec (ic * h * w) → Vec (oc * h * w)
                                                                                    Equations
                                                                                    • One or more equations did not get rendered due to their size.
                                                                                    Instances For
                                                                                      noncomputable def Proofs.cnnForward {ic c oc h w kHs kWs kH₁ kW₁ kH₂ kW₂ kH₁' kW₁' kH₂' kW₂' kHp kWp nClasses : ℕ} (Ws : Kernel4 c ic kHs kWs) (bs : Vec c) (εs γs βs : ℝ) (W₁ : Kernel4 c c kH₁ kW₁) (b₁ : Vec c) (W₂ : Kernel4 c c kH₂ kW₂) (b₂ : Vec c) (e₁ g₁ bb₁ e₂ g₂ bb₂ : ℝ) (W₁' : Kernel4 oc c kH₁' kW₁') (b₁' : Vec oc) (W₂' : Kernel4 oc oc kH₂' kW₂') (b₂' : Vec oc) (Wp : Kernel4 oc c kHp kWp) (bp : Vec oc) (f₁ h₁ i₁ f₂ h₂ i₂ fp hp ip : ℝ) (Wd : Mat oc nClasses) (bd : Vec nClasses) :
                                                                                      Vec (ic * (2 * h) * (2 * w)) → Vec nClasses

                                                                                      The forward CNN: stem(convBnRelu) → maxpool → resblock(id) → resblockProj → gap → dense.

                                                                                      Equations
                                                                                      • One or more equations did not get rendered due to their size.
                                                                                      Instances For
                                                                                        noncomputable def Proofs.cnnHasVJPAt {ic c oc h w kHs kWs kH₁ kW₁ kH₂ kW₂ kH₁' kW₁' kH₂' kW₂' kHp kWp nClasses : ℕ} (Ws : Kernel4 c ic kHs kWs) (bs : Vec c) (εs γs βs : ℝ) (hεs : 0 < εs) (W₁ : Kernel4 c c kH₁ kW₁) (b₁ : Vec c) (W₂ : Kernel4 c c kH₂ kW₂) (b₂ : Vec c) (e₁ g₁ bb₁ e₂ g₂ bb₂ : ℝ) (he₁ : 0 < e₁) (he₂ : 0 < e₂) (W₁' : Kernel4 oc c kH₁' kW₁') (b₁' : Vec oc) (W₂' : Kernel4 oc oc kH₂' kW₂') (b₂' : Vec oc) (Wp : Kernel4 oc c kHp kWp) (bp : Vec oc) (f₁ hh₁ i₁ f₂ hh₂ i₂ fp hhp ip : ℝ) (hf₁ : 0 < f₁) (hf₂ : 0 < f₂) (hfp : 0 < fp) (Wd : Mat oc nClasses) (bd : Vec nClasses) (x : Vec (ic * (2 * h) * (2 * w))) (h_stem : ∀ (k : Fin (c * (2 * h) * (2 * w))), bnForward (c * (2 * h) * (2 * w)) εs γs βs (flatConv Ws bs x) k ≠ 0) (h_mp : MaxPool2Smooth (Tensor3.unflatten (cbr Ws bs εs γs βs x))) (h_rb1 : ∀ (k : Fin (c * h * w)), bnForward (c * h * w) f₁ hh₁ i₁ (flatConv W₁ b₁ (maxPoolFlat c h w (cbr Ws bs εs γs βs x))) k ≠ 0) (h_rb1o : ∀ (k : Fin (c * h * w)), ((bnForward (c * h * w) f₂ hh₂ i₂ ∘ flatConv W₂ b₂) ∘ relu (c * h * w) ∘ bnForward (c * h * w) f₁ hh₁ i₁ ∘ flatConv W₁ b₁) (maxPoolFlat c h w (cbr Ws bs εs γs βs x)) k + maxPoolFlat c h w (cbr Ws bs εs γs βs x) k ≠ 0) (h_rb2 : ∀ (k : Fin (oc * h * w)), bnForward (oc * h * w) e₁ g₁ bb₁ (flatConv W₁' b₁' (rblk W₁ b₁ W₂ b₂ f₁ hh₁ i₁ f₂ hh₂ i₂ (maxPoolFlat c h w (cbr Ws bs εs γs βs x)))) k ≠ 0) (h_rb2o : ∀ (k : Fin (oc * h * w)), (bnForward (oc * h * w) fp hhp ip ∘ flatConv Wp bp) (rblk W₁ b₁ W₂ b₂ f₁ hh₁ i₁ f₂ hh₂ i₂ (maxPoolFlat c h w (cbr Ws bs εs γs βs x))) k + ((bnForward (oc * h * w) e₂ g₂ bb₂ ∘ flatConv W₂' b₂') ∘ relu (oc * h * w) ∘ bnForward (oc * h * w) e₁ g₁ bb₁ ∘ flatConv W₁' b₁') (rblk W₁ b₁ W₂ b₂ f₁ hh₁ i₁ f₂ hh₂ i₂ (maxPoolFlat c h w (cbr Ws bs εs γs βs x))) k ≠ 0) :
                                                                                        HasVJPAt (cnnForward Ws bs εs γs βs W₁ b₁ W₂ b₂ e₁ g₁ bb₁ e₂ g₂ bb₂ W₁' b₁' W₂' b₂' Wp bp f₁ hh₁ i₁ f₂ hh₂ i₂ fp hhp ip Wd bd) x

                                                                                        The whole-net VJP of cnnForward at x, folded by vjpCompAt, given positive BN εs and six smoothness binders: h_stem (stem BN output nonzero), h_mp (unique window maxima of the post-ReLU stem output the pool reads), h_rb1 / h_rb1o (the identity block's inner BN output and pre-ReLU residual sum nonzero) and h_rb2 / h_rb2o (the same two for the projection block).

                                                                                        Equations
                                                                                        • One or more equations did not get rendered due to their size.
                                                                                        Instances For
                                                                                          theorem Proofs.cnnHasVJPAt_correct {ic c oc h w kHs kWs kH₁ kW₁ kH₂ kW₂ kH₁' kW₁' kH₂' kW₂' kHp kWp nClasses : ℕ} (Ws : Kernel4 c ic kHs kWs) (bs : Vec c) (εs γs βs : ℝ) (hεs : 0 < εs) (W₁ : Kernel4 c c kH₁ kW₁) (b₁ : Vec c) (W₂ : Kernel4 c c kH₂ kW₂) (b₂ : Vec c) (e₁ g₁ bb₁ e₂ g₂ bb₂ : ℝ) (he₁ : 0 < e₁) (he₂ : 0 < e₂) (W₁' : Kernel4 oc c kH₁' kW₁') (b₁' : Vec oc) (W₂' : Kernel4 oc oc kH₂' kW₂') (b₂' : Vec oc) (Wp : Kernel4 oc c kHp kWp) (bp : Vec oc) (f₁ hh₁ i₁ f₂ hh₂ i₂ fp hhp ip : ℝ) (hf₁ : 0 < f₁) (hf₂ : 0 < f₂) (hfp : 0 < fp) (Wd : Mat oc nClasses) (bd : Vec nClasses) (x : Vec (ic * (2 * h) * (2 * w))) (h_stem : ∀ (k : Fin (c * (2 * h) * (2 * w))), bnForward (c * (2 * h) * (2 * w)) εs γs βs (flatConv Ws bs x) k ≠ 0) (h_mp : MaxPool2Smooth (Tensor3.unflatten (cbr Ws bs εs γs βs x))) (h_rb1 : ∀ (k : Fin (c * h * w)), bnForward (c * h * w) f₁ hh₁ i₁ (flatConv W₁ b₁ (maxPoolFlat c h w (cbr Ws bs εs γs βs x))) k ≠ 0) (h_rb1o : ∀ (k : Fin (c * h * w)), ((bnForward (c * h * w) f₂ hh₂ i₂ ∘ flatConv W₂ b₂) ∘ relu (c * h * w) ∘ bnForward (c * h * w) f₁ hh₁ i₁ ∘ flatConv W₁ b₁) (maxPoolFlat c h w (cbr Ws bs εs γs βs x)) k + maxPoolFlat c h w (cbr Ws bs εs γs βs x) k ≠ 0) (h_rb2 : ∀ (k : Fin (oc * h * w)), bnForward (oc * h * w) e₁ g₁ bb₁ (flatConv W₁' b₁' (rblk W₁ b₁ W₂ b₂ f₁ hh₁ i₁ f₂ hh₂ i₂ (maxPoolFlat c h w (cbr Ws bs εs γs βs x)))) k ≠ 0) (h_rb2o : ∀ (k : Fin (oc * h * w)), (bnForward (oc * h * w) fp hhp ip ∘ flatConv Wp bp) (rblk W₁ b₁ W₂ b₂ f₁ hh₁ i₁ f₂ hh₂ i₂ (maxPoolFlat c h w (cbr Ws bs εs γs βs x))) k + ((bnForward (oc * h * w) e₂ g₂ bb₂ ∘ flatConv W₂' b₂') ∘ relu (oc * h * w) ∘ bnForward (oc * h * w) e₁ g₁ bb₁ ∘ flatConv W₁' b₁') (rblk W₁ b₁ W₂ b₂ f₁ hh₁ i₁ f₂ hh₂ i₂ (maxPoolFlat c h w (cbr Ws bs εs γs βs x))) k ≠ 0) (dy : Vec nClasses) (i : Fin (ic * (2 * h) * (2 * w))) :
                                                                                          (cnnHasVJPAt Ws bs εs γs βs hεs W₁ b₁ W₂ b₂ e₁ g₁ bb₁ e₂ g₂ bb₂ he₁ he₂ W₁' b₁' W₂' b₂' Wp bp f₁ hh₁ i₁ f₂ hh₂ i₂ fp hhp ip hf₁ hf₂ hfp Wd bd x h_stem h_mp h_rb1 h_rb1o h_rb2 h_rb2o).backward dy i = ∑ j : Fin nClasses, pdiv (cnnForward Ws bs εs γs βs W₁ b₁ W₂ b₂ e₁ g₁ bb₁ e₂ g₂ bb₂ W₁' b₁' W₂' b₂' Wp bp f₁ hh₁ i₁ f₂ hh₂ i₂ fp hhp ip Wd bd) x i j * dy j

                                                                                          Public correctness theorem for cnnHasVJPAt — exposes the witness's .correct field as a top-level proposition: the full ResNet-style CNN's backward equals the pdiv-contracted Jacobian (Jacobian-transpose applied to the cotangent). CNN analogue of vitFullHasVJP_correct.

                                                                                          theorem Proofs.globalAvgPool_shift {c h w : ℕ} (hh : 0 < h) (hw : 0 < w) (x y : Tensor3 c h w) (δ : ℝ) (ci : Fin c) (hxy : ∀ (i : Fin h) (j : Fin w), x ci i j = y ci i j + δ) :

                                                                                          GAP of a uniformly shifted channel is shifted by the same constant.