Skip to content

CI follow-up: cache source-built wheels and extensions, bump CI containers, trim slow GPU tests - #2836

Merged
kevalmorabia97 merged 38 commits into
mainfrom
kmorabia/ci-gpu-followup
Oct 20, 2026
Merged

kevalmorabia97 merged 38 commits into
mainfrom
kmorabia/ci-gpu-followup

Conversation

@kevalmorabia97

@kevalmorabia97 kevalmorabia97 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Type of change: CI / tests (follow-up to #2832)

Related: #2870 (independent, targets main) runs GPU CI only for PRs targeting main.

  • Worker pool: a failing rank is now reported before its teardown runs. In gpu_megatron, teardown starts with dist.barrier(), which blocked against a still-running peer, so the parent never heard of the failure and the 30 s grace period could not start (it waited for the pytest timeout). New test fails without the fix. Also the remaining Fix post-merge unit CI and the gpu_megatron nightly timeout #2832 review nits (fixture-scope note, comments) and the sub-millisecond get_live_pool test moved to tests/unit.
  • Wheel cache for the gpu job: mamba_ssm, causal-conv1d and fast-hadamard-transform were compiled from sdist on every run (~10 min of the job, e.g. 4 + 6 min on 10-16). They are now pinned in noxfile.py and built into a wheelhouse that actions/cache keeps per container image (key includes the noxfile.py hash); a wheel is only built when the wheelhouse lacks it. A plain wheelhouse rather than pip's or uv's cache: pip does not cache wheels it builds from a local source tree (the C++20-patched sdist) or reliably from a git URL, its cache lives under HOME (/github/home in container jobs), and the GPU sessions use the container's own pip (venv_backend="none"), not uv. A directory of wheels keyed on the image and noxfile.py is explicit and is saved only once complete.
  • CUDA-extension cache never worked for the nightly: across 25 nightlies the gpu job never found its torch_extensions cache (205-350 s of extension compile each night; gpu_megatron hit about half the nights). actions/cache saves only when the job succeeds, and the nightly was failing or timing out every night, so main's cache scope was never populated (PR runs save into their own pull-request/N scope, which the nightly cannot read). The extension cache and the wheelhouse are now restored with actions/cache/restore and saved with actions/cache/save under always() (new .github/actions/save-extensions).
  • Container bumps: pytorch 26.05/26.07 -> 26.09. TensorRT-LLM stays at rc20: no later release runs the multimodal quickstart that test_qwen_vl uses (rc21-rc23 import MultimodalConfig, which tensorrt_llm.llmapi does not export yet; rc24+ import fla, which the image lacks). NeMo 26.08 is already the newest public image. vLLM stays at v0.30.0: v0.31.0 breaks the vLLM plugin (test_kimi_gemm_rs_ar_bypasses_enabled_fake_quantizers: gemm_rs_ar has no .apply; test_flashinfer_metadata_builder_patch_stashes_common_metadata: FlashInferMetadataBuilder.cache_config; test_tiny_llama_quantize times out) and needs plugin changes by its owners. The gpu_vllm job on v0.20.0 (legacy FusedMoE registration, vLLM < 0.24) is dropped; v0.30.0 is the only vLLM job, and its timeout goes from 15 to 30 min: on PRs (1 GPU) its tests take ~10 min plus ~5 min to pull the image, so it timed out twice with all tests passing; the 2-GPU nightly also runs the TP2 tests and needs ~22 min.
  • Extension cache key: keyed only on the C++/CUDA sources and their build scripts, so editing a Triton kernel no longer forces a rebuild (that rebuild pushed gpu_vllm past its 15 min limit). The key now also includes the job name: example and GPU jobs on the same image shared one key, and an example job that builds no extensions finished first and saved an empty cache. gpu and gpu_megatron then restored it as an exact hit, recompiled on every run (test_cuda_ext took 35-46 s in every gpu run of this PR), and could never save their own (Unable to reserve cache ... another job may be creating this cache). This also affects main. The save step now skips when no .so was built, so a run cancelled before building anything cannot claim the key with an empty cache. The tensorrt:26.05 images used by the ONNX/diffusers examples stay: torch-tensorrt (<2.13) needs libnvinfer.so.10, which newer images drop.
  • mamba_ssm / causal-conv1d on pytorch:26.09: their setup.py hardcodes -std=c++17 but the 26.09 torch headers need C++20 (ATen requires C++20), so the wheels are built with the standard patched to C++20 (checked to compile and import against torch 2.13 headers).
  • NVFP4 scale conversion without TensorRT-LLM's auto_deploy (removed in rc29, so the next bump would otherwise break): ModelOpt's NVFP4 dequantize/export paths (and gpu_trtllm collection) failed with ModuleNotFoundError. The cutlass-swizzled block-scale conversion is a tensor permutation, now implemented in ModelOpt as the inverse of swizzle_nvfp4_scales (same math as TensorRT-LLM's helper; bit-identical to rc20's on 6 shapes) with a CPU round-trip test.
  • conv3d implicit-GEMM JIT build: implicit_gemm_cuda.py passed -std=c++17, which fails against the 26.09 torch headers (165 GPU test failures); torch's load() now picks the standard.
  • FSDP2 + layerwise calibration bug: persistent_materialization patched FSDPParamGroup.unshard/reshard on the class, so while one layer was materialized every FSDP module skipped unshard; a full forward then ran on sharded DTensor params (test_layerwise_calibrate_fsdp2 failed on torch 2.14 with 'FSDPParam' object has no attribute '_unsharded_param'). Now patched only on the param groups inside the layer, and only when the layer itself is FSDP-wrapped (otherwise nothing was gathered for its FSDP submodules, so they must still unshard). CPU/gloo regression test fails without the fix; a second test covers a shared fully_shard([a, b]) group, nested contexts, a pre-existing instance override and a non-FSDP layer (fails without the group dedup or the FSDP-wrapped check).
  • EAGLE training crash (found by the new container): hf_eagle.py compiled _eagle_forward with mode="max-autotune", which enables inductor CUDA graphs; recording them intermittently fails (cudaStreamCaptureStatusInvalidated / "graph recording observed an input tensor deallocate"). It failed test_llama_eagle3 on 5 of the last 10 nightlies and on the pytorch:26.09 dispatch run. Now max-autotune-no-cudagraphs (same autotuning, no graphs). Training-speed impact not measured.
  • NVFP4 FP8-sweep kernel compile time: the kernel was autotuned over three tile configs, so first use compiled all three (~70 s per dtype on the pytorch:26.09 runner), pushing the MSE-sweep export and calibration tests past the 120 s timeout. It now uses the config that was fastest on B300 (BLOCKS_PER_PROGRAM=64, 8 warps; on Ada all three were within ~8% from 256² to 16384x8192). Cold first call drops from 13.6 s to 5.4 s locally, which also helps users' first MSE calibration in each process.
  • Stale xfails: removed the five xfail(raises=RuntimeError) markers in test_megatron_preprocess_data.py; they xpassed in all 151 PR runs and every nightly that reached them over the last week (one of them never touches HF).
  • Dedupe: removed tests/gpu_megatron/_extensions/test_torch_extensions.py, an exact copy of tests/gpu/_extensions/ (the megatron conftest already prebuilds the extensions).
  • Puzzletron e2e trimmed: Puzzletron is not actively maintained, so CI runs only Nemotron-3-Nano (hybrid MoE), gpt-oss and Qwen3-VL; the other six models are manual (~40 s each, ~4 min of the gpu job). Run them with --run-manual.
  • New 2-GPU nightly failures (from tests merged Oct 19): gpu_vllm's new TP2 tests (Sync the amax of vLLM's tensor-parallel linears across TP ranks #2842) failed because vLLM needs 160 MiB of /dev/shm and the container had Docker's default 64 MiB; the GPU test container now gets --shm-size=2gb, like the example jobs. test_distill_llm_sft_hf_chat[True] (Megatron-Bridge: GGML PTQ, QAD and fake-quant export for large MoE and VLM models #2873, packed SFT on a tiny MoE at TP2) failed on the sm_120 runner with "No dot product attention backend is available": the tiny model's head_dim of 2 leaves only TE's unfused attention, which the 2-GPU sm_120 runner rejects for packed input (it passes on Ada); the packed case now uses head_dim 8 so the flash and cuDNN backends also qualify. Both only run on the 2-GPU nightly, not in single-GPU PR CI.
  • gpu_megatron timeout 60 -> 75 min: PR runs (1 GPU) take ~42 min, but the nightly runs every test on 2 GPUs: 46.5 min on Oct 19, then cancelled at 60 min with 99% done on Oct 20, and at 89% in this branch's dispatch run.
  • Not included: destroying each test's NCCL groups in the worker teardown. An earlier commit here did that to stop communicators piling up in the persistent workers, but on the 1-GPU PR runners the branch then hit CUDA illegal memory accesses in random test_megatron.py tests (3 of 7 full-suite runs: test_dsa_kv_cache_quant, test_fp8_real_quantize, test_layerwise_calibrate_vlm_text_forward_loop), never with main's conftest (0 of 2 bisect runs, none on main). Reverted (6bc0119); the 2-GPU full suite takes the same time without it (43:36 vs 44:21), so the 60→75 min job timeout covers it.
  • Unit CI timeout on main (Oct 20): test_hf_dflash2.py::test_overfits_a_single_batch took 51-61 s on most unit runners (2-7 s on others) and hit its 60 s timeout, also in main's nightly; both accuracies pass 0.9 by step ~21, so it now runs 30 steps instead of 60 (8 s on one CPU thread, was 16 s).
  • test_mamba_search_space timeout 120 -> 150 s: across ~135 PR gpu_megatron jobs on Oct 16-17 it usually took 64-76 s, ~15% took 85-103 s on slow runners, and it reached 112, 119 and 123 s (twice; one timed out here). Nightlies were ~72 s.

Testing

  • Pool: the new test (rank 0 fails, rank 1 hangs, teardown barrier) fails without the fix (queue.Empty after the 30 s job timeout) and passes with it; CPU unit tests that use the shared pool pass.
  • Cache helper: stub-session check, cold cache runs pip wheel per missing package, warm cache runs none and installs the cached wheels.
  • Dispatched on this branch: Regression tests on pytorch:26.09 passed; Unit tests passed. PR CI on 9d9ab38: everything green except gpu (2 FP8-sweep timeouts, fixed above); gpu_megatron passed in 32.5 min (60 min limit); the torch-extension cache was restored.
  • FP8 sweep: tests/gpu/torch/quantization/test_nvfp4_fp8_sweep_kernel.py and the accelerate MSE tests pass locally (Ada, torch 2.14); the hf_ptq.py export case was not run locally.
  • PR CI on f222d46: all checks green except gpu_vllm (all 190 tests passed, then hit the old 15 min limit) and gpu_megatron (test_mamba_search_space at 123 s, timeout raised above). The gpu job restored the wheelhouse and installed all three wheels without compiling.
  • FSDP: tests/unit/torch/quantization/test_utils.py passes on CPU; GPU FSDP2/layerwise/export tests pass in nemo:26.08 (56 passed, 1 skipped).
  • Puzzletron/mamba: in nemo:26.08 the six puzzletron models skip as manual and test_mamba_search_space passes with the new marker.
  • PR CI on f1311ea (per-job cache keys): all checks green. Each GPU job started cold on its new key and saved a populated extension cache (gpu 7 MB, gpu_megatron 5 MB, gpu_trtllm 5 MB, gpu_vllm 2 MB; before, gpu and gpu_megatron only ever had a 0 MB one). Re-running gpu_trtllm restored its cache: test_cuda_ext 33.2 s -> 3.1 s and the pytest step 202 s -> 79 s.
  • Eagle mode: torch._inductor.list_mode_options confirms max-autotune sets triton.cudagraphs and the new mode does not; tests/unit/torch/speculative (339) and tests/gpu/torch/speculative (14) pass.
  • 2-GPU nightly fixes (8f58d40): not reproduced locally (the packed test passes on Ada). Dispatched on this branch (2-GPU runners): Example tests all 13 lanes passed, including test_distill_llm_sft_hf_chat[True]; in GPU tests gpu and gpu_trtllm passed and both gpu_vllm TP2 tests passed, but gpu_vllm hit its 20 min limit at 51% of tests (raised to 30 min below) and gpu_megatron hit its 60 min limit at 89% (raised to 75 min below). Re-dispatched GPU tests on f25ff98: gpu, gpu_trtllm and gpu_vllm passed (gpu_vllm 211 passed in 15 min); gpu_megatron 402 passed and failed only test_fp8_real_quantize (the Blackwell flake above).

Looked at but left alone: the 11 unified-export cases (each hits a different fuse-flag branch). Shared worker pools across megatron modules and a preloaded forkserver (importing modelopt initializes CUDA) did not work out.

  • Is this change backward compatible?: ✅
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: N/A
  • Did you get Claude approval on this PR?: ❌

🤖 Generated with Claude Code

kevalmorabia97 and others added 3 commits October 16, 2026 04:07
…r test to unit

A failing rank blocked in megatron_worker_teardown's barrier never reported, so the 30 s failure grace
period could not start and the job waited for the pytest timeout instead. Also doc/comment fixes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
- gpu job: pin and cache mamba_ssm / causal-conv1d / fast-hadamard-transform wheels per container image
  (~10 min of compilation per run); noxfile builds a wheel only if the wheelhouse lacks it
- Bump pytorch 26.05/26.07 -> 26.09, trtllm 1.3.0rc20 -> rc29, vllm v0.30.0 -> v0.31.0 (legacy v0.20.0 kept);
  nemo 26.08 is already the newest public. The tensorrt:26.05 images used by the ONNX/diffusers examples stay
  (torch-tensorrt needs libnvinfer.so.10)
- benchmark.py: tolerate TensorRT builds without NetworkDefinitionCreationFlag.EXPLICIT_BATCH (removed in 11)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
tests/gpu_megatron/_extensions/test_torch_extensions.py was identical to tests/gpu/_extensions/; the megatron conftest already prebuilds the extensions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97
kevalmorabia97 requested review from a team as code owners October 16, 2026 11:08
@coderabbitai

coderabbitai Bot commented Oct 16, 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: 003096da-2c11-4858-88da-ee5e5dca6398
📥 Commits

Reviewing files that changed from the base of the PR and between e0dafde and 27235c7.

📒 Files selected for processing: 6
  • .github/workflows/gpu_tests.yml
  • modelopt/onnx/quantization/autotune/benchmark.py
  • noxfile.py
  • tests/_test_utils/torch/distributed/utils.py
  • tests/gpu/torch/utils/test_distributed_worker_pool.py
  • tests/unit/torch/utils/test_get_live_pool.py
🚧 Files skipped from review as they are similar to previous changes: 1
  • tests/unit/torch/utils/test_get_live_pool.py

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

📝 Walkthrough

The pull request updates test container images and cache workflows, adds local NVFP4 scale conversion, changes TensorRT flag handling and Eagle’s compile mode, and reports distributed-worker errors before teardown. Related tests and fixture documentation are updated.

Changes: CI images and dependency caches

Layer / File(s) Summary
Test container versions
.github/workflows/example_tests.yml, .github/workflows/gpu_tests.yml, .github/workflows/regression_tests.yml
Updates PyTorch and TensorRT-LLM container versions used in test jobs.
GPU dependency wheel builds and cache
noxfile.py, .github/workflows/gpu_tests.yml
Builds or reuses pinned GPU dependency wheels and installs them in the GPU session. Adds wheelhouse cache restore and save steps.
Extension cache restore and save
.github/actions/cache-extensions/action.yml, .github/actions/save-extensions/action.yml, .github/workflows/_example_tests_runner.yml, .github/workflows/gpu_tests.yml
Exposes extension cache hit and key outputs, adds a cache save action, and wires cache saves into example and GPU workflows.

NVFP4 scale conversion

Layer / File(s) Summary
Local NVFP4 scale conversion
modelopt/torch/quantization/qtensor/nvfp4_tensor.py, modelopt/torch/export/unified_export_hf.py, tests/gpu_trtllm/torch/quantization/backends/test_nvfp4_gemm.py, tests/unit/torch/quantization/test_nvfp4_tensor.py
Adds a local CUTLASS scale-layout converter and uses it in export and dequantization. Adds conversion tests and imports the helper from ModelOpt in the GPU test.

Distributed worker pool

Layer / File(s) Summary
Report worker errors before teardown
tests/_test_utils/torch/distributed/utils.py
Sends job errors before teardown and avoids replacing or duplicating the reported job error with a teardown error.
Worker failure and pool tests
tests/gpu/torch/utils/test_distributed_worker_pool.py, tests/unit/torch/utils/test_get_live_pool.py, tests/gpu_megatron/conftest.py
Tests failure reporting while teardown blocks and tests live-pool reuse and broken-pool replacement. Updates fixture-scope documentation.

Runtime compatibility and compile mode

Layer / File(s) Summary
Conditional TensorRT network flags
modelopt/onnx/quantization/autotune/benchmark.py
Adds EXPLICIT_BATCH only when TensorRT exposes the flag. The strongly typed flag remains enabled.
Eagle forward compile mode
modelopt/torch/speculative/plugins/hf_eagle.py
Changes _eagle_forward to use max-autotune-no-cudagraphs. Other compile targets are unchanged.

GPU test references

Layer / File(s) Summary
GPU test comments and references
tests/gpu/torch/speculative/plugins/test_hf_dflash.py, tests/gpu_megatron/_extensions/test_torch_extensions.py, tests/gpu_megatron/conftest.py
Revises the DFlash layer-placement comment, removes an extension-test forwarding file, and updates the documented extension-test location.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Other

Sequence Diagram(s)

sequenceDiagram
  participant GPUWorkflow
  participant WheelhouseCache
  participant NoxGPU
  GPUWorkflow->>WheelhouseCache: restore wheelhouse using image and noxfile key
  GPUWorkflow->>NoxGPU: run GPU session with MODELOPT_WHEELHOUSE
  NoxGPU->>WheelhouseCache: reuse or build and install selected wheels
  GPUWorkflow->>WheelhouseCache: save wheelhouse after cache miss and completion
Loading
sequenceDiagram
  participant TestWorkflow
  participant CacheExtensions
  participant SaveExtensions
  TestWorkflow->>CacheExtensions: restore extensions
  CacheExtensions-->>TestWorkflow: return cache hit and key
  TestWorkflow->>SaveExtensions: save extensions after cache miss
Loading

Suggested reviewers: chenhanyu, shengliangxu

Merge Risk: ⚪ Minimal · up to 27235

This change updates CI images, caches, and compatibility handling. No actionable merge-blocking risk remains after review.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 46.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 28 functions across 12 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 5 passed
Check name Status Explanation
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 listed security anti-pattern was introduced. The PR changes four Python files under modelopt and no Python files under examples. Their added diff lines contain no `torch.load(..., weights_only=…
Title check Passed The title clearly summarizes the main changes: CI cache updates, container image bumps, and GPU test changes. It is concise and related to the changeset.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Security Anti-Patterns

Explanation

No listed security anti-pattern was introduced. The PR changes four Python files under modelopt and no Python files under examples. Their added diff lines contain no torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), hardcoded trust_remote_code=True, eval()/exec(), or # nosec. No pyproject.toml or requirements.txt file changed.

Full details: Docstring Coverage

Explanation

Docstring coverage is 46.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 28 functions across 12 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • 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.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97

Copy link
Copy Markdown
Collaborator Author

/claude review

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-20 17:24 UTC

@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.80%. Comparing base (28a38b6) to head (cb1cac9).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2836      +/-   ##
==========================================
+ Coverage   76.90%   78.80%   +1.89%     
==========================================
  Files         658      658              
  Lines       74023    74029       +6     
==========================================
+ Hits        56930    58335    +1405     
+ Misses      17093    15694    -1399     
Flag Coverage Δ
examples-diffusers 20.05% <15.38%> (+<0.01%) ⬆️
examples-gpt-oss 13.51% <15.38%> (+<0.01%) ⬆️
examples-hf_ptq 23.19% <26.92%> (-0.04%) ⬇️
examples-llm_distill 13.59% <15.38%> (-0.01%) ⬇️
examples-llm_eval 17.24% <15.38%> (+<0.01%) ⬆️
examples-llm_qat 17.48% <15.38%> (-0.01%) ⬇️
examples-llm_sparsity 15.77% <15.38%> (+<0.01%) ⬆️
examples-megatron_bridge 26.41% <15.38%> (-0.17%) ⬇️
examples-specdec_bench 13.29% <15.38%> (+<0.01%) ⬆️
examples-speculative_decoding 17.47% <15.38%> (-0.06%) ⬇️
examples-torch_onnx 21.40% <15.38%> (+<0.01%) ⬆️
examples-torch_trt 15.24% <15.38%> (+<0.01%) ⬆️
examples-vllm_serve 15.09% <15.38%> (+<0.01%) ⬆️
gpu 59.28% <100.00%> (+8.23%) ⬆️
regression 15.10% <15.38%> (-0.02%) ⬇️
unit 60.46% <84.61%> (+0.03%) ⬆️

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.

The nightly gpu job never found its extension cache in a month of runs: actions/cache saves only when the job succeeds, and the nightly was failing or timing out every night, so the main-branch cache was never populated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
Comment thread noxfile.py Outdated
Comment thread .github/workflows/gpu_tests.yml
Comment thread noxfile.py Outdated
Comment thread tests/_test_utils/torch/distributed/utils.py
Comment thread .github/workflows/gpu_tests.yml
Comment thread .github/workflows/example_tests.yml Outdated
Comment thread tests/gpu/torch/utils/test_distributed_worker_pool.py
Comment thread tests/unit/torch/utils/test_get_live_pool.py
Comment thread modelopt/onnx/quantization/autotune/benchmark.py Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude review — CI follow-up PR

Scope: full review (the trigger comment was a bare /claude review). 11 changed files, all reviewed: modelopt/onnx/quantization/autotune/benchmark.py, noxfile.py, the three workflow files, and the five test files. Note that a two-dot git diff origin/main HEAD in the shallow CI checkout surfaces unrelated main-only changes (the external-draft removal, find_final_norm_path); I scoped the review to the 11 files GitHub reports as this PR's, and ignored that noise.

Findings — CRITICAL: 1, IMPORTANT: 4, SUGGESTION: 3

Most impactful

1. The wheel cache probably doesn't work at all (CRITICAL, noxfile.py:131). The workflow caches /root/.cache/wheelhouse and never sets MODELOPT_WHEELHOUSE, so the noxfile falls back to ~/.cache/wheelhouse. In a GitHub Actions container job HOME is remapped to /github/home, not the image's /root — so the restore fills a directory nothing reads, the glob always misses, and all three packages recompile on every run. That is precisely the ~10 min this PR exists to remove. The sibling .github/actions/cache-extensions avoids this by writing the literal path into $GITHUB_ENV rather than relying on ~; doing the same here is a one-line fix. A single echo "$HOME" in the gpu job confirms or refutes it before merge.

2. A partial wheelhouse can be frozen into the cache key permanently (IMPORTANT, gpu_tests.yml:104). always() is justified for a failing test, but a timeout can also land between wheel builds. Saving a one-of-two wheelhouse, then skipping the save on every subsequent run because cache-hit == 'true', leaves the missing wheel rebuilt forever — and the key only changes when noxfile.py does, so it cannot self-heal. Suggested either a completeness marker or run_id-suffixed keys with restore-keys.

3. The container bump collides with the repo's own cppimport note (IMPORTANT, gpu_tests.yml:47). noxfile.py still says tests/gpu/_extensions/test_onnx_extensions.py "fails for newer containers until cppimport#98", and that test is an unconditional assert with no skip marker. 26.05 → 26.09 is squarely "newer". Either the note is stale and should be deleted in this PR, or the test needs a guard. The PR body says the workflows have not yet been run on the new images, so this is unverified either way.

4. Teardown failures are now silently swallowed on the failure path (IMPORTANT, distributed/utils.py:200-210). The early put is the right fix for the reported hang, but the finally block still assembles a [teardown] … traceback that the if not reported guard then discards. That diagnostic is most valuable exactly in the gpu_megatron scenario this PR targets, where the failing rank's own teardown barrier is the thing that blocks.

5. The vLLM pin the noxfile says must stay in sync is now out of sync (IMPORTANT, example_tests.yml:151). Workflows go to v0.31.0; examples/vllm_serve/Dockerfile and its README stay at 0.30.0, so CI validates a version users building the shipped Dockerfile don't get.

Also raised three non-blocking SUGGESTIONs: glob prefix-collision and a bare IndexError in _install_built_from_source, a thin 5 s timing margin in the new pool test, and a 2024 copyright year on a file added in 2026.

Assessment

The pool fix itself is correct and well-targeted — reporting before teardown is the right resolution for a teardown barrier that blocks against a still-running peer, and the new test genuinely fails without it and terminates fast rather than hanging CI. The EXPLICIT_BATCH guard is right and consistent with the other create_network call site. The dedupe of the symlinked megatron extension test is sound given the conftest already prebuilds the extensions.

Risk: medium, and concentrated in the CI plumbing rather than the library. Nothing here can affect user-facing behavior — benchmark.py is the only modelopt/ change and it only widens TensorRT version tolerance. But the headline benefit of the PR (the wheel cache) looks likely to be a no-op as written, and two of the container bumps are unverified against the images. Running the gpu job on this branch would resolve findings 1 and 3 together and is worth doing before merge.

mode="max-autotune" enables inductor CUDA graphs, whose recording intermittently fails in training (cudaStreamCaptureStatusInvalidated, 'graph recording observed an input tensor deallocate'); it failed test_llama_eagle3 on 5 of the last 10 nightlies and on the pytorch:26.09 dispatch run. max-autotune-no-cudagraphs keeps the autotuning.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97
kevalmorabia97 requested a review from a team as a code owner October 16, 2026 11:38
Their setup.py hardcodes -std=c++17, which fails against the torch headers of pytorch:26.09 ('ATen requires C++20'). Patch the standard when building the wheel; verified to compile and import against torch 2.13 headers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
…illegal-memory-access flake

test_fp8_real_quantize (twice, 2-GPU) and test_layerwise_calibrate_vlm_text_forward_loop[True] (PR CI,
1 GPU) failed with the intermittent CUDA illegal memory access that skip_flaky_on_blackwell already
covers for this file; test_megatron.py alone passed on the same 2-GPU runners in all 4 tries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97
kevalmorabia97 enabled auto-merge (squash) October 20, 2026 09:46
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
…p the new Blackwell skips

On the 1-GPU PR runners the branch hit CUDA illegal memory accesses in random test_megatron.py tests
(3 of 7 full-suite runs), never with main's conftest (0 of 2 bisect runs, and none on main).
Destroying each test's process groups in the teardown is the only functional difference, so revert
it; the 2-GPU full suite takes the same time without it (43:36 vs 44:21). The skips added for the
same crashes in 3ae2c85 go too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97
kevalmorabia97 enabled auto-merge (squash) October 20, 2026 15:41

@yeyu-nvidia yeyu-nvidia left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the hf_eagle.py hunk you asked about. The change is right and I'd land it — flaky nightlies beat unmeasured throughput, and max-autotune-no-cudagraphs is the correct knob: same Triton autotuning, no graph capture.

Two observations, neither blocking.

1. Two comments in the same file now describe a constraint that no longer applies

hf_eagle.py:762 and :910 both carry:

# Init rotary_emb outside _eagle_forward so inv_freq is not captured by CUDAGraph.
self.eagle_module._maybe_init_rope(device=...)

Those hoists exist because _eagle_forward was capturing CUDA graphs. With graphs off, the stated reason is gone while the code stays — and the next person to read it will either think graphs are still in play, or move the call back in without realising it was load-bearing for a reason that may return.

I would not move the calls; the hoist is harmless and keeps the door open if graphs are re-enabled. But a word in each comment saying the capture is currently disabled and this guards re-enabling would stop the rationale going stale. Your call whether that belongs in this PR or a follow-up.

2. suppress_errors = True means we would not have heard about this from a user

_activate_torch_compile sets torch._dynamo.config.suppress_errors = True four lines above the change. A compile-time failure degrades to eager silently; what made these failures visible is that cudagraph recording blows up at runtime rather than at compile. So the only reason this surfaced at all is the nightly, and only as a hard crash — a quieter regression in the same block would be invisible.

Not something to fix here. Worth knowing that this file has no signal between "works" and "crashes the test".

On the unmeasured speed impact

Honest to state it, and I would not gate on it. If it does get measured later, the comparison worth having is EAGLE3 training step time with max-autotune vs max-autotune-no-cudagraphs on a real target rather than the tiny CI model — graph capture pays off on many-small-kernel workloads, and EAGLE3's draft forward at training shapes is mostly large GEMMs, so my expectation is the delta is small. If it turns out not to be, the better fix is probably making capture robust (static input buffers) rather than reverting, since the recording failure is a lifetime problem, not an autotuning one.

I did not review the rest of the PR — CI infrastructure, the worker-pool fix, and the quantization changes are outside what you asked me for and outside what I can usefully check.

@yeyu-nvidia yeyu-nvidia left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving on the basis of the modelopt/torch/speculative/plugins/hf_eagle.py change only — the one @kevalmorabia97 asked me to look at. I have not reviewed the CI infrastructure, the worker-pool fix or the quantization changes in this PR.

max-autotune → max-autotune-no-cudagraphs is the right call and the right knob: identical Triton autotuning, no graph capture. Trading unmeasured throughput for a nightly that stops failing 5 runs in 10 is a good trade, and the comment records why.

Detail in my earlier comment: #2836 (review) — two non-blocking notes, the stale CUDAGraph rationale now left behind at hf_eagle.py:762 and :910, and suppress_errors = True four lines above meaning this block has no signal between working and crashing the nightly.

@yeyu-nvidia yeyu-nvidia left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving on the basis of the hf_eagle.py change only, which is what @kevalmorabia97 asked me to look at. max-autotune -> max-autotune-no-cudagraphs is the right knob: same Triton autotuning, no graph capture. Detail in #2836 (review)

…emb init is hoisted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
@kevalmorabia97

Copy link
Copy Markdown
Collaborator Author

@yeyu-nvidia Thanks. Addressed note 1 in cb1cac9: both rotary_emb hoist comments in hf_eagle.py now say capture is currently off and the hoist keeps re-enabling it safe. Code unchanged. Leaving suppress_errors as is, per your note.

@kevalmorabia97
kevalmorabia97 merged commit ff336e7 into main Oct 20, 2026
54 checks passed
@kevalmorabia97
kevalmorabia97 deleted the kmorabia/ci-gpu-followup branch October 20, 2026 17:24
shengliangxu added a commit that referenced this pull request Oct 20, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 20, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 20, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 20, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 20, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

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

### What does this PR do?

Type of change: CI (unit-test runtime)

The unit `linux` job gates every GPU and example workflow (their pr-gate
jobs wait for it), and its tests are CPU-bound but run serially. This
runs them under pytest-xdist on the runner's four vCPUs. The `linux` job
and the multi-version matrix get `PYTEST_ADDOPTS=-n 4 --dist loadfile`
and `OMP_NUM_THREADS=1`: one worker per vCPU (`-n auto` counts physical
cores), whole files per worker, and one thread per worker, because with
torch's default threads the runner is oversubscribed and slower than
serial (1,481 s and 41 failures in my local runs).

What else changes so the suite passes under xdist:

- `noxfile.py`: the second pytest process that runs the `manual` tests
clears `PYTEST_ADDOPTS`, so it stays serial. Those tests leak global
state into each other, and a worker would run them in some order.
- `tests/_test_utils/torch/quantization/onnx_export.py`: `TEST_MODELS`
was a set of classes, whose iteration order differs between processes,
so workers collected different test ids. It is a tuple now.
- `test_hf_dflash2.py::test_overfits_a_single_batch` takes about 30 s on
one thread on main (53 s before #2836 halved its loop to 30 steps; that
version hit the 60 s default call timeout in the nightly on main on
2026-10-20). Its tiny model is built in bf16, and a CPU without native
bf16 support emulates bf16 matmuls (about 0.9 s per step, 0.1 s in
float32). The model is float32 now, which takes the test to about 4 s on
one thread, so it needs no timeout override; the loop and both
assertions are unchanged.
- `test_full_algorithms_vision.py::test_search_constraints` assigned
`ConstraintsFunc._get_true_latency` and never restored it. Serial runs
never noticed, because `test_evaluate_constraints.py` sorts first. Under
xdist a worker can get the files in the other order, and the Python 3.10
and 3.14 legs failed two tests after it. It uses `monkeypatch` now.
- `tests/conftest.py`: the unit per-test cap is 120 s on every platform.
`test_dist.py::test_data_parallel` takes 8-15 s serial and 25-35 s with
four workers sharing four vCPUs, and went over 60 s on the slowest
runner. A hung test is still reported, after 2 min instead of 1.

Why: on current main the `linux` job has a median of 12.9 min (27 runs
of other PRs since #2823 merged), and a pull request's GPU run waits a
median 14.5 min for it (125 runs of 33 PRs).

### Time improvement (measured in CI)

Unit `linux` job: serial on current main (27 runs of other PRs since
#2823 merged) against the runs of this series with `-n 4`.

| | before (serial) | with this PR |
|---|---|---|
| pytest | 630 s median (355-709 s) | 479 s in this PR's own run; 294 s
median over the 11 runs of the series (166-479 s) |
| whole job | 12.9 min median (8.8-14.8) | 10.9 min in this PR's own
run; 7.0 min median over the 11 runs (5.0-10.9) |
| unit workflow: `linux`, then the multi-version and Windows legs | 24.1
min median (12 runs) | 17.2 min median (the 7 runs that ran every leg) |

- The runs of #2846 and above also carry the sysmon core of #2846 (about
22% off the unit suite on its own, measured locally), so the 294 s
median is xdist plus sysmon; this PR's own run is the xdist-only number.
It was the slowest of the 11 (166-479 s for nearly the same tests:
runner variance), so it understates the gain. Locally on 4 CPUs with
coverage as in CI, the base took 825 s serial and 332-344 s with this
configuration (3 runs).
- The GPU, Example and Regression workflows hold their jobs until the
unit `linux` check is done (`pr-gate / wait-checks`, at least 5 min).
That wait is a median 14.5 min (IQR 12.9-15.8) on 125 GPU runs of 33 PRs
since #2823; in this PR's own GPU run it was 9.7 min (its `linux` job
took 10.9 min). With a 7 min `linux` job instead of 12.9 min, a GPU run
requested right after a push starts about 6 min earlier. That figure is
derived from the job times: the later runs of the series were requested
after their unit check had finished (the wait sat at its 5.2 min floor
in 7 of the 9 GPU runs), so they cannot show it.

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. Locally: `OMP_NUM_THREADS=1 pytest tests/unit -n 4
--dist loadfile`.

### Testing

- Full `tests/unit` on 4 CPUs with coverage as in CI, on the base before
#2823: serial 825 s, this configuration 332-344 s in three repeated runs
(4,724 passed, 0 failed).
- This PR's own CI run: the `linux` job took 10.9 min (479 s of pytest)
against the 12.9 min serial median; see the time section above for the
later runs of the series.
- `tests/unit` without `puzzletron` (it needs `hydra`, absent from my
local environment) in reversed and in shuffled file order shows no other
order dependence; `test_search_constraints` followed by
`test_evaluate_constraints.py` in one process failed before the fix and
passes after it.
- `test_hf_dflash2.py` (33 tests) on one CPU thread: 13.5 s, of which
`test_overfits_a_single_batch` takes about 4 s (30 s with the bf16 model
on main; the next slowest test takes about 1.3 s).
- Pre-commit (all hooks) 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 (`pytest-xdist` is already in the `dev-test` extra on main)
- Did you write any new necessary tests?: N/A (two existing tests fixed
or sped up so they pass under xdist)
- 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 1 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). Merge in order; 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 **(this PR)**
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
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
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
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**
* Improved consistency and performance of automated test runs across
platforms and Python configurations through controlled parallel
execution.
* Preserved predictable model-check ordering and isolated temporary test
changes.
* Increased unit-test time limits to better support process-spawning and
slower runs.
* Improved test reliability by explicitly controlling numeric precision
and ensuring serial execution for manual CPU tests.
  * No end-user functionality or product behavior changed.
<!-- 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 20, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 21, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu added a commit that referenced this pull request Oct 21, 2026
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

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
A pool that lives for the whole session turns one poisoned job into many errors. In the
first CI run of this PR, test_lora_then_quantize hit the known Blackwell "illegal memory
access" in an NCCL broadcast. A CUDA error of that kind is sticky, so the next jobs in the
same workers failed too, and because _reset_shared_pools ran a job in the poisoned pool at
the start of every module, every test of every later module errored, including the ones
that use no pool (3 failed, 180 errors). With a pool per module such a flake cost one
module.

DistributedWorkerPool.run now retires a pool after any failed job (#2836), so get_live_pool
hands the next test a new one and _reset_shared_pools skips a retired pool. What is left is
a reset job that itself fails or finds a dead worker: _reset_shared_pools no longer lets
that error the module. run() retires the pool, and the next test that needs one gets a new
pool.

Testing: the Megatron lane needs GPUs, so this PR's gpu_megatron job is the check. The
retirement it relies on is covered on CPU by
test_a_failed_job_retires_the_pool_even_when_every_rank_replied in
tests/gpu/torch/utils/test_distributed_worker_pool.py.

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

7 participants