Skip to content

docs: fix stale paths, flags, env vars and metric names - #2651

Merged
yueming-yuan merged 2 commits into
mainfrom
docs/fix-stale-paths-flags-metrics
Aug 18, 2026
Merged

docs: fix stale paths, flags, env vars and metric names#2651
yueming-yuan merged 2 commits into
mainfrom
docs/fix-stale-paths-flags-metrics

Conversation

@yueming-yuan

Copy link
Copy Markdown
Collaborator

Audit of docs/ against main. This PR only fixes claims that are wrong — commands that fail as written, symbols that no longer exist, and values that disagree with the code. Coverage gaps (flags that exist but are undocumented) are deliberately left out; see the follow-ups at the bottom.

Commands that fail as written

Page Was Now
models/glm/glm4-5, models/glm/glm4-7-flash python3 scripts/model_args.py <type> python3 miles/utils/external_utils/model_args_utils.py <type>scripts/model_args.py does not exist; the other 17 conversion snippets already use the right path
user-guide/cli-reference on_policy_distillation listed as an --advantage-estimator value removed. arguments.py:1422 has no such choice, and its own help says OPD is enabled on top of any estimator via --use-opd
ci/02-docker-build, developer/versions --variant rocm-mi300 / rocm-mi350 rocm700-mi30x / rocm700-mi35x / rocm720-mi35x — the real keys in docker/build.py and the variant choice list in docker-build.yml. rocm700-mi35x was missing from both tables
models/thinkingmachines/inkling §5.5 --optimizer-state-nvme-dir, --optimizer-state-nvme-chunk-mb neither flag exists. scripts/run_inkling.py passes --offload-train-target disk --offload-train-disk-dir when colocated and --stream-optimizer-state-to-disk --offload-train-disk-dir --offload-train-disk-chunk-mb 256 under --fully-async. Also split the grad-reduce line, which is --grad-reduce-in-bf16 vs --accumulate-allreduce-grads-in-fp32 on the same condition
cli-reference, training-backend, argument-groups, models/kimi/kimi-k2 --sglang-enable-ep-moe, --sglang-enable-deepep, --sglang-enable-deepep-moe, --sglang-enable-overlap-schedule all four are gone from the pinned sglang ServerArgs. Replaced with what the recipes actually pass: --sglang-ep-size (75 uses), --sglang-moe-a2a-backend (15), --sglang-moe-runner-backend (40), --sglang-deepep-mode (13)
examples/infra_features/train_infer_mismatch_helper/README.md --tis-mode / --tis-level / --tis-*-bound / --tis-batch-normalize / --rs-* documented as CLI flags, plus a --use-rollout-correction flag none of those are CLI flags. They are keys in mis.yaml, reached through --custom-config-path + --custom-tis-function-path; the only CLI toggles are --use-tis and --use-rollout-logprobs. --use-rollout-correction does not exist anywhere in the repo and was the header of an algorithm table

Symbols and paths that do not exist

  • miles/ray/rollout.py is a package now. advanced/fault-tolerance (4×), advanced/pd-disaggregation and examples/geo3k_vlm/README.md now point at rollout_manager.py, rollout_server.py, server_group.py and train_data_conversion.py respectively. While there, fault-tolerance now also mentions that the rollout paths need rollout in --ft-components, not just --use-fault-tolerance.
  • MILES_HACK_TRAIN_TORCH_DETERMINISTIC (models/deepseek/deepseek-v4-flash) does not exist. scripts/run_deepseek_v4.py sets five env vars unconditionally (SGLANG_SKIP_CHECKPOINT_LOAD_CHECK, SGLANG_DSV4_FP4_EXPERTS, SGLANG_HEALTH_CHECK_TIMEOUT, SGLANG_DG_CACHE_DIR_PER_PROCESS, SGLANG_OPT_FP8_WO_A_GEMM) and three more only under --train-deterministic (NCCL_ALGO, NVTE_ALLOW_NONDETERMINISTIC_ALGO, CUBLAS_WORKSPACE_CONFIG). The V4-Pro page got the same five.
  • with_transformers_patch() / miles/utils/transformers_patch.py are both gone; the deepseek_refdeepseek_v4 handling is an in-place config.json rewrite in the launcher.
  • eval/skipped_unhealthy, eval/skipped_pin_violation have no emitter. The built-in reasons are busy, export_failed, ckpt_missing, crashed, plus whatever a CheckpointEvalFn passes to EvalSkip(reason).
  • The mis_* / mismatch_* metric names in the rollout-correction example match nothing in mis.py. Replaced with the names actually emitted: tis_weight_before_bound, tis_truncate_fraction, tis_clip_fraction_low|high, tis_mask_fraction_low|high, rs_mask_fraction_low|high, rs_catastrophic_token_fraction, rs_catastrophic_seq_fraction, is_ratio_mean_after_tis_rs, batch_norm_factor, is_ratio_{mean,min,max}_final, and the unprefixed training_log_ppl / rollout_log_ppl / kl / k3_kl / log_ppl_diff / ppl_ratio / chi2_token / chi2_seq.
  • miles/utils/wandb_utils.pymiles/utils/tracking_utils/wandb_utils.py.

