Skip to content

[OMNIML-6179] [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data - #2850

Merged
shengliangxu merged 5 commits into
mainfrom
shengliangx/ci-6-local-data-tests
Oct 21, 2026
Merged

shengliangxu merged 5 commits into
mainfrom
shengliangx/ci-6-local-data-tests

Conversation

@shengliangxu

@shengliangxu shengliangxu commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Type of change: CI (test runtime and reliability, tests only)

Less data and fewer Hub round trips in four GPU-lane tests:

  • Six preprocess_data tests use the tiny local tokenizer (tests/gpu_megatron). They tokenized with gpt2 from the Hub, and megatron_preprocess_data loads the tokenizer once for the vocabulary size and again in every worker, so each test made several round trips; none depends on gpt2's vocabulary. They use the checked-in byte-level tiny_tokenizer_path fixture that the blend test already uses. Three of the six read only local data, so the tokenizer download was their only network access (the jsonl max_tokens test is among the lane's 120 s timeouts, 3 failures). Measured on a CPU host with a cold Hub cache, the 11 tests of the module take 42.8 and 44.0 s before and 35.0 and 36.1 s after (two runs each; the nine tests of the earlier base took 38.5 s and 24.4 s, and 37 s and 23 s in CI). The Qwen3 tests keep their real tokenizer (chat template and EOS handling).
  • The puzzletron test data shrinks, and a duplicate NAS test is folded into another. save_dummy_dataset wrote 2500 copies of one conversation into each split (1738 blocks of 2048 tokens), while the pruning scores read 100 blocks and the later steps 2 each; 400 conversations (278 blocks) take the three dataloader builds plus the save from 2.8 s to 0.36 s per test, and the validation loss moved by at most 0.02 on random-init toy Llamas (the scoring tolerance is 0.15). calc_subblock_stats ran 12 passes where only the entry for batch_size 96 is read: batch_sizes: [96] runs 4. test_nas_convert_ffn_pruning ran the same mtn.convert on the same toy Llama and config as test_nas_search (21.6 s with its own 2-rank spawn); its two assertions move into test_nas_search.
  • The unified HF export test calibrates on local text, with no previews or batch probing. Each of its 11 params starts a fresh hf_ptq process (22 s per param). It now reads a local jsonl of 64 random-word texts (new helper write_calib_jsonl, shared through the session-wide calib_jsonl fixture in tests/gpu/conftest.py, which fails the session if a test writes into its directory) instead of cnn_dailymail from the Hub, passes --batch_size 64 (the whole calibration set in one batch instead of probing for the largest batch) and --skip_generate. The test only checks the structure of the exported safetensors.
  • The GPTQ end-to-end test reads 8 texts from the same local jsonl instead of streaming cnn_dailymail for each of its three params (and a test that could fail on a Hub hiccup).

Time improvement (measured in CI)

Time of the changed tests; before = the runs of #2846-#2849, after = #2850, #2852, #2854 and #2855 (median). #2849 and #2850 ran on runners 1.26x and 1.29x slower for every test, so the change after speed scaling is in brackets.

lane tests before after change
gpu tests/gpu/torch/puzzletron (18 tests, 17 after the folded NAS test) 290 s 202 s -88 s (-70 s, -26%)
gpu unified HF export, 11 params 228 s 153 s -75 s (-67 s, -30%)
gpu GPTQ end-to-end, 3 params 7.2 s 1.1 s -6 s
gpu_megatron preprocess_data module 37 s 23 s -14 s (-10 s, -27%)

So about 170 s (144 s after scaling) off the gpu job and 14 s off the gpu_megatron job. This PR's own run was on a slow runner (puzzletron 310 s); the three later runs agree with each other (199-203 s). My first estimates were 50 s, 38 s and 6 s for the first three rows.

Method: every PR of the series has its own CI run, so "before" is the run of the PR(s) below that lack the change and "after" the runs that carry it. Times come from the job logs (--durations, or the gap between consecutive result lines). Where runner speed matters, the tests that no PR of the series touches scale each run, and the raw and the scaled medians are both shown. One test varies by 15-30% from run to run on its own, so smaller differences are not evidence.

Usage

Nothing user-facing.

Testing

  • preprocess_data: ran the module on CPU next to a copy of the tiny_tokenizer_path fixture with the Hub reachable: 11 passed before and after (main has dropped the HF xfail marks), with identical test ids.
  • calib_jsonl: with the real fixture and the repo's get_dataset_dataloader on a CPU host, the 8 samples read from the shared 64-text file equal those of the old 8-text file, and two reads leave the directory unmodified (the fixture's teardown check passes). --setup-plan shows the unified export test using the conftest fixture; the GPTQ and export tests themselves need a GPU.
  • puzzletron: timed the dataloader builds, the subblock-stats passes and the loss sensitivity on CPU with the repo's own helpers; collected tests/gpu/torch/puzzletron (16 tests, one fewer than before). The Nemotron-3 toy keeps its 128 experts, because shrinking it changes EXPECTED_TEACHER_MEMORY_MIB and EXPECTED_TEACHER_NUM_PARAMS, which need a GPU runner with mamba_ssm to regenerate.
  • unified export: no GPU here, so I ran the test on a CPU host through a shim that lets hf_ptq pass its GPU guard and passes --device cpu. 8 of the 11 params pass with the new command line, scale-equality assertions included (fp8, nvfp4, int4_awq, w4a8_awq_beta, int8_weight_only, w4a16_nvfp4, the Qwen3 MoE nvfp4 and the GPT-OSS fp8); the other three need CUDA or Triton. GPTQ: the flow completes on the jsonl for the FP8 and INT4 blockwise configs (NVFP4 needs CUDA).
  • Not run: the GPU tests themselves; this PR's gpu and gpu_megatron jobs are the check. Pre-commit passes.

Before your PR is "Ready for review"

Make sure you read and follow Contributor guidelines and your commits are signed (git commit -s -S).

Make sure you read and follow the Security Best Practices (e.g. avoiding hardcoded trust_remote_code=True, torch.load(..., weights_only=False), pickle, etc.).

  • Is this change backward compatible?: N/A (CI and tests)
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: no new dependency
  • Did you write any new necessary tests?: ✅ one small helper, write_calib_jsonl (tests/_test_utils/torch/calib_data.py), and one session fixture, calib_jsonl (tests/gpu/conftest.py), used by two tests; no new test functions
  • Did you update Changelog?: N/A (CI and tests)
  • Did you get Claude approval on this PR?: N/A (optional, not run)

Additional Information

