mixle.task.checkpoint_family_ladder module

J2: the checkpoint -> family ladder – iterate G3/J1 down a size ladder, receipted (roadmap J2).

Scope, read this first. J2 is explicitly named “thin orchestration” over machinery that is already built: J1’s unified compress() front door (itself wrapping G3’s coarsen() for the non-sampling/hybrid paths and the existing sampling-KD stack for the full-data path) and F10’s evaluate_checkpoint() / track_regression(). This module builds neither compression nor evaluation machinery; it drives J1 repeatedly down a sequence of decreasing target sizes (a small, laptop-sized stand-in for the roadmap’s real “70B -> 8B -> 1B -> edge” progression), collecting BOTH J1/G3’s own divergence receipts and a fresh F10 eval report at every rung, and gates each rung’s eval scores against the previous rung’s (or the headline’s) via F10’s own track_regression() – the same “score, don’t just threshold” GO/NO-GO shape mixle.task.pilot_ladder (F7) and mixle.task.capacity use for their own rung ladders.

The implementation depends only on the released in-tree compression and evaluation APIs. Historical branch and review provenance is intentionally kept out of runtime documentation; release records belong in repository governance, not durable model receipts.

A real constraint this discovered, not papered over: coarsen()’s output (CoarsenedLM) may contain MergedBlock instances in place of plain Block``s. ``MergedBlock does not expose the .attn/.ln1/.ln2/.mlp attributes coarsen()’s own internals (_block_branch) require – calling coarsen() a second time on an already-coarsened model raises AttributeError: 'MergedBlock' object has no attribute 'ln1' (confirmed directly: see this module’s own test file). So a literal CHAIN of compress() calls (rung i’s output model becomes rung i+1’s input) only works when rung i used method="sampling_kd" (a fresh architecture with plain Block``s) -- it is NOT generally safe for the default ``"non_sampling"/ "hybrid" methods this module defaults to. Rather than silently restricting the ladder to sampling_kd (which would abandon J2’s explicit “data-free except where receipts demand micro-calibration” requirement) or crash on the second rung, build_checkpoint_family() has every rung compress() the ORIGINAL headline model directly, with an increasingly generous divergence budget/trust_region per rung. This is a real, honest limitation of a single (non-chained) coarsen() pass: from n_layer blocks it can merge at most every adjacent pair once, i.e. reach ceil(n_layer / 2) at best – a real depth floor for one pass, not an unbounded cascade. The ladder still walks a genuinely DECREASING (non-increasing) sequence of measured model sizes; it just cannot go below that floor without a different method for that rung (a documented extension point, not built here).

class FamilyLadderResult(headline_eval, headline_n_params, rungs, halted_at, total_calibration_samples, calibration_pool_size, evaluation_set_size, evaluation_set_digest, selection_set_size=0, selection_set_digest='', roles_are_disjoint=True)[source]

Bases: object

The whole ladder’s receipted outcome: the headline’s own eval report, every attempted rung, where (if anywhere) it halted, and the TOTAL real calibration-sample spend across the whole ladder – J2’s “total calibration data measured and reported” acceptance criterion.

Parameters:
  • headline_eval (EvalReport)

  • headline_n_params (int)

  • rungs (list[FamilyRung])

  • halted_at (str | None)

  • total_calibration_samples (int)

  • calibration_pool_size (int)

  • evaluation_set_size (int)

  • evaluation_set_digest (str)

  • selection_set_size (int)

  • selection_set_digest (str)

  • roles_are_disjoint (bool)

full_kd_equivalent_samples()[source]

What full sampling-KD would have cost had EVERY rung used it: pool_size * n_rungs – the denominator J1’s own <=1%-of-full-KD acceptance criterion is measured against, extended to a whole ladder rather than one call.

Return type:

int

total_calibration_fraction()[source]

total_calibration_samples / full_kd_equivalent_samples() – the real fraction of a full-sampling-KD-every-rung ladder this run actually spent, 0.0 if there were no rungs.

Return type:

float

class FamilyRung(name, real_target, model, n_params, compression_ratio, compression_receipt, non_sampling_receipts, eval_report, calibration_samples_spent, within_eval_budget, regression_flags=<factory>, reason='', evaluation_set_digest='')[source]

Bases: object

One size-ladder step’s full receipted OUTPUT: the compressed model, J1/G3’s own divergence receipts, F10’s eval report, how many REAL calibration samples this rung spent, and whether it stayed within its stated eval budget.

