Documentation

LeanMlir.Proofs.Codegen.ResNet50RenderB

ResNet-50 train step rendered from the verified AST, at the BATCHED index #

R50 phase 2 (planning/archive/next_session_pipeline_then_r50.md §3.2). The bottleneck peer of ResNet34RenderB.lean, block for block: batch BN (bnBatchF, reduced over [0,2,3]), the whole graph at N := B, the un-fused *GradB parameter gradients, and the proven AdamW / heavy-ball tail — optOne/optConstsB are imported, not copied, so the optimizer has one definition across both nets.

The three block forms, and why the third exists #

ResNet50FullBVJP.lean carries the certified VJPs; this file renders them.

rendererblockwhere
bnkIdFwdB / bnkIdBackGradBidentity, oc → mid → mid → oc12 blocks
bnkProjFwdB / bnkProjBackGradBstride-1 projectionstage 1 block 0 only
bnkStridedFwdB / bnkStridedBackGradBstrided projectionstages 2/3/4 block 0

The stride is on the 3×3 (W2), not the leading 1×1 — v1.5 / torchvision, which is what jax/MainResnet50Imagenet.lean trains. So in the strided block conv1, bn1 and relu1 all run at the input resolution 2hh, and only conv2 decimates. That asymmetry is why the strided renderer carries two sets of zero-vectors (zIn/zMidIn at 2hh, zMid/zOut at hh) where the other two carry one.

mid = oc / 4, so an identity block's mid is a QUARTER of its channel count, not a multiple. VerifiedSpec.bottleneckStageSpec already computes it that way and already selects the projecting form when stride != 1 || ic != oc — which is exactly how stage 1 (64→256 at stride 1) gets a projection. This file's block sequence must agree with that spec's ORDER, because the driver packs [θ|m|v] off net.specs; r50SigList below is the render's side of that contract and #guards pin the counts.

⚠ No conv biases #

Every conv here is bias-free (convBnNB in the spec — 9 tensors per identity block, 12 per projection block), matching R34's ImageNet render. The convBias plumbing R34's renderer carries for its CIFAR-era artifacts is deliberately absent rather than threaded and passed false.

⚠⚠ There is no incumbent hand-written R50 render to tie against #

§3.2. Every other net's swap onto the verified renderer was licensed by a bit-exact numeric tie against the hand-written artifact it replaced. R50 has no such artifact, so that license does not exist here and must not be implied. The substitutes are the layer-level VJP oracle (tests/vjp_oracle/run.sh) and a keep-1 known-answer check, and whichever one is used has to be named in the commit that ships a number off this render.