Wrong values and premises

  • --update-weight-buffer-size defaults to 512 MB, not 1 GB (arguments.py:773 is 512 * 1024**2).
  • user-guide/argument-groups opened with "Miles launch scripts are bash arrays." scripts/ holds 34 Python launchers and zero .sh files, and the page contradicted user-guide/launch-script, which already describes the Python layout correctly. Rewritten around the <group>_args locals the launchers actually build, with the previously-missing misc_args group (35 launchers use it) documented and the architecture flags explained as coming from scripts/models/<megatron_model_type>.py via execute_train. The <a id> anchors are unchanged, so incoming links still resolve; concepts, training-backend, user-guide/index, developer/contributor-guide and models/kimi/kimi-k2.5 were updated to the same names.
  • developer/architecture sent new reward types to miles/rollout/sglang_rollout.py; the rm_type dispatch lives in miles/rollout/rm_hub/__init__.py, and rm_hub/ was missing from the package tree.
  • --rm-type enumerations (cli-reference, customization) omitted gemma_math — which scripts/run_gemma_4_*.py and models/gemma/gemma-4 both use, so readers would take it for an invalid value — plus deterministic_random and the boxed_ prefix modifier. --rm-type is also a free str, not an enum.
  • The canonical CI label list (ci/01-label) listed 15 of the 19 entries in KNOWN_LABELS, omitting eval, fully-async, miles-plugin and amd. ci/00-stage already tells readers how run-ci-amd behaves.