Slice 6 of 11 of the GPU-CI speed-up work, split out of the reference draft #2730 (not for merge; it holds the whole series with its measurements). Slices 1, 2, 5, 8, 9 and 10 (#2845, #2846, #2849, #2852, #2853, #2854) have merged. The remaining slices (#2847, #2848, #2850, #2851, #2855) are independent PRs on main: each applies to main on its own, and merging all of them onto main in different orders gives the same tree. The times above are from the series' CI runs before 2026-10-21, when all slices formed one linear stack; a PR number in a method note names that PR's run from then.

  1. [OMNIML-6178] [1/11] Run the unit tests in parallel with pytest-xdist #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with pytest-xdist (merged)
  2. [OMNIML-6178] [2/11] Use the sysmon coverage core in CI #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI (merged)
  3. [OMNIML-6179] [3/11] Run manual-marked tests on nightly and on-demand GPU runs #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and on-demand GPU runs
  4. [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more time #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more time
  5. [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones to the unit lane, small models #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones to the unit lane, small models (merged)
  6. [OMNIML-6179] [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data (this PR)
  7. [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and megatron_bridge example tests #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and megatron_bridge example tests
  8. [OMNIML-6180] [8/11] Trim the DFlash regression test #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
  9. [OMNIML-6180] [9/11] Trim the torch_onnx example lane #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane (merged)
  10. [OMNIML-6181] [10/11] Make the distributed worker pool fail fast when a worker dies #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail fast when a worker dies (merged)
  11. [OMNIML-6181] [11/11] Share one Megatron worker pool across the gpu_megatron modules #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across the gpu_megatron modules

Summary by CodeRabbit

  • Tests
    • Updated model export and quantization checks to use locally generated calibration data.
    • Standardized preprocessing tests on a small local tokenizer.
    • Restricted model analysis configurations to a batch size of 96.
    • Reduced sample dataset sizes for pruning-related checks.
    • Strengthened conversion verification by checking generated pruning outputs.
    • Removed the FFN pruning conversion test while retaining attention-pruning coverage.

@copy-pr-bot

copy-pr-bot Bot commented Oct 17, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: NVIDIA/Model-Optimizer/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: f28ce766-7767-4452-94ce-973bc6d0b3d0
📥 Commits

Reviewing files that changed from the base of the PR and between 126279b and 503eff3.

📒 Files selected for processing: 2
  • tests/gpu/torch/export/test_unified_hf_export_and_check_safetensors.py
  • tests/gpu/torch/puzzletron/test_nas_convert.py
💤 Files with no reviewable changes: 1
  • tests/gpu/torch/puzzletron/test_nas_convert.py

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

📝 Walkthrough

The changes add deterministic local calibration data, adjust Puzzletron workloads and NAS assertions, and replace hard-coded tokenizer and dataset identifiers in test fixtures.

Changes: Local calibration data

Layer / File(s) Summary
Generate and use local calibration data
tests/_test_utils/torch/calib_data.py, tests/gpu/conftest.py, tests/gpu/torch/export/..., tests/gpu/torch/quantization/test_gptq.py
A deterministic JSONL calibration-data helper and fixture are added. The export and GPTQ tests use the generated file instead of cnn_dailymail. The export test sets batch size to 64 and adds --skip_generate.

Puzzletron test workloads

Layer / File(s) Summary
Update Puzzletron workload and NAS tests
tests/_test_utils/torch/puzzletron/utils.py, tests/gpu/torch/puzzletron/resources/configs/..., tests/gpu/torch/puzzletron/test_nas_convert.py, tests/gpu/torch/puzzletron/test_nas_search.py
The dummy dataset repetition count changes from 2,500 to 400. Listed calc_subblock_stats.batch_sizes values are restricted to [96]. The FFN-pruning conversion test is removed, and the NAS search test checks conversion outputs.

Megatron preprocessing tests

Layer / File(s) Summary
Use the tiny tokenizer fixture
tests/gpu_megatron/torch/utils/plugins/test_megatron_preprocess_data.py
JSONL, Hugging Face, cached-resume, gzip, and streaming-warning tests use tiny_tokenizer_path instead of "gpt2".

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Other

Suggested reviewers: kevalmorabia97, danielkorzekwa

Merge Risk: ⚪ Minimal · up to 503ef

The test-only changes are mergeable; normal GPU CI remains the appropriate validation.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 5 passed
Check name Status Explanation
Title check Passed The title clearly and concisely summarizes the main changes: using local tokenizers and calibration data in GPU tests, and reducing Puzzletron test data and workload.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns Passed No security anti-patterns were introduced. The reviewed diff changes only test utilities, GPU test files, and YAML configurations; it contains no Python changes under modelopt/ or examples/. The d…
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Security Anti-Patterns

Explanation

No security anti-patterns were introduced. The reviewed diff changes only test utilities, GPU test files, and YAML configurations; it contains no Python changes under modelopt/ or examples/. The diff adds no torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), hardcoded trust_remote_code=True, external-input eval()/exec(), or # nosec comments. It also changes no pyproject.toml or requirements.txt files.

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 2d82a59

@codecov

codecov Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.91%. Comparing base (6d76b59) to head (55e1c27).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2850      +/-   ##
==========================================
+ Coverage   70.16%   78.91%   +8.75%     
==========================================
  Files         659      659              
  Lines       74081    74081              
==========================================
+ Hits        51978    58462    +6484     
+ Misses      22103    15619    -6484     
Flag Coverage Δ
examples-diffusers 20.04% <ø> (-0.01%) ⬇️
examples-gpt-oss 13.51% <ø> (-0.01%) ⬇️
examples-hf_ptq 23.19% <ø> (-0.04%) ⬇️
examples-llm_distill 13.59% <ø> (-0.01%) ⬇️
examples-llm_eval 17.24% <ø> (+<0.01%) ⬆️
examples-llm_qat 17.48% <ø> (-0.01%) ⬇️
examples-llm_sparsity 15.76% <ø> (-0.01%) ⬇️
examples-megatron_bridge 26.43% <ø> (-0.13%) ⬇️
examples-specdec_bench 13.29% <ø> (-0.01%) ⬇️
examples-speculative_decoding 17.47% <ø> (-0.01%) ⬇️
examples-torch_onnx 21.39% <ø> (-0.01%) ⬇️
examples-torch_trt 15.24% <ø> (-0.01%) ⬇️
examples-vllm_serve 15.08% <ø> (-0.01%) ⬇️
gpu 59.13% <ø> (+37.00%) ⬆️
regression 15.10% <ø> (-0.01%) ⬇️
unit 60.60% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shengliangxu shengliangxu changed the title [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data [6/12] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data Oct 17, 2026
@shengliangxu
shengliangxu removed this pull request from stack #2856 October 17, 2026 06:58
@shengliangxu
shengliangxu force-pushed the shengliangx/ci-5-onnx-tests branch from 7b998c7 to 68a297f Compare October 17, 2026 07:02
@shengliangxu
shengliangxu force-pushed the shengliangx/ci-6-local-data-tests branch from 2d82a59 to 7383ca1 Compare October 17, 2026 07:02
@shengliangxu
shengliangxu added this pull request to stack #2859 October 17, 2026 07:02
@shengliangxu shengliangxu changed the title [6/12] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data Oct 17, 2026
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 7383ca1

@shengliangxu shengliangxu changed the title [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data [OMNIML-6179] [6/11] Use local tokenizers and calibration text in GPU tests, shrink puzzletron data Oct 18, 2026
@shengliangxu
shengliangxu force-pushed the shengliangx/ci-6-local-data-tests branch from 7383ca1 to 5f3497e Compare October 18, 2026 23:15
@shengliangxu
shengliangxu marked this pull request as ready for review October 19, 2026 03:44
@shengliangxu
shengliangxu requested a review from a team as a code owner October 19, 2026 03:44
@shengliangxu
shengliangxu changed the base branch from shengliangx/ci-5-onnx-tests to shengliangx/ci-4-megatron-cold-start October 21, 2026 07:05
@shengliangxu
shengliangxu force-pushed the shengliangx/ci-6-local-data-tests branch from bd0525c to 126279b Compare October 21, 2026 07:06
@shengliangxu
shengliangxu added this pull request to stack #2907 October 21, 2026 07:06
shengliangxu added a commit that referenced this pull request Oct 21, 2026
### What does this PR do?

Type of change: CI (test runtime)

Sets `COVERAGE_CORE=sysmon` on the unit `linux` job, `gpu_tests.yml` and
`regression_tests.yml`. Python 3.12 defaults to coverage's C tracer,
which slows every line of Python run under it, and every lane starts
coverage in each spawned worker and subprocess through
`COVERAGE_PROCESS_START`, so import-heavy start-ups pay for it (the 76 s
Megatron pool start, the 22 s hf_ptq processes of the unified-export
tests, the puzzletron spawns, the vLLM engine core). sysmon records the
same lines through `sys.monitoring`. The example runner is left to
#2727, which makes the same change there. On a Python older than 3.12
coverage warns and keeps its default core, so a lane that turned out to
run an older Python would be slower, not broken (the gpu and
gpu_megatron logs show 3.12).

### Time improvement (measured in CI)

pytest time of each lane in the run of #2845 (the base of this PR) and
in this PR's run, one run each:

| lane | #2845 | this PR | change |
|---|---|---|---|
| gpu | 2,093 s | 1,289 s | -38% |
| gpu_megatron | 2,240 s | 1,212 s | -46% |
| gpu_vllm v0.20.0 | 204 s | 170 s | -17% |
| gpu_vllm v0.30.0 | 426 s | 328 s | -23% |
| regression | 366 s | 268 s | -27% |
| unit `linux` (xdist) | 479 s | 305 s | -36% |

Workflow wall time: GPU tests 59.2 -> 39.6 min, Regression 21.3 -> 15.2
min.

- **How much of that is this PR.** Two single runs of the same code
differ by 15-30% on their own: the eight example lanes that run over 100
s, which this PR does not touch, moved by -4% to -31% (llm_sparsity
+38%) between the same two runs. The deterministic part is the Megatron
worker-pool start, the setup of the first test of each gpu_megatron
module: 75-77 s in each of 14 modules before (all setups of 12 s or more
add up to 1,365 s) and 22-27 s after (575 s), which is 790 s of the
lane's 1,028 s. Locally the unit suite takes 825 s with the C tracer,
640 s with sysmon and 572 s without coverage (4 CPUs, serial).
- **A `coverage.xml`-only report change was dropped from this PR.** The
runs above are of its earlier head, which also had it. It showed no CI
effect: the pytest summary line came 1-4 s after the last test in the
#2845 runs, which still wrote the html report, and 0-3 s in this PR's
runs (median 1.1 s over the 82 lane runs of the series), so the table is
the sysmon core's. It would only have saved developers' local runs
(about 25 s per pytest session).

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

Nothing user-facing.

### Testing

Measured on this tree (tests/unit on 4 CPUs serial: 572 s without
coverage, 825 s with the C tracer, 640 s with sysmon; `import
modelopt.torch.quantization.extensions`: 17.1 s native, 23.6 s with the
C tracer, 18.3 s with sysmon, with the same 362 files and 13,821
executed lines in the spawned child's data file; one TinyLlama fp8 plus
TRT-LLM deploy test: 64.8 s without coverage, 103.3 s with the C tracer,
70.5 s with sysmon, identical covered lines). Pre-commit and actionlint
pass. The CI runs of the series are the check on real runners (time
section above).

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (CI configuration)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 2 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 5, 8 and 10 (#2849, #2852, #2854) target main and
merge independently; the other open slices form a stack and merge
bottom-up. Each PR stands on its own and passes CI without the later
ones.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI **(this
PR)**
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (targets main)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (targets
main)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (targets main)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Updated coverage collection for GPU, regression, and Linux unit-test
runs to use Python’s monitoring-based coverage core on supported Python
3.12+ environments.
* This configuration reduces overhead during coverage-enabled test
execution, particularly for import-heavy worker processes.
* No changes to application functionality or public interfaces are
included.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu
shengliangxu force-pushed the shengliangx/ci-6-local-data-tests branch from 126279b to 503eff3 Compare October 21, 2026 07:57
shengliangxu added a commit that referenced this pull request Oct 21, 2026
### What does this PR do?

Type of change: CI (test runtime, tests only)

**The DFlash regression trains 500 steps and resumes for 20.**
`test_dflash_training` trained 3 epochs (1,500 steps) and
`test_dflash_resume` then a fourth epoch (500 more). On the 1-GPU PR
runner the recorded durations solve to about 33 s of start-up plus 55-73
ms per step, so the two tests spend roughly 115-140 s and 60-70 s. The
loss assertion only needs a final loss below 3.0, and the L40 baseline
is already 1.78 at step 500. The resume still goes through the whole
restore-and-train path, and it now reads `trainer_state.json` and checks
that `global_step` is 520 and that a log entry follows step 500 (it had
no assertion besides the exit status). The step count is fixed with
`max_steps` rather than `num_train_epochs`, because the regression job
runs on 2 GPUs off pull requests, where `accelerate launch` starts one
process per visible GPU and an epoch is 250 steps. Measured: 81 s per PR
regression job. Risk: the 1.78 baseline comes from a 1,500-step run, and
with `max_steps=500` the linear schedule decays to zero at step 500, so
the final loss can differ from it; the bound leaves 1.2 of margin.

### Time improvement (measured in CI)

| lane | | before | after | change |
|---|---|---|---|---|
| regression | `test_dflash_training` | 118 s | 62 s | -56 s |
| regression | `test_dflash_resume` | 56 s | 31 s | -25 s |
| regression | **lane pytest** | 268 s | 170 s | **-98 s (-37%)** |

DFlash: before = #2846, #2848 and #2850 (#2849 ran on a slower runner),
after = #2852, #2854 and #2855 (median; the lane took 168 and 170 s on
the normal-speed runners and 243 s on the slow one).

On its own, on main (2026-10-21): this PR's regression job against
#2854's, which has the same base and lacks this change. The unchanged
DFlash tests took the same time within about 3% (equally fast runners).
`test_dflash_training` takes 144 s before and 83 s after,
`test_dflash_resume` 73 s and 47 s, and the lane's pytest time goes from
374 s to 291 s (-83 s). The lane passes (5 passed), so the loss stays
below 3.0 after 500 steps and the resume ends at `global_step` 520.

A vLLM change that sized the KV pool of the Qwen3-MoE and DeepSeek
engines down was dropped from this PR. The runs above are of its earlier
head, which had it: the engines it touched booted 5-6 s per vLLM version
faster than the ones it left alone, in line with the 1-3 s per boot
expected and below the boot-to-boot noise (the unchanged Llama engine
took 84-99 s on v0.30.0), so it saved nothing measurable.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

Nothing user-facing.

### Testing

I read the transformers 4.57.1 sources (`max_steps` overrides
`num_train_epochs`; with the steps save strategy a checkpoint is saved
at step 500 and again at the end) and the accelerate 1.10.0 launcher
defaults. Not run on a GPU here: this PR's regression job is the check,
in particular that the final DFlash loss stays below 3.0 after 500 steps
and that the resume ends at `global_step` 520. Pre-commit passes.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (existing tests made
cheaper; the DFlash resume test now also asserts on its result)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 8 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 5, 8 and 10 (#2849, #2852, #2854) target main and
merge independently; the other open slices form a stack and merge
bottom-up. Each PR stands on its own and passes CI without the later
ones. The times above are from the series' CI runs before 2026-10-21,
when all slices formed one linear stack; a PR number in a method note
names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (targets main)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (targets
main) **(this PR)**
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (targets main)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Updated regression coverage to use fixed training limits of 500 steps,
with resumed runs extending to step 520.
* Improved checkpoint and resume validation to confirm the final
training step and expected post-resume progress logs.
* Updated loss expectations to reflect the result at the 500-step
training milestone.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 21, 2026
… a worker dies (#2854)

### What does this PR do?

Type of change: CI (test reliability); changes `tests/_test_utils` only

A worker that exits without reporting (an illegal memory access aborts
the process, or an OOM kill) was only noticed when the result timeout
ran out: 600 s for a job, 120 s for the start. Until then the parent
waits on a queue nobody writes to, and the other workers wait in the
job, typically in a collective, for their dead peer. A start that fails,
for example on a bad backend, also waited out the whole ready timeout
and left the workers it had spawned behind.

`DistributedWorkerPool` now checks the workers' liveness once a second
while it waits. A dead worker is noticed within a second and fails the
run, or the start, naming its rank and exit code (about 4 s after the
death, which includes the 3 s the pool already waits for the other
workers' stack dumps). If another rank had already reported an
exception, its traceback is kept in that error. The pool is marked
`broken` and the other workers are killed with their stacks printed, the
way a hung job or a failed rank already ends in main (`get_live_pool`
then hands out a new pool). A start that fails kills the workers it had
spawned. `ready_timeout` is a constructor argument (120 s by default, as
before), so a test can reach the start timeout without waiting for it.

This builds on the hang handling of #2832 (`broken`, `get_live_pool`,
the stack dump) and does not change it.

### Time improvement (measured locally, not in CI)

No lane time changes in a passing run, and no worker died in this
series' CI runs, so CI cannot show it. What changes is how long a dead
worker costs. CPU, gloo, two ranks, the pool test's own job (rank 0
exits with code 13, rank 1 keeps waiting), seconds from `pool.run(...)`
(or the constructor) to the exception, on main and on this PR:

| scenario | before | after |
|---|---|---|
| a rank dies mid-job, its peer is stuck in a wait, `job_timeout` 30 s
(as in the test) | 33.1 s, `queue.Empty` | 4.0 s, `Worker process(es)
died (rank, exit code): [(0, 13)]` |
| the same with the default `job_timeout` of 600 s | 603.1 s,
`queue.Empty` | 4.0 s |
| the same, the peer waits in `dist.barrier()` | 33.3 s (the 30 s
`failure_grace`) | 4.3 s |
| the start fails (`backend="not-a-backend"`), default `ready_timeout`
of 120 s | 120.1 s, `queue.Empty` | 5.0 s |
| rank 0 dies at the start, rank 1 hangs in the rendezvous | 120.0 s,
one live worker left behind | 5.0 s, no worker left |

The after time is the 1 s liveness poll plus the 3 s the pool waits for
the other workers' stack dumps before it kills them. At the production
timeouts (120 s for the start, 600 s for a job) a dead worker therefore
costs 4-5 s instead of 2-10 minutes of a CI job: the test fails at once
and the next one gets a fresh pool from `get_live_pool`, instead of
waiting for a queue nobody writes to.

Cost: the four new cases add about 27 s to the `gpu` lane: the pool test
module takes 43 s in this PR's run and 17 s in #2849's run, which has
the same base (main) and lacks them (both from the job logs).

Method: a small script (not part of this PR) builds the pool the way the
repo's test does and times each scenario with `time.monotonic()` around
the call, once per commit, on a CPU host.

### Usage

```python
pool = DistributedWorkerPool(world_size=2, backend="nccl", ready_timeout=300)  # new argument, default 120
```

### Testing

`tests/gpu/torch/utils/test_distributed_worker_pool.py` runs the pool
with gloo on CPU (it extends main's three tests; the new cases take 24 s
together):

- a rank that exits with code 13 while its peer waits fails the run in
about 5 s and kills the peer. The test sets `job_timeout = 30`, so a
regression fails there with `queue.Empty` instead of at the test's own
cap. With the liveness check disabled it fails exactly that way.
- the same test with a second job: rank 0 raises an exception and rank 1
exits with code 13 two seconds later, after rank 0 has reported. The
error carries the exit code and rank 0's traceback. Without the change
it carries only the exit code, and the case fails.
- a start that fails raises and leaves no worker, both when every worker
dies (a bad backend) and when the wait times out while they are still
starting (`ready_timeout=0.01`). With `_abort()` removed from the
failed-start path, the second case fails ("workers left behind"); the
first passes either way, since all its workers die on their own, so it
covers the liveness wiring only.

Main's three tests (as reworked by #2836) still pass, and the three new
cases fail against main's unmodified pool. Pre-commit passes. This PR is
rebased onto #2836, which also edited the pool (reporting a failed rank
before its teardown, retiring a pool after any failed job); the changes
are in different places. The default start timeout stays 120 s here;
#2848 raises it to 300 s, so whichever of the two merges second resolves
that one line.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: ✅ 2 test functions (4 cases)
in `tests/gpu/torch/utils/test_distributed_worker_pool.py`, run on CPU
with gloo; each fails without its fix
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 10 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 5, 8 and 10 (#2849, #2852, #2854) target main and
merge independently; the other open slices form a stack and merge
bottom-up. Each PR stands on its own and passes CI without the later
ones. The times above are from the series' CI runs before 2026-10-21,
when all slices formed one linear stack; a PR number in a method note
names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (targets main)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (targets
main)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (targets main) **(this PR)**
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Distributed worker pools now detect workers that exit during startup
or job execution and report failures instead of waiting for the full job
timeout.
* Worker startup supports a configurable readiness timeout with prompt
timeout handling.
* Failed or timed-out initialization now cleans up spawned worker
processes, preventing orphaned processes and leaving the pool in a
consistent state.
  * Job result retrieval now responds promptly when a worker fails.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 21, 2026
…e unit lane, small models (#2849)

### What does this PR do?

Type of change: CI (test runtime, tests only)

Four changes to the ONNX tests of the `gpu` job:

- **Three CPU-only files move to the unit lane**
(`tests/gpu/onnx/quantization/` to `tests/unit/onnx/quantization/`,
content unchanged): `test_dla_qdq_rules.py`,
`test_dla_scalar_constants.py` and
`test_quantization_initializer_names.py` (added with #2678, 68 cases).
They quantize with `calibration_eps=["cpu"]` and run the result on the
CPUExecutionProvider, so they need no GPU, but they cost 26.8 s per
`gpu` job (1.5 + 5.3 + 20.0 s in the CI logs); the unit job runs them in
a few seconds on its xdist workers.
- **`test_concat_elim` calibrates a 32-channel 16x16 model on the CPU**
instead of the (1, 128, 240, 64) three-conv model on `[cpu, cuda:0,
trt]`: the two tests took about 22 s each on the base (15 s together in
the series' CI runs with the sysmon core of #2846), and their assertions
only look at where the pass places Q/DQ nodes. The convs stay above the
16-channel limit of `graph_selection.py`, and both modes produce the
same 8 Q and 8 DQ nodes as with the default size.
`build_conv_concat_model` takes optional `channels`, `height` and
`width` (defaults: the old values; with a seeded RNG the default model
serializes to the same bytes).
- **The autotune benchmark tests build a 1x32x32x32 model** instead of
the 64x32x224x224 one (103 M random floats and about 1.2 GB copied per
iteration; the five tests took 31 s, and none looks at the latency
beyond "a positive float"). The two trtexec tests also pass
`--duration=0`, because trtexec measures for at least 3 s whatever the
iteration count. `_create_simple_conv_onnx_model` takes optional
`batch_size` and `spatial_size` with the old defaults, so the unit
autotuner tests that share it build the same model (their 161 tests
pass).
- **The autotune workflow test passes `--duration=0` to trtexec**:
`test_export_quantized_model[True]` took 44 s, mostly inside trtexec's 3
s minimum measurement per scheme. It still runs the 100 requested
iterations per measurement; the model and `timing_runs` keep their size,
because the test asserts the best scheme carries Q/DQ nodes.

### Time improvement (measured in CI)

gpu lane, time per file from the per-test timestamps; before = the runs
of #2846-#2848, after = this PR's run and the 4 runs above it (median):

| | before | after | change |
|---|---|---|---|
| three CPU-only files (68 cases), moved to the unit lane | 7.4 s | 0 |
-7.4 s |
| `test_concat_elim.py` | 15.2 s | 1.2 s | -14.0 s |
| `autotune/test_benchmark.py` (36 cases) | 37.1 s | 21.0 s | -16.0 s |
| `autotune/test_workflow.py` | 44.4 s | 28.5 s | -15.9 s |
| **sum** | 104 s | 51 s | **-53 s per gpu job** (-59 s after speed
scaling) |

The whole `tests/gpu/onnx` directory takes 110-120 s before and 58 s in
the three later runs on normal-speed runners (81 s and 74 s in #2849 and
#2850, which ran on 1.3x slower runners). The 68 moved cases cost the
unit job 7-13 s of CPU, spread over four xdist workers (below the unit
job's run-to-run spread). My first estimate of the gpu saving (97-107 s)
came from base runs without sysmon, where these files were slower.

On its own, on main as of 07:06 UTC on 2026-10-21 (before #2846 added
the sysmon core): the `gpu` lane's `tests/gpu/onnx` directory takes 145
s in #2854's run, which has the same base and lacks this change, and 66
s in this PR's run (-79 s). The runners were equally fast (median ratio
0.99 over the 17 untouched files above 20 s). `test_concat_elim.py` goes
from 35 s to 2.7 s, `autotune/test_benchmark.py` from 39 s to 24 s and
`autotune/test_workflow.py` from 46 s to 28 s, and the three moved files
cost 14 s there. Times are the gaps between consecutive result lines in
the job logs.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

Nothing user-facing.

### Testing

- The three moved files: 68 passed in 7.1 s at the old location and in
13.3 s from the new one (this includes the unit conftest's session
setup) on Python 3.12 with onnx 1.21.0 and onnxruntime 1.24.4, and 68
passed in 15.3 s on Python 3.10.19 with onnxruntime 1.22.0, the oldest
runtime `pyproject` allows. The whole `tests/unit/onnx` directory passes
(756 passed, 12 xfailed).
- `test_concat_elim`: the two tests pass in 1.3 s on CPU; quantizing the
default and the small model in both modes gives Q=8, DQ=8.
- The shared helper changes: the unit autotune tests (161) pass.
- Not run: the TensorRT tests, because there is no TensorRT here; this
PR's `gpu` job is the check. Pre-commit passes.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (existing tests moved or
made smaller; the 68 moved cases are unchanged)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 5 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 1, 2, 8 and 10 (#2845, #2846, #2852, #2854) have
merged. Slice 5 (#2849) targets main and merges independently; the other
open slices form a stack and merge bottom-up. Each PR stands on its own
and passes CI without the later ones. The times above are from the
series' CI runs before 2026-10-21, when all slices formed one linear
stack; a PR number in a method note names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
(merged)
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (targets main) **(this PR)**
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (merged)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Tests**
* Added configurable dimensions to ONNX test models and smaller inputs
for selected scenarios.
* Streamlined GPU benchmark timing while retaining configured
measurement runs.
* Updated INT8 and FP8 concat-elimination coverage to use CPU
calibration and smaller model shapes.
* Reorganized DLA and quantization checks as unit tests instead of GPU
tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Six tests in test_megatron_preprocess_data.py tokenize with gpt2 from the Hugging Face
Hub. megatron_preprocess_data loads the tokenizer once for the vocabulary size and again
in every worker, so each test makes several hub round trips, and none of them depends on
gpt2's vocabulary: they check output naming, non-empty .bin and .idx files, and that
max_tokens shortens the output. Switch them to the tiny_tokenizer_path fixture, the
checked-in byte-level tokenizer that the blend test already uses.

Three of the six read only local data (the jsonl path, the jsonl max_tokens and the gzip
tests), so the tokenizer download was their only network access. The PR history lists the
jsonl max_tokens test, with the HF split max_tokens test, among the lane's 120 s timeouts
(3 failures each); it no longer has a network dependency to time out on. The other three
(HF split max_tokens, HF resume and the HF streaming warning) still read the real HF
dataset, which is the code path they exist to cover; only their tokenizer changes.

The Qwen3 tests keep their real tokenizer: they need its chat template and EOS handling.
The blend test already uses the tiny tokenizer, and its HF split source is the path it
covers, so it is unchanged. No test ids, assertions or markers change.

Measured on a CPU host with a cold Hugging Face cache, the module's 11 tests take 42.8 and
44.0 s before and 35.0 and 36.1 s after (two runs each, with the conftest's forkserver
prestart). Per test: jsonl path 2.7-3.4 s to 0.01 s, gzip 1.4 s to 0.05 s, HF split
max_tokens 5.8-5.9 s to 4.3 s, HF resume 4.4 s to 2.8-3.0 s, HF streaming warning 3.6-3.8 s
to 2.3-2.7 s. The jsonl max_tokens test takes about 14 s in both, because here it is the
first test to wait for the forkserver's cold import; in the lane the session fixture
starts that import earlier. CI jobs start without a warm hub cache, so a similar saving
is expected.

Testing: ruff check and ruff format with the pinned ruff 0.15.20, and py_compile. Ran the
module on CPU, next to a copy of the tiny_tokenizer_path fixture and the forkserver
prestart from tests/gpu_megatron/conftest.py, with the Hub reachable: 11 passed before and
after, and the collected test ids are identical. Not run: the gpu_megatron lane itself,
which needs GPUs and prebuilds CUDA extensions in its conftest.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…other

- save_dummy_dataset wrote 2500 copies of one conversation into each of the
  train and valid splits. The tiny test tokenizer turns a conversation into
  1424 tokens, so that is 1738 blocks of 2048 tokens, while the pruning
  scores read 100 blocks and the scoring and realize steps 2 each. Every
  dataloader build tokenizes the whole split, and a run builds three of
  them. The 2500 came from an older 8192-token Llama-3 setup. Use 400
  conversations (278 blocks). On a CPU the three builds plus the dataset
  save took 2.8 s per test with 2500 conversations and 0.36 s with 400. The
  scoring step checks lm_loss with a tolerance of 0.15; on random-init toy
  Llamas the validation loss moved by at most 0.02 between the two sizes,
  because the same text repeats and only the choice of blocks changes.
- calc_subblock_stats ran 12 passes (batch sizes 64, 96 and 128 times four
  dtype sets), but the MIP and the teacher memory and parameter checks only
  read the entry for the batch_size in mip.subblock_stats_args, which is 96
  in all nine configs. batch_sizes: [96] gives 4 passes (0.56 s to 0.18 s
  for a toy Llama on a CPU; MoE configs have more subblocks per pass).
- test_nas_convert_ffn_pruning ran the same mtn.convert call on the same toy
  Llama and config as test_nas_search (21.6 s in the CI logs, with its own
  2-rank spawn). Its two assertions move into test_nas_search, which checks
  them with the other rank-0 assertions after the search.

Left alone: the Nemotron-3 toy keeps its 128 experts, because shrinking it
changes EXPECTED_TEACHER_MEMORY_MIB and EXPECTED_TEACHER_NUM_PARAMS, which
need a GPU runner with mamba_ssm to regenerate.

Testing: timed the dataloader builds, the subblock-stats passes and the
loss sensitivity on CPU with the repository's own helpers and dataloader
code, collected tests/gpu/torch/puzzletron (16 tests, one fewer than
before), and ran ruff. Not run: the puzzletron tests themselves, which need
GPUs and mamba_ssm. Expect the gpu job to lose roughly 50 s (the removed
test is 22 s of it); the CI run will show the real number.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…or batch probing

Each of the 11 test_unified_hf_export_and_check_safetensors params starts a
fresh hf_ptq process, 22 s per param in the CI logs. The test only checks the
structure of the exported safetensors, so three parts of that run are not
needed:

- Calibration text came from cnn_dailymail on the Hub, a network round trip
  per param. Use a local jsonl of 64 random-word texts instead, written by a
  new helper, write_calib_jsonl, in _test_utils/torch/calib_data.py. Each text
  fills the 512-token calibration window and differs from the others, so the
  activation statistics stay varied.
- hf_ptq probed for the largest calibration batch. --batch_size 64 puts the
  whole calibration set (calib_size 64) in one batch.
- hf_ptq ran model.generate() before and after quantization. --skip_generate
  skips those previews; the export is not affected.

Testing: no GPU here, so I ran the test on a CPU host through a python shim
that lets hf_ptq pass its GPU guard and passes --device cpu. 8 of the 11
params pass with the new command line, scale-equality assertions included:
fp8, nvfp4, int4_awq, w4a8_awq_beta, int8_weight_only, w4a16_nvfp4, the
Qwen3 MoE nvfp4 and the GPT-OSS fp8. The other three (nvfp4 AWQ-lite,
SVDQuant and the MSE sweep) need CUDA or Triton and did not run. The
dataloader returns one (64, 512) batch with a full attention mask from the
helper's file. Not run: the GPU job itself. From the per-param CI timings
this should save about 38 s per `gpu` job.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
test_gptq_e2e_flow streamed cnn_dailymail from the Hub for each of its three params, a
network round trip per param (about 6 s per job in the CI logs) and a test that can fail
on a Hub hiccup. The GPTQ calibration only needs some text, and the test has no accuracy
assertion, so it now reads 8 texts from the local jsonl that write_calib_jsonl (added with
the unified-export change) creates.

Testing: ruff check and format with the pinned ruff 0.15.20. On a CPU host the GPTQ flow
completes on that jsonl for the FP8 and INT4 blockwise configs; the NVFP4 config needs
CUDA. Not run: the GPU test itself. Expected saving is a few seconds per gpu job.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Review feedback on this PR: the calibration texts are never modified by a test, so one
session-wide fixture can serve every test that needs some text instead of each test or module
writing its own file.

`calib_jsonl` in tests/gpu/conftest.py writes the 64 texts once per session and wraps the
directory in assert_unmodified_tree, the check the shared tiny-tokenizer fixture already uses
(chmod would not stop a root CI container). The unified HF export test drops its module-scoped
fixture of the same name, and the GPTQ end-to-end test takes the fixture instead of writing 8
texts into its tmp_path. It still asks the dataloader for 8 samples, and the file starts with
the same 8 texts the old 8-text file held, since the generator is seeded and sequential.

tests/gpu_megatron has no test that calibrates on text (its tests use model.get_input()), so
it gets no copy of the fixture.

Testing: pre-commit passes. On a CPU host, with the real fixture and the repo's
get_dataset_dataloader: the 8 samples read from the shared file equal those of the old 8-text
file, and two reads leave the directory unmodified (the fixture's teardown check passes).
`--setup-plan` shows the unified export test using the conftest fixture. Not run: the GPTQ
and export tests themselves, which need a GPU.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu
shengliangxu removed this pull request from stack #2907 October 21, 2026 16:59
@shengliangxu
shengliangxu changed the base branch from shengliangx/ci-4-megatron-cold-start to main October 21, 2026 16:59
@shengliangxu
shengliangxu requested a review from a team as a code owner October 21, 2026 16:59
@shengliangxu
shengliangxu force-pushed the shengliangx/ci-6-local-data-tests branch from 503eff3 to 55e1c27 Compare October 21, 2026 17:00

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Bot review (gpt-6.1-sol) — DM the bot to share feedback.

LGTM: the test reductions preserve the relevant assertions; human sign-off is required for the justified test consolidation and input changes.

Needs action:

  • Confirm the justified test edits: test_nas_search.py retains both deleted FFN-conversion assertions, and local calibration/tokenizer inputs preserve export, GPTQ, and preprocessing coverage.

No action needed:

  • The design reuses existing tokenizer fixtures, dataset loaders, and filesystem guards; the PR explains why Hub data and larger inputs are unnecessary.
  • The new helper’s header matches LICENSE_HEADER.
  • Tests were inspected, not executed during this review.

shengliangxu added a commit that referenced this pull request Oct 21, 2026
### What does this PR do?

Type of change: CI (test runtime); the only non-test file is
`examples/torch_onnx/torch_quant_to_onnx.py`

Three cuts to the torch_onnx example lane:

- **No DataLoader workers in the calibration loaders.**
`load_calibration_data` builds its DataLoaders over lists of tensors
that are already on the GPU, so the four workers load nothing, and since
the script forced the `spawn` start method (CUDA cannot be used in
forked workers), every loader pass spawned four processes that
re-imported torch, timm and modelopt and received the dataset over CUDA
IPC: about 14 s per pass and about 300 s per torch_onnx CI job. Both
loaders use `num_workers=0`, and the module-level
`mp.set_start_method("spawn", force=True)` is dropped, which also stops
importing the script from changing the start method of the importing
process (two tests import it inside the pytest process). Nothing else in
the script uses multiprocessing. The `--evaluate` path keeps the
CPU-only DataLoader workers of `examples/onnx_ptq/evaluation.py`, now
under the default start method, exactly as in the `onnx_ptq` example
that calls the same function.
- **swinv2 int8, mxfp8 and auto are skipped on pull requests.**
`swinv2_tiny` runs all five formats on top of `swin_tiny`, which already
covers every format through the same exporter and TensorRT path; swinv2
only adds its cosine attention and post-norm blocks. On PRs
(`GITHUB_REF` under `refs/heads/pull-request/`) fp8 and nvfp4 still run
and exercise those blocks; scheduled and local runs keep every
combination, and test ids are unchanged. `swinv2_tiny[auto]` alone
averages 121 s over 304 CI runs; the three skipped cases cost about 110
s of every PR run.
- **`build_trt_engine` runs trtexec for one iteration.** It let trtexec
run its default benchmark after the build (200 ms warm-up, then at least
3 s and 10 iterations) and discarded the output. `--warmUp=0
--duration=0 --iterations=1` still executes the engine once and removes
about 3 s per engine, roughly 40 s per lane run. `--trt_build` users see
no difference besides it returning sooner.

### Time improvement (measured in CI)

torch_onnx lane; before = #2846, #2848 and #2850 (875-880 s; #2849 ran
on a slower runner: 1,060 s), after = #2853, #2854 and #2855 (452-463
s).

| | before | after | change |
|---|---|---|---|
| **lane pytest** | 877 s | 458 s | **-419 s (-48%)** |
| of which: the three swinv2 cases skipped on PRs | 143 s | 0 | -143 s |
| of which: the cases that still run (no DataLoader workers, trtexec
runs once) | | | -277 s |
| whole job | 18.6 min | 12.2 min | -6.4 min |

This lane repeats to within 1% (875-880 s before, 452-463 s after), so
it is the cleanest measurement of the series. For example `swin_tiny`
with auto quantization goes from 66 s to 24 s and `vit_tiny` with auto
from 60 s to 17 s.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

No API change. The skip is keyed on `GITHUB_REF`, so a local run keeps
every combination.

### Testing

`py_compile`, ruff 0.15.20 and `pytest --collect-only` on CPU (24 tests,
ids unchanged). With the example command stubbed,
`GITHUB_REF=refs/heads/pull-request/123` skips exactly swinv2 int8,
mxfp8 and auto, and any other ref skips none; the two in-process mapping
tests that import the script pass on CPU, and `build_trt_engine` passes
the three flags to a fake trtexec. Not run: the lane itself (it needs a
GPU, TensorRT and model downloads) and the flags against a real trtexec,
so this PR's torch_onnx job is the check. Pre-commit passes. #2685
(open) also edits this example; the changes touch different lines.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (existing tests; the
PR-only skip keeps test ids)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 9 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 1, 2, 5, 8 and 10 (#2845, #2846, #2849, #2852,
#2854) have merged. The remaining slices (#2847, #2848, #2850, #2851,
#2853, #2855) are independent PRs on main: each applies to main on its
own, and merging all six onto main in different orders gives the same
tree. The times above are from the series' CI runs before 2026-10-21,
when all slices formed one linear stack; a PR number in a method note
names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
(merged)
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (merged)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane **(this
PR)**
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (merged)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Updates**
* Quantization examples now use zero-worker calibration loading with
device-resident samples.
* TensorRT example builds perform a single inference iteration without
warmup or timing.
* **Tests**
* Pull-request validation skips selected SwinV2 Tiny formats, while
scheduled validation continues to cover them.
* Pull-request detection uses the repository’s CI reference environment.
* **Impact**
* These changes affect example workflows and validation coverage only;
no application user-facing functionality is changed.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu
shengliangxu merged commit adc517d into main Oct 21, 2026
62 of 64 checks passed
@shengliangxu
shengliangxu deleted the shengliangx/ci-6-local-data-tests branch October 21, 2026 19:59
shengliangxu added a commit that referenced this pull request Oct 21, 2026
…ridge example tests (#2851)

### What does this PR do?

Type of change: CI (test runtime, tests only)

Five cuts to the example lanes, each with its reasoning:

- **`test_lm_eval_hf` evaluates one MMLU subtask.** It ran `--tasks mmlu
--limit 0.1`: the 57-subtask group, about 5.6k loglikelihood requests,
on a tiny Qwen3 whose `max_position_embeddings` is 32, so its runtime is
dominated by loading one HF dataset config per subtask (over 331 CI
runs: min 198.9 s, median 212.5 s). It runs `mmlu_abstract_algebra` (100
test documents, 5-shot) through the same `lm_eval_hf.py` path and the
same accuracy gate (exactly one task, lower bound 0.1 unchanged; the
label distribution of that split is 22/26/31/21, so even a model that
always predicts one letter scores at least 0.21).
- **The llm_qat QAT and QAD tests quantize once per module.**
`test_qwen3_qat_nvfp4` (3 backends), `test_qwen3_qad_nvfp4` (2) and
`test_qwen3_qlora_nvfp4` ran 6 `quantize.py` and 12 cold
tokenized-dataset builds per module (104-121 s each, qlora 169 s). A
module-scoped fixture runs `quantize.py` once (guarded by
`assert_unmodified_tree`, like the other shared tiny-model fixtures) and
all quantize and train calls share one dataset cache; the qlora test
keeps its own PTQ because it passes `--compress True`.
- **The 1-GPU EAGLE3 compile smoke test skips the Triton GEMM autotune**
(`TORCHINDUCTOR_MAX_AUTOTUNE_GEMM_BACKENDS=ATEN`, the mechanism
`test_diffusers.py` already uses). `test_llama_eagle3[1-False]` takes
175.5 s against 40.6 s for `[1-True]` (the same flow without compile),
about 135 s of it `torch.compile` benchmarking Triton GEMM templates for
a draft model that is one tiny decoder layer. The mode stays
`max-autotune`, and the shortcut applies only when the run has one GPU:
`[2-False]` and any run on more GPUs (the nightly) keep the full
autotune.
- **The duplicate tiny-llama offline EAGLE3 training test is dropped**
(`test_offline_eagle3_training[tinyllama]`, 34.9 s per job, almost all
launcher start-up). `test_offline_eagle_training` already runs that mode
on the same model with hidden states from `compute_hidden_states_hf.py`,
and the FakeBaseModel case keeps the synthetic path. The one thing the
dropped case covered that stage 2 did not, the collator truncating
dumps, moves there (`training_seq_len=16`, so 28-token dumps are
truncated), and the collator is also unit tested on CPU.
- **`megatron_bridge` tests that use no GPU skip the Megatron state
reset.** The conftest resets Megatron's global state around every test
(`destroy_model_parallel`, `gc.collect()`, `empty_cache`), about 1.3 s
per test; 77 tests in the directory are pure Python (the mlflow wiring
and checkpoint-path tests) and leave nothing to reset (the 82 tests that
took under 3 s each add up to 118 s in the series' runs before this PR
and to 0.3 s after it). The reset now runs only for tests that request
`num_gpus`; the environment restore stays for every test. The job runs
at 68-75 minutes against its 75-minute nightly limit.

### Time improvement (measured in CI)

Example lanes, pytest time of the changed tests and of the lane; before
= #2846 (plus #2848 where its runner was as fast), after = #2851, #2854
and #2855 (median). #2849 and #2850 ran on runners 1.3-1.5x slower for
every test and are left out of the before column.

| lane | | before | after | change |
|---|---|---|---|---|
| llm_eval | `test_lm_eval_hf` | 130 s | 19 s | -111 s |
| llm_eval | **lane** | 332 s | 212 s | **-120 s (-36%)** |
| llm_qat | six QAT, QAD and QLoRA tests (call time) | 563 s | 428 s |
-135 s (the shared quantize adds 20 s to the first test's setup) |
| llm_qat | **lane** | 591 s | 475 s | **-116 s (-20%)** |
| speculative_decoding | `test_llama_eagle3[1-False]` | 131 s | 87 s |
-44 s |
| speculative_decoding | dropped
`test_offline_eagle3_training[tinyllama]` | 20 s | - | -20 s |
| speculative_decoding | **lane** | 407 s | 341 s | **-66 s (-16%)** |
| megatron | the 82 megatron_bridge tests that use no GPU (the state
reset) | 118 s | 0.3 s | -118 s |
| megatron | **lane** | 583 s | 460 s | **-123 s (-21%)** |

- A `mmlu_limit=1` change for `test_qwen3_eval_fp8` was dropped from
this PR. The runs above are of its earlier head, which had it: the test
took 175-246 s in all 7 runs, before and after (187 s against 176 s on
comparable runners), so the CI history it was based on (mean 723.7 s,
2026-09-25 to 10-09) is not reproduced by the current lane and the
change saved nothing measurable. The llm_eval lane figure above is
`test_lm_eval_hf`'s.
- That is 425 s of lane time per PR run. The Example workflow ends with
its slowest lane, torch_onnx (18.1-18.8 min of job time) until #2853, so
the gain reaches the workflow's wall time only with that PR.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

Nothing user-facing.

### Testing

- llm_eval: ran the test's command on CPU with the tiny Qwen3 it builds:
old configuration 223.4 s, `mmlu/acc` 0.2343; new 22.4-24.0 s,
`mmlu_abstract_algebra/acc` 0.27, gate passes.
- llm_qat: with `run_example_command` faked, `quantize.py` runs 6 -> 2
times and cold dataset builds go 12 -> 2; real `quantize.py` and
`train.py` (QAT, then QAD from the checkpoint) ran on CPU with the int4
and fp8 recipes (NVFP4 needs CUDA) and left the checkpoint unmodified.
- EAGLE: stage 1 and 2 of the offline flow ran on CPU with
`training_seq_len` 64 and 16 (the collator pads 28 -> 64 and truncates
28 -> 16); with `run_example_command` faked, only `[1-False]` on one GPU
gets the autotune variable (that case on 2 GPUs and every other case
keep the full autotune) and the only removed test id is
`test_offline_eagle3_training[tinyllama]`.
- megatron_bridge: a throwaway pytest module (not in this PR) against
the patched conftest counts the resets: plain tests trigger none, tests
that request `num_gpus` trigger one before and one after; it fails
against the old conftest.
- Not run: the example lanes themselves (they need GPUs, TRT-LLM and
model downloads), so the accuracy with CUDA bf16 numerics, the savings
and the autotune variable are checked by this PR's example jobs.
Pre-commit passes.
- `trust_remote_code=True` in `test_offline_eagle3_training` is not new:
it is the line on main (the Kimi and MiniMax repos it loads need remote
code), and this PR only renames its variable after dropping the
tinyllama case.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (existing tests made
cheaper; one duplicate test dropped, see the description)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 7 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 1, 2, 5, 8, 9 and 10 (#2845, #2846, #2849, #2852,
#2853, #2854) have merged. The remaining slices (#2847, #2848, #2850,
#2851, #2855) are independent PRs on main: each applies to main on its
own, and merging all of them onto main in different orders gives the
same tree. The times above are from the series' CI runs before
2026-10-21, when all slices formed one linear stack; a PR number in a
method note names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
(merged)
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (merged)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests **(this PR)**
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane (merged)
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (merged)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Tests**
* Focused language-model evaluation coverage on the abstract algebra
task while retaining five-shot evaluation.
* Streamlined quantization-aware training scenarios by reusing cached
datasets and checkpoints, reducing repeated setup.
* Improved Megatron test isolation by applying state cleanup only where
needed.
* Refined EAGLE test configurations for single-GPU runs, remote model
coverage, and offline training with shorter sequence lengths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-21 19:59 UTC

shengliangxu added a commit that referenced this pull request Oct 21, 2026
…2848)

### What does this PR do?

Type of change: CI (test reliability)

`DistributedWorkerPool` waited 120 s for its workers to import the
Megatron/TE/modelopt stack and report ready. The start takes 75-77 s in
14 modules of the PR run I read and has a p90 of 94-98 s, so slow hosts
time out and the whole module errors: 15 jobs (903 job-minutes) in two
weeks, 15 of the 63 failed gpu_megatron PR jobs in that period. It waits
300 s.

An earlier version of this PR also gave `test_mamba_search_space` more
time. Main's #2836 has since set its cap to 150 s, and that holds: in 58
gpu_megatron jobs from the last six days the test took 64-132 s (median
73 s, none above 150 s), so nothing is left to change there.

### Time improvement (measured in CI)

None in a passing run: this PR raises one limit that only failing runs
reach.

- In the 9 gpu_megatron runs of this series, the worker-pool start took
75-77 s before #2846 and 18-27 s after it, against the old 120 s ready
timeout. No run came near the limit, so the series cannot show the
failures this PR prevents (the sysmon core of #2846 also takes most
starts out of reach of the old ready timeout).
- What it prevents, from the CI history of the two weeks before the
series: the ready timeout failed 15 jobs (903 job-minutes), 15 of the 63
failed gpu_megatron PR jobs. Each failed job costs the author a re-run
of the lane, 20-45 min of job time in this series.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

Nothing user-facing.

### Testing

Pre-commit passes. Not run on a GPU here: the effect is on cold-cache
runs, so this PR's gpu_megatron job can only show that nothing
regresses.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (a timeout in the shared
test utilities)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 4 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 1, 2, 5, 8, 9 and 10 (#2845, #2846, #2849, #2852,
#2853, #2854) have merged. The remaining slices (#2847, #2848, #2850,
#2851, #2855) are independent PRs on main: each applies to main on its
own, and merging all of them onto main in different orders gives the
same tree. The times above are from the series' CI runs before
2026-10-21, when all slices formed one linear stack; a PR number in a
method note names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
(merged)
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time **(this PR)**
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (merged)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane (merged)
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (merged)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Increased the wait time for distributed worker initialization to
improve reliability during startup.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 21, 2026
…egatron modules (#2855)

### What does this PR do?

Type of change: CI (test runtime); changes `tests/gpu_megatron` and
`tests/_test_utils` only

Each of the 15 gpu_megatron modules that use `dist_workers` started its
own worker pool: about 19 of the lane's 36 minutes. Fourteen logged
module starts took 75-77 s each (the GDN module 179 s: 76 s pool plus
103 s Triton JIT), plus 3.6-10 s of shutdown, and the start has a p90 of
94-98 s.

The 76 s is not distributed init (an in-process init takes 0.1 s): the
spawned child unpickles `megatron_worker_teardown` by reference, which
imports the heavy `tests/gpu_megatron/conftest.py` (transformers,
megatron.core, all of modelopt and its plugins, TE) before it reaches
`init_process_group`. The same import chain takes pytest itself 89 s to
start here against 17 s in the gpu lane, whose session-scoped pool, with
a light teardown function, starts in 1.7 s. Module scope was chosen when
a spawn cost about 10 s ("avoids cross-test state contamination"); the
git history shows no contamination incident, and the cost has grown
7.6x.

`_pool_cache` is now session-scoped, so the pools are keyed by world
size and shared by every module. The `dist_workers` fixtures stay
function-scoped and get their pool from `get_live_pool`, which replaces
a pool that a hang killed. What isolation the module scope gave is kept
explicitly:

- `megatron_worker_teardown`, after every job: synchronizes CUDA first
(an asynchronous CUDA error then surfaces in the test that caused it),
destroys Megatron's model-parallel state as before, and now also the
process groups it leaves behind (about 20 NCCL groups per dispatch, each
with a communicator and a watchdog thread; `test_megatron.py` alone
dispatches about 110 times). A pool that lives through the whole session
needs it more than a per-module pool did.
- `_reset_shared_pools`, an autouse module-scoped fixture: when a module
starts, it resets the process-global torch settings that helpers like
`set_seed` change (cudnn and TF32 flags, deterministic algorithms, RNG)
to what a fresh worker starts with (`reset_worker_state`; the workers
record those settings right after they start), in the pools that already
exist. It is not part of `_get_pool`, which runs once per test and would
add a job to each.
- `get_live_pool` and the pool's abort handling: a job that times out,
is interrupted, loses a worker **or fails on any rank** retires the
pool, and the next test gets a fresh one, for the cost of one pool
start. `DistributedWorkerPool.run` has retired a pool after a plain
failed job since #2836, which matters more with a session-wide pool: a
CUDA error such as the known Blackwell illegal memory access is sticky,
the workers stay poisoned, and every later test would fail with it.
`_reset_shared_pools` no longer lets a failed reset error the module's
tests (they may not use a pool at all).

Measured saving on top of #2846's sysmon core: 216 s of the gpu_megatron
lane (1,038 s to 822 s; see the time section). Without sysmon a pool
start costs 76 s, and the saving would be about 17 min (an estimate; the
series was not run that way). One pool start instead of 15 also means
fewer chances to hit the ready timeout.

### Time improvement (measured in CI)

gpu_megatron lane, pytest time:

| main (#2845) | + #2846 | slices 1-10 (the earlier base) | + this PR |
|---|---|---|---|
| 2,240 s | 1,212 s | 1,038 s (1,029 s in #2852) | **822 s** |

This PR on its own: 1,038 s -> 822 s, **-216 s (-21%)**, against the two
earlier runs (#2852: 1,029 s, #2854: 1,038 s). This PR's run was on a
slightly slower runner (the heavy `call` phases of the tests it does not
change took 6% longer than in #2854), so the gain is not runner luck.
The setups of 12 s or more, which are the pool starts and the
module-level compiles, fall from 16 (489 s) to 3 (246 s): the pool
starts once instead of in each of 14 modules, and what remains is the
extension build (124 s), the GDN module's Triton JIT (105 s) and one 18
s pool start.

With sysmon (#2846) a pool start takes 18-27 s instead of 75-77 s, so
each avoided start saves less than the 76 s this PR was designed around.
Without #2846 the saving would be 14 starts of 76 s plus the 3.6-10 s
shutdowns, about 17 min; that is an estimate, because the series was
never run without sysmon on this PR.

This is one CI run of the final head: the two runs before it failed on
the sticky CUDA error and on the missing `gc.collect()` described below.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

Nothing user-facing (test fixtures).

### Testing

- `reset_worker_state` has a gloo test next to the other pool tests (it
runs on CPU): flags and seed changed in a job are back at their
spawn-time values afterwards; it fails when the helper forgets one of
the settings.
- The Megatron lane needs GPUs, so this PR's gpu_megatron job is the
check: 822 s of pytest on the final head, against 1,038 s for the
earlier base, #2854's run (381 passed, 129 skipped, as before).
- The first CI run of this PR failed in gpu_megatron: the Blackwell
illegal-memory-access flake hit `test_lora_then_quantize` and poisoned
the shared pool, and every later module errored (3 failed, 180 errors),
because the pool was reused after a failed job and `_reset_shared_pools`
ran a job in it at each module start. The second commit retired a pool
after any failed job and made a failed reset harmless; with it the lane
ended with 1 failure instead of 183, in
`test_forward_with_one_lora[lora_config1]` (the same CUDA error).
Re-running the job on that commit failed at the same test again, so it
was not a random flake: the teardown of this PR, unlike the draft's, did
not collect garbage after destroying the groups, and the draft passed
the lane three times. The third commit adds that `gc.collect()`; the
lane then passes. The cause is not established (the finished test's
Megatron and TE modules can still reference the destroyed groups, and
their finalizers then run at an arbitrary point of the next job). #2836
has since landed the retirement in `run()` for its own reasons (with its
own gloo test), so the second commit now only makes a failed reset
harmless; main's teardown has neither the process-group loop nor the
collection, and a session-wide pool needs both.
- Pre-commit passes.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: ✅ 1 test function in
`tests/gpu/torch/utils/test_distributed_worker_pool.py` (gloo, CPU) for
the new helper; the session pool itself is exercised by the whole
gpu_megatron lane
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 11 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 1, 2, 4, 5, 6, 7, 8, 9 and 10 (#2845, #2846,
#2848, #2849, #2850, #2851, #2852, #2853, #2854) have merged. The two
remaining slices (#2847, #2855) are independent PRs on main: each
applies to main on its own. The times above are from the series' CI runs
before 2026-10-21, when all slices formed one linear stack; a PR number
in a method note names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
(merged)
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time (merged)
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (merged)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data (merged)
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests (merged)
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane (merged)
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (merged)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules **(this PR)**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Tests**
* Distributed GPU test workers now restore their initial PyTorch
settings and random seed between test modules.
* Worker teardown now synchronizes CUDA, removes non-default process
groups, and cleans up Python and GPU resources.
* Worker pools are shared across test modules and shut down at the end
of the test session for more consistent test execution.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 21, 2026
… GPU runs (#2847)

### What does this PR do?

Type of change: CI (test runtime)

Tests marked `manual` collect only when pytest gets `--run-manual`. The
unit workflow passes it for every run that is not a pull request, but
the GPU workflow never did, so a `manual` GPU test never ran in CI. This
sets `PYTEST_ADDOPTS=--run-manual` in the "Run gpu tests" step for every
run that is not a pull-request run (the nightly schedule and manual
dispatches), which lets slow or network-heavy checks leave the
pull-request jobs without leaving CI. Pull-request runs are unchanged by
the flag itself: copy-pr-bot runs them as `push` events, the workflow's
only `push` trigger, and the expression is `github.event_name != 'push'
&& '--run-manual' || ''`. An earlier version of this PR used the
`release` marker; review feedback pointed out that `release` is the QA
team's marker for long real-model runs in their own pipeline, while
`manual` is what the unit tests already use for nightly and on-demand
runs.

**Side effect:** `--run-manual` also runs the existing `manual`-marked
tests of `tests/gpu` on those runs, which no GPU run executed before: 12
NAS vision-model cases (`test_search_space_with_vision_models.py`), 6
puzzletron cases (about 40 s each by their marker) and the TRT client
benchmark models (they need TensorRT, so I could not count them on a CPU
host). The unit lane already runs its `manual` tests nightly the same
way. A `workflow_dispatch` run of the GPU workflow on this branch (run
39668011982: 2-GPU runners and `--run-manual`, the nightly's setup)
passed all four GPU jobs: `gpu` in 28 min (1,866 passed, 40 skipped; cap
60 min) and `gpu_megatron` in 27 min (403 passed, 87 skipped; cap 75
min). In `gpu` the six puzzletron params took 174 s (26-32 s each), the
14 NAS vision results 29 s in total and the Nemotron v3 test 20 s, about
4 min together; the TRT client tests are skipped in that container
(`libnvinfer.so.10` is missing), so they add nothing.

Two tests use it:

- `test_megatron_generate_and_mmlu[tp]` builds a 28-layer Qwen3-0.6B
Megatron model, downloads 1.2-1.5 GB of real weights into a fresh
container cache and scores about 1,400 MMLU prompts: a 21-39 s call in
every PR gpu_megatron job (21 s with the sysmon core of #2846, 39 s
without it), and the only test in the lane that downloads real weights.
- `test_get_dataset_dataloader_nemotron_v3_chat_template` streams the
seven gated Nemotron post-training v3 datasets from the Hub (a 17-24 s
call that needs an HF token and the network, and a source of Hub
flakes). The combo expansion and sample distribution it goes through are
covered by CPU unit tests that record the per-dataset calls.

**Trade-off:** these two checks no longer run on pull requests. The
real-weight accuracy check (0.36 < MMLU < 0.39) and the gated-data smoke
test run every night and on demand, a day after a change instead of on
the PR.

### Time improvement (measured in CI)

Time of the module that holds the skipped test, from the per-test
timestamps; before = the run of #2846, after = this PR's run and the
runs above it that carry the change (median):

| lane | skipped test (call time before) | module before | module after
| change |
|---|---|---|---|---|
| gpu_megatron | `test_megatron_generate_and_mmlu[tp]` (21 s) | 64 s |
41 s (39 s in this PR's run) | -22 s (-35% after speed scaling) |
| gpu | `test_get_dataset_dataloader_nemotron_v3_chat_template` (17 s) |
32 s | 13.5 s (13 s in this PR's run) | -19 s (-62%) |

So 22 s off the gpu_megatron job and 19 s off the gpu job of every PR
run. The lane totals cannot show a change this small (gpu_megatron 1,212
-> 1,206 s, gpu 1,289 -> 1,351 s: their run-to-run spread is several
times larger). The price is that the two checks run only on the nightly
and on demand.

Method: every PR of the series has its own CI run, so "before" is the
run of the PR(s) below that lack the change and "after" the runs that
carry it. Times come from the job logs (`--durations`, or the gap
between consecutive result lines). Where runner speed matters, the tests
that no PR of the series touches scale each run, and the raw and the
scaled medians are both shown. One test varies by 15-30% from run to run
on its own, so smaller differences are not evidence.

### Usage

`pytest --run-manual` runs the manual-marked tests locally.

### Testing

On a CPU host the Nemotron v3 test is skipped with "Only run when
--run-manual is given" without the flag and runs with it, and `-m
manual` selects it. The expression `github.event_name != 'push'` is
false for the `push` events that run pull requests and true for the
nightly and manual dispatches. Pre-commit and actionlint pass. Run on
GPU runners with the flag (dispatch run 39668011982, the nightly's
setup): the Nemotron v3 test passed in 20 s and the four Megatron MMLU
cases in 145 s (tp 66 s, cp 39 s, pp 32 s, dp 8 s). This PR's own GPU
run shows both skipped on a pull request.

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (marks two existing tests;
no new tests)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: N/A (optional, not run)

### Additional Information

Slice 3 of 11 of the GPU-CI speed-up work, split out of the reference
draft #2730 (not for merge; it holds the whole series with its
measurements). Slices 1, 2, 4, 5, 6, 7, 8, 9 and 10 (#2845, #2846,
#2848, #2849, #2850, #2851, #2852, #2853, #2854) have merged. The two
remaining slices (#2847, #2855) are independent PRs on main: each
applies to main on its own. The times above are from the series' CI runs
before 2026-10-21, when all slices formed one linear stack; a PR number
in a method note names that PR's run from then.

1. #2845 [OMNIML-6178] [1/11] Run the unit tests in parallel with
pytest-xdist (merged)
2. #2846 [OMNIML-6178] [2/11] Use the sysmon coverage core in CI
(merged)
3. #2847 [OMNIML-6179] [3/11] Run manual-marked tests on nightly and
on-demand GPU runs **(this PR)**
4. #2848 [OMNIML-6181] [4/11] Give gpu_megatron's slow cold starts more
time (merged)
5. #2849 [OMNIML-6179] [5/11] Make the ONNX tests cheaper: CPU-only ones
to the unit lane, small models (merged)
6. #2850 [OMNIML-6179] [6/11] Use local tokenizers and calibration text
in GPU tests, shrink puzzletron data (merged)
7. #2851 [OMNIML-6180] [7/11] Trim the llm_eval, llm_qat, EAGLE and
megatron_bridge example tests (merged)
8. #2852 [OMNIML-6180] [8/11] Trim the DFlash regression test (merged)
9. #2853 [OMNIML-6180] [9/11] Trim the torch_onnx example lane (merged)
10. #2854 [OMNIML-6181] [10/11] Make the distributed worker pool fail
fast when a worker dies (merged)
11. #2855 [OMNIML-6181] [11/11] Share one Megatron worker pool across
the gpu_megatron modules


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

- **Tests**
- GPU test coverage now includes selected manual tests during scheduled
and on-demand workflow runs.
- Tests requiring gated datasets or model downloads are excluded from
standard push-based runs to keep routine checks focused and efficient.

- **Chores**
- Updated GPU test execution settings to support explicitly enabled
manual test runs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shantanusharma pushed a commit to shantanusharma/Model-Optimizer that referenced this pull request Oct 22, 2026
…U worker pool (NVIDIA#2923)

### What does this PR do?

Type of change: CI (test runtime, tests only)

The six `ensure_local_checkpoint` multi-rank cases are gloo/CPU tests
with a fake `HfApi`, but they lived in `tests/gpu` and each one spawned
a fresh 2-process job (`spawn_multiprocess_job`). The spawn re-imports
torch, transformers and modelopt in both children: every case took
11.2-11.8 s in the `gpu` job of a pull request (13.7-14.3 s on the
nightly's 2-GPU runner), 69 s and 85 s per run, to test a few lines of
rank-0 logic.

The file moves to `tests/unit/torch/utils/`, next to the module's other
unit tests, and runs on the session-scoped `cpu_dist_workers` pool that
other multi-rank unit tests already share (CONTRIBUTING: "reuse the
session-scoped `cpu_dist_workers` fixture for multi-rank CPU tests").
Cases and assertions are unchanged. A case takes 0.01-0.02 s once the
pool is up.

The pool's workers outlive a case, so each job now undoes what it
changes instead of relying on a throwaway process: `snapshot_download`,
`HfApi` and `os.path.isdir` are patched with `mock.patch.object`,
`HF_HUB_OFFLINE` (which `tests/unit/conftest.py` sets for every unit
test) is restored with `mock.patch.dict(os.environ)`, and the process
group a job creates is destroyed. The default group is never touched.

### Time improvement (measured)

`gpu` job, per-case time from the result-line gaps of two CI logs of the
base:

| run | before (per case) | before (6 cases) | after |
|---|---|---|---|
| 1-GPU pull-request run (NVIDIA#2850, gpu job) | 11.2-11.8 s | 69 s | 0 s
(the file leaves the lane) |
| 2-GPU dispatch run with `--run-manual`, the nightly's setup (run
39668011982) | 13.7-14.3 s | 85 s | 0 s |

`unit` job: the six cases take 0.01-0.02 s each on a warm pool (CPU
host, torch 2.14.1). The first test of an xdist worker that touches the
pool pays the pool start (about 7 s on this host), but that cost is
already paid by the six existing pool-using files, so the unit job gains
about 0.1 s.

Memory (the pool question from the xdist work): this is a seventh file
that uses `cpu_dist_workers`; the six existing ones are
`test_fsdp2_parallel_export.py`, `test_autoquant.py`,
`test_autoquant_shapley.py`, `quantization/test_dist.py`,
`test_quantize_cpu.py` and `quantization/test_utils.py`. A pool is one
per xdist worker (two gloo processes, about 0.62 GB each after their
first job), so the ceiling is still four pools. The jobs add about 5 MiB
to each pool process (623-624 MiB after the six jobs, against 619 MiB
after the imports of a typical existing user). The only way this file
could raise the unit job's peak is by giving a pool to an xdist worker
that would otherwise have none, at most about 1.2-1.4 GB on that worker,
which is unlikely with six other users. A separate change to the unit
job's xdist grouping (every file with a `cpu_dist_workers` test goes to
one xdist group, chosen from `item.fixturenames`) would pick this file
up with no change.

### Usage

Nothing user-facing.

### Testing

All on CPU with `CUDA_VISIBLE_DEVICES=""`, in the CI-like venv (Python
3.12, torch 2.14.1 CPU, xdist 3.8.0) unless noted:

- The moved file alone: 6 passed, also on torch 2.10. Warm pool (other
pool users ran first): 0.01-0.02 s per case.
- `tests/unit/torch/utils` under `-n 4 --dist loadfile`: 371 passed.
- That directory plus the other `cpu_dist_workers` users
(`quantization/test_dist.py`, `export/test_fsdp2_parallel_export.py`,
`quantization/test_utils.py`) in one process in default, reversed and
two shuffled file orders (seeds 11 and 12): 403 passed each.
- A probe runs the six jobs on one persistent 2-rank pool and compares
the workers' state before and after (`HF_HUB_OFFLINE`,
`snapshot_download`, `HfApi`, `os.path.isdir`, the default group and the
process-group registry): identical on torch 2.10 and 2.14.1. The same
probe on the old job bodies, which patch globals and never restore them,
reports `HF_HUB_OFFLINE` lost, the two names still patched,
`os.path.isdir` patched on rank 1 and extra process groups left behind.
- The cases still catch regressions: with the rank-0 check in
`ensure_local_checkpoint` replaced by `if True`, 3 of the 6 fail; with
the "this rank cannot see" check removed, 1 fails.
- Pre-commit passes (ruff, mypy, license, bandit).

This PR's own unit job on a hosted runner passed. The `gpu` workflow was
skipped by path gating (the file only leaves `tests/gpu`), so the saving
shows in CI on the first gpu run after this merges; the old times come
from the CI logs above. Not run: Windows (`cpu_dist_workers` skips
there).

### Before your PR is "*Ready for review*"

Make sure you read and follow [Contributor
guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md)
and your commits are signed (`git commit -s -S`).

Make sure you read and follow the [Security Best
Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors)
(e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(...,
weights_only=False)`, `pickle`, etc.).

- Is this change backward compatible?: N/A (CI and tests)
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: no new
dependency
- Did you write any new necessary tests?: N/A (existing tests moved; no
new cases)
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A (CI and tests)
- Did you get Claude approval on this PR?: ✅ (`/claude review` approved
d836289; its one suggestion is applied in 9ce78ae)

### Additional Information

Found by a survey of the GPU lanes for per-case process starts (the
pattern NVIDIA#2855 removed from `gpu_megatron`). Jira: OMNIML-6179, under the
CI runtime epic OMNIML-6177.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Tests**
* Improved the reliability and isolation of distributed checkpoint test
runs. Test environments are now cleaned up after each run, including
when a run exits early or encounters an error, and rank-specific path
checks are more precisely scoped. These changes improve test consistency
without changing end-user behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Shengliang Xu <shengliangx@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.

3 participants