ViT-Tiny train step rendered from the verified AST (the §1 render) — FORWARD portion #
The ViT peer of ConvNeXtRender: the full depth-12 ViT-Tiny forward rendered as
pretty of the verified multi-head vector-LN graph (vitBlockGraphMHV × 12 + patch embed + final
vector-LN + CLS-slice dense head). The committed LeanMlir/ViTRender.lean is a hand-written String
emitter (faithful per-op, NOT pretty(provenGraph)); this renders the SAME forward as pretty of the
proven SHlo graph, so den(graph) = vitForward (via vitFwdGraphKMHV_faithful, at depth 12).
Render is value-independent (skel erases the ℝ/Mat/Vec fields), so placeholders (0, zero
mats/vecs) are passed; the emitted epsStr/sStr literals carry the real ε / SDPA-scale. This file
is the FORWARD half of the §1 train-step render; the backward-cotangent chain (via the *Back ops)
- the param-SGD tail (
veclnGammaSgd/patchEmbedWeightSgd/denseWeightSgdB/denseBiasSgdB) follow.
ViT-Tiny: ic=3, 224², patch 16×16/s16 (N=196 patches, 197 tokens), D=192 = 3 heads × 64, MLP 768,
12 blocks, nClasses classes (10 as committed), BS=32, ε=1e-5, SDPA scale = 1/√64 = 0.125.
The width knobs of a ViT. Was six private def constants pinned at ViT-Tiny; a record
so one renderer serves Ti/S/B instead of one size per file.
⭐ d and tok are DERIVED, not stored, and that is what keeps the bodies unchanged.
d = heads * hd definitionally, so the places the old code wrote vbD and the places it
wrote vbH * vbHd (the head-slice operand types) are still the same type with no rewriting
and no Nat lemma. Stored as a field with a heads * hd = d proof they would only be
PROPOSITIONALLY equal and every one of those sites would need a cast.
- tk : ℕ
patch tokens; the token axis is
tk + 1for CLS. - heads : ℕ
- hd : ℕ
per-head dim. ViT keeps this at 64 across Ti/S/B and widens by adding heads.
- m : ℕ
MLP hidden width.
Instances For
ViT-Tiny: D = 192 = 3 × 64, MLP 768. The default everywhere, so every existing call site
and every committed artifact is untouched by the parameterisation.
Equations
- Proofs.StableHLO.vitTiDims = { tk := 196, heads := 3, hd := 64, m := 768, heads_pos := Proofs.StableHLO.vitTiDims._proof_1 }
Instances For
ViT-Small: D = 384 = 6 × 64, MLP 1536. Same depth and same patch grid as Tiny — S widens
only, which is why it needs no new proof and no new block chain.
Equations
- Proofs.StableHLO.vitSDims = { tk := 196, heads := 6, hd := 64, m := 1536, heads_pos := Proofs.StableHLO.vitSDims._proof_1 }
Instances For
ViT-Base (DeiT-B): D = 768 = 12 × 64, MLP 3072. Still depth 12 and still 16×16 patches.
Equations
- Proofs.StableHLO.vitBDims = { tk := 196, heads := 12, hd := 64, m := 3072, heads_pos := Proofs.StableHLO.vitBDims._proof_1 }
Instances For
Total encoder blocks = the reference's totalDrop, i.e. the ramp DENOMINATOR is this minus 1.
Instances For
Two sites per block — the attention branch and the MLP branch, dropping independently.
Instances For
The mask-input ordinal of block i's branch br (br = 0 attention, 1 MLP). The single
source for the numbering: the forward walks blocks upward, the backward downward, and both call
this. ⚠ Distinct from the RAMP index, which is i for BOTH branches.
Equations
- Proofs.StableHLO.vitSiteIdx i br = 2 * i + br
Instances For
The ramp index of a site ordinal — the inverse direction, used by the driver's keep table.
Equations
- Proofs.StableHLO.vitRampOf site = site / 2
Instances For
The %dp<i>: tensor<Bxf32> inputs an SD ViT render appends to its signature — one per SITE, in
ordinal order, which is the order the driver's dropScales writes them into the blob.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The forward SSA names a block's backward + param-SGD reference (the ConvNeXt-FNames analogue).
Per-head arrays hold the 3 heads' slices + pre-softmax + softmax-output.
- xin : String
- ln1 : String
- q : String
- k : String
- v : String
- att : String
- hres : String
- ln2 : String
- f1 : String
- g : String
- bout : String
Instances For
Equations
Forward saves the whole-net backward references: the patch embed SSA, the per-block saves, the final-LN input (last block output) + output, and the logits SSA.
Instances For
Equations
ViT-Tiny depth-12 forward rendered ENTIRELY from the verified AST. Every line is pretty of a
verified SHlo node; den(graph) = vitForward by vitFwdGraphKMHV_faithful (at depth 12). The
output is the [BS,10] logits. (FORWARD half of the §1 train-step render.)
Equations
- One or more equations did not get rendered due to their size.
Instances For
The whole-net backward traversal, SHARED by the SGD and AdamW renders. Returns the emitted code
and, in func-arg order, one SSA per parameter — the updated param at adam := false, the
un-fused gradient at adam := true. One traversal, two tails: the alternative was a second
copy of the depth-12 backward, which is the double-writer disease one level down.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The 200 parameter (name, shape) pairs in func-arg order — the single source for the argument
signature, the return types, and (in the AdamW render) the %<nm>m/%<nm>v moment slots.
nClasses is a real parameter as of 2026-07-31: it was the literal 10 here and in ~28 other
places, which pinned the whole render to Imagenette and blocked the matched pair with
jax/MainVitImagenet.lean (a 1000-class ViT-Tiny that already exists).
⚠ V is TRAILING and defaulted to ViT-Tiny, for the same reason vbB is: every existing
call site is untouched and every committed artifact re-renders byte-identically.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Does this parameter get weight decay? The renderer's half of the timm rule.
⚠ It keys the positional embedding by NAME where the reference keys it by SHAPE
(p.shape == _WD_POS_SHAPE), and that is deliberate rather than a transcription slip: the
reference walks an unnamed pytree and has nothing else to key on, while a shape test here
would also exclude any other param that happened to be 197×192. The name is the more precise
identifier when you have one. #guards below pin the resulting counts against the reference's
own, which is what stops the two readings drifting.
⚠ The rule reads the RANK, and rank is the one thing that survives the layout difference
between the two sides — the render carries Wfc1 as [192,768] where the reference has
(768,192). Both are 2-D, so both decay. Measured, not assumed (§4's one-layout rule).
Instances For
ViT-Tiny depth-12 train step rendered ENTIRELY from the verified AST — the §1 backward render.
Forward (vitFwd12) → softmax-CE cotangent (softmax(logits) − onehot, the lossCotGraph form) →
head-dense back (dotOut + weightSgd/biasSgd) → clsPadF → final-LN back (vlnBack) → 12×
vBlockBack (reversed, cotangent threaded) → patch-embed back (patchEmbedWeightSgd/patchEmbedBiasSgd
clsSliceF→denseBiasSgdBfor cls +posEmbedSgdfor pos). Returns the 200 SGD-updated params in func-arg order.lrStris the mean-loss-equiv literal (base/BS); cotangent has NO /B (folded into lr). The traversal itself isvitBackAll false, shared with the AdamW render.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The driver's variant slug for a (per-device batch, replica count, EMA) triple: the artifact
is verified_mlir/vit_<variant>_train_step.mlir, the entry point is @vit_<variant>_train_step
and LEAN_MLIR_VARIANT=<variant> selects it at run time.
This is the ViT peer of cnxAdamVariant / r34AdamVariant / mnv2AdamVariant, and unlike
theirs it is documentation plus a drift guard rather than the name's producer —
vitAdamTrainStepFaithful takes funcName explicitly (it predates the slug convention) and the
#eval paths must stay string literals for regen_verified_mlir.sh's writer audit to see them.
So the #guards at the bottom of this file are what tie the literals to this function; the
contract is checked at lake build rather than merely described.
⚠ ViT's spelling breaks the "the number is the per-device batch" convention at 4 replicas
(adamdp32x4, adamdp128x4) and that is deliberate — vit_adamdp_train_step.mlir is a
COMMITTED 2-replica artifact at bs32, so a 4-replica render reusing adamdp would give one path
two writers computing different graphs. Encoded here so the exception cannot be forgotten.
⚠ The ema marker LEADS. trainAdamSched keys its 4-region [θ|m|v|ema] blob off
variant.startsWith "ema", so a trailing marker would silently select the 3-region layout for a
4-region graph — every parameter misaligned. And note what that cost on EfficientNet: its
RMSProp+EMA variant is emarms, which does not start with "rms", so the mean-square would
have initialised to 0 through a prefix test. ViT is AdamW-only, so there is no second axis here
today; if one is ever added, make both predicates substring tests first.
Equations
- One or more equations did not get rendered due to their size.
Instances For
ViT-Tiny depth-12 AdamW train step, rendered from the verified AST. The certified peer of
the hand-written ViTRender.vitTrainStepModuleAdamSched that vit-verified-adam has been
emitting at startup.
Same backward as vit_train_step (vitBackAll, one traversal) but taking the un-fused
gradients, each fed to the proven AdamW triple. The cotangent is the LABEL-SMOOTHED one with
an explicit ÷B, matching the AdamW recipe — the SGD render folds the mean into lr and does no
smoothing, so the two are different functions and this parameter is not optional.
Interface: 605 in (%x, 200 θ, 200 m, 200 v, %lr/%bc1/%bc2, %onehot) / 603 out
(200 θ', 200 m', 200 v', %loss/%bc1/%bc2) — positionally identical to the hand-written
render, so trainAdamSched's packed [θ|m|v] protocol is unchanged.
At ema := true (planning/archive/ema.md) the blob gains a fourth region and the scalar tail goes
3 → 5, so the interface becomes 807 in / 805 out = 605/603 + 200 (the shadow) + 2
(%emad/%oemad). ⚠ ema is LAST in this signature on purpose: inserted mid-list it would
capture an existing positional argument at every call site, which is the mnv2/enet convBias
lesson (§2m).
Equations
- One or more equations did not get rendered due to their size.