-
Notifications
You must be signed in to change notification settings - Fork 2.6k
[None][test] Reuse MPI sessions and cache HF weights across grouped multi-GPU LLM tests #15873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
1abe0cd
[None][test] Optimize LLM test startup overhead
sunnyqgg a99bef2
[None][test] Group multi-GPU LLM API tests
sunnyqgg f7ed74f
[None][test] Reuse MPI sessions with pytest fixtures
sunnyqgg 125a861
[None][test] Parameterize DeepSeek NVFP4 pre-merge group
sunnyqgg 6b92142
[None][fix] Reuse PP NCCL communicator across LLMs sharing worker pro…
sunnyqgg eed25e3
[None][test] Harden shared-MPI-session reuse for grouped NVFP4 tests
sunnyqgg 3759071
[None][test] Reset torch.compile state between grouped NVFP4 cases
sunnyqgg ec8bb23
[None][test] Extract shared session-reuse helpers into a single in-pa…
sunnyqgg 9f663e8
[None][test] Extract hf_weight_cache_env into the shared helper module
sunnyqgg 2a1ecd7
[None][test] Simplify multi-GPU session plumbing via mpi_kwargs fixtures
sunnyqgg c9a1015
[None][test] Simplify NVFP4 premerge case table and shared-LLM factory
sunnyqgg 3ea78e0
[None][test] Explicitly invalidate HF weight cache on grouped-test te…
sunnyqgg a90c97c
[None][test] Organize multi-GPU tests into gpu2/gpu4 classes with cla…
sunnyqgg d6be2d6
[None][test] Add CUTEDSL cases to grouped NVFP4 premerge matrix
sunnyqgg a5eb810
[None][test] Drop carried id from NVFP4 premerge case dicts
sunnyqgg 6a99211
[None][test] Mark HfWeightLoader weight-cache clear as private
sunnyqgg 202f2c9
[None][test] Group bf16 4-GPU pre-merge cases into the shared 4-GPU s…
sunnyqgg 384a8e0
[None][fix] Evict HF weight cache before load on miss to avoid 2x CPU…
sunnyqgg 19321b2
[None][test] Dedup shared-session compile-reset teardown; trim docstring
sunnyqgg 5d50aa0
[None][test] Use fully-parametrized ids for grouped test-db entries
sunnyqgg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Shared helpers for grouped multi-GPU tests that reuse one ``MpiPoolSession`` | ||
| across many ``LLM`` instances. | ||
|
|
||
| This lives in-package (rather than under ``tests/``) because the unittest and | ||
| integration test roots have separate ``sys.path`` bases and cannot import a | ||
| single shared module otherwise. It is test-infrastructure only and must not be | ||
| used on production inference paths. Heavy imports are done lazily so importing | ||
| this module never triggers a circular import during package initialization. | ||
| """ | ||
|
|
||
| import os | ||
| from contextlib import contextmanager | ||
| from typing import Optional | ||
|
|
||
|
|
||
| def restore_env_var(name: str, value: Optional[str]) -> None: | ||
| """Restore an env var to a previously captured value (or remove it).""" | ||
| if value is None: | ||
| os.environ.pop(name, None) | ||
| else: | ||
| os.environ[name] = value | ||
|
|
||
|
|
||
| @contextmanager | ||
| def hf_weight_cache_env(max_entries: str = "1"): | ||
| """Enable the HF weight cache via env vars, restoring prior values on exit. | ||
|
|
||
| IMPORTANT (load-bearing ordering): enter this BEFORE spawning the shared | ||
| ``MpiPoolSession``. ``MpiPoolSession`` snapshots ``TRTLLM*``/``TLLM*`` env at | ||
| spawn time and passes that copy to the workers (which are the processes that | ||
| actually load weights); enabling the cache AFTER the pool spawns leaves it | ||
| silently disabled in the workers. Back a module-scoped ``hf_weight_cache`` | ||
| fixture with this and have the session fixture depend on that fixture so the | ||
| env is exported first. | ||
| """ | ||
| prev = os.environ.get("TRTLLM_HF_WEIGHT_CACHE") | ||
| prev_entries = os.environ.get("TRTLLM_HF_WEIGHT_CACHE_MAX_ENTRIES") | ||
| os.environ["TRTLLM_HF_WEIGHT_CACHE"] = "1" | ||
| os.environ["TRTLLM_HF_WEIGHT_CACHE_MAX_ENTRIES"] = max_entries | ||
| try: | ||
| yield | ||
| finally: | ||
| restore_env_var("TRTLLM_HF_WEIGHT_CACHE", prev) | ||
| restore_env_var("TRTLLM_HF_WEIGHT_CACHE_MAX_ENTRIES", prev_entries) | ||
|
|
||
|
|
||
| def shared_mpi_session(n_workers: int): | ||
| """Generator yielding a shared ``MpiPoolSession`` (or ``None`` when MPI is | ||
| disabled), shutting it down on teardown. Intended to back a module-scoped | ||
| pytest fixture so a pool of workers is reused across many LLMs.""" | ||
| from tensorrt_llm._utils import mpi_disabled | ||
|
|
||
| if mpi_disabled(): | ||
| yield None | ||
| return | ||
|
|
||
| from tensorrt_llm.llmapi.mpi_session import MpiPoolSession | ||
|
|
||
| mpi_session = MpiPoolSession(n_workers=n_workers) | ||
| try: | ||
| yield mpi_session | ||
| finally: | ||
| mpi_session.shutdown() | ||
|
|
||
|
|
||
| def mpi_session_kwargs(mpi_session) -> dict: | ||
| """LLM kwargs that inject a shared MPI session, or ``{}`` when there is | ||
| none (e.g. MPI disabled). Lets harness-based tests forward the session.""" | ||
| return {"_mpi_session": mpi_session} if mpi_session is not None else {} | ||
|
|
||
|
|
||
| def reset_worker_torch_compile_state() -> None: | ||
| """Reset per-worker torch.compile / Dynamo state (runs inside each worker). | ||
|
|
||
| Dynamo's recompile counter is process-global and per-code-object. When | ||
| worker processes are reused across LLMs (shared ``MpiPoolSession``), each | ||
| ``torch_compile`` case recompiles the same ``model.forward`` code object | ||
| under new guards; the count accumulates and eventually trips | ||
| ``recompile_limit`` (16), which is a HARD failure under ``fullgraph=True`` | ||
| (``FailOnRecompileLimitHit``) and aborts the whole MPI job. Resetting | ||
| between cases makes each LLM start from a clean compile cache, like a fresh | ||
| process. Submit this to each worker via ``mpi_session.submit_sync(...)``. | ||
| """ | ||
| import torch | ||
|
|
||
| torch._dynamo.reset() | ||
|
|
||
|
|
||
| def reset_shared_session_torch_compile_state(make_llm) -> None: | ||
| """Reset per-worker torch.compile state on the shared session behind a | ||
| ``make_shared_llm`` factory (no-op if there is no shared session). | ||
|
|
||
| Call from a grouped test's per-case teardown so recompile counts don't | ||
| accumulate across cases on reused workers (see | ||
| ``reset_worker_torch_compile_state`` for the failure it prevents). | ||
| """ | ||
| mpi_session = getattr(make_llm, "mpi_session", None) | ||
| if mpi_session is not None: | ||
| mpi_session.submit_sync(reset_worker_torch_compile_state) | ||
|
|
||
|
|
||
| def clear_worker_weight_cache() -> None: | ||
| """Drop the per-worker HF raw-weight cache (runs inside each worker). | ||
|
|
||
| The cache is a process-global keyed by checkpoint file fingerprints, so it | ||
| otherwise lives until the worker process exits. Submit this to each worker | ||
| via ``mpi_session.submit_sync(...)`` on group teardown to invalidate it | ||
| explicitly instead of relying on process death. | ||
| """ | ||
| from tensorrt_llm._torch.models.checkpoints import HfWeightLoader | ||
|
|
||
| HfWeightLoader._clear_weight_cache() | ||
|
|
||
|
|
||
| def make_shared_llm(mpi_session): | ||
| """Return an ``LLM`` factory that transparently injects a shared MPI session. | ||
|
|
||
| Tests build the LLM by calling the factory exactly like ``LLM(...)``; the | ||
| shared session (if any) is passed through without the test having to know it | ||
| exists. Falls back to a private per-LLM session when ``mpi_session`` is None. | ||
| The factory exposes ``.mpi_session`` so callers can reset per-worker compile | ||
| state between cases without threading the session separately. | ||
| """ | ||
| from tensorrt_llm import LLM | ||
|
|
||
| def shared_llm(*args, **kwargs): | ||
| return LLM(*args, **kwargs, **mpi_session_kwargs(mpi_session)) | ||
|
|
||
| shared_llm.mpi_session = mpi_session | ||
| return shared_llm |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Include
rankin the communicator reuse guard.NcclCommunicatorOpis constructed with bothworld_sizeandrank(Lines 1180-1183), and the new comment also says the communicator depends on(world_size, rank). Reusing when onlyworld_sizematches can leave_pp_comm.nccl_commbound to an old rank while_pp_comm.mappingis refreshed to a new one.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents