Skip to content

[None][test] Add ModelExpress post-merge accuracy canaries - #18561

Draft
moraxu wants to merge 7 commits into
NVIDIA:mainfrom
moraxu:user/mguzek/mx-accuracy-canaries
Draft

[None][test] Add ModelExpress post-merge accuracy canaries#18561
moraxu wants to merge 7 commits into
NVIDIA:mainfrom
moraxu:user/mguzek/mx-accuracy-canaries

Conversation

@moraxu

@moraxu moraxu commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Post-merge accuracy tier of the MX Model Family Testing Proposal (Levers 2 and 3), stacked on #18560 (weight manifests + widened probe).

  • Receiver-side evaluation (mx_e2e_worker.py): an opt-in --eval-* flag group sizes the engine for the task (max_seq_len = input + output, max_num_tokens >= 8192, eval batch size, kv_cache_config fraction), keeps the tokenizer, self-checks its own MX transfer logs right after load and exits with status 3 before spending any eval time on a fallen-back run, runs the tensorrt_llm.evaluate task with the reference sampling params (greedy, truncate_prompt_tokens), and writes the score to JSON. The receiver stays a fresh subprocess, so CUDA_VISIBLE_DEVICES, offline flags, OMPI_MCA_*, and MX_TRANSFER_LOG_DIR are set before any MPI/UCX import; the smoke path is byte-for-byte unchanged when the flags are absent.
  • Orchestrator (test_model_express_accuracy.py): MxAccuracyCase(MxE2ECase) rows + MxEvalSpec.from_task(...) derive num_samples, dataset path, and input/output lengths from the accuracy_core task class, so the sample count always matches the threshold's n. The pytest process never builds an LLM: it asserts HypothesisTestingParams.assert_passing(score) from the reference YAMLs (no paired HF-baseline eval — the reference value is the baseline), after assert_transfer_evidence and a donor↔receiver weight-manifest comparison (transfer + final tiers). Load/eval seconds, score, and threshold are recorded as junit properties and as model_express_accuracy/<id>.json under --output-dir; load times are observed, not gated.
  • Canaries (TP1, BF16, inside the qualified envelope, all with existing bare reference entries): llama3-8b-instruct-mmlu-tp1 (meta-llama/Meta-Llama-3-8B-Instruct, MMLU), qwen2.5-7b-instruct-mmlu-tp1 (Qwen/Qwen2.5-7B-Instruct, MMLU), qwen3-8b-gsm8k-tp1 (Qwen3/Qwen3-8B, GSM8K). Llama-3.1/3.2 are out of the envelope (rope_type != default / tied embeddings). No Mistral row yet ([TRTLLM-14881][feat] qualify Mistral dense for MX #18558); adding one is a single MxAccuracyCase param plus a reference entry.
  • CI: new DGX_H100-2_GPUs-PyTorch-ModelExpress-Post-Merge-1 stage (jenkins/L0_Test.groovy, tuple boolean true = Redis + ModelExpress sidecars; the -ModelExpress- substring selects the pip installs; Post-Merge selects stage=post_merge) and a stage: post_merge block in l0_model_express.yml — in the same commit, because tests/unittest/tools/test_test_to_stage_mapping.py requires every post_merge row to map to a Post-Merge stage. The stage runs on every main commit and never pre-merge; exact --stage-list names need no label.
  • test_mx_accuracy_cases_have_references (CPU) guards the reference keys (note the nonstandard Qwen3/Qwen3-8B key) and round-trips MxEvalSpec.to_argv() through the worker's argument parser.

Test Coverage

  • pytest -sv tests/integration/defs/model_express/test_model_express_accuracy.py -k cases_have_references (CPU)
  • pytest -sv tests/unittest/tools/test_test_to_stage_mapping.py (CPU; same-commit stage/row invariant)
  • python scripts/test_to_stage_mapping.py --stages "DGX_H100-2_GPUs-PyTorch-ModelExpress-Post-Merge-1" → the four accuracy test IDs
  • E2E: /bot run --stage-list "DGX_H100-2_GPUs-PyTorch-ModelExpress-Post-Merge-1"; expected pass thresholds (α=0.05, σ=50): MMLU 67.74→65.92 and 75.32→73.50, GSM8K 87.11→83.91. To fill in from the stage run: per case donor/receiver load_seconds, eval_seconds, score vs threshold, Matched m/n counts.

PR Checklist

Follow-ups (out of scope): OpenSearch posting of load/eval times, TP2 canary rows in the OnDemand stage, Mistral row, family rotation.

Add tensorrt_llm/_torch/weight_sharing/weight_manifest.py: a SHA-256 per-tensor
manifest of a root module's registered parameters and buffers with layout
metadata, storage-alias partitions, skipped-tensor records, a whole-manifest
digest, and a format version. It is env-gated by MX_WEIGHT_MANIFEST_DIR and
MX_WEIGHT_MANIFEST_ROLE so production loads never hash or write anything.

The comparison is byte-for-byte, deliberately stronger than the exact-value
equality of torch.testing.assert_close(rtol=0, atol=0, equal_nan=True): it
distinguishes signed zeros and NaN payloads. Unit tests pin the contract,
including a corruption-injection trio (single bit flip, +0.0 -> -0.0, NaN
payload change).

Signed-off-by: Michal Guzek <mguzek@nvidia.com>
…aries

ModelLoader.load writes the final-state manifest once per rank at its single
return point, after every post-load hook and MoE load-balancer finalization
and before engine warmup, and records the cost as the weight_manifest_seconds
metric. MXCheckpointLoader writes the transfer-boundary manifest at the
receiver's full P2P success (now CUDA-synchronized first) and at the donor's
publish point, outside the best-effort publish guard so a manifest problem is
loud. Both hooks are no-ops unless MX_WEIGHT_MANIFEST_DIR is set.

The unit lifecycle harness additionally compares canonical tensor bytes so the
staged receiver is held to the same byte-level contract.

Signed-off-by: Michal Guzek <mguzek@nvidia.com>
… probe

Move the reusable pieces of the ModelExpress E2E test into
tests/integration/defs/model_express/mx_harness.py and the stdlib-only
mx_evidence.py (transfer-log rules shared with the worker script), and make
tests/integration/defs/model_express a package.

test_mx_donor_receiver now also collects per-rank weight manifests from all
three roles and enforces two tiers: donor-at-publish and receiver-at-receive
parameters must be byte-identical, and the final manifests of baseline,
donor, and receiver must be pairwise byte-identical (parameters and buffers,
skipped sets, alias partitions), with only a per-row, documented exemption
mechanism and no numeric tolerance. The behavioral probe grows from 2 prompts
x 8 tokens to 8 prompts x 32 greedy tokens with engine limits raised to fit.
Payloads, logs, manifests, and timing are archived under --output-dir.

Signed-off-by: Michal Guzek <mguzek@nvidia.com>
…n probe

Signed-off-by: Michal Guzek <mguzek@nvidia.com>
Extend mx_e2e_worker.py with an opt-in --eval-* mode: the receiver sizes its
engine for the task, keeps the tokenizer, self-checks its own MX transfer logs
before spending any evaluation time (exit status 3 on a fallback), evaluates
the tensorrt_llm.evaluate task with the reference sampling parameters in its
own subprocess, and writes the score to JSON. The smoke path is unchanged when
the flags are absent.

test_model_express_accuracy.py runs one reference-backed task per qualified
family (Meta-Llama-3-8B-Instruct MMLU, Qwen2.5-7B-Instruct MMLU, Qwen3-8B
GSM8K) on an MX receiver started from a metadata-only snapshot and asserts the
accuracy_core hypothesis-testing threshold without constructing an LLM in the
pytest process. There is no paired HF-baseline evaluation: the reference value
is the baseline. Transfer evidence and donor/receiver weight manifests are
checked first, and load/eval times are recorded as junit properties and
--output-dir JSON without thresholds.

Signed-off-by: Michal Guzek <mguzek@nvidia.com>
Add DGX_H100-2_GPUs-PyTorch-ModelExpress-Post-Merge-1 (Redis + ModelExpress
sidecars, post-merge only) and register the accuracy canaries as a
stage: post_merge block in l0_model_express.yml. Both land together because
every post_merge test row must map to a Post-Merge stage of its test-db file.

Signed-off-by: Michal Guzek <mguzek@nvidia.com>
Signed-off-by: Michal Guzek <mguzek@nvidia.com>
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.

1 participant