ℝ→Float32 bridge: per-op FloatClose instances for the conv-net op set #
FloatClose (FloatClose.lean): on inputs within magnitude A, the float
fF is within an error modulus L e of the real f (per coordinate, at input
error e), and both real and float outputs are within B (so the next layer's
magnitude precondition is met). FloatClose.comp composes two — the moduli
compose as Lg ∘ Lf, magnitudes thread A → B → C.
Instances proved here: floatClose_flatConv (modulus = the conv-fan-in
layerBudget), the pools (floatClose_maxPool, floatClose_maxPool3s2, floatClose_gap)
and the skips (floatClose_addResidual, floatClose_residualBlock).
A whole-net bound would be .comp of these; none is assembled in the repo.
Convolution is FloatClose with modulus the conv-fan-in layerBudget.
Real output ≤ layerAct; float output ≤ layerAct + layerBudget(e=0) (the
extra rounding) — that sum is the propagated magnitude B.
MaxPool is FloatClose with modulus id — exact in float, 1-Lipschitz,
never grows magnitudes (maxPoolFlat_close / maxPoolFlat_abs_le).
He et al.'s 3×3/s2 stem pool is FloatClose — the peer of floatClose_maxPool, and
identical in shape: a max is exact (it selects an existing cell, so the modulus is id and the
magnitude is unchanged) whatever the window size. The overlap that makes the backward
accumulate is invisible here, because the forward at one output still reads one cell.
Global-average-pool is FloatClose — Vec (c·h·w) → Vec c, the SE squeeze.
GAP is a per-channel bnMean (globalAvgPoolFlat_eq_bnMean), so the real output
never exceeds the input magnitude A (bnMean_abs_le) and is 1-Lipschitz in the
input (bnMean_input_close, the spatial mean averages the per-coordinate error
back to e); the float roundoff is gapFlat_close's budget gb. Output magnitude
A + gb, modulus e ↦ gb + e.
Additive residual F(x) + x (no trailing activation) is FloatClose — the
MBConv / transformer skip, the skip of floatClose_residualBlock without its ReLU. The
rounded skip-add fl(FF(x) ⊕ x) is within add_close's budget of the real
F(x) + x; output magnitude (1+u)(B+A). fun v j => F v j + v j is residual F
(Residual.lean) by definition, so this lemma serves that spelling too.
Residual block relu(F(x) + x) is FloatClose — the branching combinator
(the skip reuses the input, so it's not a plain .comp). Given the body F
FloatClose A B, the block's float (rounded skip-add) is within
B + A + u·(B + A) of the real relu(F(x)+x); output magnitude
(1+u)(B+A). The defining ResNet op: floatClose_addResidual then floatClose_relu.