mixle.models.self_distillation module¶
Self-distillation during training (roadmap J3): EMA-teacher consistency + stochastic-depth targets, wired into normal training as loss-hooks – not a separate post-hoc distillation pass, and not a new trainer either.
The idea, concretely¶
Two self-consistency pressures applied during ordinary next-token training:
EMA-teacher consistency (
EMATeacher): maintain an exponential-moving-average copy of the model’s own weights, updated every step (teacher = decay * teacher + (1 - decay) * student, the standard mean-teacher/BYOL/DINO pattern). The actively-trained (student) model is pushed to agree with this temporally-smoothed version of itself viaconsistency_loss()– an implicit regularizer with no extra labels or extra model.Stochastic-depth consistency (
stochastic_depth_forward()): each step, run the SAME input through the model twice – once at full depth, once with a random subset of blocks skipped entirely (the standard stochastic-depth / drop-path regularizer) – and add a consistency term pulling the partial-depth output toward the full-depth output. This directly trains the model to tolerate missing blocks, which is exactly the redundancy G3’smixle.models.coarseningdepth-merge exploits.
Why this belongs at the loss-hook level, not a new trainer¶
mixle.models.grad_leaf already establishes the “compose via wrapping” pattern for this codebase’s
M-step: a training loop is generic, and custom OBJECTIVES are a loss(module, x, w) -> scalar hook, not
a subclass tree (see GradLeaf/GradEstimator). CausalLM doesn’t fit GradLeaf directly (it has
no log_density; its own dense-teacher-forcing loop lives in mixle.models.language_model and
mixle.models.streaming_transformer_leaf), so train_with_self_distillation() mirrors THOSE
loops’ own conventions (F.cross_entropy over (context, next_token) micro-batches from
mixle.data.stream_token_source.stream_token_source(), a plain torch.optim.Adam M-step) and adds
the two consistency terms as extra, addable loss components on top of the same per-step cross-entropy –
the loss-hook composition pattern, applied at the place this model family’s training loop actually lives.
- class EMATeacher(model, decay=0.999)[source]
Bases:
objectAn exponential-moving-average copy of a model’s own weights: the standard mean-teacher/BYOL/DINO self-distillation teacher.
update(student_model)appliesteacher = decay * teacher + (1 - decay) * studentto every tensor in the teacher’sstate_dict(parameters AND buffers, so e.g. non-trainable statistics stay consistent too) – called once per training step, AFTER the optimizer step, so the teacher always tracks a temporally-smoothed trailing average of the student. The teacher is a real, independent, forward-passable module (forward/predict), held in eval mode with gradients disabled: it is a read-only distillation TARGET, never itself directly optimized.- Parameters:
model (Any)
decay (float)
- update(student_model)[source]
One EMA step: pull every teacher tensor toward the student’s current value.
CausalLMtieshead.weighttotok.weight(weight tying), so severalstate_dict()keys alias the SAME underlying storage – updating each key naively would apply the EMA formula to that storage more than once per step (a double update).seendedupes by storage identity (data_ptr()) so every real tensor is updated exactly once, however many names alias it.- Parameters:
student_model (Any)
- Return type:
None
- forward(x)[source]
Run
xthrough the EMA-teacher weights (eval mode, no grad).
- class TrainStats(ce_loss=<factory>, stochastic_depth_loss=<factory>, ema_consistency_loss=<factory>, total_loss=<factory>)[source]
Bases:
objectPer-step telemetry from
train_with_self_distillation()– cross-entropy, stochastic-depth consistency, and EMA-teacher consistency losses, kept separately so a caller can see which pressure is doing what (and the combined total actually optimized).
- consistency_loss(student_output, teacher_output, mode='mse')[source]
The self-distillation consistency term between a student prediction and a teacher/target prediction on the SAME input –
mode="mse"(default, plain squared-error between logits, the mean-teacher convention) ormode="kl"(KL(teacher_softmax || student_log_softmax), the classic soft-target distillation loss).
- stochastic_depth_forward(model, x, drop_prob, generator=None)[source]
Run
modelonxtwice: once at full depth, once with each block independently dropped with probabilitydrop_prob(at least one block is always kept, so the partial pass never degenerates to the bare embedding/head). Returns(full_output, partial_output)– the pairtrain_with_self_distillation()feeds toconsistency_loss().At
drop_prob == 0both passes keep every block, so the two outputs are IDENTICAL (no dropout elsewhere inBlock) – the degenerate-case sanity check pinned inmixle/tests/self_distillation_test.py.
- train_with_self_distillation(model, data, steps, *, ema_decay=0.999, drop_prob=0.1, consistency_weight=1.0, ema_weight=None, stochastic_depth_weight=None, consistency_mode='mse', lr=3e-3, device='cpu', optimizer=None, seed=0, log=None)[source]
Train
model(aCausalLM, trained in place and also returned) forstepsnext-token cross-entropy steps, with EMA-teacher consistency and stochastic-depth consistency added as extra loss terms on top of the SAME per-step batch – both self-distillation pressures happen DURING training, not as a separate post-hoc pass.datayields(context, next_token)micro-batches shaped exactly likemixle.data.stream_token_source.stream_token_source()(context: (batch, block)float ids,next_token: (batch,)int ids).datamay be:a zero-arg CALLABLE returning a fresh iterator each time (e.g.
lambda: stream_token_source(ids, block=64, batch_size=32)) – restarted automatically whenever it runs dry beforestepsis reached, so training can outlast one epoch; ora plain iterable/iterator (e.g. a list of batches, or a single generator object) – consumed once, sized to yield at least
stepsbatches (a bare generator can’t be rewound).
Per step:
loss = cross_entropy(full_depth_logits, target) + stochastic_depth_weight * consistency(partial_depth_logits, full_depth_logits.detach()) + ema_weight * consistency(full_depth_logits, ema_teacher(context)), then one optimizer step, then one EMA-teacher update.ema_weight/stochastic_depth_weighteach default toconsistency_weightwhen unset.