Operations and troubleshooting
Establish completion
Use python -m open_instruct.miles status run.toml, then inspect all current
Beaker attempts, taking the latest for each task and replica rank. A zero exit
status, completed workflow state, expected update/eval/checkpoint counts and
retained audit artifacts together establish what finished. A run appearing in
W&B or generating responses does not prove updates, weight publication or clean
shutdown succeeded.
| Artifact | What to inspect |
|---|---|
| Local launch receipt | Submitted specification, image ID, submitter revision, allocation and experiment |
workflow.json under output.root |
Preparing/training/complete/failed state; a killed job can leave stale state |
prepared/ |
Model descriptor, immutable data preparation and verifier provenance |
rollouts/ |
Per-collection samples, behavior versions, rewards and generations |
checkpoints/ |
Completed native optimizer/model checkpoints for resume |
wandb/ |
Offline W&B files or online tracking state |
driver_timing.jsonl and contract reports |
Stage timings, policy agreement, replay and optimizer/publication diagnostics |
Beaker /output results |
run.log, submitted-run.json and bounded JSON/log copies; large tensors stay on WEKA |
Use the exact artifact paths reported by the run; low-level/historical harnesses
can choose different subdirectories. status reports attempts and config identity,
not live WEKA metrics. Preserve receipts or set MILES_LAUNCH_RECEIPTS when moving
between laptop and session.
Interpret measurements
Report rollout/evaluation latency, scoring, optimization, publication, checkpoint, and orchestration costs separately. Async stages overlap: their durations cannot simply be summed to derive wall time. Separate cold compilation/restore from warm steady state, and compare tokens/second and GPU-seconds as well as update time. Record generated length, cap fraction, mixed-reward groups, reward, lag and TIS clipping to distinguish a workload change from a kernel speedup.
Centered advantages can make the scalar PPO loss zero at unchanged weights even when its gradient is nonzero. Check mixed-reward groups, gradients and parameter changes together. Total gradient norms can include router auxiliary losses; they do not prove that a batch with identical rewards supplied a policy gradient.
The policy-agreement guard named max_train_rollout_logprob_abs_diff measures the
mean absolute active-token gap, despite its legacy name. Replay diagnostics
check supplied expert IDs; native route agreement is a different experiment.
With mixed-policy refresh, retained token spans identify historical behavior
versions, while routes describe the final forward that rebuilt the route table.
Inspect refresh_scores for mixed-response counts and historical-prefix versus
latest-forward gaps/TIS clipping; those gaps include actual policy age.
See implementation contracts for exact semantics and limits.
Core MoE runs report router load every optimizer update, including with auxiliary
losses and expert-aware packing disabled. W&B keys under train/moe/ include
max_expert_load (largest assignment count for any layer/expert), dead_experts
(total layer/expert pairs receiving zero assignments this update),
dead_experts_max_per_layer, load_cv_mean, load_cv_max, and
max_mean_load_ratio. CV is population standard deviation divided by mean load;
zero means uniform. The maximum/mean ratio is one for uniform nonempty layers.
Both normalized metrics are defined as zero for an empty layer.
Counts are aggregated over the complete optimizer batch before each layer's
statistics are computed. replica_load_cv_max, replica_max_mean_load_ratio,
and replica_dead_experts_max_per_layer also expose the worst layer within any
complete expert replica, since pooling replicas can hide local imbalance.
The router_load contract/log event includes each layer's global statistics and
assignment total. These are training dispatch counts, not gate-weight mass
or a separate evaluation of current router preferences: replayed expert choices
are counted when replay is enabled. All model tokens, including prompts and the
per-document tail row, count. “Dead” means unused in this update, not permanently
inactive. These update totals do not measure worst individual microbatch load.
The implementation reuses Core's forward counters, excludes scoring and backward
recomputation, and gathers only small layer/expert histograms.
Rollout error recovery and tracking
Fully async training retries whole prompt groups for known serving failures:
connection loss (including the MILES router's wrapped transport 503), HTTP 429,
SGLang queue-full/priority-eviction responses, temporary absence of healthy router
workers, and generation waiting/running/request deadlines. These are recognized
by the pinned /generate error contract, not by HTTP 503 alone: SGLang also uses
503 for configuration errors, which remain fatal.
The producer requeues the group's pristine prompts with their original identities. The pinned group generator cancels and joins sibling sample tasks before returning the error. Partial/completed responses from a failed attempt are discarded, and regenerated responses carry fresh behavior-version metadata. No failed prompt is silently dropped or assigned zero reward. New submissions back off for one second after a failure; already-active groups continue. Retrying an unavailable pool does not re-register engines or bypass weight-readiness checks.
There is one serving-error stop rule: stop if at least 50% of completed group attempts fail for five continuous minutes. The fraction uses a trailing five-minute window; its sustained-failure timer resets when the fraction falls below 50% or the window becomes empty. A group counts once, even if multiple sibling requests fail; retried groups are new attempts. Cancellations for lifecycle operations, pending requests, and reward-based filtering are not serving failures. Successful attempts count before reward filtering. A short error burst cannot become fatal just because no more requests finish. The check runs while waiting for generation or buffer space, not only at optimizer boundaries.
Unknown HTTP/server errors, invalid configuration, and invalid sample/policy provenance remain fatal. Only typed generation deadlines are recoverable; an unrelated timeout is not automatically treated as a serving failure. Server-side work whose connection was lost may already have executed; no exactly-once generation guarantee is implied.
W&B has an errors/ section plotted against errors/elapsed_seconds, independently
of training updates. Every 15 seconds, and at producer exit, it reports:
transport_groups,overload_groups,unavailable_groups,timeout_groups: cumulative failed/requeued group attempts by category.completed_group_attempts,requeued_groups: cumulative totals.window_group_attempts,window_failed_groups,failure_fraction_5m: counts and failure fraction in the recent five-minute window.high_failure_seconds: duration of the current >=50% interval.fatal: one on a terminal producer failure, zero otherwise.
These metrics also reach rollout metrics, pipeline_occupancy.jsonl, and the
standalone rollout_errors.jsonl. The latter includes wall-clock timestamps and a
bounded terminal error summary and is retained with Beaker artifacts. Offline W&B
records the same metrics locally for later sync. Tracking failures are logged and
do not stop recovery. Counts are per producer process and reset on a fresh run or
resume. Detailed request IDs, group IDs, exception types and bounded HTTP error
bodies stay in logs, rather than becoming W&B metric names.
Recovery policy is part of the application image; an already running job keeps its image's behavior.
HTTP keep-alive margin
The pinned MILES router uses a 60-second server idle keep-alive timeout. HTTPX clients retain their default five-second idle connection reuse window. This gives the client time to retire a connection before the router closes it. The server timeout applies between requests; it is not a generation deadline, does not change admission limits, and does not hold an inference slot open. Idle sockets can remain open longer. Transport errors alone do not establish engine overload.
If the server idle timeout equals the client reuse window, a client can select a
pooled connection just as the server closes it; the request written to that
connection then fails with a transport ReadError. A server timeout well above the
client window removes this race without the extra connection churn of a shorter
client expiry. Preserve error reporting and whole-group recovery; do not silently
retry ambiguous generation requests at the HTTP layer.
Failure triage
Trainer workers and serving engines use private HF_MODULES_CACHE directories
under /tmp, independently of compiler-cache settings. Model/download caches
remain shared. The serving parent loads the configuration and tokenizer before
spawning SGLang children, so they read completed copies of checkpoint Python
modules. Transformers' local custom-code cache copies are not atomic across
processes; sharing a cold module cache can expose a partially written module
during concurrent startup.
For a publication stall, set launch.env.OI_MILES_PUBLICATION_DIAGNOSTICS="1"
on a fresh run. Trainer actors
log connection, export, bucket synchronization, broadcast and engine-load progress,
and dump their Python thread stacks every 60 seconds while update_weights is
active. Progress logs identify trainer GPU UUIDs and engine addresses, including
each engine's request start, completion or exception. Enable NCCL INFO logging
with INIT,NET subsystems to correlate communicator ranks with host/device setup.
This adds no CUDA synchronization and does not change publication deadlines.
The watchdog is canceled when the call returns or raises. These logs distinguish
where publication stopped; a driver timeout alone does not identify the cause.
Set launch.env.MILES_INFRA_TIMEOUT_MULTIPLIER="5" to allow longer infrastructure
waits on a diagnostic run. The default is 1; values must be finite and at least 1.
Covered waits warn at the earlier of 30 seconds or the original deadline, then
every 30 seconds until completion or failure. This covers Core publication/drain,
refresh requests, router control and health requests, readiness, judge transport,
coordination, evaluation submission and cleanup. It does not multiply polling
intervals, verifier execution budgets or the run duration. SGLang watchdog and
trainer distributed deadlines remain explicit MILES run options. Longer waits preserve cancellation and hard
deadlines; idempotent router readiness confirmation alone retries transport errors.
| Symptom | Next check |
|---|---|
| Configuration rejected | Error field/context, TOML types, conflicting aliases; rerun with --debug |
| Queued job | beaker job events JOB_ID; use the scheduler's reason and latest attempt |
| One node running, another queued; rendezvous timeout | beaker experiment get EXPERIMENT_ID --format json: check shared execution.replicaGroupID, expected ranks and execution.spec.leaderSelection, then inspect events for every replica |
| Import/model failure | Selected image provenance, lock, architecture/tokenizer descriptor |
| Out of memory | Trainer resident state, optimizer initialization, pack/context budget, actual KV/recurrent pools and graph capture |
| Engine unavailable | Per-engine logs, health/recovery events, published policy version and Ray actor state |
| Policy agreement/replay failure | Exact checkpoint/template, token alignment, precision, replay fields and version provenance |
| Save/resume failure | Completed-checkpoint marker, original run specification, output-root ownership and disk capacity |
| "Another process owns this run" after node loss | Check the previous attempt and shared-filesystem lock recovery before retrying. Lock ownership may still be recovering; do not delete the lock file to bypass it. |
| Slow shutdown | Compiler-cache phase records and cancellation result; do not attribute all delay to archive compression |
For distributed startup failures, first check the scheduling contract. Positive minimum runtimes on separate tasks do not make a replica group. A free-GPU snapshot does not establish why a group waits or whether Beaker can preempt eligible work. Do not substitute idle-node polling or a longer application rendezvous timeout for correcting an incorrectly submitted group. If the group is correct, use events and logs to distinguish quota/capacity, image startup, and node failures.
Do not bypass contract failures merely to finish a run. A healthy HTTP process is not proof that it has the current weights. The runtime's health/recovery support does not establish recovery of every Core trainer failure.
Compiler-cache publication is best effort. It stages locally, limits publishers per node and uses a shared bounded publication wait; timeout must not turn completed training into failure. See cache guide. Report metadata I/O is outside that publication wait, so it is not an absolute shutdown deadline.
Checkpoints while inference continues
Native trainer checkpoints are saved between optimizer updates without draining inference, including mixed-policy refresh and rolling engine publication. The driver saves the dataset cursor and pending-prompt ledger, writes the trainer's model/optimizer/scheduler/RNG state and policy clock, then commits the checkpoint with the cursor checksum. It does not consume another training batch during this sequence. Only a checkpoint with its completion marker is resumable.
The async data source snapshots its cursor and pristine pending prompts under one lock. This briefly serializes prompt admission/bookkeeping with the cursor write; it does not wait for HTTP requests, generation, reward services, or the completed queue to empty. Live requests and buffered responses remain usable in the running job. On resume, all unconsumed groups in that snapshot are regenerated under the restored policy, including completed-but-unused responses. Request caches, partial responses and historical behavior log-probabilities are not restored. This preserves prompt accounting and trainer state, not an identical uninterrupted sampling trajectory. Work admitted after the snapshot is reached again through the restored dataset cursor.
Evaluation on shared engines, final export and shutdown still have their own lifecycle boundaries and are not nonblocking. The native trainer write is synchronous with training; inference continues during it.
Evaluation admission in mixed-policy refresh
With core.publication_mode="refresh", evaluation closes admission to training
HTTP generation. Samples still waiting for a serving slot stay parked in their
original groups; only already-admitted generation calls must finish before
evaluation uses the engines. Evaluation has separate admission and generation
state. Successful evaluation reopens the training gate without resampling parked
prompts or changing completed siblings. Reward verification that has already
started can finish while the generation gate is closed. An evaluation failure
leaves training paused and propagates to teardown.
Final export and shutdown use the same generation boundary. Shutdown cancels unused parked work; checkpoint recovery still regenerates unconsumed prompts from the saved ledger as described above. Ordinary weight refresh continues to preserve live requests and does not invoke this evaluation drain.
pipeline_occupancy.jsonl records generation_admission_paused,
generation_admission_waiters and generation_active_calls alongside existing
HTTP occupancy. pipeline_lifecycle.jsonl records generation_paused and
generation_resumed. Admission waiters include calls parked outside the HTTP
semaphore; they are not requests already running on SGLang. The drain timeout
still bounds active calls, including requests already submitted to a server but
waiting there, so long responses can still delay evaluation.
For a structured run, omit async.async_max_concurrent_samples to use the
automatic bound: the larger of one rollout collection and two waves of serving
slots, rounded to whole prompt groups. Measure trainer wait and serving occupancy
before increasing that budget: more queued work does not add inference capacity.
Analysis and service checks
Use the prepared run's output directory, containing workflow.json, plan.json
and checkpoints/. These tools read structured artifacts; preserve the original
files alongside their reports. Choose a warmup exclusion appropriate to the run
length. Excluding early updates does not prove that compilation has finished.
| Tool | Purpose and inputs | Interface |
|---|---|---|
analyze_throughput |
Summarizes completed-run update timing, trainer wait, delivered tokens, stale drops and unused terminal work from driver/rollout reports. Rejects incomplete workflows by default. | CLI; writes throughput-analysis.json under the run root and prints JSON. |
capacity_metrics |
Combines the allocation, per-rank training contracts and occupancy into per-update token rates and hardware coverage. | Python helper: measurements(root, warmup=6). |
throughput_occupancy |
Computes time-weighted pipeline, engine and GPU occupancy; reports gaps as missing coverage rather than zero utilization. | Python helper: analyze(root, warmup=6). |
sample_gpu_usage |
Samples nvidia-smi utilization and memory on an existing worker node; optionally counts local Triton artifacts. Requires NVIDIA tooling, but creates no CUDA context. |
CLI; writes JSONL to the specified path. |
readiness_services |
Sends code-verifier probes through a private loopback fault proxy to check retries, exhaustion and recovery against the supplied service. | CLI; writes a JSON readiness report. |
Run the completed-run CLI from a checkout:
python -m scripts.miles.analyze_throughput /path/to/run --warmup 3
The two library helpers can be used together without a GPU runtime:
import json
from pathlib import Path
from scripts.miles import capacity_metrics, throughput_occupancy
root = Path("/path/to/run")
reports = {
"capacity-analysis.json": capacity_metrics.measurements(root, warmup=3),
"occupancy-analysis.json": throughput_occupancy.analyze(root, warmup=3),
}
for filename, report in reports.items():
(root / filename).write_text(json.dumps(report, indent=2) + "\n")
Collect GPU samples on each worker node, using that node's rank in the filename, and probe the intended code service explicitly:
python -m scripts.miles.sample_gpu_usage /path/to/run/checkpoints/gpu_usage_node0.jsonl --seconds 60 --interval 5
python -m scripts.miles.readiness_services http://CODE_SERVICE:1234 --output runs/services.json
The service probe makes real code-execution requests. Run it against the service
being checked. Use --help on the CLI tools for their remaining options.
Bounded wall-clock runs
Set core.max_run_seconds to a positive number to finish at a completed collection
after that many driver wall-clock seconds, including driver startup. This is a
soft deadline: a running collection, checkpoint, export, and teardown can extend
past it. Leave headroom in launch.timeout. With checkpoint saving enabled, the
deadline forces a native checkpoint; output.export_hf=true also produces the
final HF export. Background evaluation with final=true requests evaluation at
the actual final update, including off-cadence stops. Submission remains
best-effort: inspect receipts and explicitly resubmit a failed or skipped final
evaluation. Debug exits retain their separate resumable, non-final semantics.