mixle.task.edge module¶
Edge distillation: jointly optimize the student’s structure and its training process under a hard device budget – driven by a model that designs models.
mixle.task.tune_recipe() tunes one family’s knobs with a soft cost penalty. An edge deployment
is a different problem: the budget is hard (bytes of flash, ops per inference, “no torch on the
device”), and the biggest wins come from choosing the right kind of student – a hashed-feature MLP
versus a structured probabilistic classifier (a learned Bayesian network: kilobytes, torch-free,
exactly calibrated posteriors) – not from tweaking widths inside one kind.
This module makes both choices searchable, and makes the search itself a model:
DeviceSpec/EdgeFootprint– the deployment budget and a student’s measured cost (serialized bytes, per-inference op count, torch dependence).EdgeSpace– one design space spanning family (structure) and each family’s training recipe (process), decoded from a unit cube so any optimizer can drive it.DesignModel– the model that writes the model: GP surrogates over design -> (quality, budget violations), proposing the next design by feasibility-weighted expected improvement (mixle.doe.propose_next_constrained()). It persists (to_json/from_json) and keys every row on atask_fingerprint(), so design knowledge accumulates across tasks: the surrogate learns task-similarity through the fingerprint coords, and a warm-started search – even on a different task – skips the designs that never work anywhere near it.distill_for_edge()– the front door: screen candidates at reduced fidelity, promote the promising ones to full training, return the best student that fits the device, with the Pareto front over (bytes, agreement) and the updatedDesignModel.distill_designer()– the recursion made useful: distill the accumulated design ledger into a tiny torch-free structured student that predicts whether a design is worth training – the design model, compressed by the very machinery it steers.
Teachers are consulted once per dataset (labels are cached), never per candidate.
- class DeviceSpec(max_bytes=None, max_ops=None, torch_free=False)[source]
Bases:
objectA hard deployment budget.
Noneleaves an axis unconstrained.max_bytes: model size on flash/disk.max_ops: per-inference op budget (a latency proxy – calibrate ops/sec once per device to turn a latency target into this number).torch_free: the device cannot run torch, so only pure-mixle students qualify.- torch_free: bool = False
- classmethod for_latency(max_ms, ops_per_second, *, max_bytes=None, torch_free=False)[source]
A budget from a latency target:
max_ops = ops_per_second * max_ms / 1000.ops_per_secondmust come from a probe run on the target device for the student kind you deploy (measure_ops_per_second()measures it for a representative student) – throughput differs by orders of magnitude across devices and student kinds, so there is no honest built-in constant.
- violations(fp)[source]
Normalized constraint values, feasible when
<= 0(the form constrained BO consumes).
- class EdgeFootprint(bytes, ops, torch_free)[source]
Bases:
objectA student’s measured deployment cost: serialized
bytes, per-inferenceops(multiply- accumulates for an MLP; factor evaluations for a structured classifier), andtorch_free.- bytes: int
- ops: int
- torch_free: bool
- class EdgeSpace(families=('mlp', 'structured'), dim_choices=(64, 128, 256, 512), hidden_range=(4, 96), epochs_range=(40, 320), log10_lr_range=(-3.0, -1.0), bits_choices=(32, 8), ngram=3, components_range=(1, 4), bins_range=(2, 8), max_its_range=(10, 60), min_gain_range=(0.0, 5.0))[source]
Bases:
objectOne unit-cube design space over family (structure) and each family’s recipe (process).
Coordinate 0 selects the family; 1..4 decode family-specifically.
familiesdefaults to what the input kind and device allow: hashed-MLP students need torch; structured students need fixed-schema records. Decode is deterministic, so a design point is a reproducible recipe.- Parameters:
ngram (int)
- ngram: int = 3
- signature()[source]
Fingerprint of the space so persisted design knowledge is only reused where it applies.
- Return type:
- class DesignModel(signature, n_constraints, n_fingerprint=0)[source]
Bases:
objectA probabilistic model of the design space itself: design point -> (quality, budget violations).
Every evaluated design is a row; GP surrogates fitted on the rows drive
propose()(the next design worth training, by feasibility-weighted expected improvement) andpredict()(mean, sd, and probability-of-fitting-the-device for untrained designs). It serializes, so what was learned designing students for one task warm-starts the next – the design knowledge is itself a model artifact. (Anddistill_designer()compresses it into a student – models all the way down, each level a real artifact.)- add(point, quality, violations, *, fingerprint=None, **tag)[source]
- propose(bounds, *, seed=None, n_candidates=256, prefilter=None, max_tries=8, fingerprint=None)[source]
The next design worth training: feasibility-weighted EI over everything seen so far.
prefiltercloses the designer loop: pass a design judge – typically the tiny student fromdistill_designer(), called asprefilter(point_tuple) -> label– and any proposal it labels"weak"is vetoed and re-drawn (fresh acquisition seed), up tomax_tries. The distilled design knowledge thus skips known-bad designs before a single training run is spent; if every retry is vetoed the last proposal is returned anyway (the judge advises, the surrogate decides).fingerprintconditions the proposal on the current task (see_fingerprint_bounds()); the returned point has design coords only.
- predict(points, *, fingerprint=None)[source]
For untrained designs: predicted quality (mean, sd) and P(fits the device).
pointscarry design coords only;fingerprint(required when the ledger is fingerprinted) selects which task’s slice the prediction conditions on.
- class EdgeDistillResult(model, family, recipe, agreement, footprint, feasible, pareto, design, trials=<factory>)[source]
Bases:
objectOutcome of an edge search: the winning student (with measured footprint), whether it truly fits the device, the (bytes, agreement) Pareto front over everything trained, and the updated
DesignModelcarrying the accumulated design knowledge.- Parameters:
- model: TaskModel
- family: str
- agreement: float
- footprint: EdgeFootprint
- feasible: bool
- design: DesignModel
- distill_for_edge(teacher, train_data, val_data, device, *, labels=None, train_labels=None, val_labels=None, space=None, design=None, designer=None, n_init=4, n_iter=6, screen_fidelity=0.3, promote=2, seed=0, task='')[source]
Search structure x training-process for the best student that fits
device.The teacher labels
train_data/val_dataonce (cached) – or passtrain_labels/val_labelswhen the labels already exist (a harvested dataset, an upstreamsolvesplit) and the teacher is then never called (it may beNone). Candidates proposed by theDesignModelare trained atscreen_fidelity(cheap), scored by held-out agreement, and measured (footprint()); the toppromotefeasible screens are re-trained at full fidelity and the best feasible one wins (ties -> smaller). Pass a previous search’sdesign(same space + device shape) to warm-start: the surrogate already knows which regions blow the budget. Passdesigner(the tiny judge fromdistill_designer()) to veto known-weak proposals before any training is spent. If nothing fits the device, the least-infeasible student is returned withfeasible=False– inspectresult.paretofor the real trade-off frontier.
- distill_designer(design, *, quality_quantile=0.5, seed=0)[source]
Distill the design ledger into a tiny torch-free student that judges designs: point -> good/weak.
The
DesignModel’s rows are records (the design coordinates); a row is labeledgoodwhen it was feasible on the device and its quality reached the ledger’squality_quantile.distill_structured_from_labels()– the same machinery the design model tunes – compresses that knowledge into a kilobyte Bayesian-network student usable as a zero-cost pre-filter for future searches. Teacher trains student; a model designs the students; the designer is distilled into a student: each level of the tower is a real artifact.
- footprint(student)[source]
Measure a student’s deployment cost. Bytes are real (fp32 weights, int8 arrays for a quantized student, or serialized JSON for a structured one); ops are the closed-form per-inference count for the student’s kind.
- Parameters:
student (TaskModel)
- Return type:
EdgeFootprint
- task_fingerprint(data, labels)[source]
A fixed small vector describing which task this is: the coords cross-task warm start keys on.
(log10 #examples, #labels, #fields, fraction of categorical fields, normalized label entropy)– cheap invariants of the dataset, O(1)-scaled so the design surrogate’s default lengthscale treats similar tasks as informative neighbors and dissimilar ones as weakly coupled.
- measure_inference_seconds(student, inputs, *, repeats=5)[source]
Median measured wall-clock seconds per single-input inference for
studenton this host.Runs
student.batch(inputs)repeatstimes (after one untimed warm-up) and reports the median per-item time. Measured, not modeled – run it on the machine whose latency you care about (the deploy device, not the dev laptop) for a number that means anything there.
- measure_ops_per_second(student, inputs, *, repeats=5)[source]
Measured throughput (footprint ops / measured second) for this student kind on this host.
The calibration constant that turns a latency budget into
DeviceSpecmax_ops(DeviceSpec.for_latency()): probe once per (device, student kind), reuse across searches.