Skip to content

[None][test] Optimize LLM test startup overhead#15777

Open
sunnyqgg wants to merge 12 commits into
NVIDIA:mainfrom
sunnyqgg:ci_time_opt
Open

[None][test] Optimize LLM test startup overhead#15777
sunnyqgg wants to merge 12 commits into
NVIDIA:mainfrom
sunnyqgg:ci_time_opt

Conversation

@sunnyqgg

@sunnyqgg sunnyqgg commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Reuse externally managed MPI sessions across grouped LLM API test cases to avoid repeated worker startup/shutdown overhead.
  • Add opt-in HF checkpoint raw weight caching for grouped CI tests so repeated configs can reuse checkpoint tensors within the same worker lifetime.
  • Replace selected GB200/H100 pre-merge standalone test entries with grouped coverage while keeping non-compatible cases standalone.

Changes

  • Adds HF weight cache controls and lifecycle handling for external MPI sessions.
  • Groups compatible multi-GPU LLM API and DeepSeek V3 Lite pre-merge cases.
  • Updates relevant test-list entries for grouped execution.

Test plan

  • pre-commit run --files on changed Python/YAML files
  • commit hooks passed for local commits
  • CI pre-merge validation

Summary by CodeRabbit

  • New Features

    • Added optional checkpoint weight caching to speed up repeated model loads when enabled.
    • Improved multi-GPU execution handling by preserving externally managed MPI sessions.
  • Bug Fixes

    • Prevented MPI sessions from being shut down when they are not owned by the current component.
    • Ensured multi-GPU runs and cached loads continue to coordinate correctly across ranks.
  • Tests

    • Expanded multi-GPU and weight-loading test coverage, including grouped pre-merge cases.

@sunnyqgg sunnyqgg requested review from a team as code owners June 30, 2026 09:30
@sunnyqgg sunnyqgg requested review from JunyiXu-nv and brb-nv June 30, 2026 09:30
@sunnyqgg

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

1 similar comment
@sunnyqgg

sunnyqgg commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces an in-memory LRU cache for HF checkpoint weight loading, controlled via environment variables. Adds MPI session ownership tracking across executor, proxy, and LLM classes to avoid shutting down externally-owned sessions. Updates tests to share MPI sessions and adds a grouped NVFP4 4-GPU premerge test with corresponding test-list changes.

Changes

Weight caching and MPI session sharing

Layer / File(s) Summary
HF weight LRU cache implementation
tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py
Adds an env-configurable LRU cache keyed by file fingerprints and consolidation flag; wires cache lookup/store/eviction into safetensors and bin/pth load_weights branches, with barrier participation on cache hit.
Weight cache unit tests
tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py
Adds tests verifying raw weight object reuse when caching is enabled and reload behavior (two calls to _load_weights_in_parallel) when disabled.
MPI session ownership tracking
tensorrt_llm/executor/executor.py, tensorrt_llm/executor/proxy.py, tensorrt_llm/executor/rpc_proxy.py, tensorrt_llm/llmapi/llm.py
Adds _owns_mpi_session flags gating MPI shutdown calls in GenerationExecutorProxy, GenerationExecutorRpcProxy, and BaseLLM; passes an existing mpi_session through in GenerationExecutor.create() instead of forcing None.
Test harness extra kwargs forwarding
tests/unittest/llmapi/test_llm.py
Updates logprobs/stats/capture-error test harnesses to accept and forward **extra_llm_kwargs, and adds llm.shutdown() calls, including in a try/finally.
Shared MPI session fixtures in multi-GPU tests
tests/unittest/llmapi/test_llm_multi_gpu_pytorch.py
Adds _shared_mpi_session, shared_mpi_session_2gpu/4gpu fixtures, and _mpi_session_kwargs; updates multiple GPU/RPC/stats tests to use shared sessions.
Grouped NVFP4 4-GPU premerge test
tests/integration/defs/accuracy/test_llm_api_pytorch.py, tests/integration/test_lists/test-db/l0_gb200_multi_gpus.yml, tests/integration/test_lists/test-db/l0_dgx_h100.yml
Adds premerge case/grouping metadata, _run_nvfp4_4gpus_case helper, test_nvfp4_4gpus_premerge_grouped that shares an MPI session per group and enables the weight cache, and updates test-list YAMLs accordingly.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: arysef, govind-ramnarayan, jieli-matrix, QiJune, suyoggupta

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the PR's main goal of reducing LLM test startup overhead.
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.
Description check ✅ Passed The PR clearly explains the goal, changes, and testing, though it uses Summary/Changes/Test plan instead of the template headings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/unittest/llmapi/test_llm.py (1)

