mixle.utils.parallel.training_health module¶
Training-health receipts: MFU, loss/grad-norm anomaly detection, precision checks, restart continuity.
The frontier-scale trainer (the vendored TorchTitan/Megatron loop atop mixle.models.transformer,
sharded via mixle.utils.parallel.torchrun / mixle.utils.parallel.dcp_checkpoint) runs for
weeks unattended – the only way to know it is healthy is receipts computed from the loop itself, not a
human staring at a loss curve. TrainingHealthMonitor is that receipt machine: call
observe_step(...) once per optimizer step (mirroring mixle.telemetry.core.Telemetry.record()) and
call report() once at the end (mirroring mixle.evolve.ledger.EvolutionLedger /
mixle.evolve.population.OperatorBandit.report()) for a structured, JSON-serializable summary.
Four things are tracked:
MFU –
ModelFlopConfigcomputes the theoretical FLOPs/step for a transformer config (the standard6N + attentionaccounting, same formula nanoGPT’sestimate_mfuuses);achieved FLOPs/seccomes from a caller-supplied wall-clock step time (real timing, whatever hardware the loop runs on); MFU is the ratio against a caller-supplied hardware peak.Loss-spike / changepoint detection – a robust (median/MAD) rolling z-score per step.
Grad-norm / precision anomalies – the same rolling z-score on grad-norm, plus NaN/Inf checks on both streams (these always fire, independent of the rolling window’s warmup).
Per-restart continuity –
restart=Trueon the first step after a checkpoint resume marks the rolling baseline boundary; the next step’s z-score is evaluated against the pre-restart baseline (it has not been updated with any post-restart value yet), so a resume that silently drops optimizer/RNG state and produces a real loss jump is caught asrestart_discontinuity– a well-behaved resume is not.Dead-rank liveness –
observe_rank_step(rank, step)is a per-rank heartbeat: a data-parallel loop calls it once per step per rank (mirroring howElasticTrainingJobalready tracksdead_ranksfor gradient-averaging purposes, but that bookkeeping never surfaced as a health receipt – this does).check_rank_liveness(current_step)flags any rank that has gone silent for more thanrank_heartbeat_thresholdsteps asdead_rank, once per outage.
No cluster is required to exercise any of this: the FLOPs accounting and anomaly math are exact regardless
of scale, and the tests drive a real (tiny) mixle.models.transformer.build_causal_lm() for a handful
of steps. The absolute MFU number a laptop/CI runner produces is not comparable to a real cluster’s –
that comparison is deferred until the real distributed trainer (roadmap F1) exists.
- class Anomaly(step, kind, value, baseline, z_score, detected_at_step)[source]
Bases:
objectOne flagged anomaly: what kind, at which step, and the baseline it was judged against.
- Parameters:
- class ModelFlopConfig(n_params, n_layer, n_head, d_model, seq_len)[source]
Bases:
objectThe shape a transformer’s FLOPs/step depends on – enough to compute theoretical FLOPs exactly.
n_paramsshould exclude position-embedding params (they are a lookup, not a matmul); useflop_config_from_causal_lm()to derive this correctly from a realmixle.models.transformer.CausalLM.
- class MFUSample(step, step_flops, step_time_s, peak_flops_per_sec)[source]
Bases:
objectOne step’s MFU measurement: real wall-clock timing against the theoretical FLOPs for that step.
- class RollingBaseline(window=20, min_periods=5)[source]
Bases:
objectA robust rolling baseline (median + scaled MAD) over a trailing window of values.
Median/MAD rather than mean/std so a previous spike does not itself blow up the spread used to judge the next one.
z_score(value)is evaluated against the window as it stood beforevaluewas seen, so callingupdateonly after scoring makes the check causal (no leakage of the current point into its own baseline) – this is also what makes the restart-continuity check work: the window carries only pre-restart history until the caller updates it.- baseline()[source]
(median, scaled_mad)of the current window, orNoneduring warmup.
- z_score(value)[source]
Robust z-score of
valueagainst the window beforevalueis added, orNonein warmup.
- class StepRecord(step, loss, grad_norm=None, step_time_s=None, restart=False)[source]
Bases:
objectOne observed training step.
- class TrainingHealthMonitor(*, flop_config=None, peak_flops_per_sec=None, loss_window=20, loss_min_periods=5, loss_z_thresh=6.0, grad_window=20, grad_min_periods=5, grad_z_thresh=6.0, rank_heartbeat_threshold=50)[source]
Bases:
objectThe
runobject:observe_step(...)per optimizer step,report()once at the end.Follows the shape of
mixle.telemetry.core.Telemetry(record/buffer) andmixle.evolve.ledger.EvolutionLedger(.report()-style terminal summary): no I/O, pure in-process accounting, JSON-serializable output.- Parameters:
- observe_step(step, loss, *, grad_norm=None, step_time_s=None, batch_size=None, restart=False)[source]
Record one step; returns any anomalies raised at this step (also appended to
self.anomalies).
- observe_rank_step(rank, step)[source]
Record a per-rank heartbeat:
rankreported liveness (e.g. completed a local forward+backward) atstep. Call once per step per rank; combine withcheck_rank_liveness()to detect a rank that has stopped reporting. A rank reporting again after an outage clears itsdead_rankflag so a later re-death can be flagged again.
- check_rank_liveness(current_step)[source]
Flag any known rank that has not reported a heartbeat (
observe_rank_step()) for more thanrank_heartbeat_thresholdsteps asdead_rank. Raised once per outage (not once per step) – the flag is cleared the next time that rank reports in, so a respawned/recovered rank can be caught going dead again later.
- theoretical_flops_per_iter(*, n_params, n_layer, n_head, d_model, seq_len, batch_size)[source]
Theoretical forward+backward FLOPs for one training iteration of a decoder-only transformer.
The standard two-term accounting (see nanoGPT’s
estimate_mfu/ the PaLM paper appendix):6*Nper token for the dense matmuls (forward+backward is ~3x forward, and each matmul is a multiply-add = 2 FLOPs, giving the well-known6Nper-token constant), plus12*L*H*Q*Tper token for the attention matmuls (QK^T and attn@V), which scale with context lengthTand are not captured by the6Nparam-count term. Multiplying byT(all tokens in the sequence) andbatch_sizegives the FLOPs for one full iteration.
- flop_config_from_causal_lm(model, seq_len)[source]
Derive a
ModelFlopConfigfrom a realmixle.models.transformer.CausalLM.Position-embedding params are excluded from the count (a lookup table, not a matmul) – the same convention nanoGPT’s
get_num_params(non_embedding=True)uses.