mixle.experimental.growth_operators module¶
Growth operators (roadmap H1): net2net widening + progressive depth stacking – G3’s
mixle.models.coarsening run backwards, over the real transformer in
mixle.models.transformer.
Where G3’s coarsen() FOLDS capacity down (depth-merge, width-merge,
structure-projection) under a divergence budget, H1 SPLITS capacity up, function-preservingly, so that a
rung-(k) checkpoint can be grown into a rung-(k+1) initialization and continued-trained rather than
retrained from scratch. Concretely, the two moves here are genuine inverses of G3’s shrink operators:
Net2Net widening (
net2net_widen(),widen_block()– Chen, Goodfellow & Shlens, “Net2Net: Accelerating Learning via Knowledge Transfer”, 2016): the real duplication rule. Given two consecutivenn.LinearlayersL_in: R^d -> R^handL_out: R^h -> R^ocomposed asL_out(act(L_in(x))), widen the hidden dimensionh -> h'by choosing a mappingg: {0,...,h'-1} -> {0,...,h-1}that is the identity on the firsthindices and, for each new index, COPIES a (systematically or randomly chosen) existing columnj. The newL_incopies rowjof the oldL_in(and its bias) into the new row – the widened hidden unit computes literally the same pre-activation as its source, so any pointwise activation between the two layers is also identical on the new unit. The newL_outcopies columnjof the oldL_outinto the new column, and then DIVIDES every column of the newL_outby its post-widening replication count (1for never-duplicated original columns,k+1for a column duplicatedkextra times) – this is the step that keeps the SUML_out(h)invariant despite now havingk+1identical copies of that unit’s activation feeding into it. This is the textbook Net2WiderNet rule, not an approximation: the pre-widening and post-widening compositions compute the exact same function on any input, up to floating-point round-off (verified below by an actual forward-pass receipt, per this track’s stated invariant).Progressive depth stacking (
insert_block()): the direct inverse of G3’sdepth_merge– instead of folding two adjacent blocksx -> x + f(x),x -> x + g(x)into one merged block via a second-order Taylor approximation offandg’s composition, SPLIT capacity by inserting a brand new block atpositionwhose residual branches are zero-initialized. ABlockhas TWO separate residual adds (x = x + attn(ln1(x));x = x + mlp(ln2(x))), so BOTH final linears that get summed back onto the residual stream – attention’sprojand the MLP’s second Linear – have their weight and bias set to exactly zero, so the new block computesx -> x + 0 + 0 = x, an exact identity, immediately after insertion. The residual connection is what makes this trivial: unlikedepth_merge’s Taylor approximation (needed because composing two already-nonlinear branches has no closed form), inserting an literal no-op block needs no approximation at all – the new block’s output is bit-for-bit identical to its input by construction, so the WHOLE model’s output is unchanged by insertion, exactly (not to second order).
Function-preservation receipt. Per the track’s stated invariant (“all function-preserving edits carry
an output-parity receipt at the moment of the edit”), every growth move here is checked with a REAL
forward-pass comparison (verify_output_parity()) of the model before vs. after growth on the same
input batch – not a closed-form law-level argument the way G3’s KL receipts are. Net2Net widening and
zero-init depth stacking are ALGEBRAICALLY exact (not second-order truncations like depth_merge), so
the measured max-abs/max-rel differences here are expected to sit at plain float round-off, not at some
larger truncation-controlled scale.
Symmetric naming with G3, deliberately: net2net_widen()/widen_block() undo width_merge,
insert_block() undoes depth_merge, and verify_output_parity() plays the same role here that
gaussian_kl() plays there – a real, verifiable, reported number, not a
hand-wave.
- class ParityReceipt(max_abs_diff, max_rel_diff, tolerance, within_tolerance, batch_shape)[source]
Bases:
objectA real, measured before/after forward-pass comparison – the function-preservation receipt this track requires “at the moment of the edit”.
max_abs_diff/max_rel_diffare computed over every output element on the SAME input batch;within_toleranceismax_abs_diff <= tolerance.
- class GrowthReceipt(name, parity=None)[source]
Bases:
objectReceipt for one growth operation: its own name, the width/depth change it made, and the
ParityReceiptfrom a real forward-pass comparison – filled in by the caller (widen_block,insert_block) once the grown module/model exists, viaverify_output_parity().- Parameters:
name (str)
parity (ParityReceipt | None)
- verify_output_parity(model_before, model_after, batch, tolerance=1e-5)[source]
The actual before/after forward-pass comparison used by every growth operator below: run
model_beforeandmodel_afteron the SAMEbatch(ineval()mode, no-grad, so dropout/BN – not present inBlocktoday, but this keeps the receipt honest if that ever changes – doesn’t inject spurious stochastic differences) and report the real measured max absolute and max relative difference between their outputs.This is the module’s one hard requirement per the track’s stated invariant: “every growth operation must be verified via a real forward-pass comparison… showing the output is unchanged to a stated bitwise/numerical tolerance.” Both net2net_widen (an exact algebraic identity) and insert_block (an exact zero-residual identity) are expected to pass at a tight tolerance close to float precision.
- net2net_widen(linear_in, linear_out, new_width, seed=0, systematic=True)[source]
Widen the hidden dimension between two consecutive
nn.Linearlayerslinear_in: R^d -> R^h,linear_out: R^h -> R^o(composed aslinear_out(act(linear_in(x)))for any pointwise activationact, or no activation at all) tonew_width = h'via the real Net2Net duplication rule (see module docstring): a mappinggpicks, for each new hidden unit, an existing unit to copy;linear_in’s new row is copied verbatim from its source row (so the new unit’s pre-activation, and hence any pointwise post-activation, is IDENTICAL to its source);linear_out’s new column is copied from its source column and then every column (including the original, now-duplicated ones) is divided by its total replication count, so the SUMlinear_out(h)is invariant.Returns
(new_linear_in, new_linear_out, receipt)–receipt.parityis leftNonehere (no model to run a forward pass on at this granularity); callers needing the parity number should usewiden_block()orverify_output_parity()directly on an assembled module.
- widen_block(block, new_d_model, seed=0, systematic=True)[source]
Widen an entire transformer
Block’sd_modelfrom its current widthold_dtonew_d_model, applying net2net-style widening COHERENTLY across everyd_model-shaped surface (both LayerNorms, attentionqkv/proj, and the MLP’s two Linears) so the block’s overall function is unchanged, not just one isolated Linear pair.new_d_modelmust be an exact integer multiple ofold_d(r = new_d_model // old_d), and every source coordinate is duplicated EXACTLYrtimes (see_residual_widen_mapping()). This uniform-ratio restriction is what makesLayerNormexact under duplication:LayerNorm’s mean/variance are population statistics over the FULL width, and duplicating a valueva NON-uniform number of times changes the weighted mean/variance relative to the original – with a UNIFORMr-fold duplication, every source value contributes the same relative weight it always did (rcopies out ofr * old_dtotal, same as1copy out ofold_d), so the widened mean and (population) variance are algebraically identical to the pre-widening ones, andln1/ln2’s elementwiseweight/biasneed only a plain coordinate-duplicate (no division) to match.Every OTHER layer that reads the widened residual stream (
qkv,proj, the MLP’s first Linear) divides its incoming weights byrto correct for the fact that a plain matmul SUMS over its (nowr-times-duplicated) input axis – exactly analogous to net2net’s own outgoing-weight correction, just needed on the input side here becaused_modelis read repeatedly through the block rather than produced once. Attention gets one further correction (see_widen_attention()) for theQ . Kdot-product scale. The MLP’s OWN internal hidden width (4 * d_model) widens by ordinary (non-uniform, arbitrary) net2net duplication, exactly as innet2net_widen(), since it is not part of the residual stream and has no LayerNorm-style population-statistic constraint.Returns
(new_block, receipt)wherereceipt.parityis filled in by an actual forward-pass comparison (verify_output_parity()) ofblockvs.new_blockon the SAMEold_d-wide random batch (both blocks accept the same input width at the moment of growth).
- insert_block(model, position, seed=0)[source]
Progressive depth stacking: insert a brand-new, near-identity
Blockintomodel.blocksat indexposition(0 <= position <= len(model.blocks)), function-preservingly, by ZERO-INITIALIZING BOTH of the new block’s residual branches – attention’sproj(the last thing summed onto the residual stream inx + self.attn(self.ln1(x))) AND the MLP’s second Linear (the last thing summed on inx + self.mlp(self.ln2(x))), weight and bias both – so the new block computesx -> (x + 0) + 0 = x, an EXACT identity (not a second-order approximation the way G3’sdepth_mergeneeds, since there is no composition of two nonlinear branches to linearize here – a literal no-op block needs no approximation at all). Zeroing only one of the two branches is NOT sufficient: aBlockhas two separate residual adds, and either one left non-zero would perturbxbefore the block’s output is reached.This is the direct inverse move of
depth_merge: where that operator folds two adjacent blocks into one via a Taylor-approximated composition, this operator splits capacity by inserting one new block that starts as a no-op and is left for the optimizer to grow into useful capacity during continued training – exactly the roadmap’s “rung-(k) checkpoints initialize rung-(k+1) function-preservingly” role.Returns
(new_model, receipt)– a freshCausalLM-shaped module (built by shallow-copying the input model’s embeddings/head and inserting the new block into a freshblockslist, mirroringCoarsenedLM’s own approach on the shrink side) whoseforwardis identical tomodel’s at the moment of insertion, verified by an actual forward pass on real token ids viaverify_output_parity().