1846-1925: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing try/finally around llm.shutdown() in llm_return_logprobs_test_harness.

llm.shutdown() is only reached if none of the asserts in the generate loop (or streaming block) raise. Unlike llm_get_stats_test_harness/llm_get_stats_async_test_harness (use with LLM_CLASS(...) as llm:) and _test_llm_capture_request_error (explicit try/finally), a failing assertion here skips cleanup entirely.

This matters more now that this harness is called with a shared, module-scoped MpiPoolSession (test_llm_return_logprobs_streaming_tp2 in test_llm_multi_gpu_pytorch.py). Per mpi4py's docs, workers serve submitted tasks until signaled for completion; an un-shut-down proxy leaves its MPI worker(s) still executing, which can block or delay subsequent tests reusing the same shared session, since tasks queue behind busy workers.

🔧 Proposed fix: wrap the harness body in try/finally
-    llm = LLM_CLASS(
-        llama_model_path,
-        kv_cache_config=KvCacheConfig(free_gpu_memory_fraction=0.4,
-                                      **kv_cache_args_extra),
-        tensor_parallel_size=tp_size,
-        gather_generation_logits=True,
-        **llm_args_extra,
-        **extra_llm_kwargs,
-    )
-
-    prompts = ["A B C D E F G H I J K"]
-    sampling_params = SamplingParams(
-        logprobs=logprobs,
-        prompt_logprobs=prompt_logprobs,
-        return_context_logits=return_context_logits,
-        return_generation_logits=return_generation_logits)
-
-    for output in llm.generate(prompts, sampling_params):
+    llm = LLM_CLASS(
+        llama_model_path,
+        kv_cache_config=KvCacheConfig(free_gpu_memory_fraction=0.4,
+                                      **kv_cache_args_extra),
+        tensor_parallel_size=tp_size,
+        gather_generation_logits=True,
+        **llm_args_extra,
+        **extra_llm_kwargs,
+    )
+    try:
+        prompts = ["A B C D E F G H I J K"]
+        sampling_params = SamplingParams(
+            logprobs=logprobs,
+            prompt_logprobs=prompt_logprobs,
+            return_context_logits=return_context_logits,
+            return_generation_logits=return_generation_logits)
+
+        for output in llm.generate(prompts, sampling_params):
             ...  # (unchanged body)
-    if streaming:
+        if streaming:
             ...  # (unchanged body, indented one level)
-        asyncio.run(main())
-    llm.shutdown()
+            asyncio.run(main())
+    finally:
+        llm.shutdown()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/llmapi/test_llm.py` around lines 1846 - 1925,
`llm_return_logprobs_test_harness` should always clean up the `LLM_CLASS`
instance even when an assertion in the generate/streaming checks fails. Wrap the
harness body that creates `llm` and runs `llm.generate` / `llm.generate_async`
in a `try/finally`, and move `llm.shutdown()` into the `finally` block so the
shared MPI-backed session is not left running. Use the existing `llm` variable
and keep the cleanup guaranteed regardless of failures.
🧹 Nitpick comments (3)
tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py (1)

74-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Python 3.10 built-in generics for the new annotation.

Line 74 adds List[str]; prefer list[str] for new Python 3.10+ code.

Proposed fix
-    def _weight_files_cache_key(weight_files: List[str],
+    def _weight_files_cache_key(weight_files: list[str],
                                 use_consolidated: bool) -> tuple:

Based on learnings, Python 3.10+ features are available throughout the codebase. As per coding guidelines, “Prefer using the built-in types list, dict, and tuple.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py` around lines 74 -
75, The new type annotation in _weight_files_cache_key still uses typing.List;
update the weight_files parameter to use the Python 3.10 built-in generic
list[str] instead. Keep the rest of the signature unchanged and apply the same
built-in typing style consistently in this function if any related annotations
are added.

