Documentation

LeanMlir.Proofs.Nets.MobileNet.MobileNetV4BackB0

MobileNetV4 — the batched UIB backward, and the four families as one chain #

The block- and stage-level backward of MobileNetV4-Conv-M at the batched index: the depthwise stages, the UIB body, the stride-2 body, the fused stage and the head, each a CertLayer whose backward graph is faithful to its certified VJP. The identity skip is CertLayer.residual, applied by the caller.

The four families are one chain #

The UIB body is preDW? → expand → postDW? → project, and k = 0 omits a depthwise. Both depthwise positions are channel- and shape-preserving — preDW : ic → ic, postDW : mid → mid — so an absent one is the identity layer (CertLayer.id') in the same slot:

familyprepostas a chain
ExtraDW✓✓chain [preDW, expand, postDW, project]
ConvNeXt-like✓✗chain [preDW, expand, id', project]
IB / MBConv✗✓chain [id', expand, postDW, project]
FFN✗✗chain [id', expand, id', project]

mnv4UibBody takes the two depthwise slots as CertLayer arguments and the caller passes id' where the table says k = 0 — one body, no case split, and no dispatch that could disagree with the forward's (mnv4BodyOfRow reads the slots off the UibSpec row).

The two depthwise stages, and where the activation is #

timm's mobilenetv4_conv_medium (the pinned spec, checked by scripts/parity/mnv4_timm_parity.py): the pre-DW (dw_start) is depthwise → BN with no activation (dwbB, globally certified); the post-DW (dw_mid) is depthwise → BN → relu (dwbReluB, and its strided peer dwbReluBstrided). Relu, not relu6 — MobileNetV2 uses relu6 one file over. The relu stage is bnReluStageHasVJPAt (Foundation/Batched/StageLayers, generic in the op) at depthwiseFlat; its backward graph masks with .selectPos, relu's one-sided mask.

Contents #

The net level consumes these block by block: the whole-net forward, graph and VJP are MobileNetV4FullB / MobileNetV4FullBVJP, the train-step tie is MobileNetV4StepTieB, and the input-gradient tie is MobileNetV4WholeBackCertifiedTieB.

The stem is not here and is not a CertLayer: no render emits a gradient into %x, so there is no backward graph for it, and a net-level forward composes the stem's VJP by vjpCompAt rather than CertLayer.comp.

@[reducible]
noncomputable def Proofs.StableHLO.dwbB (N : ℕ) {c h w kH kW : ℕ} (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (γ β : Vec c) :
Vec (N * (c * h * w)) → Vec (N * (c * h * w))

Batched depthwise → bn stage, no activation — MobileNetV4's pre-DW (dw_start in timm's UniversalInvertedResidual, whose BatchNormAct2d carries Identity).

Equations
Instances For
    noncomputable def Proofs.StableHLO.mnv4DWBnLayer (N : ℕ) {c h w kH kW : ℕ} (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
    CertLayer (N * (c * h * w)) (N * (c * h * w))

    The pre-DW stage as a CertLayer. Globally certified (ok = True): with no activation there is no kink — the projLayer situation, at a depthwise.

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For
      @[reducible]
      noncomputable def Proofs.StableHLO.dwbReluB (N : ℕ) {c h w kH kW : ℕ} (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (γ β : Vec c) :
      Vec (N * (c * h * w)) → Vec (N * (c * h * w))

      Batched depthwise → bn → relu stage. Plain relu, not relu6: MNv4's UIB blocks use relu where MobileNetV2's use relu6, and dwbrB (one file over) is the relu6 one.

      Equations
      Instances For
        @[reducible]
        noncomputable def Proofs.StableHLO.dwbReluBstrided (N : ℕ) {c h w kH kW : ℕ} (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (γ β : Vec c) :
        Vec (N * (c * (2 * h) * (2 * w))) → Vec (N * (c * h * w))

        Batched STRIDE-2 depthwise → bn → relu stage — the post-DW of a downsampling UIB block, which carries its stride (timm's dw_mid).

        Equations
        Instances For
          noncomputable def Proofs.StableHLO.mnv4DWReluLayer (N : ℕ) {c h w kH kW : ℕ} (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
          CertLayer (N * (c * h * w)) (N * (c * h * w))

          A depthwise-bn-relu stage as a CertLayer — the stride-1 post-DW (c := mid). The pre-DW is mnv4DWBnLayer above: same depthwise, no activation.

          Equations
          • One or more equations did not get rendered due to their size.
          Instances For
            noncomputable def Proofs.StableHLO.mnv4UibBody (N : ℕ) {ic mid oc h w : ℕ} (preDW : CertLayer (N * (ic * h * w)) (N * (ic * h * w))) (expand : CertLayer (N * (ic * h * w)) (N * (mid * h * w))) (postDW : CertLayer (N * (mid * h * w)) (N * (mid * h * w))) (project : CertLayer (N * (mid * h * w)) (N * (oc * h * w))) :
            CertLayer (N * (ic * h * w)) (N * (oc * h * w))

            The UIB body, for all four families at once.

            preDW and postDW are CertLayer arguments, so the caller passes mnv4DWReluLayer where the block table has k > 0 and CertLayer.id' where it has k = 0. ExtraDW, IB, ConvNeXt-like and FFN are then four applications of this one definition, not four proofs.

            This is only possible because both depthwise positions are shape-preserving: an absent depthwise leaves the chain's types unchanged, so id' slots in without a case split.

            Equations
            Instances For

              The stride-1 collapse does not extend here, and the reason is the type.

              At stride 1 an absent depthwise is id' because the slot is shape-preserving. At stride 2 the depthwise that carries the stride maps (2h, 2w) ↦ (h, w) — a different type — so it cannot be replaced by an identity. timm puts the stride on dw_mid whenever a block has one, so in all three Conv-M downsamples (rows 1, 3, 11, all ExtraDW) the optional pre-DW and the expand run at the INPUT resolution 2h and the strided post-DW takes it to h. The pre-DW is still a slot (at 2h). Putting the stride on the pre-DW instead (expand at h) is a different function with the same parameter shapes.

              All three stride-2 blocks change channels (ic ≠ oc), so none has a skip: the block IS the body, with no CertLayer.residual wrapper. Adding one would not typecheck, which is the good case.

              noncomputable def Proofs.StableHLO.mnv4DWReluStridedLayer (N : ℕ) {c h w kH kW : ℕ} (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
              CertLayer (N * (c * (2 * h) * (2 * w))) (N * (c * h * w))

              The STRIDE-2 depthwise-bn-relu stage as a CertLayer — the depthwise that carries a UIB block's stride. Not an endomorphism (that is the whole point), so it composes via comp.

              Equations
              • One or more equations did not get rendered due to their size.
              Instances For
                noncomputable def Proofs.StableHLO.mnv4UibStridedBody (N : ℕ) {ic mid oc h w : ℕ} (preDW : CertLayer (N * (ic * (2 * h) * (2 * w))) (N * (ic * (2 * h) * (2 * w)))) (expand : CertLayer (N * (ic * (2 * h) * (2 * w))) (N * (mid * (2 * h) * (2 * w)))) (postDW : CertLayer (N * (mid * (2 * h) * (2 * w))) (N * (mid * h * w))) (project : CertLayer (N * (mid * h * w)) (N * (oc * h * w))) :
                CertLayer (N * (ic * (2 * h) * (2 * w))) (N * (oc * h * w))

                Stride-2 UIB body: the optional pre-DW and the expand at the input resolution 2h, the post-DW carrying the stride to h, the project at h.

                Equations
                Instances For

                  MNv4's stage 0 is .fusedMbConv 32 48 4 3 2 1 false .relu (timm's EdgeResidual): a regular k×k conv (not a depthwise) doing expansion and downsampling at once, then a 1×1 project. 32 → mid = 32·4 = 128 → 48, stride 2, symmetric padding, relu — so the strided conv-bn-relu stage is cbReluStridedLayer and the project is projLayer (both Batched.StageLayers).

                  noncomputable def Proofs.StableHLO.mnv4FusedStage (N : ℕ) {ic mid oc h w : ℕ} (fusedConv : CertLayer (N * (ic * (2 * h) * (2 * w))) (N * (mid * h * w))) (project : CertLayer (N * (mid * h * w)) (N * (oc * h * w))) :
                  CertLayer (N * (ic * (2 * h) * (2 * w))) (N * (oc * h * w))

                  MNv4's fused stage (stage 0) — the strided k×k conv-bn-relu, then the 1×1 project. No skip: ic = 32 ≠ 48 = oc and stride 2, so the stage IS the body.

                  Equations
                  Instances For

                    timm's head: Conv-M's cn_r1_k1_s1_c960 (1×1 256 → 960, BN, relu) at 7×7, then global pool, then conv_head (1×1 960 → 1280) → norm_head (BN) → relu on the POOLED [N, 960, 1, 1], then the classifier. The second conv-bn-relu is cbReluLayer at h = w = 1, so its BatchNorm normalises over the batch alone. Running conv_head at 7×7 before the pool would be a different function: batch BN and relu do not commute with the pool.

                    noncomputable def Proofs.StableHLO.castLayer {n m : ℕ} (h : n = m) :

                    An index relabelling as a CertLayer: the same vector read at m instead of n along a proved n = m. The forward is the Fin.cast gather, the VJP reindexHasVJP, and the graph castIdx, which emits no text (skel erases indices). The head needs two: the pooled [N, c] is conv_head's [N, c, 1, 1], and its [N, oc, 1, 1] output is the classifier's [N, oc]; c * 1 * 1 and c are equal but not definitionally so at a variable c.

                    Equations
                    • One or more equations did not get rendered due to their size.
                    Instances For
                      theorem Proofs.StableHLO.castLayer_fwd_apply {n m : ℕ} (h : n = m) (v : Vec n) :
                      (castLayer h).fwd v = fun (j : Fin m) => v (Fin.cast ⋯ j)
                      theorem Proofs.StableHLO.mnv4_pool11 (N c : ℕ) :
                      N * c = N * (c * 1 * 1)

                      The pooled [N, c] read as [N, c, 1, 1].

                      noncomputable def Proofs.StableHLO.mnv4Head (N : ℕ) {c mid oc nC h w : ℕ} (headConv : CertLayer (N * (c * h * w)) (N * (mid * h * w))) (gap : CertLayer (N * (mid * h * w)) (N * mid)) (convHead : CertLayer (N * (mid * 1 * 1)) (N * (oc * 1 * 1))) (cls : CertLayer (N * oc) (N * nC)) :
                      CertLayer (N * (c * h * w)) (N * nC)

                      MNv4's head: conv-bn-relu, GAP, conv_head-bn-relu on the pooled features, classifier, with the two 1×1 relabellings between. Only the two conv stages carry a smoothness condition (their relus); GAP, the casts and dense are global.

                      Equations
                      Instances For

                        What this section fixes. Above, mnv4UibBody takes its depthwise slots as arguments, so passing id' where block 4's real pre-DW belongs is well-typed and still certified — graph and VJP both move with the caller's arguments, so the theorem stays true about the wrong net. Types catch the stride split (resolution is in the type); they catch nothing about k = 0 vs k > 0, because that slot is shape-preserving — the very property that made the collapse possible.

                        The fix is to make the slot a function of the block table's k, so the proof side runs the same k = 0 dispatch the render does, off the same mnv4Blocks list — one table, not two readings. mnv4-fwd-smoke already pins the render against that table; these #guards pin the table's own shape, so a bad edit fails at lake env lean rather than becoming a silent net.

                        noncomputable def Proofs.StableHLO.mnv4PreDWSlot (N : ℕ) {c h w kH kW : ℕ} (preDWk : ℕ) (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
                        CertLayer (N * (c * h * w)) (N * (c * h * w))

                        The pre-depthwise slot, dispatched on the table's preDWk. k = 0 ⇒ id' — the same rule uibFwdSkipB emits, computed rather than chosen. k > 0 ⇒ the BN-only mnv4DWBnLayer.

                        Equations
                        Instances For
                          noncomputable def Proofs.StableHLO.mnv4PostDWSlot (N : ℕ) {c h w kH kW : ℕ} (postDWk : ℕ) (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
                          CertLayer (N * (c * h * w)) (N * (c * h * w))

                          The post-depthwise slot, same dispatch on postDWk.

                          Equations
                          Instances For
                            theorem Proofs.StableHLO.mnv4PreDWSlot_of_eq_zero (N : ℕ) {c h w kH kW k : ℕ} (hk : k = 0) (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
                            mnv4PreDWSlot N k W b ε hε γ β = CertLayer.id' (N * (c * h * w))
                            theorem Proofs.StableHLO.mnv4PreDWSlot_of_ne_zero (N : ℕ) {c h w kH kW k : ℕ} (hk : k ≠ 0) (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
                            mnv4PreDWSlot N k W b ε hε γ β = mnv4DWBnLayer N W b ε hε γ β
                            theorem Proofs.StableHLO.mnv4PostDWSlot_of_eq_zero (N : ℕ) {c h w kH kW k : ℕ} (hk : k = 0) (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
                            mnv4PostDWSlot N k W b ε hε γ β = CertLayer.id' (N * (c * h * w))
                            theorem Proofs.StableHLO.mnv4PostDWSlot_of_ne_zero (N : ℕ) {c h w kH kW k : ℕ} (hk : k ≠ 0) (W : DepthwiseKernel c kH kW) (b : Vec c) (ε : ℝ) (hε : 0 < ε) (γ β : Vec c) :
                            mnv4PostDWSlot N k W b ε hε γ β = mnv4DWReluLayer N W b ε hε γ β

                            The four families, named — read off the two kernel slots by exactly the rule the slots dispatch on and the render emits.

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

                                Which family a table row denotes.

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

                                  The gap this closes. A block builder that reads the dispatch from the table but takes its weights as separate arguments lets a caller pair row 4's ks with row 7's widths. The dispatch was table-driven; the wiring was not.

                                  Fix: index the parameter record by the row. Every width in UibParams s is a projection of s — s.ic, s.oc, s.ic * s.expand, s.preDWk, s.postDWk. A record with widths that disagree with its row cannot be constructed, so the block builder needs no side conditions and no #guard: it is impossible by typing rather than checked after the fact.

                                  What this still does not pin. Rows with the same (ic, oc, expand, preDWk, postDWk, h) — {4, 5, 7}, {8, 10}, {12, 18}, {13, 14} and {15, 19, 20} — have records of the same type, so swapping two such blocks' weights typechecks. Typing pins shape (channels, expand ratio, kernel extents, resolution); it cannot pin identity between rows that are shape-identical. Closing that needs the weights to come from one indexed array — a renderer concern, since the render already folds mnv4Blocks in order.

                                  A depthwise-BN stage's parameters at kernel extent k: kernel, conv bias, the BN ε with its positivity, and the BN affine.

                                  Instances For

                                    A depthwise slot's parameters, typed by its kernel extent. None at k = 0, where the slot is id' and nothing reads a weight; a DWBnParams at k > 0. Two records that agree on every weight the net reads are therefore equal.

                                    Equations
                                    Instances For

                                      The slot's parameters as a DWBnParams at any k: the stored ones at k > 0; at k = 0, a fixed placeholder (zero kernel, ε = 1) that the id' slot never reads.

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

                                        Store a DWBnParams in a slot (dropped at k = 0).

                                        Equations
                                        Instances For

                                          One UIB block's parameters, typed by its table row. Every width is a projection of s, so a record whose widths disagree with the row is not constructible, and the two depthwise slots carry parameters only when the row's kernel is nonzero (DWSlot). Bias-free convs still carry a b because the stage vocabulary takes one; the render binds it to %zb{c}.

                                          Instances For

                                            The slot parameters by name, read through DWSlot.params (placeholders at k = 0).

                                            Equations
                                            Instances For
                                              Equations
                                              Instances For
                                                Equations
                                                Instances For
                                                  Equations
                                                  Instances For
                                                    Equations
                                                    Instances For
                                                      Equations
                                                      Instances For
                                                        Equations
                                                        Instances For
                                                          noncomputable def Proofs.StableHLO.mnv4BodyOfRow (N : ℕ) (s : UibSpec) (p : UibParams s) :
                                                          CertLayer (N * (s.ic * s.h * s.h)) (N * (s.oc * s.h * s.h))

                                                          A UIB body built entirely from its table row. Dispatch from s.preDWk/s.postDWk, widths and resolution from s, weights from a record that cannot disagree with s. Nothing here is a free argument: given s, the only freedom left is the numeric values.

                                                          This is the body (ic -> oc). The identity skip is CertLayer.residual on top and needs oc = ic, which holds for exactly the eighteen non-stride2 rows (guarded below) — applied by the caller at a concrete row, where it is rfl and needs no transport.

                                                          Equations
                                                          • One or more equations did not get rendered due to their size.
                                                          Instances For
                                                            noncomputable def Proofs.StableHLO.mnv4StridedBodyOfRow (N : ℕ) (s : UibSpec) (p : UibParams s) :
                                                            CertLayer (N * (s.ic * (2 * s.h) * (2 * s.h))) (N * (s.oc * s.h * s.h))

                                                            A strided body built entirely from its table row — mnv4BodyOfRow's sibling for the three stride-2 rows (1, 3, 11). The pre-DW is a slot at the input resolution 2h, dispatched on s.preDWk; the post-DW carries the stride and so is NOT a slot (it cannot be id', which is why every strided row must have one — guarded above).

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