The 161 R50 parameter inputs, in func-arg order: stem (3), the [3,4,6,3] bottleneck stages, then the head. Single source for %p, %pm, %pv and the return order.

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

    The same list as MLIR types. Derived, so the shapes have one definition.

    Equations
    Instances For

      The 106 running-stat inputs — 53 BN layers × (μ, var), μ and var interleaved per layer, which is how the driver packs runningBnStats off bnChannels.

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

        Saved forward SSA names the bottleneck's backward + gradient passes reference. The 3-conv peer of BFwdB: two interior ReLUs (r1, r2) where the basic block has one.

        Instances For

          R50's stochastic-depth site count — one per bottleneck block, [3,4,6,3] = 16.

          ⚠ It is the count the RAMP's denominator is read against (totalDrop − 1 = 15), so it is a definition rather than a literal 16 sprinkled at three sites.

          Equations
          Instances For

            The %dp0 … %dp15 mask arguments, tensor<Bxf32> each — appended to the train step's signature only under sd. Mirrors cnxDropSig.

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

              some i under sd, none otherwise — the per-block ramp index handed to the six block emitters. ⚠ The index is the BLOCK index and the two coincide here, because every R50 bottleneck drops; EfficientNet's do not (its reference advances the ramp counter inside a skip guard), which is why efficientnetVerified.dropKeeps is a literal array and this is a range. ⚠ At sd := false this is none at every site and NOT ONE pretty call happens, so every committed R50 artifact re-renders byte-identically.

              Equations
              Instances For

                Everything the whole-net render needs out of ONE forward traversal of ResNet-50: the emitted code, the logits, and every saved activation the backward reads.

                ⭐⭐ This exists so @resnet50_fwd and @resnet50_adam_train_step cannot be different nets. They were: resnet50FwdFaithfulV built its forward from the PER-EXAMPLE chain (r50FwdChain, which reaches bnSite and therefore bnPerChannelF, reduce [2,3], divisor H·W) while the train step is batch BN (reduce [0,2,3], divisor B·H·W). Its own docstring claimed "the same forward the train step differentiates", which was the invariant that did not hold. scripts/regen_verified_mlir.sh check could not see it: it only ever paired a forward with the SGD train step, and R50 has none (planning/archive/mnv4_verified.md §3d(b)).

                ⚠ The eval forward is deliberately NOT moved onto this chain. bnEval reads frozen per-channel statistics as graph inputs, so it is the same arithmetic in either vocabulary and has no BN-world to disagree about — and it is the artifact that actually scores, so leaving its bytes untouched keeps 89.86% (runs/r50_imagenette_adam_80ep.log) exactly where it is.

                Instances For
                  def Proofs.StableHLO.r50FwdChainB (B nClasses : ) (epsStr : String) (q : := 7) (bf16 sd : Bool := false) (replicas : := 1) (sync : Bool := false) :

                  The ResNet-50 forward chain at the BATCHED index — one traversal, consumed by both @resnet50_fwd and the train step that differentiates it.

                  Equations
                  • One or more equations did not get rendered due to their size.
                  Instances For
                    def Proofs.StableHLO.resnet50TrainStepFaithfulB (B nClasses : ) (epsStr : String) (replicas : := 1) (opt : R34Opt := R34Opt.adamw) (slug : String := "resnet50in") (bce : Bool := false) (wdStr : String := "") (q : := 7) (wdExclude gradClip : Bool := false) (clipNorm : Float := 1.0) (bf16 ema sd forceSync noSync : Bool := false) :

                    ResNet-50 [3,4,6,3] bottleneck train step, batch-BN, rendered at N := B.

                    161 θ / 161 m / 161 v, %lr/%bc1/%bc2, 106 running-stat slots and %onehot in; 161 θ' / 161 m' / 161 v', %loss/%bc1/%bc2 and 106 batch stats out. Parameter ORDER comes from r50SigList, the same single source the signature and the return list use, so the arity/order contract cannot drift within this file — and it is written to agree with VerifiedSpec.bottleneckStageSpec, which is what the DRIVER walks.

                    ⚠ The block sequence is [3,4,6,3] with block 0 of every stage projecting. Stage 1's projects at stride 1 (bnkProjFwdB); stages 2/3/4 project strided.

                    Instances For

                      ⚠ These live here rather than in a ResNet50Render.lean peer of R34's split. That split is historical — R34 has a per-example-BN net AND a batch-BN net, and they are different functions, so they get different files. R50 has only the batch-BN net, so one file keeps r50SigList and every consumer of it together.

                      ⚠⚠ The train and eval forwards MUST be the same chain with one switch, which is why bnSite (shared with R34, now public) takes the mode rather than each render spelling its BN. §2g's mobilenetv2_fwd defect was exactly this drifting: a net trained with one pool and scored with another, logits rel 1.86, silent.

                      def Proofs.StableHLO.resnet50FwdFaithfulV (B nClasses : ) (epsStr : String) (slug : String := "resnet50in") (q : := 7) (vSuffix : String := "") :

                      @resnet50in_fwd — 162 inputs (%x + 161 params), logits [B, nClasses]. Batch-statistic BN, i.e. the same forward the train step differentiates.

                      ⚠⚠ That sentence was FALSE until 2026-08-10 and is now enforced. This built its forward from r50FwdChain .train, which reaches R34's shared bnSite and therefore bnPerChannelF — reduce [2,3], divisor H·W, PER-EXAMPLE — while resnet50TrainStepFaithfulB is bnBatchF, reduce [0,2,3], divisor B·H·W. Two different functions under one net's name (§3d(b)), and regen_verified_mlir.sh check could not see it because it only ever paired a forward with the SGD train step and R50 has none. It now renders from r50FwdChainB — literally the traversal the train step differentiates — so check_adam_prefix holds it as a byte prefix.

                      r50FwdChain's .train branch is now UNUSED by R50 and must stay that way; it survives only because .eval shares the function. Rendering a forward from it reopens the split.

                      Equations
                      • One or more equations did not get rendered due to their size.
                      Instances For
                        def Proofs.StableHLO.resnet50FwdEvalFaithfulV (B nClasses : ) (epsStr : String) (slug : String := "resnet50in") (q : := 7) (vSuffix : String := "") :

                        @resnet50in_fwd_eval — the inference forward, every BN site reading frozen running stats. 161 params + 106 stat inputs + %x = 268 inputs. This is what the driver scores through, so its BN order must match r50StatSigList, which it does by sharing the chain.

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