Sources: Coding guidelines, Learnings

tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py (1)

103-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add return annotations to the new test functions.

Line 103 and Line 136 should use -> None.

Proposed fix
-def test_weight_cache_reuses_raw_weights_with_fresh_consumable_wrapper(tmp_path, monkeypatch):
+def test_weight_cache_reuses_raw_weights_with_fresh_consumable_wrapper(tmp_path, monkeypatch) -> None:
-def test_weight_cache_disabled_by_default(tmp_path, monkeypatch):
+def test_weight_cache_disabled_by_default(tmp_path, monkeypatch) -> None:

As per coding guidelines, “Always annotate functions. Make the return type None if the function does not return anything.”

Also applies to: 136-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py` at line
103, Add explicit return type annotations to the new pytest functions in
test_weight_loader, including
test_weight_cache_reuses_raw_weights_with_fresh_consumable_wrapper and the other
new test at the referenced location; both should be annotated with -> None to
match the project’s function annotation guideline. Use the test function names
to locate and update their definitions without changing any test logic.

Source: Coding guidelines

tests/integration/defs/accuracy/test_llm_api_pytorch.py (1)

2585-2593: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Guard against silent coverage loss from duplicate case IDs.

Line 2585 builds a dict keyed by id, so duplicate entries in _NVFP4_4GPU_PREMERGE_CASES would overwrite earlier cases before the set comparison runs. Add a uniqueness assertion before creating cases_by_id.

Proposed check
+        case_ids = tuple(case["id"] for case in self._NVFP4_4GPU_PREMERGE_CASES)
+        assert len(case_ids) == len(set(case_ids)), (
+            "Duplicate DeepSeek V3 Lite NVFP4 premerge case IDs"
+        )
         cases_by_id = {
             case["id"]: case
             for case in self._NVFP4_4GPU_PREMERGE_CASES
         }

As per path instructions, tests/**: act as a QA engineer reviewing test changes and coverage for TensorRT-LLM.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/defs/accuracy/test_llm_api_pytorch.py` around lines 2585 -
2593, Add a pre-check in the NVFP4 4GPU premerge coverage test to fail fast on
duplicate case IDs before building cases_by_id, since the dict comprehension in
the accuracy test can silently overwrite earlier entries. In the test method
that uses _NVFP4_4GPU_PREMERGE_CASES and _NVFP4_4GPU_PREMERGE_GROUPS, assert
that the list of case["id"] values is unique, then keep the existing
grouped_case_ids coverage assertions after cases_by_id is created.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py`:
- Around line 55-71: The HF weight cache max-entry setting is only enforced on
cache writes, so existing entries in `_WEIGHT_CACHE` can still be reused when
`TRTLLM_HF_WEIGHT_CACHE_MAX_ENTRIES` is set to 0 or invalid. Update the cache
lookup path in `weight_loader.py` so `_weight_cache_max_entries()` is checked
before returning any cached weights, and treat non-positive/disabled values as a
hard disable for both read and write behavior. Apply the same guard consistently
in the affected load/save flow around `_WEIGHT_CACHE`,
`_is_weight_cache_enabled()`, and `_weight_cache_max_entries()` so cached
entries are never reused when the cache is disabled.

In `@tensorrt_llm/executor/executor.py`:
- Around line 639-645: Guard the caller-supplied MPI session in
GenerationExecutor._create_ipc_executor so a reused MpiPoolSession cannot
mismatch the expected worker count. Add a size check before using mpi_session,
comparing its worker count against model_world_size (or the derived IPC worker
count), and reject or ignore the injected session when the sizes differ. Keep
the validation close to the existing need_spawn_mpi_workers() logic in
executor.py and use the existing symbols mpi_session, model_world_size, and
_create_ipc_executor to locate the change.

In `@tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py`:
- Around line 103-105: The cache tests currently only set TRTLLM_HF_WEIGHT_CACHE
in test_weight_cache_reuses_raw_weights_with_fresh_consumable_wrapper, so
TRTLLM_HF_WEIGHT_CACHE_MAX_ENTRIES can leak in from the environment and change
reuse behavior. Update the cache test setup in test_weight_loader.py to
explicitly control both cache env vars, and add a focused regression around
HfWeightLoader’s cache-control path for max entries set to 0 and invalid values
so the reload/reuse behavior is covered. Coverage for this path is currently
insufficient and should be expanded with a dedicated test.

---

Outside diff comments:
In `@tests/unittest/llmapi/test_llm.py`:
- Around line 1846-1925: `llm_return_logprobs_test_harness` should always clean
up the `LLM_CLASS` instance even when an assertion in the generate/streaming
checks fails. Wrap the harness body that creates `llm` and runs `llm.generate` /
`llm.generate_async` in a `try/finally`, and move `llm.shutdown()` into the
`finally` block so the shared MPI-backed session is not left running. Use the
existing `llm` variable and keep the cleanup guaranteed regardless of failures.

---

Nitpick comments:
In `@tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py`:
- Around line 74-75: The new type annotation in _weight_files_cache_key still
uses typing.List; update the weight_files parameter to use the Python 3.10
built-in generic list[str] instead. Keep the rest of the signature unchanged and
apply the same built-in typing style consistently in this function if any
related annotations are added.

In `@tests/integration/defs/accuracy/test_llm_api_pytorch.py`:
- Around line 2585-2593: Add a pre-check in the NVFP4 4GPU premerge coverage
test to fail fast on duplicate case IDs before building cases_by_id, since the
dict comprehension in the accuracy test can silently overwrite earlier entries.
In the test method that uses _NVFP4_4GPU_PREMERGE_CASES and
_NVFP4_4GPU_PREMERGE_GROUPS, assert that the list of case["id"] values is
unique, then keep the existing grouped_case_ids coverage assertions after
cases_by_id is created.

In `@tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py`:
- Line 103: Add explicit return type annotations to the new pytest functions in
test_weight_loader, including
test_weight_cache_reuses_raw_weights_with_fresh_consumable_wrapper and the other
new test at the referenced location; both should be annotated with -> None to
match the project’s function annotation guideline. Use the test function names
to locate and update their definitions without changing any test logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e4c1ec00-9f13-4c67-88a1-4d8ae0f406b8

📥 Commits

Reviewing files that changed from the base of the PR and between e6046df and 9ce7fa4.

📒 Files selected for processing (11)
  • tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py
  • tensorrt_llm/executor/executor.py
  • tensorrt_llm/executor/proxy.py
  • tensorrt_llm/executor/rpc_proxy.py
  • tensorrt_llm/llmapi/llm.py
  • tests/integration/defs/accuracy/test_llm_api_pytorch.py
  • tests/integration/test_lists/test-db/l0_dgx_h100.yml
  • tests/integration/test_lists/test-db/l0_gb200_multi_gpus.yml
  • tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py
  • tests/unittest/llmapi/test_llm.py
  • tests/unittest/llmapi/test_llm_multi_gpu_pytorch.py

Comment thread tensorrt_llm/_torch/models/checkpoints/hf/weight_loader.py
Comment thread tensorrt_llm/executor/executor.py
Comment thread tests/unittest/_torch/models/checkpoints/hf/test_weight_loader.py Outdated
@sunnyqgg

sunnyqgg commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@sunnyqgg sunnyqgg requested a review from a team as a code owner July 1, 2026 06:10
@sunnyqgg sunnyqgg requested review from HuiGao-NV and byshiue July 1, 2026 06:10
@sunnyqgg

sunnyqgg commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #57095 [ run ] triggered by Bot. Commit: 6a99211 Link to invocation

@sunnyqgg

sunnyqgg commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #57105 [ run ] triggered by Bot. Commit: 19321b2 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #57095 [ run ] completed with state ABORTED. Commit: 6a99211

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #57105 [ run ] completed with state SUCCESS. Commit: 19321b2
/LLM/main/L0_MergeRequest_PR pipeline #45892 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

Comment thread tests/test_common/grouped_test_utils.py Outdated
Comment thread tests/integration/defs/accuracy/test_llm_api_pytorch.py Outdated
Comment thread tensorrt_llm/executor/rpc_proxy.py Outdated
@litaotju litaotju requested review from Superjomn and yuxianq July 2, 2026 08:49

@Superjomn Superjomn 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.

LGTM on the mpi_session related changes

Comment thread tests/test_common/grouped_test_utils.py Outdated
@sunnyqgg

sunnyqgg commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #57341 [ run ] triggered by Bot. Commit: bbe92bf Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #57341 [ run ] completed with state SUCCESS. Commit: bbe92bf
/LLM/main/L0_MergeRequest_PR pipeline #46096 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

Comment thread tensorrt_llm/llmapi/llm.py
sunnyqgg added 4 commits July 12, 2026 22:30
Three AutoDeploy tests have now failed on reused workers with the same
failure class (tensor expand-size mismatch such as 'expanded size of the
tensor (128) must match the existing size (256)', at pool use #2):
registry accuracy, guided decoding, and eagle3 one-model. AutoDeploy's
executor adapter keeps worker-process state sized for the previous
engine's config, so per-test markers are whack-a-mole.

Replace the individual marker with a plugin-level node-id pattern
opt-out (autodeploy / auto_deploy / test_ad_) in session_reuse_hooks and
revert the now-redundant marker on the registry accuracy test. Remove
the patterns once AutoDeploy re-initializes that state per engine.

Signed-off-by: qgai <qgai@nvidia.com>
Two robustness gaps let a single broken pool hang the whole suite,
observed when a test's executor dies mid-shutdown ('Failed to send
object') and poisons the shared pool:

- The health probe called submit_sync, whose future.result() has no
  timeout. Workers that are alive but wedged in a collective never
  complete the probe task, so the next acquire blocked forever. Bound
  each probe round with concurrent.futures.wait(round_timeout) and
  treat a timeout as probe failure (retire + fresh spawn).

- _retire disposed pools with a graceful shutdown(), which blocks
  indefinitely on wedged workers and leaks their GPU memory into
  subsequent tests. Prefer shutdown_abort (bounded grace, then kill)
  with shutdown(wait=False) as fallback.

Signed-off-by: qgai <qgai@nvidia.com>
The previous hardening disposed of broken pools via shutdown_abort,
which calls MPI_COMM_WORLD.Abort and kills the parent test process
along with the workers (observed as 'MPI_ABORT was invoked on rank 0'
taking down the whole pytest session right after a pool rebuild).

Record the worker PIDs at spawn (best-effort submit_sync rounds on the
fresh, healthy pool) and have _retire SIGKILL them before reaping the
client side with a plain shutdown. Retired pools are discarded, so
nothing needs a graceful stop, and the driver reclaims GPU memory on
process death. Missing PIDs simply fall back to graceful shutdown.

Signed-off-by: qgai <qgai@nvidia.com>
Healthy retires (lifetime cap, stale env snapshot, duplicate cache
slot) go back to a graceful background shutdown: those workers are idle
and exit cleanly, and abnormal termination of MPI-spawned children can
upset the MPI runtime in the parent test process. The SIGKILL path is
kept only for pools that failed the health probe, where workers may be
wedged in a collective and a graceful shutdown would block forever.

Signed-off-by: qgai <qgai@nvidia.com>
@sunnyqgg

Copy link
Copy Markdown
Collaborator Author

/bot reuse-pipeline

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58899 [ reuse-pipeline ] triggered by Bot. Commit: 027be09 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58899 [ reuse-pipeline ] completed with state SUCCESS. Commit: 027be09
Reusing PR_Github #58875 for commit 027be09

Link to invocation

if prior is not None and prior is not real:
self._retire(real) # a pool of this size is already cached
return
self._pools[real.n_workers] = real

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.

Overall, this is a useful optimization and the normal test isolation semantics remain unchanged: a failed pytest item does not automatically fail other items.

The existing health-probe path should also handle a hard worker failure: if a worker future raises or times out, the cached pool is retired with broken=True and the next test gets a fresh pool.

The remaining gap is a failed test whose pool still appears healthy to the probe. Since shutdown() is overloaded to return the pool to the cache, a test that leaves undetected worker-side state behind could make that pool available to up to 15 subsequent same-size tests (max_uses=16). This is not expected to happen for every test failure, but it could create a difficult-to-diagnose cascade when it does.

Could we add a lightweight pytest-level failure fence?

  • Record any failed item from pytest_runtest_logreport.
  • After teardown, in pytest_runtest_logfinish, call
    REUSE.drain() if that item failed.
  • Keep the current reuse behavior unchanged for passing
    tests.

For example:

      def pytest_runtest_logreport(report):
          if report.failed:
              _FAILED_ITEMS.add(report.nodeid)

      def pytest_runtest_logfinish(nodeid, location):
          if nodeid in _FAILED_ITEMS:
              _FAILED_ITEMS.remove(nodeid)
              REUSE.drain()

Draining all cached pools is intentionally conservative and only adds the pool-shutdown cost after a failure; the normal passing-test path has no additional overhead. This should be treated as a fallback for state contamination that the health probe cannot detect, not as a replacement for the existing broken-pool retirement path.

Please also add a regression test showing that after a failed item, the next acquire does not reuse the previously cached pool.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, implemented in b3e7a1f exactly as you sketched: pytest_runtest_logreport
records failed items, and pytest_runtest_logfinish drains all cached pools after the
failed item's teardown. Passing tests are unchanged. Also added the two regression tests:
after a failed item the next acquire gets a fresh pool, and after a passing item reuse
stays intact.

A failed test's pool can pass the health probe (workers alive and
responsive) while still carrying worker-side state the probe cannot
see, and would otherwise serve up to max_uses-1 subsequent same-size
tests. Add a pytest-level failure fence per review feedback: record
failed items in pytest_runtest_logreport and drain ALL cached pools in
pytest_runtest_logfinish once the failed item's teardown has returned
its pool to the cache.

This is a conservative fallback for contamination the probe cannot
detect, not a replacement for the broken-pool retirement path. The
passing-test path is unchanged and pays no extra cost; a failure adds
one pool-shutdown plus a fresh spawn for the next test.

Regression tests: after a failed item the next acquire gets a fresh
pool; after a passing item reuse stays intact.

Signed-off-by: qgai <qgai@nvidia.com>
@sunnyqgg

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58921 [ run ] triggered by Bot. Commit: b3e7a1f Link to invocation


@property
def max_uses(self) -> int:
return int(os.environ.get("TRTLLM_TEST_REUSE_MAX_USES", "16"))

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.

Why we need to limit the max uses? What happens if we reuse unlimited times?

Comment thread tests/test_common/session_reuse.py Outdated
Comment thread tests/test_common/session_reuse.py
Comment thread tests/test_common/session_reuse.py Outdated
sunnyqgg added 3 commits July 13, 2026 01:28
A wedged pool shutdown inside a drain thread must not keep the
interpreter alive at process exit: the bounded join only protects the
drain caller, while a non-daemon thread would make pytest hang until
the CI stage timeout. The retire path already uses a daemon thread;
apply the same to drain, per review feedback.

Signed-off-by: qgai <qgai@nvidia.com>
If a broken pool's worker has already exited, the OS may recycle its
PID before the background retire thread runs, and os.kill would then
hit an unrelated process owned by the same user - most plausibly a
worker of the replacement pool being spawned concurrently. Record
(pid, start_time from /proc/pid/stat) pairs at spawn and re-verify the
start time right before SIGKILL; a mismatch or missing process means
the PID no longer belongs to the recorded worker and is skipped,
falling back to the graceful client-side reap. Per review feedback.

Signed-off-by: qgai <qgai@nvidia.com>
Retire threads were fire-and-forget: a disposal still in flight when
the session ends would be cut off by interpreter exit (daemon threads),
leaving a graceful shutdown half-done. Track them in a registry and
have drain() - which runs at the natural rendezvous points (failure
fence, opt-out, session finish) - join them with the same bounded wait
used for pool shutdowns, per review feedback. The per-test hot path
stays non-blocking, and daemon=True still guarantees a wedged disposal
cannot hang interpreter exit.

Signed-off-by: qgai <qgai@nvidia.com>
Comment thread tests/test_common/grouped_test_utils.py Outdated
expected = mpi_session.n_workers
seen: set = set()
for _ in range(max_rounds):
futures = mpi_session.submit(_run_and_get_worker_rank, fn)

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.

Forgive me, I dont fully understand this/we can discuss.

Will this cause the fn to be run multiple times?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, it could — and you were right to flag it. Fixed in b086d20: each task now
starts with an MPI_Barrier, so a worker that grabs a task blocks until every
worker has one. The n tasks land exactly one per worker, in one round. No more
retry loop, and fn never runs twice.

Comment thread tests/test_common/session_reuse.py Outdated
try:
for _ in range(4):
pids.update(real.submit_sync(_get_worker_pid))
if len(pids) >= n_workers:

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.

May work, but I dont fully understand this one, why need 4 tries?
Shall we use more reliable way to collect pids if the submit_sync does not work reliably?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. The 4 tries were working around the same issue as the probe loop:
MPIPoolExecutor doesn't guarantee one task per worker. b086d20 replaces both
with one barrier-based helper that runs the task exactly once per worker, so
the magic numbers (8 rounds / 4 tries) are gone. Verified on a real 4-GPU
pool: 20/20 runs hit all 4 workers exactly once, and a wedged worker still
fails the probe at the timeout.

MPIPoolExecutor has no one-task-per-worker guarantee, so both the pool
health probe (8 resubmit rounds) and worker PID collection (4 tries)
compensated with retry-and-dedup loops. Replace both with one
exactly-once primitive: each submitted task opens with an MPI_Barrier
across the worker world, so a worker holding a task blocks until every
worker holds one — by pigeonhole the n_workers tasks land exactly one
per worker, in a single round.

This also strengthens the probe: completing the barrier proves the
worker world's collectives still function (the failure mode wedged
pools actually exhibit), not just the task loop. Timeout semantics and
the broken-pool retirement path are unchanged.

Validated on a real 4-GPU MPIPoolExecutor spawn: 20/20 probes covered
exactly 4 workers with worker COMM_WORLD size 4, and a wedged worker
turned the probe into a bounded RuntimeError at the timeout.

Signed-off-by: qgai <qgai@nvidia.com>

@QiJune QiJune 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.

LGTM

@sunnyqgg

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58954 [ run ] triggered by Bot. Commit: b086d20 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58921 [ run ] completed with state ABORTED. Commit: b3e7a1f

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58954 [ run ] completed with state SUCCESS. Commit: b086d20
/LLM/main/L0_MergeRequest_PR pipeline #47486 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@sunnyqgg

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58991 [ run ] triggered by Bot. Commit: b086d20 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #58991 [ run ] completed with state SUCCESS. Commit: b086d20
/LLM/main/L0_MergeRequest_PR pipeline #47521 completed with status: 'SUCCESS'

CI Report

Link to invocation

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