Cut the loop in half
The two
--debug-*-only flags are mutually exclusive and argument validation rejects
setting both.
The pattern worth internalizing:
Replaying without killing the engines
--load-debug-rollout-data gives up the engines, so it cannot tell you whether the
weights that reached the engines are right. --ci-inject-rollout-data-path is the
variant that keeps them:
--ci-inject-rollout-data-min-match-ratio (default 0.9), the engines are
holding the wrong weights. Legitimate ulp-level drift only flips the occasional sampled
token, so the ratio stays high when the sync is correct.
Cut a single step into pieces
Make two runs comparable
--debug-deterministic-collective runs the training world on the det_nccl backend from
miles.utils.test_utils.det_process_group, which folds order-sensitive SUM and AVG
reductions in a fixed tree order. That is what makes two different reduction topologies
bitwise comparable, and it is how the Megatron-versus-FSDP alignment test can assert
equality at all. It is slow; never enable it in production.
For end-to-end reproducibility of a whole run, including the sampling path, see the
Reproducibility recipe.
The assertion harness CI uses
An e2e test in Miles is mostly a normal training run with--ci-test added. That flag
turns on a set of in-process assertions, so a violated invariant fails the run where it
happens instead of showing up as a wrong number hours later. You can use the same flags on
your own runs, and each checker has an escape hatch for the case where your change makes it
legitimately not hold.
Three more take values rather than switching off:
Accuracy gate.
--ci-metric-checker-key <key> --ci-metric-checker-threshold <x>
checks eval metrics when the checker is disposed at the end of the run. By default, at
least one eval must report key >= x. Set --ci-metric-checker-expect-num <n> to
require exactly n eval checks and require all of them to succeed. A run with no eval
always fails.
Gradient-norm comparison. --ci-save-grad-norm <path> writes the grad norm, and
--ci-load-grad-norm <path> asserts a later run matches within rel_tol=abs_tol=0.03.
Both accept {role}, {rollout_id} and {step_id} in the path, and only rank 0
participates. This is how the Megatron-versus-FSDP alignment test compares two backends
that cannot share a process.
Per-layer weight hashes. --ci-save-model-hash and --ci-check-model-hash compute
SHA256 over each layer’s parameter bytes, including name, shape and dtype, and write one
JSON per rank under iter_<iteration>/model_hash_tp*_pp*_dp*_cp*.json. Layer granularity
is deliberate: a mismatch names the layer instead of just saying the model differs.
Fault injection. --ci-ft-test-actions takes a JSON array of actions such as
{"at_rollout": 3, "action": "stop_cell_at_end", "cell_id": "trainer-actor-0"}, with
stop_cell_at_end, start_cell_at_end and crash_before_allreduce available and cell_id
naming the target cell. It is how the fault-tolerance suite kills things on purpose. See
Fault Tolerance.
Aligning precision
The most common class of bug. Walk these checks before anything else.Is the first rollout coherent?
If the very first rollout is gibberish:- Parameters didn’t load. Megatron logs a clear load line; if it is absent, fix
--load/--ref-load. - Parameter mapping is wrong. With
pp_size > 1, second-stage layer IDs are a common offset bug. Dump the parameters in the SGLang model’sload_weightsand compare against the checkpoint. - SGLang dropped buffers. Some buffers can be released during the parameter-release path; check they are re-loaded after weight sync.
- Pretrained versus instruct. If the instruct model of the same architecture works, your base model plus chat template combination is wrong.
Are log_probs and ref_log_probs equal at step 1?
They should be, because the actor and the reference are the same weights. This is exactly
what the log-probs checker above asserts, so running with --ci-test turns the question
into a hard failure. If KL is non-zero:
- Non-deterministic kernels. Some Transformer Engine versions need
--attention-backend flashto force deterministic Flash Attention under context parallelism. - KL below
1e-4. Kernel-level jitter, acceptable. - KL above 1. A configuration error; re-check parallelism and precision.
- About 0.8 per token on an instruct model. Almost always a chat-template mismatch. Run the chat template verifier.
Is grad_norm reasonable?
Step 1 with num_steps_per_rollout=1 should produce a tiny gradient. If it does not, look
at MoE fusion first (--moe-permute-fusion), then at whether the reward is being computed
from the right key (a --label-key typo is the usual cause).
Does step 2 OOM under colocate?
The trainer’s offload and reload cycle is colliding with the engine’s static memory. Lower--sglang-mem-fraction-static to 0.7 or 0.6. See
Training Backends for the layout and offload knobs.
Common kernel pitfalls
Reading logs
Useful environment variables while debugging:
train/ppo_kl, train/pg_clipfrac, train/kl_loss, rollout/log_probs,
rollout/ref_log_probs, rollout/rollout_log_probs, rollout/entropy. Trainer steps log
as step <n>: {...}, and the rollout side logs one reduced dict per rollout. See
Monitoring and Logging for the full metric surface.
When all else fails
- Drop to a tiny model on a known-good recipe (the Reproducibility one) to separate framework from model.
git bisectbetween a known-good commit and HEAD, with the record-and-replay pattern above pinning the inputs and--debug-deterministic-collectivepinning the reductions.- Open a GitHub issue with the launch script,
pip freeze, the first 200 lines of trainer stdout, and what you have already ruled out.

