Per-channel BatchNorm (Chapter 5 Milestone B8) — the block-diagonal VJP #
Chapters 4–5 used a per-example global BatchNorm: one scalar (γ, β) over the
whole oc·h·w activation (LayerNorm-shaped). Real ResNet wants per-channel BN:
normalize each channel-slice independently with its own (γ_c, β_c), γ/β : Vec oc.
Because each channel is independent, the whole Jacobian is block-diagonal across
the channel axis — the genuinely-new piece. We get it for free by generalizing the
existing rowwise_has_vjp_mat (Tensor.lean, multi-head attention) from a single
per-row map to a per-row family g : Fin m → (Vec n → Vec p): viewing the
activation as Mat oc (h·w) (row = channel), per-channel BN is exactly
fun A => fun c => bnForward (h·w) ε (γ c) (β c) (A c). Its VJP runs each channel's
bn_has_vjp on that channel's cotangent slice; the cross-channel blocks vanish.
The file also holds inference BN (frozen statistics), batch BN on the [N,C,H,W] layout
(bnBatchTensor4, chapter 7) and the sync-BN op at supplied statistics (bnSyncTensor4, its
γ and input gradients) — the forms StableHLO's den reads. Sharding that layout across
replicas (batchShard and the shard = global identities) is DataParallelSync's.
Everything closes under [propext, Classical.choice, Quot.sound].
Row-wise lifting of a per-row HasVJP family. Each row r gets its own map
g r (with its own VJP); the matrix backward runs (g r).backward on row r's
cotangent. The per-row peer of rowwise_has_vjp_mat.
Equations
- Proofs.rowwisePerRow_has_vjp_mat g hg hg_diff = { backward := fun (A : Proofs.Mat m n) (dY : Proofs.Mat m p) (r : Fin m) (c : Fin n) => (hg r).backward (A r) (dY r) c, correct := ⋯ }
Instances For
A per-row family flattens to a differentiable Vec → Vec map. The
Differentiable witness vjp_comp_at / the network composition needs to thread a
per-channel BN through a block.
Per-channel BatchNorm (matrix view): BN each row (= channel-slice of m = h·w
spatial cells) with its own (γ_c, β_c). The real-ResNet BN that bnForward's
global scalar version approximates.
Equations
- Proofs.bnPerChannelMat oc m ε γ β A c = Proofs.bnForward m ε (γ c) (β c) (A c)
Instances For
Per-channel BN VJP (block-diagonal). Each channel runs its own bn_has_vjp;
the cross-channel Jacobian blocks vanish (pdivMat_rowIndep_perRow_at).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Per-channel BN as a flat-vector op Vec (oc·m) → Vec (oc·m) (row-major, channel
c = the m-wide slab at flat positions finProdFinEquiv (c, ·)).
Equations
- Proofs.bnPerChannelFlat oc m ε γ β v = (Proofs.bnPerChannelMat oc m ε γ β (Proofs.Mat.unflatten v)).flatten
Instances For
Per-channel BN flat VJP — the block-diagonal matrix VJP bridged to Vec.
Equations
- Proofs.bnPerChannelFlat_has_vjp oc m ε hε γ β = Proofs.hasVJPMat_to_hasVJP (Proofs.bnPerChannelMat_has_vjp oc m ε hε γ β)
Instances For
Per-channel BN is differentiable everywhere (ε > 0). The composition witness.
Per-channel BN VJP correctness (ℝ-headline): the flat backward equals the
pdiv-contracted (block-diagonal) Jacobian of per-channel BN.
Per-channel consolidated BN input-gradient — the renderable closed form: run
the per-example three-term bn_grad_input on each channel-slice (m = h·w spatial
cells), reusing that channel's γ_c. This is exactly what a bnPerChannelBack SHlo
op / renderLNBack-per-channel emits; the abstract bnPerChannelFlat_has_vjp.backward
is the spec it must match.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Renderable backward is faithful (ℝ-headline): the per-channel consolidated
gradient equals the pdiv-contracted Jacobian of per-channel BN, under 0 < ε.
Each channel reduces to the per-example bn_input_grad_correct. The licence to
render per-channel BN's backward as the three-term formula per channel.
The network carries its activations in the Tensor3 flat layout (oc*h)*w
(flatConv etc.), but bnPerChannelFlat is defined on the Mat-split layout
oc*(h*w) (row c = the h·w spatial cells of channel c). The two Vecs have
the same size; they differ only in how finProdFinEquiv associates the product. So
the bridge is a pure re-association reindex (a permutation of coordinates) — a
reindexCLM whose VJP is the scatter pdiv_reindex gives, exactly like
decimateFlat. Conjugating bnPerChannelFlat by this bridge yields per-channel BN
acting on the network's Tensor3 activations, with its VJP for free via vjp_comp.
The two re-association indices are mutual inverses — the bridge is a genuine
relabeling (so conjugating by it really is per-channel BN, just in Tensor3
coordinates). Pure finProdFinEquiv round-trip.
Tensor3 → Mat-split reindex: read the ((c,hi),wi) cell at Mat position
(c, (hi,wi)). A reindexCLM, hence continuous-linear / differentiable.
Equations
- Proofs.reassocFwd oc h w y k = y (Proofs.reassocFwdIdx oc h w k)
Instances For
Mat-split → Tensor3 reindex (the inverse relabeling).
Equations
- Proofs.reassocBack oc h w y k = y (Proofs.reassocBackIdx oc h w k)
Instances For
Training BN computes μ/σ² from the activation it is normalizing; inference BN consumes frozen statistics (the driver's EMA'd running mean/var) and is therefore a plain affine map — pointwise in the activation, with no reduction at all. That is exactly why an eval forward built on it is class-batch-independent: an example's logits do not depend on which other examples share its batch.
The chain below mirrors bnForward → bnPerChannelMat → bnPerChannelFlat → bnPerChannelTensor3
one-for-one, so the eval op drops into the same layout bridge as the training op.
Inference BN on one channel's m activations: yᵢ = γ · (xᵢ − μ) · (var + ε)^(−1/2) + β,
with μ/var supplied rather than computed from x. The bnForward peer — note it takes
x pointwise, where bnForward reduces over all of x to get its own μ/σ².
Instances For
Per-channel inference BN (Mat layout) — row c gets channel c's frozen stats.
Equations
- Proofs.bnPerChannelEvalMat oc m ε γ β μ v A c = Proofs.bnEvalForward m ε (γ c) (β c) (μ c) (v c) (A c)
Instances For
Per-channel inference BN (flat layout) — the bnPerChannelFlat peer.
Equations
- Proofs.bnPerChannelEvalFlat oc m ε γ β μ v x = (Proofs.bnPerChannelEvalMat oc m ε γ β μ v (Proofs.Mat.unflatten x)).flatten
Instances For
Per-channel inference BN (Tensor3 layout) — the bnPerChannelTensor3 peer, through
the same reassoc bridge. This is what SHlo.bnPerChannelEvalF denotes.
Equations
- Proofs.bnPerChannelEvalTensor3 oc h w ε γ β μ v = Proofs.reassocBack oc h w ∘ Proofs.bnPerChannelEvalFlat oc (h * w) ε γ β μ v ∘ Proofs.reassocFwd oc h w
Instances For
⭐⭐ Frozen-stats BN at a channel's OWN statistics is the training BN.
Hand bnEvalForward the mean and the second moment of x itself and it reproduces
bnForward exactly, the variance arriving through bnVar_eq_bnMeanSq_sub_sq.
This is the R = 1 anchor of synchronised BatchNorm, and the reason the sync render is
a drop-in: a graph that normalises with handed-in statistics denotes the same function
as one that computes them, whenever the handed-in ones are the right ones. At R = 1
every allReduceMeanF threads its operand, so the sync forward collapses to exactly this
and the single-device artifacts need not move. See planning/global_bn_verified.md §2b.
Inference BN is differentiable everywhere — it is affine in x, so unlike the
training BN this needs no 0 < ε hypothesis: ε only enters the constant scale factor.
The rendered per-channel γ gradient: dγ_c = Σ_{s} dy_(c,s) · x̂_(c,s) (the
reduce over batch/spatial of dy·x̂ that the bnGammaSgd op emits).
x̂ is recomputed from the saved BN input v (the conv output). Lives here (not
PerChannelBNGrad) so the bnGammaSgd SHlo op's den can reference it.
Equations
- Proofs.bnPerChannel_grad_gamma oc m ε v dy c = ∑ s : Fin m, dy (finProdFinEquiv (c, s)) * Proofs.bnXhat m ε (Proofs.Mat.unflatten v c) s
Instances For
The rendered per-channel β gradient: dβ_c = Σ_{s} dy_(c,s).
Equations
- Proofs.bnPerChannel_grad_beta oc m dy c = ∑ s : Fin m, dy (finProdFinEquiv (c, s))
Instances For
VJP of the forward reindex — the scatter pdiv_reindex gives. Mirrors
decimateFlat_has_vjp.
Equations
- Proofs.reassocFwd_has_vjp oc h w = Proofs.reindexVJP (Proofs.reassocFwdIdx oc h w)
Instances For
Equations
- Proofs.reassocBack_has_vjp oc h w = Proofs.reindexVJP (Proofs.reassocBackIdx oc h w)
Instances For
The bridge is a permutation, so each reindex's VJP backward is just the inverse
reindex (the single matching delta survives the scatter). These two collapse the
vjp_comp backwards into a clean closed form for bnPerChannelTensor3.
Per-channel BatchNorm on the Tensor3 (oc*h)*w activation layout. Conjugate
the Mat-split bnPerChannelFlat by the layout bridge: relabel to Mat-split,
normalize each channel over its h·w spatial cells, relabel back. This is the
op B9 wires into the ResNet-34 trainer (its den target).
Equations
- Proofs.bnPerChannelTensor3 oc h w ε γ β = Proofs.reassocBack oc h w ∘ Proofs.bnPerChannelFlat oc (h * w) ε γ β ∘ Proofs.reassocFwd oc h w
Instances For
Per-channel BN (Tensor3 layout) VJP — block-diagonal across channels, lifted
through the layout bridge by vjp_comp (twice).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Per-channel BN (Tensor3 layout) VJP correctness (ℝ-headline): the backward
equals the pdiv-contracted (block-diagonal) Jacobian of per-channel BN on the
network's activation layout. The licence to wire per-channel BN into ResNet-34.
The composed vjp_comp backward collapses (the bridge reindexes are permutations):
per-channel BN's Tensor3 backward is the Mat-split block-diagonal backward,
conjugated by the layout bridge.
Renderable per-channel BN backward on the Tensor3 (oc*h)*w layout — relabel
to Mat-split, run the per-channel consolidated three-term bnPerChannel_grad_input,
relabel back. This is exactly what the bnPerChannelBack SHlo op emits (per-channel
renderLNBack, reducing over the spatial axis); its faithfulness spec is below.
Equations
- Proofs.bnPerChannelTensor3_grad_input oc h w ε γ x dy = Proofs.reassocBack oc h w (Proofs.bnPerChannel_grad_input oc (h * w) ε γ (Proofs.reassocFwd oc h w x) (Proofs.reassocFwd oc h w dy))
Instances For
Renderable Tensor3 backward is faithful (ℝ-headline): equals the
pdiv-contracted (block-diagonal) Jacobian of per-channel BN on the network's
activation layout, under 0 < ε. The licence to render per-channel BN's backward
in ResNet-34.
Transpose-reindex Fin (oc*(N*(h*w))) → Fin (N*(oc*(h*w))): a per-channel Mat
index (c, (n, s)) maps to the network [N,C,H,W] flat index (n, (c, s)) —
swap the batch and channel axes (s ↔ (hi,wi) carried along). A permutation.
Equations
- One or more equations did not get rendered due to their size.
Instances For
[N,C,H,W] → [C,N·H·W] reindex (gather the network cell at the Mat position).
Equations
- Proofs.bnchwFwd N oc h w y k = y (Proofs.bnchwFwdIdx N oc h w k)
Instances For
[C,N·H·W] → [N,C,H,W] reindex (the inverse relabeling).
Equations
- Proofs.bnchwBack N oc h w y k = y (Proofs.bnchwBackIdx N oc h w k)
Instances For
Equations
- Proofs.bnchwFwd_has_vjp N oc h w = Proofs.reindexVJP (Proofs.bnchwFwdIdx N oc h w)
Instances For
Equations
- Proofs.bnchwBack_has_vjp N oc h w = Proofs.reindexVJP (Proofs.bnchwBackIdx N oc h w)
Instances For
Batch-norm per channel on the network's [N,C,H,W] layout. Conjugate the
Mat-split bnPerChannelFlat (with m = N·h·w, the whole batch's cells per channel)
by the transpose bridge: relabel [N,C,H,W] → [C, N·H·W], normalize each channel
over ALL its batch+spatial cells, relabel back. The EfficientNet normalization.
Equations
- Proofs.bnBatchTensor4 N oc h w ε γ β = Proofs.bnchwBack N oc h w ∘ Proofs.bnPerChannelFlat oc (N * (h * w)) ε γ β ∘ Proofs.bnchwFwd N oc h w
Instances For
⭐⭐ Synchronised batch-norm on the [N,C,H,W] layout — statistics HANDED IN.
bnBatchTensor4's peer, conjugated by the same [N,C,H,W] → [C, N·H·W] bridge, but the
per-channel normalisation reads μ and the second moment m2 from its arguments instead
of reducing x for them. Under data parallelism those arguments are the ALL-REDUCED
global statistics — which is how one replica normalises over a batch it cannot see.
⚠ It takes the SECOND MOMENT and forms the variance itself as m2 − μ². That is not a
convenience: E[x²] of a union of equal shards is the mean of the shards' E[x²], so it
survives an allReduceMeanF, whereas the variance of a union is not the mean of the
shards' variances and does not.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Frozen-stats per-channel BN is POINTWISE, with the channel read off the index: the only
thing the Mat round-trip does is decide which channel's γ β μ v a cell gets.
⭐⭐ R = 1: the sync forward at the batch's own statistics IS bnBatchTensor4.
The anchor the whole sync-BN render rests on, and the reason single-device artifacts do
not move: at R = 1 every allReduceMeanF threads its operand, so the sync graph hands
in exactly the statistics the batch would have computed, and this says that graph denotes
the function the existing tier is already tied to. planning/global_bn_verified.md §2b.
Batch-norm (network layout) VJP — block-diagonal across channels (now coupling the whole batch within each channel), lifted through the transpose bridge.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Renderable batch-norm backward on the [N,C,H,W] layout — relabel to the
per-channel Mat, run the consolidated three-term bnPerChannel_grad_input over the
whole batch (m = N·h·w), relabel back. Exactly what the batched batch-norm backward
StableHLO fragment emits (reduce over [0,2,3] per channel).
Equations
- Proofs.bnBatchTensor4_grad_input N oc h w ε γ x dy = Proofs.bnchwBack N oc h w (Proofs.bnPerChannel_grad_input oc (N * (h * w)) ε γ (Proofs.bnchwFwd N oc h w x) (Proofs.bnchwFwd N oc h w dy))
Instances For
Per-channel SYNC backward (flat layout) — bnPerChannel_grad_input's peer, with each
channel's μ, E[x²] and two reduction means supplied rather than reduced out of x/dy.
Under data parallelism those are the all-reduced global ones.
Equations
- One or more equations did not get rendered due to their size.
Instances For
⭐⭐ The per-channel γ gradient with x̂ at HANDED-IN statistics —
bnPerChannel_grad_gamma's peer. Under sync-BN the forward normalised with the all-reduced
global μ/E[x²], so ∂L/∂γ_c = Σ dy·x̂ must use the SAME x̂; bnPerChannel_grad_gamma
rebuilds it from the shard's own statistics (bnXhat), which is a different function once
R > 1. β's gradient reads no statistic and needs no peer.
Equations
- Proofs.bnSyncPerChannel_grad_gamma oc m ε μ m2 v dy c = ∑ s : Fin m, Proofs.Mat.unflatten dy c s * Proofs.bnSyncXhat m ε (μ c) (m2 c) (Proofs.Mat.unflatten v c) s
Instances For
R = 1: the sync γ gradient at its own statistics IS bnPerChannel_grad_gamma. The
γ-gradient anchor beside bnSyncTensor4_at_own_stats: a single-device sync render's γ node
denotes what today's bnGammaGradB denotes.
⭐⭐ The SYNC batch-norm input-VJP on [N,C,H,W] — bnBatchTensor4_grad_input's peer,
through the same bnchwFwd/bnchwBack bridge. What a replica emits for its shard of the
backward, given the four all-reduced per-channel statistic vectors.
Equations
- One or more equations did not get rendered due to their size.
Instances For
⭐⭐ R = 1: the sync backward at its own statistics IS bnBatchTensor4_grad_input.
The [N,C,H,W] lift of bnSync_grad_input_at_own_stats, and the backward half of the
drop-in claim: a single-device sync render computes the same gradient the committed tier is
tied to, so the R = 1 artifacts need not move. planning/global_bn_verified.md §2c.
Renderable batch-norm backward is faithful (ℝ-headline): equals the
pdiv-contracted (block-diagonal-across-channels, batch-coupled) Jacobian of
batch-norm on the network's [N,C,H,W] layout, under 0 < ε. The licence to render
EfficientNet's batch-norm backward as the per-channel three-term formula over the batch.