Skip to content

[https://nvbugs/6419139][test] Guard against CUDA context creation at import#15985

Open
thorjohnsen wants to merge 1 commit into
NVIDIA:mainfrom
thorjohnsen:fix/nvbug-6419139-deep-gemm-pdl-import-context
Open

[https://nvbugs/6419139][test] Guard against CUDA context creation at import#15985
thorjohnsen wants to merge 1 commit into
NVIDIA:mainfrom
thorjohnsen:fix/nvbug-6419139-deep-gemm-pdl-import-context

Conversation

@thorjohnsen

@thorjohnsen thorjohnsen commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai summary

Description

Adds a regression test guarding the root cause of nvbug 6419139 (~10% throughput regression on RTX 6000D for llama_v3.3_nemotron_super_49b_fp8, 1.3.0rc19 → rc20; same root cause as nvbugs 6405760, 6418453, 6419078).

Between rc19 and rc20, #15402 added an import-time deep_gemm.set_pdl() call. set_pdl() instantiates DeepGEMM's DeviceRuntime (cuBLASLt handle + 32 MiB workspace tensor), creating a CUDA context (~0.5–1.2 GiB with loaded modules, arch-dependent) in every process importing tensorrt_llm — including the trtllm-bench parent (which never launches a kernel) and every MPI worker on its default device before torch.cuda.set_device(). Those contexts are resident when the KV cache pool is sized from free GPU memory, shrinking the pool (RTX 6000D: 48.33 → 47.39 GiB, max concurrent requests 513 → 503). With the QA benchmark's 512 uniform requests, 9 spill into a ~750-iteration low-batch tail = +10% inference time; GPUs with more headroom lose the same memory but show no regression, which made this look GPU-specific.

The root cause was independently fixed on main by #15632 (_configure_deep_gemm_pdl() deferred to PyTorchModelEngine init) — this PR originally carried an equivalent fix and has been rebased to keep only the regression test, so the import path stays free of device side effects going forward.

Verification of the mechanism (rc19/rc20 release wheels, 2×H100, exact QA bench command): available KV cache memory 44.14 GiB (rc19) vs 43.59 GiB (rc20, deterministic across 9 runs); disabling only the import-time call restores 44.14 GiB exactly; import tensorrt_llm creates a 550 MiB context on rc20 and none on rc19.

Test Coverage

  • New: tests/unittest/others/test_import_side_effects.py
    • test_deep_gemm_pdl_configuration_is_lazy — asserts import tensorrt_llm does not configure DeepGEMM PDL (no GPU required).
    • test_import_creates_no_cuda_context — asserts import tensorrt_llm creates no CUDA context (pynvml, GPU-gated).

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

🤖 Generated with Claude Code

@thorjohnsen
thorjohnsen requested review from a team as code owners July 6, 2026 17:08
@thorjohnsen
thorjohnsen requested review from QiJune and liji-nv July 6, 2026 17:08
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

DeepGEMM PDL initialization is refactored from an eager, import-time call into a lazy, one-time initializer guarded by a module-level flag. The initializer is now invoked at the start of FP8 quantization, MoE grouped GEMM, and sparse attention metadata initialization. New tests verify no CUDA side effects occur at import time.

Changes

Lazy DeepGEMM PDL Initialization

Layer / File(s) Summary
Lazy initializer and import-time removal
tensorrt_llm/_torch/custom_ops/torch_custom_ops.py
Adds a guarded _init_deep_gemm_pdl helper with a _deep_gemm_pdl_initialized flag that checks CUDA availability before calling deep_gemm.set_pdl(...) once, and removes the prior unconditional call that ran at import time.
Call-site wiring for lazy init
tensorrt_llm/_torch/custom_ops/torch_custom_ops.py, tensorrt_llm/_torch/modules/fused_moe/fused_moe_deepgemm.py, tensorrt_llm/_torch/attention_backend/sparse/dsa.py
Calls _init_deep_gemm_pdl() at the start of _fp8_quantize_1x128_ue8m0, deepgemm_fp8_group_blockwise_gemm, and DSAtrtllmAttentionMetadata.__init__ before their respective DeepGEMM operations.
Import side-effect tests
tests/unittest/others/test_import_side_effects.py
Adds a subprocess runner helper and tests confirming DeepGEMM PDL is not eagerly initialized and no CUDA context is created upon import tensorrt_llm, using NVML to inspect GPU processes.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller as FP8Quantize / MoEGemm / DSAMetadata
  participant Init as _init_deep_gemm_pdl
  participant DeepGEMM as deep_gemm

  Caller->>Init: _init_deep_gemm_pdl()
  alt not yet initialized and CUDA available
    Init->>DeepGEMM: set_pdl(get_env_enable_pdl())
    Init->>Init: set _deep_gemm_pdl_initialized = True
  else already initialized
    Init-->>Caller: no-op
  end
  Caller->>DeepGEMM: proceed with GEMM/quantize/metadata setup
Loading

Suggested reviewers: hlu1, tfogal, tomeras91, Superjomn, brb-nv, aswinvisva, greg-kwasniewski1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title matches the main change and follows the repo's ticket-plus-type pattern.
Description check ✅ Passed The description includes the required summary, Description, Test Coverage, and PR Checklist sections with relevant details.
✨ 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: 2

🧹 Nitpick comments (1)
tests/unittest/others/test_import_side_effects.py (1)

78-93: 🎯 Functional Correctness | 🔵 Trivial

Coverage gap: no positive test that PDL init actually fires on first DeepGEMM use.

The two tests here only verify the flag stays False at import time; there's no test exercising one of the three call sites (_fp8_quantize_1x128_ue8m0, deepgemm_fp8_group_blockwise_gemm, DSAtrtllmAttentionMetadata.__init__) and asserting _deep_gemm_pdl_initialized becomes True afterward. As per path instructions, coverage here is currently insufficient for verifying the "deferred, not removed" behavior.

Do you want me to draft a CUDA-gated test that calls _init_deep_gemm_pdl() (or one of the entry points) directly and asserts the flag flips to True?

🤖 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/others/test_import_side_effects.py` around lines 78 - 93, Add
a positive CUDA-gated test that exercises one of the real DeepGEMM PDL entry
points and verifies deferred initialization actually happens on first use. In
tests/unittest/others/test_import_side_effects.py, extend the existing
import-side-effect coverage by invoking a call site such as
_fp8_quantize_1x128_ue8m0, deepgemm_fp8_group_blockwise_gemm, or
DSAtrtllmAttentionMetadata.__init__ after import, then assert
_deep_gemm_pdl_initialized flips from False to True. Keep the current
import-time tests, but add this runtime assertion so the behavior is verified as
“lazy initialization,” not just “never initializes.”

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 `@tests/unittest/others/test_import_side_effects.py`:
- Around line 78-79: The test functions are missing explicit return type
annotations, so update the affected test definitions to include a None return
type. Apply this to the named test helper(s) in test_import_side_effects.py,
including test_deep_gemm_pdl_init_is_lazy and the other test referenced in the
review, keeping the function behavior unchanged while conforming to the function
annotation guideline.
- Around line 43-62: The import-side-effect test script assumes
CUDA_VISIBLE_DEVICES is always a numeric index, but it may be a UUID-style
selector and fail before the NVML check runs. Update _NO_CONTEXT_SCRIPT to
resolve the NVML device handle from the CUDA_VISIBLE_DEVICES token in a way that
works for both numeric indices and non-numeric identifiers, so the subprocess
can still reach the tensorrt_llm import assertion.

---

Nitpick comments:
In `@tests/unittest/others/test_import_side_effects.py`:
- Around line 78-93: Add a positive CUDA-gated test that exercises one of the
real DeepGEMM PDL entry points and verifies deferred initialization actually
happens on first use. In tests/unittest/others/test_import_side_effects.py,
extend the existing import-side-effect coverage by invoking a call site such as
_fp8_quantize_1x128_ue8m0, deepgemm_fp8_group_blockwise_gemm, or
DSAtrtllmAttentionMetadata.__init__ after import, then assert
_deep_gemm_pdl_initialized flips from False to True. Keep the current
import-time tests, but add this runtime assertion so the behavior is verified as
“lazy initialization,” not just “never initializes.”
🪄 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: bc7dbc13-7210-4c89-bd92-c16b925648e2

📥 Commits

Reviewing files that changed from the base of the PR and between c5bdcaa and deac457.

📒 Files selected for processing (4)
  • tensorrt_llm/_torch/attention_backend/sparse/dsa.py
  • tensorrt_llm/_torch/custom_ops/torch_custom_ops.py
  • tensorrt_llm/_torch/modules/fused_moe/fused_moe_deepgemm.py
  • tests/unittest/others/test_import_side_effects.py

Comment on lines +43 to +62
_NO_CONTEXT_SCRIPT = r"""
import os
import sys

import pynvml

pynvml.nvmlInit()
# NVML indices are physical and ignore CUDA_VISIBLE_DEVICES; the test runner
# pins CUDA_VISIBLE_DEVICES to a single physical index, so use that one.
physical = int(os.environ["CUDA_VISIBLE_DEVICES"])
handle = pynvml.nvmlDeviceGetHandleByIndex(physical)

import tensorrt_llm # noqa: F401 (the import under test)

procs = pynvml.nvmlDeviceGetComputeRunningProcesses(handle)
mine = [p for p in procs if p.pid == os.getpid()]
if mine:
used = (mine[0].usedGpuMemory or 0) >> 20
sys.exit(f"import tensorrt_llm created a CUDA context using {used} MiB")
"""

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how CI configures CUDA_VISIBLE_DEVICES for GPU test jobs.
rg -n "CUDA_VISIBLE_DEVICES" --type=yaml
rg -n "CUDA_VISIBLE_DEVICES" -g '*.groovy' -g '*.sh'

Repository: NVIDIA/TensorRT-LLM

Length of output: 212


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- test_import_side_effects.py (outline) ---'
ast-grep outline tests/unittest/others/test_import_side_effects.py --view expanded || true

echo
echo '--- test_import_side_effects.py (lines 1-220) ---'
sed -n '1,220p' tests/unittest/others/test_import_side_effects.py

echo
echo '--- search for CUDA_VISIBLE_DEVICES in repo ---'
rg -n "CUDA_VISIBLE_DEVICES" .

echo
echo '--- likely CI / workflow files mentioning GPU or nvidia ---'
find . -path '*/.github/workflows/*' -o -path '*/.gitlab-ci*' -o -path '*/Jenkinsfile*' -o -path '*/.azure/*' -o -path '*/.circleci/*' | sed 's#^\./##' | sort

echo
echo '--- search workflows for GPU env setup ---'
rg -n "CUDA_VISIBLE_DEVICES|pynvml|nvml|gpu" .github/workflows .gitlab-ci* Jenkinsfile* .azure .circleci 2>/dev/null || true

Repository: NVIDIA/TensorRT-LLM

Length of output: 19103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- target file with line numbers ---'
cat -n tests/unittest/others/test_import_side_effects.py | sed -n '1,220p'

echo
echo '--- repo files under .github/workflows ---'
if [ -d .github/workflows ]; then
  find .github/workflows -maxdepth 1 -type f | sed 's#^\./##' | sort
else
  echo '(none)'
fi

echo
echo '--- focused search in workflows / ci-like files ---'
rg -n "CUDA_VISIBLE_DEVICES|pynvml|nvml|nvidia-smi|GPU" .github/workflows .gitlab-ci* .azure .circleci scripts tests 2>/dev/null || true

echo
echo '--- test_import_side_effects.py references ---'
rg -n "test_import_side_effects|NO_CONTEXT_SCRIPT|_run_in_subprocess|pynvml" tests -g '*.py'

Repository: NVIDIA/TensorRT-LLM

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- usages of pynvml ---'
rg -n "\bpynvml\b|import pynvml" . --glob '!**/.git/**'

echo
echo '--- dependency manifests mentioning pynvml/nvml ---'
rg -n "pynvml|nvidia-ml-py|nvml" pyproject.toml requirements*.txt setup.py setup.cfg tox.ini .github/workflows tests scripts 2>/dev/null || true

echo
echo '--- files that look like test dependency docs ---'
find . -maxdepth 2 \( -name 'requirements*.txt' -o -name 'pyproject.toml' -o -name 'setup.py' -o -name 'environment*.yml' -o -name 'dev*.txt' \) | sed 's#^\./##' | sort

Repository: NVIDIA/TensorRT-LLM

Length of output: 32601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- jenkins/L0_Test.groovy around CUDA_VISIBLE_DEVICES handling ---'
sed -n '820,855p' jenkins/L0_Test.groovy

echo
echo '--- l0-test workflow references ---'
rg -n "L0_Test|CUDA_VISIBLE_DEVICES|gpu-type|gpu" .github/workflows/l0-test.yml jenkins/L0_Test.groovy 2>/dev/null || true

echo
echo '--- any docs that mention UUID-style CUDA_VISIBLE_DEVICES ---'
rg -n "CUDA_VISIBLE_DEVICES.*UUID|UUID.*CUDA_VISIBLE_DEVICES|GPU-" tests docs examples jenkins .github 2>/dev/null || true

Repository: NVIDIA/TensorRT-LLM

Length of output: 19683


CUDA_VISIBLE_DEVICES needs a non-numeric fallback. int(os.environ["CUDA_VISIBLE_DEVICES"]) will fail for UUID-style device selectors, so this subprocess can abort before it checks the import side effect. Resolve the NVML handle from the token instead of assuming a bare index.

🤖 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/others/test_import_side_effects.py` around lines 43 - 62, The
import-side-effect test script assumes CUDA_VISIBLE_DEVICES is always a numeric
index, but it may be a UUID-style selector and fail before the NVML check runs.
Update _NO_CONTEXT_SCRIPT to resolve the NVML device handle from the
CUDA_VISIBLE_DEVICES token in a way that works for both numeric indices and
non-numeric identifiers, so the subprocess can still reach the tensorrt_llm
import assertion.

Comment on lines +78 to +79
def test_deep_gemm_pdl_init_is_lazy():
"""DeepGEMM PDL setup must not run at import (nvbug 6419139).

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing return type annotations on test functions.

As per coding guidelines, "Always annotate functions with return types (use None if no return)."

🧹 Proposed fix
-def test_deep_gemm_pdl_init_is_lazy():
+def test_deep_gemm_pdl_init_is_lazy() -> None:
-def test_import_creates_no_cuda_context():
+def test_import_creates_no_cuda_context() -> None:

Also applies to: 91-91

🤖 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/others/test_import_side_effects.py` around lines 78 - 79, The
test functions are missing explicit return type annotations, so update the
affected test definitions to include a None return type. Apply this to the named
test helper(s) in test_import_side_effects.py, including
test_deep_gemm_pdl_init_is_lazy and the other test referenced in the review,
keeping the function behavior unchanged while conforming to the function
annotation guideline.

Source: Coding guidelines

… import

Between 1.3.0rc19 and rc20, an import-time deep_gemm.set_pdl() call
(added in NVIDIA#15402) instantiated DeepGEMM's DeviceRuntime (cuBLASLt handle
+ 32 MiB workspace tensor), creating a CUDA context (~0.5-1.2 GiB with
loaded modules, arch-dependent) in every process importing tensorrt_llm:
the trtllm-bench parent (which never launches a kernel) and every MPI
worker on its default device before torch.cuda.set_device. Those
contexts are resident when the KV cache pool is sized from free GPU
memory, shrinking the pool (RTX 6000D: 48.33 -> 47.39 GiB, max
concurrent requests 513 -> 503) and causing ~10% throughput regressions
on memory-tight GPUs (this bug and nvbugs 6405760, 6418453, 6419078)
via a long low-batch scheduling tail. The root cause was fixed on main
by NVIDIA#15632, which defers PDL configuration to PyTorchModelEngine init.

Add a regression test asserting that importing tensorrt_llm neither
configures DeepGEMM PDL nor creates a CUDA context, so the import path
stays free of device side effects.

Signed-off-by: Thor Johnsen <41591019+thorjohnsen@users.noreply.github.com>
@thorjohnsen
thorjohnsen force-pushed the fix/nvbug-6419139-deep-gemm-pdl-import-context branch from deac457 to a9b53e6 Compare July 6, 2026 17:28
@thorjohnsen thorjohnsen changed the title [https://nvbugs/6419139][fix] Defer DeepGEMM PDL init to first use [https://nvbugs/6419139][test] Guard against CUDA context creation at import Jul 6, 2026
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.

2 participants