Documentation

LeanMlir.SpecHelpers

Spec → tensor-shape / packed-bytes / parameter-init helpers.

These were copy-pasted into every Main*Train.lean file. Centralizing them here means each trainer just imports LeanMlir and asks for spec.paramShapes, spec.bnShapesBA, etc.

Anything new added here should be a pure function of NetSpec — no IO, no architecture-specific specialization. Adding a new layer type means adding a case here once and every trainer picks it up.

Per-parameter tensor shapes for the entire spec, in the same order MlirCodegen.emitTrainStepSig walks them. Used to pack/unpack the params ++ m ++ v ByteArray that goes into and out of every trainStepAdamF32 call.

Equations
  • One or more equations did not get rendered due to their size.
Instances For

    Packed params ++ m ++ v shape array (3× the param shapes), as int32 LE ByteArray. This is what gets passed to trainStepAdamF32.

    Equations
    Instances For

      BN-layer (pidx, oc) pairs as discovered by the codegen. ViT-style transformer specs return an empty array.

      Equations
      Instances For

        Total float count needed to store running BN stats (mean + var per BN layer). 0 for ViT and any non-BN architecture.

        Equations
        Instances For

          BN shapes packed for the FFI: [n_bn_layers, oc0, oc1, ...] as int32 LE. The trainStepAdamF32 FFI uses this to know how many BN-stat outputs to pop after the params/loss.

          Equations
          • One or more equations did not get rendered due to their size.
          Instances For

            Param shapes for the eval forward pass: regular params followed by one [oc] mean and [oc] var per BN layer. ViT-style specs collapse this to just paramShapes because there are no BN layers.

            Equations
            • One or more equations did not get rendered due to their size.
            Instances For

              Packed evalShapes as int32 LE for the eval forward FFI.

              Equations
              Instances For
                def NetSpec.xShape (spec : NetSpec) (batch : Nat) :

                Packed input-tensor shape for a flat-image batch (NCHW collapsed to [batch, channels*H*W]). Channel count comes from the first conv-style layer; defaults to 1 for pure-MLP specs.

                Equations
                Instances For

                  Sanitized base name for the spec — same transformation the codegen applies when generating MLIR module names.

                  Equations
                  Instances For

                    The eval forward function name to pass to forwardF32. The codegen emits modules of the form @<sanitized_name>_eval containing func.func @forward_eval, so the FFI call wants the qualified "<sanitized_name>_eval.forward_eval". Computing this from the spec means trainers can never get the spelling wrong by hand.

                    Equations
                    Instances For

                      He-initialize all parameters for a spec, walking layer-by-layer.

                      Equations
                      • One or more equations did not get rendered due to their size.
                      Instances For
                        def NetSpec.applyHeadPriorBias (spec : NetSpec) (params : ByteArray) (priors : List Float) :

                        Overwrite the head's bias with log priors[c] — RetinaNet's prior-bias init (Lin et al. §3.3, "prior"), for segmentation heads.

                        heInitParams lays layers out in order and emits each conv's bias directly after its weights, so the head's bias is the final NC floats of the buffer. That is what this patches; it is a no-op on everything else.

                        What it buys, and why it is focal's other half. A zero-bias head starts at a uniform softmax: every class at 1/NC, background included. The net's first job is therefore to discover the class prior, and on BraTS it does that by walking straight into the trivial predictor — the collapse is decided in the first ~100 steps (planning/archive/brats_demo.md Workstream A). A log π_c bias hands it the prior at step 0 instead, so the first gradient step is spent on the actual task.

                        The quantitative version is in scripts/seg_grad_scorecard.py, whose sweep lands on this exact row. Prior-bias init starts the net at z₀ - z₃ = log(π₀/π₃) = log(0.9746/0.0050) = 5.27 (verified against the emitted checkpoint: softmax(head bias) == π to 2e-09). Its measured balance ratio — rare-class gradient over majority gradient — reads:

                        (C) atcedicewcefocal
                        z0 = 0 (uniform)5.09e-032.84e-029.96e-015.15e-03
                        z0 = 5.27 (this)2.60e-011.12e-015.08e+019.90e+01

                        One bias vector is worth ~19,000× to focal, at step 0 — and flips it from the worst arm (tied with CE, a literal no-op) to the best (~2× wce). That is the whole content of "focal needs confidence to suppress": this manufactures the confidence up front instead of waiting for training to produce it too late. It is why Lin et al. ship the two together; they are one idea, and reading them as separate tricks is how the pairing gets lost.

                        It helps every arm — wce's ratio rises 51× too — because starting at the prior is simply a better starting point than uniform. focal is the one that goes from inert to leading.

                        priors need not be normalized: adding a constant to every logit is a no-op under softmax, so an overall scale on priors shifts every bias by log k and changes nothing. Only the ratios matter — the same scale-invariance that perPixelWeightedCE's reduction enjoys, for the same reason.

                        Returns a NEW ByteArray.

                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For

                          RetinaNet prior-bias init for the FPN detector head (planning/archive/yolo_fpn.md Tier 2). Sets every objectness logit's bias to −log((1−π)/π) so the head starts predicting sigmoid = π (π ≈ 0.01) on every cell; box and class biases stay at zero.

                          This is the classifier trick of applyHeadPriorBias transposed to a sigmoid/one-vs-all head, and it is aimed at a measured failure rather than a guess. On the e12 run every objectness logit sat in ≈[−2.7, −1.2] (p5..p95) with pos/neg means −1.549/−1.803: the head had real signal (AUC 0.742) but almost no dynamic range, because a bias-free 1×1 conv has to synthesize the constant background offset out of weights that also have to discriminate. The bias hands it that constant for free, which is the whole point — and per-class mAP is bounded by objectness ranking, so this is the lever that can move it (both Tier-1 levers were measured out; see the T1a/T1b write-ups).

                          Assumes the .fpnDetect layer is last, so its 3 [A·15] biases are the final 3·A·15 floats of the buffer — the same tail-splice applyHeadPriorBias does. Within each [A·15] block, anchor a's objectness is channel a·15 + 4 (emitAnchorYoloLoss slices box at base..base+4, obj at base+4, class at base+5..base+15).

                          Returns a NEW ByteArray.

                          Equations
                          • One or more equations did not get rendered due to their size.
                          Instances For
                            def NetSpec.patchInitWithPretrainedPrefix (initParams : ByteArray) (pretrainedPath : String) (prefixBytes : Nat) :

                            Patch the first prefixBytes of initParams with bytes read from pretrainedPath. Used to bootstrap a fresh init from a pretrained backbone — e.g. load R34-Imagenette weights into a YOLOv1 init, keeping the YOLOv1 head's He-init untouched.

                            prefixBytes is computed by the caller (usually 4 * (spec.totalParams - <last layer fanOut + fanIn*fanOut>) for a spec whose final dense layer differs from the pretrained source).

                            Returns a NEW ByteArray; initParams is not modified.

                            Equations
                            • One or more equations did not get rendered due to their size.
                            Instances For
                              def NetSpec.patchInitWithPretrainedRange (initParams : ByteArray) (pretrainedPath : String) (dstOffBytes srcOffBytes countBytes : Nat) :

                              Offset-aware sibling of patchInitWithPretrainedPrefix: copy countBytes from pretrainedPath starting at srcOffBytes into initParams starting at dstOffBytes, leaving everything outside that window at its He-init.

                              The prefix version can only bootstrap weights that are contiguous at the FRONT of the layout, which forces the first layer to match the checkpoint's first layer exactly. That is the wrong constraint the moment the input channel count differs: an R34 trained on 3-channel RGB has a [64,3,7,7] stem, and a 4-modality MRI net needs [64,4,7,7]. The shapes disagree by 3,136 floats, so a prefix patch would land every subsequent weight at the wrong offset — silently, since the sizes still "fit".

                              With a range, the fresh stem keeps its He-init and the rest of the backbone lands where it belongs: dstOff = stem floats in THIS spec, srcOff = stem floats in the CHECKPOINT, count = backbone floats − srcOff.

                              Both windows are bounds-checked, because the failure mode this exists to prevent is a silent misalignment rather than a crash.

                              Equations
                              • One or more equations did not get rendered due to their size.
                              Instances For