Documentation

LeanMlir.Proofs.Architectures.MaxPool3s2

maxPool3s2 — the 3×3 stride-2 max pool of He et al.'s ResNet stem #

Written 2026-08-03. planning/archive/rsb_a3_r50_verified.md / the stem-pool finding.

The verified path's only pooling op is maxPool2 — 2×2, stride 2, non-overlapping. Every ResNet in He et al. (18/34/50/101/152) specifies a 3×3 stride-2 pool after the stem conv, so resnet34Verified has been pooling a different function from the paper and from the reference it is paired against. This file is the missing op.

⚠⚠ WHICH 3×3 pool — the PAPER's, which is not what the JAX reference emits today #

This file implements He et al. / torchvision: nn.MaxPool2d(3, stride=2, padding=1)symmetric padding, so window i covers input [2i−1, 2i+1].

⚠ The repo's JAX references emit reduce_window(…, (1,1,3,3), (1,1,2,2), 'SAME'), and XLA's SAME on a 112→56 axis gives pad_total = max((56−1)·2 + 3 − 112, 0) = 1, split pad_low = 0, pad_high = 1 — window i = [2i, 2i+2], padded at the end. Measured on device at n = 12: SAME windows peak at [2,4,6,8,10,11], symmetric at [1,3,5,7,9,11]. The two grids are offset by one input position and are different functions everywhere.

Paper-faithfulness is the goal, so this file is symmetric and the JAX max_pool2d helper moves to match. ⚠ Until both land and are re-run, verified and JAX disagree at the stem pool.

⭐ Measured: symmetric (k−1)//2 padding is bit-identical to SAME for every 2×2 pool, so no cifar/mnist net moves — only the 3×3 users (R34-ImageNet, R50).

⭐ THE PADDING NEEDS NO EXTENDED-REALS TYPE #

reduce_window pads with -∞, which Tensor3 _ _ _ = … → ℝ cannot hold. It does not need to: for max, clamping the index is equivalent to -∞ padding. The only out-of-range read is 2i−1 at i = 0, and Nat's truncated subtraction clamps it to 0 — a cell the window already contains at offset a = 1. So max over the clamped triple equals max over the unpadded pair, which is exactly what -∞ padding computes. win3RowInv_first_dup is that statement.

⭐ The symmetric form needs no min: the upper end 2(h−1)+2−1 = 2h−1 is in range by construction, so truncated subtraction is the whole story.

The shape of the VJP, and why overlap costs less than it looks #

maxPool2's backward is a lookup (dy at hi/2), sound only because 2×2 windows are disjoint. Here windows overlap — odd input p lies in windows (p−1)/2 and (p+1)/2 — so an input feeds up to 4 outputs and the backward must accumulate.

That needs no new analytic argument. HasVJPAt3.correct already states the backward as ∑ co ∑ ho ∑ wo, pdiv3 f x … * dy co ho wo — a sum over all outputs; maxPool2's codegen_matches_canonical merely collapses it to one term using disjointness. Here it collapses to ≤4. The generic route (maxPool2LocalReindexreindexCLMpdiv3) is indifferent: at a smooth point the pool is locally a reindexing map, and overlap only makes that map non-injective, which reindexCLM's adjoint already handles by summing over preimages.

This file is the DEFINITIONAL layer. The HasVJPAt3 witness mirrors maxPool2_has_vjp_at3 (33 declarations in CNN.lean) and is not built here yet. Nothing downstream references these definitions, so nothing renders or moves.

def Proofs.win3RowInv {h : } (hi_out : Fin h) (a : Fin 3) :
Fin (2 * h)

Input row of offset a ∈ Fin 3 inside output window hi_out: 2·hi + a − 1, in Nat. The truncated subtraction is the low pad (see the header) — it is the only clamp needed, because the high end 2(h−1)+2−1 = 2h−1 is in range by construction.

