mixle.utils.parallel.em_observability module¶
Runtime observability for distributed EM backends (MP/MPI/Spark).
Companion to mixle.utils.parallel.balance: balance produces a static worker grid
from a FLOPs-under-memory model before the fit starts; this module captures what actually
happened per rank per round once the fit is running, so stragglers and data-volume skew that
the static plan could not see (heterogeneous hardware, cold caches, a lopsided shard) are
caught within a single round instead of silently eating wall-clock for the whole fit.
The unlock is the same one that makes shard recomputation a corruption audit and accumulators
checkpoints: EM’s sufficient statistics are additive, so per-rank records are additive too –
a round’s RankRecord list is a complete, replayable receipt of what each worker did, and
folding/analyzing them needs no coordination beyond what already flows back to the driver.
- Pieces:
RankRecord– one worker-rank’s timing/bytes/accumulator-size receipt for one round.record_rank_round()– append aRankRecordas amixle.telemetryevent.detect_stragglers()– robust (median/MAD) outlier test over one round’s rank times.imbalance_receipt()– quantitative data-volume skew across ranks for one round.plan_rebalance_weights()– turn observed timings into a feedback signal formixle.utils.parallel.balance.balance_plan()’s next static plan.fit_report()– one human/machine-readable summary of a whole fit’s collected records.
- class FitReport(n_rounds, n_ranks, total_seconds, total_bytes, total_obs, rounds, stragglers_by_round, imbalance_by_round, worst_straggler_ratio, worst_imbalance_ratio)[source]
Bases:
objectA whole-fit summary rolled up from per-rank, per-round records – the EM-side
run.report().Mirrors the shape F4’s training-health
run.report()is expected to have (per-round health, an overall verdict, a printable render) without depending on F4, which had not landed when this was written; aligning field names/shape once F4 exists is a natural, tracked follow-up.- Parameters:
- class ImbalanceReceipt(round, bytes_per_rank, n_obs_per_rank, mean_bytes, max_bytes_ratio, skew_by_rank)[source]
Bases:
objectQuantitative data-volume skew across ranks for one round (bytes and observation counts).
- class RankRecord(rank, round, e_step_seconds, m_step_seconds=0.0, bytes_processed=0, accumulator_bytes=0, n_obs=0, extra=<factory>)[source]
Bases:
objectOne worker-rank’s structured receipt for one EM round.
e_step_seconds/m_step_secondsare wall-clock durations measured on the rank itself (E-step = the accumulate/score pass over the shard, M-step = folding+``estimate`` when a rank does its own partial M-step,0.0when the M-step is driver-side only).bytes_processedis the raw/encoded data volume the rank touched this round;accumulator_bytesis the size of the sufficient-statistics payload it shipped back – both additive across ranks, so a round’s totals are a plain sum.- Parameters:
- class StragglerReport(round, rank_seconds, median_seconds, mad_seconds, ratios, z_scores, threshold_ratio, z_threshold, slow_ranks)[source]
Bases:
objectStraggler/imbalance verdict for one EM round, from a robust median/MAD outlier test.
- detect_stragglers(records, *, round=None, threshold_ratio=1.5, z_threshold=3.0)[source]
Flag ranks that are meaningfully slower than the rest for one round.
A robust (median / median-absolute-deviation) outlier test rather than a mean/stddev test, since a single straggler should not be allowed to inflate the scale it is measured against. A rank is flagged when it is BOTH
threshold_ratio``x slower than the median rank time AND (when the round has enough spread to have a nonzero MAD) ``z_thresholdrobust-z above the median – the ratio catches the practically-significant case, the z-score guards against flagging normal jitter when every rank is close together.
- fit_report(records, *, threshold_ratio=1.5, z_threshold=3.0)[source]
Build one summary report from a completed (or in-progress) distributed EM fit’s records.
Groups
recordsby round, runsdetect_stragglers()andimbalance_receipt()per round, and rolls the results into oneFitReport. Safe to call mid-fit with only the rounds collected so far – an emptyrecordssequence yields a zeroed report rather than raising.
- imbalance_receipt(records, *, round=None)[source]
Measure how unevenly data volume was actually spread across ranks for one round.
skew_by_rank[rank]is that rank’sbytes_processeddivided by the mean across ranks – a planted “rank 0 gets 10x the data” skew shows up directly asskew_by_rank[0] ~= 10 * (P / (P - 1 + 10))scaling (exactly proportional to the planted ratio for the standard case of one heavy rank among otherwise-equal peers; see the test for the exact algebra), so this is a real correctness check against the planted skew, not just a boolean “imbalanced” flag.
- plan_rebalance_weights(records, *, round=None)[source]
Turn one round’s observed rank timings into a feedback signal for the next static plan.
balance_plan(mixle.utils.parallel.balance) picks a static(D, M)worker grid and, whenM > 1, splits the model’s units across shards with_balance_units()weighted by a predicted per-unit FLOP cost (best.unit_works). This function produces the observed analogue: a per-rank weight inversely proportional to how long that rank actually took, normalized to sum to the rank count – a rank that ran 2x slower than average gets ~0.5x the weight, meaning “give this rank half as much data/model next round”. The weights are in exactly the shape_balance_unitsconsumes (a sequence of relative per-unit works, here one “unit” per rank), so a re-planning loop can plug them in directly; wiring that end-to-end (re-runningbalance_planwith per-worker throughput overrides derived from these weights) is future work – this function only produces the signal, documented here as the integration point.
- record_rank_round(telemetry, *, rank, round, e_step_seconds, m_step_seconds=0.0, bytes_processed=0, accumulator_bytes=0, n_obs=0, run_id=None, extra=None)[source]
Append one rank’s per-round receipt to
telemetryas an"em_round"event.Thin wrapper around
Telemetry.record()– reuses the existing typed-event/JSONL recorder rather than inventing a parallel logging object.run_id(if given) tags the event so records from concurrent fits sharing one recorder can be told apart.