Parameters:
  • name (str)

  • real_target (str)

  • model (Any)

  • n_params (int)

  • compression_ratio (float)

  • compression_receipt (CompressionReceipt)

  • non_sampling_receipts (dict[str, ScaleReceipt])

  • eval_report (EvalReport)

  • calibration_samples_spent (int)

  • within_eval_budget (bool)

  • regression_flags (list[RegressionFlag])

  • reason (str)

  • evaluation_set_digest (str)

class RungSpec(name, real_target, method='hybrid', budget=1.0, trust_region=1.0, n_mc=32, sample_budget=None, hybrid_sample_fraction=0.01, hybrid_max_stages=3, hybrid_epochs=20, hybrid_lr=0.0005, kd_epochs=200, kd_lr=0.01, target_n_layer=None, seed=0, max_relative_eval_regression=0.15, regression_reference='prior')[source]

Bases: object

One size-ladder step’s INPUT: a documentary label for the real rung this stands in for, plus the compress() and eval-budget knobs for that step.

method defaults to "hybrid" – J1’s receipt-directed micro-calibration path – per J2’s “data-free except where receipts demand micro-calibration” requirement: every rung starts from G1/G3’s free, data-free coarsen() result and spends a REAL but tiny calibration budget only on the stages G1’s own closure-error receipts flag as poorly approximated (see mixle.models.compress’s _hybrid). budget/trust_region are the lever that makes successive rungs SMALLER (a more generous divergence budget lets coarsen() accept more depth merges); callers walk them in an increasing sequence across a RungSpec list to build a decreasing size ladder – this module does not choose that sequence for you (there is no honest way to invert “target size” to “divergence budget” without running coarsen() itself), matching G3’s own data-free, budget-not-size-parameterized interface.

Parameters:
  • name (str)

  • real_target (str)

  • method (str)

  • budget (float)

  • trust_region (float)

  • n_mc (int)

  • sample_budget (int | None)

  • hybrid_sample_fraction (float)

  • hybrid_max_stages (int)

  • hybrid_epochs (int)

  • hybrid_lr (float)

  • kd_epochs (int)

  • kd_lr (float)

  • target_n_layer (int | None)

  • seed (int)

  • max_relative_eval_regression (float)

  • regression_reference (str)

build_checkpoint_family(headline_model, rung_specs, *, calibration_data, eval_data=None, selection_data=None, eval_seed=0, eval_n_examples=256)[source]

Walk rung_specs in order, compress()-ing headline_model at each rung’s own divergence budget, collecting J1/G3’s receipts plus a fresh F10 eval report, and gating progression on whether the rung’s eval scores stayed within its stated budget of the reference report (F10’s own track_regression(), mirroring F7’s GO/NO-GO gate one level up).

Every rung compress()-es the ORIGINAL headline_model (not the previous rung’s output) – see this module’s docstring for why chaining is unsafe for the default non_sampling/hybrid methods. The ladder still halts the first time a rung fails its own eval budget, exactly like mixle.task.pilot_ladder.run_pilot_ladder().

Three immutable data roles (MXR-080-1891). eval_data=None used to alias calibration_data, so every rung was compressed with, quality-measured on, and then GO/NO-GO gated against one identical set of rows – compress() said so itself, stamping every receipt quality_basis="training_data" – and the ladder’s “stayed within its eval budget” claim was in-sample. calibration_data is now a POOL that is cut into disjoint contiguous roles the moment it arrives: calibration (the rows compress() fits on), selection (the rows compress() measures its own quality on and, under method="auto", picks a method with), and evaluation (the rows the gate scores, carved here only when eval_data is not supplied). Supplying eval_data or selection_data keeps that argument as-is and carves one fewer role. Deliberately NOT checked: whether a caller-supplied eval_data is actually disjoint from calibration_data – this module cannot compare arbitrary row types soundly, compress() already labels that case quality_basis="training_data", and refusing it would break callers who legitimately declare their own held-out set. The result records the roles it controls in selection_set_size/selection_set_digest/roles_are_disjoint.

Parameters:
  • headline_model (Any)

  • rung_specs (Sequence[RungSpec])

  • calibration_data (Any)

  • eval_data (Any)

  • selection_data (Any)

  • eval_seed (int)

  • eval_n_examples (int)

Return type:

FamilyLadderResult

count_params(model)[source]

Real parameter count of a torch module – the scalar every rung’s “target size” claim is measured against (never assumed from a nominal label).

Parameters:

model (Any)

Return type:

int