Equations
Instances For
    def Proofs.win3ColInv {w : } (wi_out : Fin w) (b : Fin 3) :
    Fin (2 * w)

    Column peer of win3RowInv.

    Equations
    Instances For
      @[simp]
      theorem Proofs.win3RowInv_val {h : } (hi_out : Fin h) (a : Fin 3) :
      (win3RowInv hi_out a) = 2 * hi_out + a - 1
      @[simp]
      theorem Proofs.win3ColInv_val {w : } (wi_out : Fin w) (b : Fin 3) :
      (win3ColInv wi_out b) = 2 * wi_out + b - 1
      theorem Proofs.win3RowInv_first_dup {h : } (hi_out : Fin h) (hfirst : hi_out = 0) :
      win3RowInv hi_out 0, = win3RowInv hi_out 1,

      The padding statement. In the FIRST window offset a = 0 duplicates a = 1 rather than reading out of range — exactly what a -∞ pad contributes to a max.

      theorem Proofs.win3ColInv_first_dup {w : } (wi_out : Fin w) (hfirst : wi_out = 0) :
      win3ColInv wi_out 0, = win3ColInv wi_out 1,
      noncomputable def Proofs.maxPool3s2 {c h w : } (x : Tensor3 c (2 * h) (2 * w)) :
      Tensor3 c h w

      3×3 stride-2 symmetrically-padded max pool, [c, 2h, 2w] → [c, h, w]: the max over the window [2i−1, 2i+1] × [2j−1, 2j+1], clamped at the near edge (= -∞ padded, header).

      Equations
      Instances For
        theorem Proofs.le_maxPool3s2 {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (ch : Fin c) (hi : Fin h) (wi : Fin w) (ab : Fin 3 × Fin 3) :
        x ch (win3RowInv hi ab.1) (win3ColInv wi ab.2) maxPool3s2 x ch hi wi

        Every window cell is ≤ the pooled value.

        theorem Proofs.maxPool3s2_attained {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (ch : Fin c) (hi : Fin h) (wi : Fin w) :
        ∃ (ab : Fin 3 × Fin 3), maxPool3s2 x ch hi wi = x ch (win3RowInv hi ab.1) (win3ColInv wi ab.2)

        The pooled value is attained by some window cell.

        theorem Proofs.maxPool3s2_abs_le {c h w : } {x : Tensor3 c (2 * h) (2 * w)} {A : } (hx : ∀ (ci : Fin c) (hi : Fin (2 * h)) (wi : Fin (2 * w)), |x ci hi wi| A) (ci : Fin c) (hi : Fin h) (wi : Fin w) :
        |maxPool3s2 x ci hi wi| A

        The 3×3 pool never grows magnitudes — it selects an existing window cell. ⭐ Finset.sup' again makes the window size stop mattering: maxPool2_abs_le needs a nested abs_max_le (abs_max_le _ _) (abs_max_le _ _) for 4 cells, which at 9 would be worse; here it is sup'_le plus one le_sup', independent of the window. Fourth collapse of the same kind.

        theorem Proofs.maxPool3s2_close {c h w : } (xt xa : Tensor3 c (2 * h) (2 * w)) {e : } (hx : ∀ (ci : Fin c) (hi : Fin (2 * h)) (wi : Fin (2 * w)), |xt ci hi wi - xa ci hi wi| e) (ci : Fin c) (hi : Fin h) (wi : Fin w) :
        |maxPool3s2 xt ci hi wi - maxPool3s2 xa ci hi wi| e

        The 3×3 pool is 1-Lipschitz in the sup norm — the peer of maxPool2_close. Standard sup-vs-sup argument: each side is ≤ the other plus e, from sup'_le and le_sup'.

        def Proofs.MaxPool3s2Smooth {c h w : } (x : Tensor3 c (2 * h) (2 * w)) :

        Smoothness: every 3×3 window has a strict argmax. Stated as "distinct offsets that land on distinct input POSITIONS have distinct values", so the clamped duplicate in the first window (a = 0a = 1) is not counted as a tie. ⚠ That carve-out is forced by the padding and has no maxPool2 analogue — there, distinct offsets always meant distinct positions.

        Equations
        • One or more equations did not get rendered due to their size.
        Instances For
          theorem Proofs.maxPool3s2Smooth_of_injective {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (hinj : ∀ (ci : Fin c) (r r' : Fin (2 * h)) (s s' : Fin (2 * w)), x ci r s = x ci r' s'r = r' s = s') :

          Positional injectivity ⇒ MaxPool3s2Smooth — the discharge lemma for the whole-net live and seal witnesses, the peer of MnistCNN's maxPool2Smooth_of_injective. One injectivity argument in place of 36·c·h·w per-window decides (9 offsets pairwise, against 2×2's 6), which at ResNet-34's stem is why case-bashing is not an option.

          ⚠⚠ And it is STRICTLY SHORTER than its 2×2 peer, for the reason the padding forced. MaxPool2Smooth is quantified over offsets, so its discharge lemma has to get from "the two input positions coincide" back to "the two offsets coincide" — two Fin.mk.injEq + omega decodes, valid only because there distinct offsets always meant distinct positions. MaxPool3s2Smooth is quantified over positions precisely because that is false here (the clamped duplicate a = 0 ≡ a = 1 in the first window, win3RowInv_first_dup), so injectivity lands directly on the hypothesis and the decode step does not exist. The carve-out that made the predicate awkward to state is what makes it cheap to discharge.

          The hypothesis is the same one the 2×2 sites already supply: on each channel the position map (r, s) ↦ x ci r s is injective.

          def Proofs.MaxPool3s2IsArgmaxAt {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (ci : Fin c) (ho : Fin h) (wo : Fin w) (hi_in : Fin (2 * h)) (wi_in : Fin (2 * w)) :

          Input (ci, hi_in, wi_in) attains the max of the window at output (ho, wo). ⚠ Unlike MaxPool2IsArgmax this takes the OUTPUT position explicitly: with overlapping windows an input has no single owning window, which is the whole difference.

          Equations
          • One or more equations did not get rendered due to their size.
          Instances For
            theorem Proofs.win3Row_mem_le_two {h : } (p : Fin (2 * h)) (hi_out : Fin h) (hmem : ∃ (a : Fin 3), win3RowInv hi_out a = p) :
            hi_out = p / 2 2 * hi_out = p + 1

            The overlap fact, stated rather than assumed: an input row lies in at most TWO windows — p/2 and (p+1)/2. With symmetric padding the shared cell is at ODD p (window (p−1)/2 takes it at offset 2, window (p+1)/2 at offset 0); even p lies in exactly one. So an input feeds at most 4 outputs and the backward accumulates at most 4 terms — the count maxPool2 does not have.

            noncomputable def Proofs.maxPool3s2Argmax {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) :
            Fin 3 × Fin 3

            A (not necessarily unique) argmax of the 3×3 window at output (co, ho, wo). Unique under MaxPool3s2Smooth up to position — the clamped duplicate in the first window is two offsets naming one cell.

            Equations
            Instances For
              theorem Proofs.maxPool3s2Argmax_max {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) (ab : Fin 3 × Fin 3) :
              x co (win3RowInv ho ab.1) (win3ColInv wo ab.2) x co (win3RowInv ho (maxPool3s2Argmax x co ho wo).1) (win3ColInv wo (maxPool3s2Argmax x co ho wo).2)
              theorem Proofs.maxPool3s2_eq_at_max {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) (a b : Fin 3) (h_max : ∀ (a' b' : Fin 3), x co (win3RowInv ho a') (win3ColInv wo b') x co (win3RowInv ho a) (win3ColInv wo b)) :
              maxPool3s2 x co ho wo = x co (win3RowInv ho a) (win3ColInv wo b)

              If (a, b) dominates every window cell, the pooled value is the value there. ⭐ The sup' formulation makes this two lines where maxPool2_eq_at_max needs a four-way fin_cases against an explicit max (max _ _) (max _ _) — and nine ways here.

              theorem Proofs.maxPool3s2_eq_argmax_value {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) :
              maxPool3s2 x co ho wo = x co (win3RowInv ho (maxPool3s2Argmax x co ho wo).1) (win3ColInv wo (maxPool3s2Argmax x co ho wo).2)
              noncomputable def Proofs.maxPool3s2LocalReindex {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, the flat index of its argmax's input position. ⚠ Not injective — two overlapping windows may select the same input. That is exactly what makes the backward accumulate, and reindexCLM's adjoint already sums over preimages, so nothing here needs to change relative to maxPool2LocalReindex.

              Equations
              • One or more equations did not get rendered due to their size.
              Instances For
                theorem Proofs.maxPool3s2_flat_hasFDerivAt {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool3s2Smooth x) (hc : 0 < c) (hh : 0 < h) (hw : 0 < w) :

                Smooth-point local linearisation. On a metric ball around flatten x the flattened pool agrees with the reindex y ↦ y ∘ σ. Mirrors maxPool2_flat_hasFDerivAt; the one structural change is that the gap function and the domination argument branch on whether the two offsets name the same position (the clamped duplicate), not on whether the offsets are equal — MaxPool3s2Smooth says nothing about coincident positions because there the values are literally the same number.

                theorem Proofs.pdiv3_maxPool3s2_smooth {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool3s2Smooth x) (ci : Fin c) (hi_in : Fin (2 * h)) (wi_in : Fin (2 * w)) (co : Fin c) (ho : Fin h) (wo : Fin w) :

                Smooth-point Jacobian. pdiv3 is the 0/1 indicator that the local reindex sends output (co, ho, wo) to input (ci, hi_in, wi_in).

                ⚠⚠ This is where the overlapping case genuinely differs from maxPool2, and it differs by being SIMPLER to state. pdiv3_maxPool2_smooth decodes the condition into co = ci ∧ ho = winRow hi_in ∧ wo = winCol wi_in ∧ IsArgmax — legitimate there because each input has exactly ONE owning window, so winRow/winCol name it. Here an input has up to two windows per axis and no such decoding exists. Leaving the condition as the reindex equation is both correct and shorter; the accumulation then happens in correct's sum over outputs, with no extra argument.

                noncomputable def Proofs.maxPool3s2_has_vjp_at3 {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool3s2Smooth x) :

                The VJP witness. The backward accumulates dy over every output whose window selects this input — at most 4 of them (win3Row_mem_le_two squared). maxPool2's peer is a single lookup; this is the same statement without the disjointness collapse.

                Equations
                • One or more equations did not get rendered due to their size.
                Instances For
                  noncomputable def Proofs.maxPool3s2Flat (c h w : ) :
                  Vec (c * (2 * h) * (2 * w))Vec (c * h * w)

                  Flattened 3×3/s2 pool, the Vec-level form the codegen denotes.

                  Equations
                  Instances For
                    theorem Proofs.maxPool3s2Flat_differentiableAt {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool3s2Smooth x) (hc : 0 < c) (hh : 0 < h) (hw : 0 < w) :
                    noncomputable def Proofs.maxPool3s2Flat_has_vjp_at {c h w : } (x : Tensor3 c (2 * h) (2 * w)) (h_smooth : MaxPool3s2Smooth x) :
                    Equations
                    Instances For
                      theorem Proofs.maxPool3s2Flat_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)) :
                      |maxPool3s2Flat c h w v k| A

                      Flattened magnitude bound — the form floatClose_maxPool3s2 (FloatComposeBridge.lean) threads (maxPoolFlat_abs_le's peer).

                      theorem Proofs.maxPool3s2Flat_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)) :
                      |maxPool3s2Flat c h w vt k - maxPool3s2Flat c h w va k| e

                      Flattened closeness — maxPoolFlat_close's peer.