Backward-graph faithfulness for the VERIFIED ResNet-50 bottleneck block #
The R50 peer of ResNet34BackB0.lean, and the step planning/archive/mnv4_verified.md §8 calls
<blk>BackBatchedGraph + <blk>BackBatchedGraph_faithful — the two theorems that make the
render's backward the certified one. R50 shipped a trained number (89.86%, Imagenette) off a
certified renderer with no whole-net backward at all; this file is that gap.
⚠⚠ WHAT §8 GOT WRONG — "R50 is one step from done" was measured against the WRONG phase 1 #
§8 records R50's block-level VJP as ✓ (ResNet50BlocksCertified.lean, retired 2026-09-19) and concludes the
job is only (2) + (3). That certificate is real, but it is for the per-channel, non-batched
forms — bblkPC / bblkPProjPC / bblkPStridedPC are built from bnPerChannelTensor3 and plain
flatConv, with no N. The backward-graph vocabulary is batched: bnBatchLA, batchMap,
convBackBatched. Grepped before starting: there is no batched R50 block VJP anywhere in the
repo. So phase 1 had to be redone in the batched world here, exactly as §8 says MNv4 needs — R50
was two steps from done, not one.
⭐ It was still cheap, and for the reason §1 of the R50 file already gives: every stage this
needs already exists. ResNet34BackB0 builds its own batched stages rather than lifting the PC
ones, and those stages are generic in {ic oc h w kH kW}, so R50 reuses all four verbatim:
| stage | what R50 uses it for | from |
|---|---|---|
cbReluB | the 1×1 reduce AND the 3×3 (stride-1 blocks) | ResNet34BackB0 |
cbReluStridedB | the 3×3 in a downsample block | ResNet34BackB0 |
projB | the 1×1 expand (no activation) and the stride-1 skip | EfficientNetBackB0 |
projStridedB | the strided projection skip | ResNet34BackB0 |
Zero new stages, zero new SHlo ops, zero new VJP obligations. The bottleneck's third conv is
one more CertLayer.comp, and the whole file is composition — §6's "a family from one
constructor" landing on R50's backward the way §3i records it landing on MNv4's.
The three forms, and why the third exists #
| form | where in R50 | R34 analogue |
|---|---|---|
r50Bottleneck… — identity | 12 blocks | r34BasicBlock… |
r50DownBlock… — strided projection | stages 2/3/4, block 0 | r34DownBlock… |
⭐ r50ProjBlock… — stride-1 projection | stage 1 block 0 ONLY | ⛔ none |
R34 never needed the third: its stage 1 is ic = oc = 64, so block 0 is an identity block. R50's
stage 1 goes 64 → 256 at stride 1 — the channels change so it needs a projection, the resolution
does not so that projection is not strided. ⚠ r50DownBlock cannot be substituted for it: its type
reads Vec (N * (ic * (2*h) * (2*w))) → Vec (N * (oc * h * w)), so the halving is in the
signature and the substitution is a shape error. Reaching for the identity form instead is the
dangerous one — an identity skip where a projection belongs is well-typed only if ic = oc.
⚠ THE STRIDE IS ON THE 3×3, NOT THE LEADING 1×1 #
r50DownBody puts cbReluStridedB on the second conv. That is ResNet v1.5 / torchvision,
which is what jax/MainResnet50Imagenet.lean trains. The v1 placement (stride on the leading 1×1)
compiles, trains and descends — and is a different net (§3's trap, and VerifiedSpec.lean:46
records it costing ~0.5 pt of top-1). The leading 1×1 therefore runs at the INPUT resolution
(2*h)×(2*w) and carries mid channels there until W₂ decimates.
Relu, and why every statement here is _at #
R50 is relu throughout, so — per §8's design note — the VJPs are pointwise and hypothesis-threaded
via vjp_comp_at, never the global form. A bottleneck has three kinks, not the basic block's
two: the two interior relus (h_s1, h_s2) and the outer post-residual relu (h_out). The
per-op backward token is .selectPos, whose faithfulness is the already-proven selectPos_faithful.
Structure #
r50BodyBackBatchedGraph— the stride-1 bottleneck bodyprojB ∘ cbReluB ∘ cbReluB's backward graph (r50DownBodyBackBatchedGraphis its strided peer).r50BottleneckBackBatchedGraph_faithful— CAPSTONE 1, the identity blockrelu ∘ residual(F).r50ProjBlockBackBatchedGraph_faithful— CAPSTONE 2,relu ∘ residualProj(projB, F), the form with no R34 analogue.r50DownBlockBackBatchedGraph_faithful— CAPSTONE 3, the strided projection blockrelu ∘ residualProj(projStridedB, F_s).
Each block is a CertLayer (r50BottleneckLayer, r50ProjBlockLayer, r50DownBlockLayer)
composed from ResNet34BackB0's stage layers, and each capstone is that layer's faithful.
The batched R50 bottleneck body backward graph: the three stage graphs chained at their
cumulative forward activations (cbReluB⁻¹ ∘ cbReluB⁻¹ ∘ projB⁻¹).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The identity bottleneck as a CertLayer: residual (cbReluLayer ; cbReluLayer ; projLayer),
then reluOut. ⭐ An endomorphism (ic = oc, resolution unchanged), which is what lets
CertLayer.chain iterate it — a stage tail is n of these.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The batched R50 identity bottleneck's VJP at a smooth point — relu ∘ residual(F) with body
F = projB ∘ cbReluB ∘ cbReluB: the residual fan-in VJP, then the OUTER relu's pointwise VJP
at the pre-relu activation residual(F)(x) (r50BottleneckLayer's VJP). ⚠ h_s2 is stated
at the SECOND stage's pre-relu activation, which lives at cbReluB … x — writing it at x
typechecks nowhere.
Three smoothness families, where r34BasicBlockB_has_vjp_at needs two: the bottleneck's extra
interior conv brings its own relu.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The whole batched R50 identity bottleneck backward graph: selectPos (outer relu) ∘ residual
fan-in (body backward + identity skip). Same shape as r34BasicBlockBackBatchedGraph — the
outer relu is the LAST forward op, so its .selectPos is the OUTERMOST backward op, and the
masked cotangent feeds BOTH the body chain and the verbatim %dy skip.
Equations
- One or more equations did not get rendered due to their size.
Instances For
CAPSTONE 1 — the whole batched R50 identity bottleneck: backward graph ↔ the proven VJP.
The three batched stage backward graphs chained at their forward activations, wrapped in the
residual additive fan-in (body cotangent + identity skip) and the OUTER post-residual relu,
proven equal to r50BottleneckB_has_vjp_at. The 3-conv peer of
r34BasicBlockBackBatchedGraph_faithful, threaded through all three relu smoothness families.
Key fact, unchanged from R34: the outer relu's .selectPos mask is applied ONCE to the
incoming dy, and that masked cotangent is what the residual fan-in sees. It is
r50BottleneckLayer's faithful.
⭐ The stride-1 projection bottleneck as a CertLayer — R50 stage 1 block 0, the form with
no R34 analogue: residualProj (projLayer) (cbReluLayer ; cbReluLayer ; projLayer), then
reluOut. Changes channels, keeps resolution, so it is NOT an endomorphism and composes via
comp rather than chain.
Equations
- One or more equations did not get rendered due to their size.
Instances For
⭐ The batched R50 stride-1 projection bottleneck's VJP at a smooth point —
relu ∘ residualProj(projB, F) with body F = projB ∘ cbReluB ∘ cbReluB and a stride-1
bn∘conv projection skip.
⚠ This is the form with no R34 analogue, and it exists in exactly one place in R50: stage 1
block 0, where channels go 64 → 256 but the resolution does not change. R34's stage 1 is
ic = oc = 64, so its block 0 is an identity block and this shape never arises.
Structurally it is CAPSTONE 1 with residual (identity skip) replaced by residualProj (both
paths nontrivial), and CAPSTONE 3 with every stride-2 op replaced by its stride-1 peer.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The whole batched R50 stride-1 projection bottleneck backward graph: selectPos (outer relu) ∘
projected-residual fan-in (stride-1 projection-skip backward + body backward). Unlike the
identity block, both addV operands are nontrivial backward subgraphs; unlike CAPSTONE 3, the
skip is projBackBatchedGraph, not its strided sibling.
Equations
- One or more equations did not get rendered due to their size.
Instances For
CAPSTONE 2 — the whole batched R50 STRIDE-1 PROJECTION bottleneck: backward graph ↔ the
proven VJP. The R50-only block form (stage 1 block 0), with no R34 analogue to mirror. It is
r50ProjBlockLayer's faithful.
The batched R50 downsample body backward graph: the three stage graphs chained at their
cumulative forward activations. convStridedBackBatched appears at the 3×3, matching the
forward's stride placement.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The strided projection bottleneck as a CertLayer — stages 2/3/4, block 0:
residualProj (projStridedLayer) (cbReluLayer ; cbReluStridedLayer ; projLayer), then
reluOut. Halves the resolution, which is why its input type carries 2*h/2*w. ⚠ The stride
is on the 3×3 (cbReluStridedLayer at W₂), so h_s1 is stated at the input resolution and
h_s2 at the output one.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The batched R50 strided projection bottleneck's VJP at a smooth point —
relu ∘ residualProj(projStridedB, F_s). The R50 peer of r34DownBlockB_has_vjp_at, with the
bottleneck's third conv and its extra relu family.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The whole batched R50 strided projection bottleneck backward graph: selectPos (outer relu) ∘
projected-residual fan-in (strided projection-skip backward + body backward).
Equations
- One or more equations did not get rendered due to their size.
Instances For
CAPSTONE 3 — the whole batched R50 STRIDED PROJECTION bottleneck: backward graph ↔ the proven
VJP. With CAPSTONES 1 and 2 this closes every block form in ResNet-50: 12 identity blocks,
3 strided projections and the one stride-1 projection. It is r50DownBlockLayer's
faithful.