Verification

  • pre-commit run --all-files clean (includes the examples→docs mirror check).
  • python scripts/tools/sync_example_docs.py --check clean; the two generated pages were regenerated from their READMEs rather than edited directly.
  • Re-ran the audit passes on the result: 0 broken internal links, 0 missing images, 0 nonexistent repo paths outside the pages that deliberately document unmerged PRs (kimi-k3Kimi-K3 lora RL day-0 support #1825, qwen3-8Qwen 3.8 day-0 lora RL support #2488), 0 --sglang-* flags absent from the pinned ServerArgs.

Deliberately not in this PR

Coverage and consistency work, worth separate PRs:

  • cli-reference says it lists every Miles flag; it covers 91 of 344, and 133 registered flags appear nowhere in the docs — including all of MLflow (--use-mlflow + 4), TensorBoard (--use-tensorboard + 2) and Prometheus (--prometheus-port + 1). user-guide/monitoring documents only wandb as a result.
  • customization says it lists every hook; six load_function hooks are missing (--custom-async-data-buffer-path, --custom-update-weight-post-write-path, --generate-{execute-tool-function,tool-specs}-path, --session-sample-{picker,postprocessor}-path).
  • Ascend NPU has docker/npu_patch/, scripts/run_qwen3_4b_npu.py and execute_train_npu(), but no page mentions it; index and installation list only NVIDIA and AMD, and disagree on MI350 vs MI350X.
  • 24 links across 17 pages still point at the removed /advanced/architecture-support and survive only on the docs.json redirect, with link text that no longer names the destination.
  • Several model and advanced pages still illustrate flags with uppercase SGLANG_ARGS=(...) shell blocks, a leftover of the same bash-array framing this PR removed from argument-groups.
  • low-precision writes GroupLinear / GroupGEMM where int4-qat writes TE GroupedLinear; a stray empty ## TODO section in examples/infra_features/low_precision/README.md; an unrendered {/* FIGURE PLACEHOLDER */} in launch-script; and five British spellings.

Audited docs/ against main. Fixes only the claims that are wrong, not
coverage gaps.

Commands that fail as written:
- glm4-5 / glm4-7-flash conversion used scripts/model_args.py, which does
  not exist; the helper is miles/utils/external_utils/model_args_utils.py
- cli-reference listed on_policy_distillation as an --advantage-estimator
  choice; it is not in the choices list, OPD is --use-opd on top of any
  estimator
- ci/02-docker-build and developer/versions used --variant rocm-mi300 /
  rocm-mi350; build.py and docker-build.yml define rocm700-mi30x,
  rocm700-mi35x and rocm720-mi35x, and rocm700-mi35x was missing entirely
- inkling documented --optimizer-state-nvme-dir / -chunk-mb, which do not
  exist; the launcher passes --offload-train-target disk (colocated) or
  --stream-optimizer-state-to-disk + --offload-train-disk-chunk-mb
  (fully-async), and picks grad reduce dtype the same way
- --sglang-enable-ep-moe / -deepep / -deepep-moe / -overlap-schedule are
  gone from the pinned sglang ServerArgs; recipes use --sglang-ep-size,
  --sglang-moe-a2a-backend, --sglang-moe-runner-backend, --sglang-deepep-mode
- the rollout-correction example presented mis.yaml keys as CLI flags and
  documented a --use-rollout-correction flag that does not exist; the
  interface is --use-tis plus --custom-config-path / --custom-tis-function-path

Symbols and paths that do not exist:
- miles/ray/rollout.py is a package now; point at rollout_manager.py,
  rollout_server.py, server_group.py and train_data_conversion.py
- deepseek-v4-flash claimed the launcher sets
  MILES_HACK_TRAIN_TORCH_DETERMINISTIC=1; list the five env vars it
  actually sets, plus the three --train-deterministic adds
- deepseek-v4-flash described with_transformers_patch() in
  miles/utils/transformers_patch.py; both are gone
- eval/skipped_unhealthy and eval/skipped_pin_violation have no emitter
- the mis_* / mismatch_* metric names are emitted as tis_*, rs_* and
  unprefixed names
- monitoring pointed at miles/utils/wandb_utils.py, now under tracking_utils/

Wrong values and premises:
- --update-weight-buffer-size defaults to 512 MB, not 1 GB
- argument-groups opened with "launch scripts are bash arrays"; scripts/
  holds 34 Python launchers and no shell scripts, and misc_args was
  missing from the group inventory
- architecture sent new reward types to sglang_rollout.py; the rm_type
  dispatch is in miles/rollout/rm_hub/
- --rm-type enumerations omitted gemma_math (used by the gemma-4 recipe),
  deterministic_random and the boxed_ prefix
- the canonical CI label list omitted eval, fully-async, miles-plugin and
  amd, the last of which ci/00-stage.md already refers to

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@nblintao

Copy link
Copy Markdown
Contributor

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — docs-only PR, and the four inline nits found are self-contained documentation inconsistencies (a stale claim in a file this PR didn't touch, and two spots where the PR's own rewritten metric/env-var tables don't fully match the wiring they describe). None affect runtime behavior or code correctness.

Extended reasoning...

Overview

This PR only touches Markdown files under docs/ and two examples/**/README.md files (the second pair are mirrored into generated docs via a checked pre-commit hook). It corrects stale paths, removed flags, wrong defaults, and renamed modules across ~27 files, replacing them with values cross-checked against the current source (arguments.py, docker/build.py, scripts/run_*.py, mis.py, etc.). No application code, build scripts, or CI configuration is modified.

Security risks

None. This is a documentation-only change with no executable code paths, no secrets, and no changes to auth, CI permissions, or build artifacts.

Level of scrutiny

Low-to-moderate is appropriate: the change is large in file count but each edit is a mechanical fact correction (path rename, removed flag, corrected default, renamed metric), not a design decision. The bug-hunting pass found four nits, all of the same class the PR itself is trying to eliminate — a stale cross-reference in an untouched file, an opt-in-vs-default framing error, a metrics table missing a prefix its own recommended wiring adds, and two doc rows describing metrics with a stale premise. These are useful corrections but don't change any behavior and don't block merging a docs PR whose overall content is accurate and net-positive.

Other factors

The author's PR description shows a careful methodology (grep-based audits, verification against pre-commit run --all-files and sync_example_docs.py --check), and explicitly scopes out coverage gaps as follow-up work rather than conflating them with this fix pass. Given the docs-only nature and the nit-level severity of the findings, human review is optional rather than required here.

Comment on lines +176 to +195
<a id="misc-args"></a>
## `misc_args` - GPU layout and everything else

`misc_args` carries what the other groups do not: how many GPUs the actor gets, whether
it shares them with the rollout engines, the Megatron knobs a recipe pins once and never
tunes, and the optional dashboard.

Common entries:

| Concern | Flags |
|---|---|
| GPU layout | `--actor-num-nodes`, `--actor-num-gpus-per-node`, `--num-gpus-per-node` |
| Colocation | `--colocate` |
| Numerics pinned by the recipe | `--attention-dropout 0.0`, `--hidden-dropout 0.0`, `--attention-softmax-in-fp32`, `--accumulate-allreduce-grads-in-fp32` |
| Attention kernel | `--attention-backend` |
| Observability | `--use-miles-dashboard`, `--dump-details` |

Under `--colocate` the actor and the engines share the same GPUs and take turns, so
`--rollout-num-gpus` is ignored; see
[Training Backends](/user-guide/training-backend#3-choosing-the-gpu-layout).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 This PR adds a new ## misc_args Argument Groups section (docs/user-guide/argument-groups.md:176-195, with anchor #misc-args), but doesn't update docs/user-guide/launch-script.md:145-148, which still says "Two blocks have no Argument Groups section: misc_args carries the cluster shape ... and the wandb flags..." — that claim is now false, since misc_args has its own section. Fix: update the sentence to name only the wandb block, and add a misc_args row to the preceding "Block -> Flags documented at" table (launch-script.md:136-143).

Extended reasoning...

docs/user-guide/launch-script.md (not touched by this PR) contains a "Block → Flags documented at" table (lines 136–143) followed by this sentence at lines 145–148:

Two blocks have no Argument Groups section: misc_args carries the cluster shape (--colocate, --actor-num-nodes, --actor-num-gpus-per-node), and the wandb flags come from U.get_default_wandb_args, which returns them only when WANDB_API_KEY is set...

This PR adds a brand-new ## misc_args - GPU layout and everything else section to docs/user-guide/argument-groups.md (diff lines 176–195), complete with an <a id="misc-args"></a> anchor and a table row documenting --actor-num-nodes, --actor-num-gpus-per-node, --colocate, and the other flags launch-script.md attributes to the "undocumented" misc_args block. The PR is thorough about propagating this new group elsewhere — concepts.md, developer/contributor-guide.md, and models/kimi/kimi-k2.5.md all gained a misc_args row or reference — but it missed updating launch-script.md's explicit negative claim, which it directly falsifies.

Why existing code/docs don't catch this: launch-script.md is a separate file with no automated cross-reference check tying its prose claims to the section list in argument-groups.md. The PR's own stated theme is fixing "commands that fail as written... symbols that no longer exist... values that disagree with the code" via a careful audit, and it explicitly reworked argument-groups.md's framing (bash arrays → Python launcher variables) while cross-updating every other page that names the argument groups — so this is a straightforward miss in an otherwise systematic pass, not a design decision.

Concrete proof, step by step:

  1. Before this PR: argument-groups.md had 8 sections (MODEL_ARGS through SGLANG_ARGS), none named misc_args. launch-script.md's claim "two blocks have no Argument Groups section: misc_args ... and the wandb flags" was accurate.
  2. This PR adds a 9th section, ## misc_args - GPU layout and everything else, with anchor #misc-args, documenting exactly the flags (--colocate, --actor-num-nodes, --actor-num-gpus-per-node, etc.) that launch-script.md says have no section.
  3. Reading launch-script.md today (verified via sed -n '130,150p' on the current checkout — the PR does not touch this file) still shows the same "Two blocks have no Argument Groups section: misc_args ..." sentence, and the preceding table still lists only 7 blocks with no misc_args row.
  4. A reader who follows launch-script.md's claim will incorrectly believe misc_args flags are documented nowhere, when argument-groups.md#misc-args now covers them — the opposite of what this PR intends by cross-linking every other page to the new section.

Fix: in docs/user-guide/launch-script.md, add a misc_args row to the "Block → Flags documented at" table (pointing at /user-guide/argument-groups#misc-args), and rewrite the sentence to say only the wandb block lacks a dedicated section (e.g. "One block has no Argument Groups section: the wandb flags come from U.get_default_wandb_args...").

This is docs-only with no runtime/behavioral impact, so it's a nit, not a blocking issue — but it directly contradicts a factual claim this same PR just made false, which is exactly the class of staleness the PR is otherwise trying to eliminate.

```

The launcher sets the required env vars for you: `SGLANG_SKIP_CHECKPOINT_LOAD_CHECK=1`, `SGLANG_DSV4_FP4_EXPERTS=0`, `MILES_HACK_TRAIN_TORCH_DETERMINISTIC=1`, and `NCCL_ALGO=Ring`.
The launcher sets the required env vars for you: `SGLANG_SKIP_CHECKPOINT_LOAD_CHECK=1`, `SGLANG_DSV4_FP4_EXPERTS=0`, `SGLANG_HEALTH_CHECK_TIMEOUT=120`, `SGLANG_DG_CACHE_DIR_PER_PROCESS=1`, and `SGLANG_OPT_FP8_WO_A_GEMM=0`. Passing `--train-deterministic` adds `NCCL_ALGO=Ring`, `NVTE_ALLOW_NONDETERMINISTIC_ALGO=0`, and `CUBLAS_WORKSPACE_CONFIG=:4096:8` on top of those.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The doc says "Passing --train-deterministic adds NCCL_ALGO=Ring, NVTE_ALLOW_NONDETERMINISTIC_ALGO=0, and CUBLAS_WORKSPACE_CONFIG=:4096:8 on top of those" — but in scripts/run_deepseek_v4.py, train_deterministic defaults to True, so these three env vars (plus --deterministic-mode) are set in every stock run, not just when the flag is explicitly passed. A user would need --no-train-deterministic to remove them. The same misleading framing is repeated on the V4-Pro page.

Extended reasoning...

The bug: docs/models/deepseek/deepseek-v4-flash.md line 173 reads: "The launcher sets the required env vars for you: ... Passing --train-deterministic adds NCCL_ALGO=Ring, NVTE_ALLOW_NONDETERMINISTIC_ALGO=0, and CUBLAS_WORKSPACE_CONFIG=:4096:8 on top of those." The word "Passing" frames --train-deterministic as an opt-in flag that is off unless the user explicitly adds it to their command line.

Why that's backwards: In scripts/run_deepseek_v4.py, ScriptArgs declares train_deterministic: bool = True as the dataclass default (alongside the docstring-adjacent "precision configs" block). The launcher is a Typer app built from that dataclass, so a bool field defaulting to True becomes a --train-deterministic / --no-train-deterministic pair whose effective default is on. The three env vars, plus --deterministic-mode, are gated purely on if args.train_deterministic: — a condition that is true for every stock invocation of full-train or train unless the user actively passes --no-train-deterministic.

The code path that triggers it: Any reader who runs the one-line launch command shown earlier on the same page (python scripts/run_deepseek_v4.py full-train --model-name DeepSeek-V4-Flash-FP8 ...) gets all eight env vars — the five unconditional ones plus these three — without ever touching --train-deterministic. The doc's framing would lead that same reader to conclude, incorrectly, that only the first five are active in their run.

Why nothing else catches this: This is a pure prose/code disagreement — there's no test or lint that checks doc text against a launcher's dataclass defaults, so the mismatch survives silently. It was introduced in this very PR when the sentence was rewritten to add the three previously-undocumented vars (replacing a doc line that referenced a nonexistent MILES_HACK_TRAIN_TORCH_DETERMINISTIC var) but kept the old opt-in phrasing structure.

Step-by-step proof:

  1. Open scripts/run_deepseek_v4.py; find train_deterministic: bool = True in ScriptArgs.
  2. Find the block if args.train_deterministic: misc_args += "--deterministic-mode "; extra_env_vars |= {"NCCL_ALGO": "Ring", "NVTE_ALLOW_NONDETERMINISTIC_ALGO": "0", "CUBLAS_WORKSPACE_CONFIG": ":4096:8"}.
  3. Since the dataclass default is True and Typer exposes it as a flag pair defaulting to the dataclass value, running python scripts/run_deepseek_v4.py full-train --model-name DeepSeek-V4-Flash-FP8 --num-nodes 8 --num-gpus-per-node 8 (the exact command shown earlier in this same doc page, §3.1) sets all three vars and --deterministic-mode, with zero mention of --train-deterministic on the command line.
  4. A reader trusting the doc's "Passing --train-deterministic adds ... on top of those" would instead expect these three vars to be absent in that exact command, which is the opposite of what actually happens.

The fix: Reword to something like: "The launcher sets the required env vars for you: SGLANG_SKIP_CHECKPOINT_LOAD_CHECK=1, ..., and (by default, via --train-deterministic) NCCL_ALGO=Ring, NVTE_ALLOW_NONDETERMINISTIC_ALGO=0, and CUBLAS_WORKSPACE_CONFIG=:4096:8; pass --no-train-deterministic to disable the latter three." The same fix applies to the parallel sentence on docs/models/deepseek/deepseek-v4-pro.md ("Required env vars ... and the Pro-only pair ..."), which inherits the same launcher default and is not itself opt-in-framed but should be checked for the same assumption if it implies these are conditional.

Comment on lines 212 to 233

These metrics track importance sampling weights and corrections. They are only computed when `--use-tis` is enabled.

When using `--custom-tis-function-path` pointing to MIS implementation (e.g., `mis.py`), additional fine-grained metrics become available:

| Metric Name | Description | Required Args | Optional Control Args |
|------------|-------------|---------------|----------------------|
| `ois` | On-policy importance sampling ratio: $\exp(\log \pi_{\text{train}} - \log \pi_{\text{old}})$ | `--use-tis` | Only for Algorithm 2 (Decoupled PPO) |
| `mis_mean_is_weight_before_clip` | Raw IS weights before any correction: $\exp(\text{log-ratio})$ | `--use-tis` | `--mis-level` (token/sequence/geometric) |
| `mis_ratio_mean_after_mis` | IS weights after correction (bounded or masked) | `--use-tis` | `--mis-mode`, bounds |
| `mis_truncate_fraction` | Fraction of weights truncated (mode-specific) | `--use-tis`, `--mis-mode=truncate` | `--mis-upper-bound` |
| `mis_clip_fraction_low` | Fraction of weights clipped below lower bound | `--use-tis`, `--mis-mode=clip` | `--mis-lower-bound`, `--mis-upper-bound` |
| `mis_clip_fraction_high` | Fraction of weights clipped above upper bound | `--use-tis`, `--mis-mode=clip` | `--mis-lower-bound`, `--mis-upper-bound` |
| `mis_mask_fraction_low` | Fraction of tokens rejected (below lower bound) | `--use-tis`, `--mis-mode=mask` | `--mis-lower-bound`, `--mis-upper-bound` |
| `mis_mask_fraction_high` | Fraction of tokens rejected (above upper bound) | `--use-tis`, `--mis-mode=mask` | `--mis-lower-bound`, `--mis-upper-bound` |
| `mis_catastrophic_token_fraction` | Fraction of catastrophic tokens (veto-specific) | `--use-tis`, `--mis-veto-threshold` set | Sequence-level rejection |
| `mis_catastrophic_seq_fraction` | Fraction of sequences with catastrophic tokens | `--use-tis`, `--mis-veto-threshold` set | Sequence-level rejection |
| `mis_batch_norm_factor` | Batch normalization factor applied to weights | `--use-tis`, `--mis-batch-normalize` | Normalizes mean to 1.0 |
When using `--custom-tis-function-path` pointing to MIS implementation (e.g., `mis.py`), additional fine-grained metrics become available. The `tis_` and `rs_` prefixes say which stage produced the number.

| Metric Name | Description | Emitted when |
|------------|-------------|--------------|
| `ois` | On-policy importance sampling ratio: $\exp(\log \pi_{\text{train}} - \log \pi_{\text{old}})$ | `--use-tis` (Algorithm 2 only) |
| `tis_weight_before_bound` | Raw IS weights before any bounding: $\exp(\text{log-ratio})$ | `use_tis` |
| `tis_weight_after_bound` | IS weights after the `tis_mode` bounding | `use_tis` |
| `tis_truncate_fraction` | Fraction of weights truncated | `tis_mode: truncate` |
| `tis_clip_fraction_low` / `tis_clip_fraction_high` | Fraction of weights clipped below / above the bound | `tis_mode: clip` |
| `tis_mask_fraction_low` / `tis_mask_fraction_high` | Fraction of tokens rejected below / above the bound | `tis_mode: mask` |
| `rs_mask_fraction_low` / `rs_mask_fraction_high` | Fraction of tokens rejected by rejection sampling | `use_rs` |
| `rs_catastrophic_token_fraction` | Fraction of catastrophic tokens (below the veto threshold) | `rs_veto_threshold` set |
| `rs_catastrophic_seq_fraction` | Fraction of sequences holding a catastrophic token | `rs_veto_threshold` set |
| `is_ratio_mean_after_tis_rs` | Mean IS weight after both TIS and RS | `use_tis` |
| `batch_norm_factor` | Batch normalization factor applied to weights (1.0 when off) | `use_tis` |
| `is_ratio_mean_final` / `is_ratio_min_final` / `is_ratio_max_final` | Final IS weight statistics actually multiplied into the loss | `use_tis` |

## Reference

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The rewritten Mismatch/IS-RS metric tables list bare names (training_log_ppl, kl, tis_weight_before_bound, is_ratio_mean_final, etc.), but the doc's own recommended wiring (--custom-tis-function-path ...mis.compute_mis_weights_with_cp) prefixes every one of these with mis_ before they reach wandb (mis.py: key_name = f"mis_{key}", unchanged by losses.py/log_utils.py), so the real names are train/mis_training_log_ppl, train/mis_kl, etc. The Mismatch Monitoring table's claim that those metrics are computed 'regardless of whether TIS/MIS correction is enabled' is also wrong for the built-in path: the default vanilla_tis_function only emits tis/tis_clipfrac/tis_abs. Mirrored in examples/infra_features/train_infer_mismatch_helper/README.md at the same tables.

Extended reasoning...

This PR's stated goal for this section was to fix wrong metric names ("The mis_* / mismatch_* metric names in the rollout-correction example match nothing in mis.py. Replaced with the names actually emitted..."), but the replacement still doesn't match what actually reaches wandb once you follow the doc's own wiring instructions.

The code path. The two tables (Mismatch Monitoring Metrics at ~L190-210 and IS/RS Correction Metrics at ~L212-233) list bare keys — training_log_ppl, rollout_log_ppl, kl, k3_kl, chi2_token, tis_weight_before_bound, tis_truncate_fraction, rs_catastrophic_token_fraction, is_ratio_mean_final, batch_norm_factor, etc. — as the literal wandb metric names. These names do originate as dict keys inside mis.py's compute_mis_weights (via add_ppl_metrics and the IS/RS bookkeeping). But the doc's own "Config keys" section, just above, tells the reader to wire the feature up with:

--custom-tis-function-path examples.infra_features.train_infer_mismatch_helper.mis.compute_mis_weights_with_cp

compute_mis_weights_with_cp (and the FSDP counterpart compute_mis_weights_fsdp) do not return the raw dict — they wrap every key before returning: key_name = f"mis_{key}". From there, miles/backends/training_utils/loss_hub/losses.py copies tis_metrics into reported_loss verbatim (key_name = f"{metric_key}", no stripping), and miles/backends/training_utils/log_utils.py logs them to wandb as train/{key} unchanged. So with the exact wiring the doc recommends, a reader who greps their wandb dashboard for train/training_log_ppl or train/tis_truncate_fraction finds nothing — the real names are train/mis_training_log_ppl, train/mis_kl, train/mis_tis_truncate_fraction, train/mis_is_ratio_mean_final, and so on for every row in both tables.

Step-by-step proof:

  1. User follows the doc's "Config keys" example and passes --custom-tis-function-path ...mis.compute_mis_weights_with_cp --use-tis.
  2. Inside mis.py, compute_mis_weights builds a metrics dict with keys like training_log_ppl and tis_weight_before_bound (via add_ppl_metrics and the TIS/RS bookkeeping code).
  3. compute_mis_weights_with_cp (the function the doc told you to point at) wraps the return: {f"mis_{key}": v for key, v in metrics.items()} (mirrored in compute_mis_weights_fsdp).
  4. losses.py merges this dict into reported_loss with no key transformation.
  5. log_utils.py logs each entry as train/{key}.
  6. Net result on wandb: train/mis_training_log_ppl, not train/training_log_ppl as the doc's table states.

Why existing review/tests don't catch it: this is a documentation-only PR (verified: mis.py, losses.py, log_utils.py are untouched by the diff), and there's no test that asserts docs match emitted metric names — the mismatch is purely between the table text and the code the doc itself points at.

Secondary claim: the Mismatch Monitoring table also states these metrics "are computed automatically when rollout_log_probs are provided, regardless of whether TIS/MIS correction is enabled." That's only true when a custom TIS function is wired in. The built-in vanilla_tis_function (miles/backends/training_utils/loss_hub/corrections.py) computes only tis, tis_clipfrac, tis_abs — it never computes training_log_ppl/kl/etc. So on the built-in path (no --custom-tis-function-path), none of the named metrics in that table appear at all, prefixed or not.

Suggested fix: either prefix every row in both tables with mis_ (to match compute_mis_weights_with_cp/compute_mis_weights_fsdp), or add a note that the bare names apply only to a custom function that returns them unprefixed, and that the shipped reference implementation (mis.py) always adds the mis_ prefix. Also soften "regardless of whether TIS/MIS correction is enabled" to clarify it's true only for rollout_log_probs-derived metrics computed by the loss hub itself, not for the mis.py-sourced ones which require both --use-tis and a custom TIS function path.

This is docs-only with no runtime impact, but it's precisely the class of error this PR set out to fix, so worth correcting in the same pass. Mirrored verbatim in examples/infra_features/train_infer_mismatch_helper/README.md, which should get the same fix.

Comment on lines 326 to 333
| `eval/skipped_busy` | At `--eval-max-in-flight` under `--eval-overflow-policy skip` |
| `eval/skipped_export_failed` | The snapshot export raised |
| `eval/skipped_ckpt_missing` | No `.complete` marker in the snapshot directory |
| `eval/skipped_unhealthy` | The fleet or its router was unreachable |
| `eval/skipped_pin_violation` | The engines did not all report the expected weight version |
| `eval/skipped_crashed` | Anything else the eval raised |
| `eval/skipped_<reason>` | A `CheckpointEvalFn` raised `EvalSkip(reason)` |

For a point that did run, `eval/{dataset}/weight_version/mean == eval/step` and
`eval/{dataset}/weight_version/mixed_version_ratio == 0` together confirm it measured

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The removed eval/skipped_unhealthy and eval/skipped_pin_violation rows are still accurate — EvalFleet.pin() in miles/ray/rollout/eval_fleet.py still raises EvalSkip("unhealthy") and EvalSkip("pin_violation") whenever --eval-num-gpus > 0 (the Dedicated fleet backend documented earlier in this same file), and rollout_manager.py's _eval_checkpoint still logs them under exactly those metric names. The new catch-all row also wrongly attributes them to a user-supplied CheckpointEvalFn when they actually come from the built-in EvalFleet.

Extended reasoning...

The PR's stated premise for deleting these two rows is that eval/skipped_unhealthy and eval/skipped_pin_violation have no emitter. That premise is false for the Dedicated-fleet eval backend, which this very doc documents two sections below ("Mode 2: Dedicated fleet", enabled via --eval-num-gpus N).

The code path

miles/ray/rollout/eval_fleet.py's EvalFleet.pin() raises EvalSkip("unhealthy") in two places (when the router or engines fail a readiness probe) and EvalSkip("pin_violation") when _pin_fleet reports the engines did not all converge on the expected weight version. rollout_manager.py's _eval_checkpoint calls self._eval_fleet.pin(...) and catches EvalSkip generically, forwarding e.reason to report_eval_skiplog_eval_skip, which logs f"eval/skipped_{reason}". Reason strings "unhealthy" and "pin_violation" therefore surface as eval/skipped_unhealthy and eval/skipped_pin_violation exactly as the deleted rows described — and the deleted rows' descriptions ("the fleet or its router was unreachable" / "the engines did not all report the expected weight version") match pin()'s behavior precisely.

Why nothing else in this PR catches it

The PR's own audit methodology (grep for the metric string, confirm no emitter) apparently missed eval_fleet.py because the emission is indirect: the string is built dynamically as f"eval/skipped_{reason}" in a shared helper, not written literally as eval/skipped_unhealthy anywhere. A textual audit of the doc's exact metric names against the codebase would not surface the fact that "unhealthy" and "pin_violation" are two of the possible values substituted into that f-string.

Impact

This is a real behavior/documentation mismatch: an operator running the Dedicated-fleet eval backend (--eval-num-gpus > 0) who sees eval/skipped_unhealthy or eval/skipped_pin_violation in their metrics will find no explanation for it in this table, and may instead misread the new catch-all row (eval/skipped_<reason> | A CheckpointEvalFn raised EvalSkip(reason)) as meaning they have a custom CheckpointEvalFn configured, which they may not.

Step-by-step proof

  1. Run with --eval-num-gpus 1 --eval-interval K (Dedicated fleet mode, per this doc's "Mode 2" section).
  2. At an eval-due step, _eval_checkpoint calls await self._eval_fleet.pin(hf_dir, version).
  3. Inside pin(), if the fleet's router does not report ready in time, it raises EvalSkip("unhealthy"); if the engines pin to the wrong weight version, it raises EvalSkip("pin_violation").
  4. _eval_checkpoint's except EvalSkip as e: catches it and calls report_eval_skip(rollout_id, e.reason).
  5. log_eval_skip logs {f"eval/skipped_{e.reason}": 1}, i.e. {"eval/skipped_unhealthy": 1} or {"eval/skipped_pin_violation": 1}.
  6. These exact metric names appear in wandb/logs, but per this PR's edit, are undocumented and their only textual match in the table is the generic CheckpointEvalFn row, which misattributes the source.

Fix

Restore the two specific rows (or fold them into a fleet-specific note) rather than deleting them, and keep the generic CheckpointEvalFn row separate since it describes a different code path (a user's custom eval function, not the built-in EvalFleet).

On the refutations

All three verifiers independently confirmed this via direct source inspection, and none refuted it — this is a straightforward, uncontested finding.

@nblintao nblintao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My agent found 2 mistakes that seems reasonable to me. But I don't have full context. Could you check if it makes sense?

Comment on lines -329 to -330
| `eval/skipped_unhealthy` | The fleet or its router was unreachable |
| `eval/skipped_pin_violation` | The engines did not all report the expected weight version |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two do have emitters — eval_fleet.py raises EvalSkip("unhealthy") / EvalSkip("pin_violation") and metrics.py logs eval/skipped_{reason}, so the literal names never show up in a grep. I'd keep both rows and add the generic one on top.

Comment on lines +221 to +231
| `tis_weight_before_bound` | Raw IS weights before any bounding: $\exp(\text{log-ratio})$ | `use_tis` |
| `tis_weight_after_bound` | IS weights after the `tis_mode` bounding | `use_tis` |
| `tis_truncate_fraction` | Fraction of weights truncated | `tis_mode: truncate` |
| `tis_clip_fraction_low` / `tis_clip_fraction_high` | Fraction of weights clipped below / above the bound | `tis_mode: clip` |
| `tis_mask_fraction_low` / `tis_mask_fraction_high` | Fraction of tokens rejected below / above the bound | `tis_mode: mask` |
| `rs_mask_fraction_low` / `rs_mask_fraction_high` | Fraction of tokens rejected by rejection sampling | `use_rs` |
| `rs_catastrophic_token_fraction` | Fraction of catastrophic tokens (below the veto threshold) | `rs_veto_threshold` set |
| `rs_catastrophic_seq_fraction` | Fraction of sequences holding a catastrophic token | `rs_veto_threshold` set |
| `is_ratio_mean_after_tis_rs` | Mean IS weight after both TIS and RS | `use_tis` |
| `batch_norm_factor` | Batch normalization factor applied to weights (1.0 when off) | `use_tis` |
| `is_ratio_mean_final` / `is_ratio_min_final` / `is_ratio_max_final` | Final IS weight statistics actually multiplied into the loss | `use_tis` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These still need the mis_ prefix: both compute_mis_weights_with_cp and compute_mis_weights_fsdp end with result_metrics[f"mis_{key}"], so wandb shows train/mis_tis_truncate_fraction, train/mis_is_ratio_mean_final, etc. Same for the PPL table above. Note batch_norm_factor was already correct as mis_batch_norm_factor before this PR.

…s_ prefix

- launch-script said two blocks have no Argument Groups section; misc_args
  now has one, so add its row to the block table and leave only wandb
- deepseek-v4 framed --train-deterministic as opt-in, but ScriptArgs
  defaults it to True, so a stock run already gets --deterministic-mode and
  the three env vars; say so and name --no-train-deterministic. Note the
  same default on the Pro page
- the mis.py metric names needed the mis_ prefix its wrapper adds
  (mis.py:391 / :506), so the wandb keys are train/mis_training_log_ppl,
  train/mis_tis_truncate_fraction, ... The ois and
  train_rollout_logprob_abs_diff pair come from miles and stay unprefixed
- the ppl / kl / chi2 metrics are not emitted "regardless of whether
  TIS/MIS correction is enabled": the built-in vanilla_tis_function reports
  only tis, tis_clipfrac and tis_abs, so they need the
  --custom-tis-function-path wiring
@yueming-yuan
yueming-yuan merged commit 748935d into main Aug 18, 2026
11 of 16 checks passed
@yueming-yuan
yueming-yuan deleted the docs/fix-stale-paths-flags-metrics branch August 18, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants