NetSpec-style layer DSL for the verified trainers #
A verified trainer should read like a NetSpec trainer — a layer
list + config + train — with the only difference being the formalization underneath.
This file provides that surface:
VLayer— the verified-vocabulary layer constructors (the ops that have provenHasVJPwitnesses), mirroring the referenceLayer;VerifiedNetSpec— a{ layers := [...] }architecture, the single source of truth;toSpecs— foldslayersinto the(dims, initKind)param layout, so the layout is derived from the architecture rather than hand-listed a second time. (Kernel-check it against the auditedXLayout.specswith#guard spec.toSpecs == XLayout.specs— seeVerified.NetsCore's#guard resnet34Verified.toSpecs == ResNet34Layout.specs.)
SpecVJP ties nine of the specs — linearVerified, mlpVerified, cnnVerified,
cifarVerified, resnet34Verified, mobilenetv2Verified, efficientnetVerified,
convnextVerified, vitVerified — to their net functions (<net>Verified*_denote_eq, by rfl
on the literal layer list, so a spec edit breaks it) and to the rendered forward graph
(<net>Verified*_fwd_faithful). The emitted train step's faithfulness is the per-net *StepTie*
capstone, stated about the render, not about layers: the slug names the committed render
under verified_mlir/, which is not derived from layers.
Which dataset a verified trainer runs on. Picks the loader, the eval-split name, and whether the training images need a 256²→224² center-crop per batch.
- mnist : VerifiedData
MNIST idx files directly under
dataDir(28×28×1, no crop). - cifar : VerifiedData
CIFAR-10
.binrecords underdataDir/cifar-10(32×32×3, no crop). - imagenette : VerifiedData
Imagenette under
dataDir/imagenette— train stored at 256² (center-cropped to 224² per batch), val at 224². - imagenet : VerifiedData
Full 1000-class ImageNet, streamed from the generated tfds shim. 1,281,167 train / 50,000 val at 224².
Unlike every case above, this one is NOT preloaded: at f32 the train split is ~938 GiB of host RAM against a 188 GB box, so it cannot be. Batches arrive over a pipe from that net's own
jax/.lake/build/generated_*_imagenet_shim.py(VerifiedNet.shimScript), already augmented, mean/std-normalized and flattened to(B, 3·224·224)— so the Lean side does no augmentation at all for this dataset, which is the point: there is exactly one definition of the transform and it is the one the JAX reference trainer uses.The shim is per net: each is generated from the
TrainConfigthat net's reference trainer runs, so the transform has one definition per net and the net selects it.The VAL split is streamed per pass too (
spawnValStream); 49,920 images after tfdsdrop_remainder, the same count the reference run reported.
Instances For
Equations
- instBEqVerifiedData.beq x✝ y✝ = (x✝.ctorIdx == y✝.ctorIdx)
Instances For
Equations
Equations
- instReprVerifiedData.repr VerifiedData.mnist prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VerifiedData.mnist")).group prec✝
- instReprVerifiedData.repr VerifiedData.cifar prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VerifiedData.cifar")).group prec✝
- instReprVerifiedData.repr VerifiedData.imagenette prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VerifiedData.imagenette")).group prec✝
- instReprVerifiedData.repr VerifiedData.imagenet prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VerifiedData.imagenet")).group prec✝
Instances For
Equations
- instReprVerifiedData = { reprPrec := instReprVerifiedData.repr }
A verified-vocabulary layer. Restricted to ops with proven HasVJP witnesses; each
carries enough to derive its slice of the param layout.
- convBn
(ic oc k stride : Nat)
: VLayer
conv (
oc←ic,k×k,stride) → per-channel BN → relu. Params{W,b,γ,β}. - convBnNB
(ic oc k stride : Nat)
: VLayer
conv → per-channel BN → relu with no conv bias —
{W, γ, β}. BN removes a conv bias ((x+b) − mean(x+b) = x − mean(x)), so a BN-followed conv carries none in He et al.'s.convBn; ResNet-34 uses this and the nets that ship a bias useconvBn(tests/TestConvBiasZero.leanmeasures that the BN-followed conv biases get zero gradient). - maxPool
(k stride : Nat)
: VLayer
max pool
k×k/stride. No params. - residualStage
(ic oc nBlocks stride : Nat)
: VLayer
A basic-block residual stage:
nBlocksblocks atocchannels. The first block downsamples (and projects the skip) iffstride ≠ 1 ∨ ic ≠ oc; the rest are identity. - bottleneckStage
(ic oc nBlocks stride : Nat)
: VLayer
A bottleneck residual stage (ResNet-50/101/152):
nBlocksblocks, each1×1 (oc/4) → BN → relu,3×3 (oc/4) → BN → relu,1×1 (oc) → BN,+skip,relu. The first block projects the skip (1×1 → BN) iffstride ≠ 1 ∨ ic ≠ oc— the same dispatchresidualStageuses, and for R50 that fires on all four stages, because stage 1 changes 64→256 at stride 1 where R34's stage 1 isic = oc.This is ResNet v1.5, not He et al.'s v1: the stride sits on the 3×3 (and on the projection), with the leading 1×1 at stride 1, as in the reference (
jax/Jax/Codegen.lean'sbottleneck_block_down). Putting it on the first 1×1 compiles, trains, descends and is a different net.No conv biases — every conv here is BN-followed, so a bias cannot reach the output (
convBnNB's argument, four convs at a time). torchvision's R50 carries none either, which is why the derived count lands on the reference's 25,557,032 with no adjustment. - globalAvgPool : VLayer
global average pool. No params.
- dense
(ic oc : Nat)
: VLayer
dense
ic→oc. Params{W,b}. - relu : VLayer
ReLU activation (pointwise). No params.
- conv
(ic oc k stride : Nat)
: VLayer
plain conv (
oc←ic,k×k,stride) + bias, NO batch-norm. Params{W,b}. - flatten : VLayer
flatten
[C,H,W]→ vector. No params (a reshape). - bn : VLayer
scalar-global BatchNorm (the proven
bnForward): normalize over the wholec·h·wfeature map per example, scalar γ/β. Params{γ, β}(rank-0). - bnPerChannel
(oc : Nat)
: VLayer
per-channel (per-example) BatchNorm (the proven
bnPerChannelFlat,m=h·w): normalize each ofocchannels over its ownh·wspatial map per example, per-channel γ/β[oc]. Train=eval (no running stats). Params{γ:[oc], β:[oc]}. - invertedResidual
(ic mid oc stride : Nat)
: VLayer
MobileNetV2 inverted-residual block (
ic→mid→oc, depthwisestride): expand 1×1 conv→per-channel BN→relu6, depthwise 3×3→BN→relu6, project 1×1→BN (linear bottleneck),- residual when
stride=1 ∧ ic=oc. Params{W,b,γ,β}×3 (expand/depthwise/project).
- residual when
- invertedResidualNB
(ic mid oc stride : Nat)
: VLayer
MobileNetV2 inverted-residual block with no conv biases —
{W,γ,β}×3. Every conv in the block is BN-followed, so a bias cannot reach the output (theconvBnNBargument, three convs at a time); the torchvision/JAX reference carries none, and with this constructor the derived count matches the reference's. Kept besideinvertedResidualforconvBnNB's reason — a net whose blocks ship biases can still say so. - mbConvSE
(ic mid oc r k : Nat)
: VLayer
EfficientNet MBConv block (
ic→mid=t·ic→oc, depthwisek×k, SE ratior): expand 1×1 (skipped whenmid=ic, i.e. t=1) → BN → swish, depthwise k×k → BN → swish, squeeze-excite (Ws₁[mid,r],bs₁[r],Ws₂[r,mid],bs₂[mid], sigmoid gate), project 1×1 → BN. Params: (expand{W,b,γ,β} if t≠1) ++ depthwise{W,b,γ,β} ++ SE{Ws₁,bs₁,Ws₂,bs₂} ++ project{W,b,γ,β}. - mbConvSENB
(ic mid oc r k : Nat)
: VLayer
EfficientNet MBConv with no conv biases on the BN-followed convs — expand/depthwise/ project become
{W,γ,β}. The squeeze-excite biases stay: SE's two 1×1 convs are followed by an activation (the sigmoid gate), not BN, so nothing absorbs them and the reference carries them; only a BN-followed conv can drop its bias. With both rules the derived count matches the reference's 5,288,548 at 1000 classes. - uib
(ic oc expand stride preDWk postDWk : Nat)
: VLayer
MobileNetV4 Universal Inverted Bottleneck:
optional pre-DW (preDWk) → 1×1 expand ic→mid → optional post-DW (postDWk) → 1×1 project mid→oc, every conv BN-followed and therefore bias-free (convBnNB's argument, and whatSpec.lean's baseline count assumes).mid = ic * expand.k = 0means "omit that depthwise", which is how one constructor expresses all four of MNv4's block families — ExtraDW (both DWs), IB/MBConv (post only), ConvNeXt-like (pre only), FFN (neither).A wrong pre/post dispatch is invisible to this function: a pre-DW and a post-DW at the same
kand the same channel count contribute identical parameter shapes, so emitting one where the table says the other yields a net that type-checks, trains, descends, and is not MobileNetV4. R50's stride-on-the-3×3 and the 2×2 stem pool are invisible in the same way. The check is a forward tie against the reference on shared weights, not a parameter count. - fusedMbConvNB
(ic oc expand k stride : Nat)
: VLayer
Fused inverted bottleneck, single block, no squeeze-excite — EfficientNetV2's early-stage block, and MobileNetV4's stage 0.
k×k regular conv ic→mid (stride) → BN → swish → 1×1 project mid→oc → BN, no activation after the project; skip iffstride = 1 ∧ ic = oc. "Fused" = the MBConv expand-1×1 and depthwise collapse into ONE regulark×kconv, which is why nothing here is depthwise.mid = ic * expand. Bias-free — both convs are BN-followed.ReLU, as timm's
EdgeResidualinmobilenetv4_conv_medium.Narrower than the baseline
Layer.fusedMbConv, which also carriesnBlocksanduseSE. MNv4 usesn = 1, useSE = false, and no render exists for the other settings, so the constructor cannot express them. - convNextBlock
(c : Nat)
: VLayer
ConvNeXt block @
cchannels (expand ratio 4): depthwise 7×7 → scalar-LN → 1×1 expand c→4c → GELU → 1×1 project 4c→c → layerScale (per-channel γ). Params: depthwise{W,b}; LN{γ,β scalar}; expand{W,b}; project{W,b}; layerScale{γ:[c]}. - convNextBlockCh
(c : Nat)
: VLayer
ConvNeXt block with the channel LayerNorm —
γ,β : [c]instead of two rank-0 scalars. The normalisation axis changes with it (overcper spatial position, not over the wholec·h·wmap), but that is invisible to the LAYOUT; what the layout sees is the affine going from 2 floats to 2c. Kept besideconvNextBlockforconvBnNB's reason. - layerNorm
(d : Nat)
: VLayer
Per-channel LayerNorm over
dfeatures (normalize ∘[d]affine). Params{γ:[d], β:[d]}— the non-scalar form (cf.bn, which is scalar-global). - transformerBlock
(d m : Nat)
: VLayer
Pre-norm transformer block (dim
d, MLP hiddenm): LN1 → MHSA (Wq/Wk/Wv/Wo[d,d]) → +x → LN2 → MLP (d→m→d) → +x. Params: LN1{γ,β}; {Wq,bq,Wk,bk,Wv,bv,Wo,bo}; LN2{γ,β}; {Wfc1[d,m],bfc1, Wfc2[m,d],bfc2} (per-channel[d]LN). - param
(dims : Array Nat)
(kind : Nat)
: VLayer
A bare learned parameter tensor
(dims, initKind)— e.g. ViT's CLS token / positional embedding (not produced by any standard layer).
Instances For
Equations
- One or more equations did not get rendered due to their size.
- instReprVLayer.repr VLayer.globalAvgPool prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VLayer.globalAvgPool")).group prec✝
- instReprVLayer.repr VLayer.relu prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VLayer.relu")).group prec✝
- instReprVLayer.repr VLayer.flatten prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VLayer.flatten")).group prec✝
- instReprVLayer.repr VLayer.bn prec✝ = Repr.addAppParen (Std.Format.nest (if prec✝ ≥ 1024 then 1 else 2) (Std.Format.text "VLayer.bn")).group prec✝
Instances For
Equations
- instReprVLayer = { reprPrec := instReprVLayer.repr }
The (dims, initKind) params this layer contributes, in func-arg order
(initKind: 0 = random weight (mkParam: conv He fan-out, dense Glorot, with the ConvNeXt
and ViT overrides), 1 = ones (γ), 2 = zeros (β / bias), 3 = 1e-6 (layer scale γ, the ConvNeXt
paper's value and the JAX reference's emitLayerScaleInit)).
Equations
- One or more equations did not get rendered due to their size.
- (VLayer.convBn a a_1 a_2 a_3).toSpecs = VLayer.convBnSpec✝ a a_1 a_2
- (VLayer.convBnNB a a_1 a_2 a_3).toSpecs = VLayer.convBnNBSpec✝ a a_1 a_2
- (VLayer.maxPool a a_1).toSpecs = #[]
- (VLayer.residualStage a a_1 a_2 a_3).toSpecs = VLayer.stageSpec✝ a a_1 a_2 a_3
- (VLayer.bottleneckStage a a_1 a_2 a_3).toSpecs = VLayer.bottleneckStageSpec✝ a a_1 a_2 a_3
- VLayer.globalAvgPool.toSpecs = #[]
- (VLayer.dense a a_1).toSpecs = #[(#[a, a_1], 0), (#[a_1], 2)]
- VLayer.relu.toSpecs = #[]
- (VLayer.conv a a_1 a_2 a_3).toSpecs = #[(#[a_1, a, a_2, a_2], 0), (#[a_1], 2)]
- VLayer.flatten.toSpecs = #[]
- VLayer.bn.toSpecs = #[(#[], 1), (#[], 2)]
- (VLayer.bnPerChannel a).toSpecs = #[(#[a], 1), (#[a], 2)]
- (VLayer.convNextBlock a).toSpecs = #[(#[a, 1, 7, 7], 0), (#[a], 2), (#[], 1), (#[], 2), (#[4 * a, a, 1, 1], 0), (#[4 * a], 2), (#[a, 4 * a, 1, 1], 0), (#[a], 2), (#[a], 3)]
- (VLayer.convNextBlockCh a).toSpecs = #[(#[a, 1, 7, 7], 0), (#[a], 2), (#[a], 1), (#[a], 2), (#[4 * a, a, 1, 1], 0), (#[4 * a], 2), (#[a, 4 * a, 1, 1], 0), (#[a], 2), (#[a], 3)]
- (VLayer.layerNorm a).toSpecs = #[(#[a], 1), (#[a], 2)]
- (VLayer.param a a_1).toSpecs = #[(a, a_1)]
Instances For
A verified net as a NetSpec-style architecture. layers is the single source of truth;
the param layout (toSpecs) and input width (d0) are derived from it.
- name : String
- name : String
- slug : String
Names the committed, audited render
verified_mlir/<slug>_{train_step,fwd}.mlir. - slug : String
Names the committed, audited render
verified_mlir/<slug>_{train_step,fwd}.mlir. - inC : Nat
- inC : Nat
- imageH : Nat
- imageH : Nat
- imageW : Nat
- imageW : Nat
- nClasses : Nat
- nClasses : Nat
- data : VerifiedData
- data : VerifiedData
- blurb : String
- blurb : String
Per-BN-layer channel counts in forward order (empty = LayerNorm / no-BN). Drives running-stats BN threading in
trainAdamSched— seeVerifiedNet.bnChannels.Per-BN-layer channel counts in forward order (empty = LayerNorm / no-BN). Drives running-stats BN threading in
trainAdamSched— seeVerifiedNet.bnChannels.Stochastic-depth keep probabilities, one per drop site, in the render's signature order. Empty = no drop sites. Read only by
*drop*variants (VerifiedVariant.sdOn).A second hand-list beside the renderers' own site tables (e.g.
Proofs.StableHLO.enetDropIdxs/Proofs.StableHLO.enetDropTotal): the spec modules do not importProofs/Codegen, so the two cannot share a definition.tests/TestDropPathRamp.leanis the#guardthat pins them together.Stochastic-depth keep probabilities, one per drop site, in the render's signature order. Empty = no drop sites. Read only by
*drop*variants (VerifiedVariant.sdOn).A second hand-list beside the renderers' own site tables (e.g.
Proofs.StableHLO.enetDropIdxs/Proofs.StableHLO.enetDropTotal): the spec modules do not importProofs/Codegen, so the two cannot share a definition.tests/TestDropPathRamp.leanis the#guardthat pins them together.- mlirDir : String
Which directory this net's artifacts live in — see
VerifiedNet.mlirDir. Defaultverified_mlir/(the certified, pinned corpus); the width/batch SWEEP specs set.lake/buildbecause they render from argv at run time and their output is a build product. - mlirDir : String
Which directory this net's artifacts live in — see
VerifiedNet.mlirDir. Defaultverified_mlir/(the certified, pinned corpus); the width/batch SWEEP specs set.lake/buildbecause they render from argv at run time and their output is a build product. Classifier dropout —
(keep_prob, per-example width),nonewhen the net has none. SeeVerifiedNet.dropoutKeepfor why the WIDTH is carried: this mask is per-ELEMENT wheredropKeepsabove is per-example, and every downstream difference (blob shape, draw count, DP shard split) falls out of that one number.Classifier dropout —
(keep_prob, per-example width),nonewhen the net has none. SeeVerifiedNet.dropoutKeepfor why the WIDTH is carried: this mask is per-ELEMENT wheredropKeepsabove is per-example, and every downstream difference (blob shape, draw count, DP shard split) falls out of that one number.- shimScript : String
The generated ImageNet batch shim this net streams — see
VerifiedNet.shimScriptfor why there is no default and why an empty one refuses instead of falling back. Required on every.imagenetspec; meaningless on the others. - shimScript : String
The generated ImageNet batch shim this net streams — see
VerifiedNet.shimScriptfor why there is no default and why an empty one refuses instead of falling back. Required on every.imagenetspec; meaningless on the others. - lossSlot : Bool
Does
<slug>_train_step.mlirreturn the trailing report-only%lossscalar? Per-RENDER, and only themlp/cnnchapter-2/3 renders carry it. SeeVerifiedNet.lossSlot. - lossSlot : Bool
Does
<slug>_train_step.mlirreturn the trailing report-only%lossscalar? Per-RENDER, and only themlp/cnnchapter-2/3 renders carry it. SeeVerifiedNet.lossSlot.