Skip to content

[None][perf] enable NCCL user-buffer registration for VisualGen collectives - #17006

Open
daichu-nv wants to merge 1 commit into
NVIDIA:mainfrom
daichu-nv:daichu/copy-pr-15013-nccl-buffer-reg
Open

[None][perf] enable NCCL user-buffer registration for VisualGen collectives#17006
daichu-nv wants to merge 1 commit into
NVIDIA:mainfrom
daichu-nv:daichu/copy-pr-15013-nccl-buffer-reg

Conversation

@daichu-nv

@daichu-nv daichu-nv commented Jul 29, 2026

Copy link
Copy Markdown

This PR copies #15013 because I do not have write permission to the original PR branch.

What this changes

Adds opt-in NCCL user-buffer registration to the VisualGen parallel inference path via ParallelConfig.nccl_buffer_reg. Pure TRT-LLM change — activates a feature NCCL already exposes.

parallel_config:
  ulysses_size: 4
  nccl_buffer_reg: true   # sets NCCL_CUMEM_ENABLE=1 before init_process_group

Files changed

tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py          (new) — registration utilities
tensorrt_llm/_torch/visual_gen/executor.py             (modified) — set CUMEM before init_process_group
tensorrt_llm/visual_gen/args.py                        (modified) — nccl_buffer_reg on ParallelConfig
examples/visual_gen/bench_nccl_ub.py                   (new) — micro + E2E benchmark script
examples/visual_gen/bench_cold_ring.py                 (new) — cold-start characterization script

Benchmark Results

System: 8× NVIDIA B200 SXM 183 GB · NCCL 2.29.2 · TRT-LLM 1.3.0rc17
Models: FLUX.1-dev (1024×1024, 20 steps) and Cosmos3-Nano (720×1280, 57 frames, 20 steps)

Micro-benchmark (collective latency, 4-GPU, 50 iters)

CUMEM improves individual collective latency at 4 GPU:

Collective Baseline CUMEM Speedup
FLUX all-to-all 0.069 ms 0.061 ms 1.13×
Wan all-to-all 0.073 ms 0.066 ms 1.11×
VAE all-reduce 0.057 ms 0.048 ms 1.19×

E2E Ulysses parallelism — CUMEM effect

CUMEM has negligible E2E impact (±2%, within noise). Communication is not the bottleneck on B200 — the collective-level improvements are real but diluted by compute time.

Model Config Latency Speedup vs 1 GPU
FLUX.1-dev 1 GPU 1.81 s 1.00×
FLUX.1-dev Ulysses 4-GPU 0.93 s 1.95×
Cosmos3-Nano 1 GPU 13.95 s 1.00×
Cosmos3-Nano Ulysses 4-GPU 6.77 s 2.06×

Ring attention cold-start (each config run as fresh isolated process)

Both CUMEM modes show the same cold-start profile and identical steady-state:

Iter No CUMEM CUMEM
1 34.0 s 29.5 s
2 14.6 s 15.9 s
3–7 ~14.15 s ~14.15 s

The cold spike is CUDA kernel JIT compilation (CUTEDSL compiles ring attention kernels on first use for each shape config), not an NCCL artifact. CUMEM does not eliminate cold-start.

Ring attention is not competitive with Ulysses for either model: ring-4 Cosmos steady-state is ~14.15 s vs Ulysses 4-GPU 6.77 s.

Conclusion

Scenario CUMEM effect
Ulysses E2E Negligible (±2%)
Ring attention steady-state None
Ring attention first call Marginal (29.5 s vs 34 s)
4-GPU collective micro-benchmark 10–19% on individual collectives

nccl_buffer_reg: true is safe to enable as a default. It may benefit configurations with higher communication-to-compute ratios (longer sequences, more layers, lower-end hardware) not tested here.


Test plan

  • Single-GPU smoke: python -m tensorrt_llm.visual_gen ... (default) — no regression
  • Multi-GPU Ulysses with nccl_buffer_reg: true — CUMEM flag visible in NCCL logs
  • nccl_buffer_reg: false (default) path unchanged — no CUMEM env var set
  • Micro-benchmark tier runs without model weights on any multi-GPU node

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added optional NCCL user-buffer registration for more efficient VisualGen multi-GPU communication.
    • Added configuration options and ready-to-use Flux and Wan serving configurations.
    • Added benchmarks for NCCL collectives and cold-start generation performance.
  • Documentation
    • Documented requirements, configuration, expected performance benefits, and benchmark commands for NCCL user-buffer registration.

@daichu-nv

daichu-nv commented Jul 29, 2026

Copy link
Copy Markdown
Author

@luyiyun1021 let me also copy your comments from the original PR to this PR as I'll be addressing your comments here later.

Hi, I am interested in this feature. I tested locally with NCCL_CUMEM_ENABLE enabled but showed no perf gain on the bench test you provided. Seems that NCCL_CUMEM_ENABLE only enable the vmm capability but not do registration for user buffer? I've tested the buffer registration manually with modification(adding PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True, _get_comm should be _comm_ptr()) that can do achieve similar bench result compared to yours. Have you tested the manual registration method? Thanks!

Also I‘ve tested on all-gather and it shows no perf gain on it. Do you know why would it not work on all-gather?

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

VisualGen adds an nccl_buffer_reg configuration, NCCL CUMEM setup, explicit buffer registration support, serving configurations, benchmark scripts, and documentation covering requirements and usage.

Changes

VisualGen NCCL User-Buffer Registration

Layer / File(s) Summary
Registration contract and NCCL bindings
tensorrt_llm/visual_gen/args.py, tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py
Adds the nccl_buffer_reg configuration and version-gated NCCL CUMEM, communicator discovery, tensor registration, deregistration, and cleanup logic.
Runtime initialization and serving configurations
tensorrt_llm/_torch/visual_gen/executor.py, examples/visual_gen/serve/configs/*_nccl_ub.yml
Enables NCCL CUMEM before distributed initialization and adds Flux1 and Wan configurations using NCCL buffer registration with Ulysses parallelism.
Benchmark scripts and result reporting
examples/visual_gen/bench_cold_ring.py, examples/visual_gen/bench_nccl_ub.py
Adds cold-start ring and NCCL collective/pipeline benchmarks with timing statistics, comparisons, and JSON output.
Feature documentation
docs/source/models/visual-generation.md
Documents NCCL user-buffer registration, requirements, configuration, effects, and benchmarks, and updates the Qwen-Image footnote.

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

Sequence Diagram(s)

sequenceDiagram
  participant Configuration
  participant DiffusionWorker
  participant NCCL
  participant VisualGen
  Configuration->>DiffusionWorker: set nccl_buffer_reg
  DiffusionWorker->>NCCL: enable CUMEM before init_process_group
  DiffusionWorker->>NCCL: initialize distributed communication
  VisualGen->>NCCL: execute configured collectives
Loading

Suggested labels: api-compatible

Suggested reviewers: bowenfu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% 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 matches the PR’s main change and uses the required bracketed ticket/type format.
Description check ✅ Passed The description covers what changed, files, benchmarks, and test plan, though the template checklist is not fully filled.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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: 5

🧹 Nitpick comments (7)
tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py (4)

129-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant import ctypes shadows the module-level import.

ctypes is already imported at Line 26; the local import ctypes here is unnecessary and shadows the outer name.

🧹 Proposed fix
         if hasattr(backend, "_comm"):
-            import ctypes
             cap = backend._comm
As per coding guidelines, "Avoid shadowing outer variables."
🤖 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/visual_gen/nccl_ub_reg.py` around lines 129 - 135, Remove
the redundant local import ctypes from the pointer retrieval block, and reuse
the existing module-level ctypes import when configuring PyCapsule_GetPointer in
the surrounding function.

Source: Coding guidelines


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

Missing return/parameter annotations on several methods.

register_all's tensors parameter (Line 209) is unannotated, and deregister (Line 213), deregister_all (Line 222), and __del__ (Line 232) lack -> None return annotations.

🧹 Proposed fix
-    def register_all(self, tensors) -> int:
+    def register_all(self, tensors: list[torch.Tensor]) -> int:
         """Register a list of tensors.  Returns the number successfully registered."""
         return sum(self.register(t) for t in tensors)

-    def deregister(self, tensor: torch.Tensor):
+    def deregister(self, tensor: torch.Tensor) -> None:
         key = tensor.data_ptr()
         ...

-    def deregister_all(self):
+    def deregister_all(self) -> None:
         if not self.available or self._lib is None:
             return
         ...

-    def __del__(self):
+    def __del__(self) -> None:
         try:
             self.deregister_all()
         except Exception:
             pass
As per coding guidelines, "Annotate every function, use `None` for non-returning functions."
🤖 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/visual_gen/nccl_ub_reg.py` around lines 209 - 236, Add
the missing type annotations in the registration class: annotate register_all’s
tensors parameter with its appropriate tensor collection type, and add -> None
to deregister, deregister_all, and __del__. Preserve the existing behavior and
register_all’s integer return annotation.

Source: Coding guidelines


29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer X | None over typing.Optional[X].

Optional[...] is used throughout (e.g., Lines 45, 110, 164-166) instead of the built-in X | None union syntax.

As per coding guidelines, "prefer built-in generic types and |."

Also applies to: 45-45, 110-110, 164-166

🤖 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/visual_gen/nccl_ub_reg.py` at line 29, Replace the
typing.Optional import and all Optional[...] annotations in nccl_ub_reg.py with
the equivalent X | None syntax, including the annotations near the referenced
lines. Preserve the existing types and annotation behavior while removing the
now-unused import.

Source: Coding guidelines


137-137: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Broad except Exception violates the narrow-exception guideline.

Both _extract_comm_ptr (Line 137) and __del__ (Line 235) catch bare Exception. Given the documented "best-effort, multiple fragile introspection paths" nature of _extract_comm_ptr, and that __del__ swallowing errors during interpreter teardown is a common defensive pattern, this is understandable, but it still departs from the coding guideline to catch the narrowest exceptions possible.

As per coding guidelines, "Catch the narrowest possible exceptions instead of using broad or bare exception handling."

Also applies to: 232-236

🤖 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/visual_gen/nccl_ub_reg.py` at line 137, Replace broad
Exception handlers in _extract_comm_ptr and __del__ with the narrowest exception
types raised by their introspection and cleanup operations. Preserve
_extract_comm_ptr’s best-effort fallback behavior and __del__’s teardown-safe
swallowing of expected cleanup failures, while allowing unexpected exceptions to
propagate.

Source: Coding guidelines

examples/visual_gen/bench_cold_ring.py (1)

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

Hardcoded absolute paths reduce portability.

/workspace/models/Cosmos3-Nano and /workspace/results are dev-machine-specific paths with no CLI override, unlike --tag, --warmup, --iters. Anyone else running this script must edit source to point at their own paths.

♻️ Proposed fix
     ap.add_argument("--tag", default="")
+    ap.add_argument("--model-path", default="/workspace/models/Cosmos3-Nano")
+    ap.add_argument("--out-dir", default="/workspace/results")
     a = ap.parse_args()
...
-    vg = VisualGen("/workspace/models/Cosmos3-Nano", args=args)
+    vg = VisualGen(a.model_path, args=args)
...
-    out = f"/workspace/results/cold_ring4_{tag}.json"
-    os.makedirs("/workspace/results", exist_ok=True)
+    out = f"{a.out_dir}/cold_ring4_{tag}.json"
+    os.makedirs(a.out_dir, exist_ok=True)

Also applies to: 72-73

🤖 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 `@examples/visual_gen/bench_cold_ring.py` at line 42, Update the VisualGen
setup and result-output handling in the benchmark entrypoint to replace the
hardcoded model and results paths with configurable CLI arguments, following the
existing args pattern used by --tag, --warmup, and --iters. Provide sensible
defaults while allowing callers to override both paths without editing source.
examples/visual_gen/bench_nccl_ub.py (2)

61-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move import statistics to module scope.

Importing statistics inside _stats on every call is unconventional; hoist it to the top-level imports alongside json/os/time.

🤖 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 `@examples/visual_gen/bench_nccl_ub.py` around lines 61 - 71, Move the
statistics import from inside _stats to the module-level import section
alongside json, os, and time, while leaving the function’s statistical
calculations unchanged.

30-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer built-in generics (dict, list) over typing.Dict/List.

from typing import Dict, List (line 34) and the resulting Dict/List[float] annotations throughout (_timed_iters, _stats, _bench_collective_one, etc.) could use PEP 585 built-in generics, consistent with the repo's Python 3.10+ target.

As per coding guidelines, "prefer built-in generic types and |."

🤖 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 `@examples/visual_gen/bench_nccl_ub.py` around lines 30 - 71, Replace the
typing.Dict and typing.List imports and all corresponding annotations, including
_timed_iters, _stats, and _bench_collective_one, with Python 3.10 built-in dict
and list generics while preserving the existing type parameters.

Source: Coding guidelines

🤖 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 `@examples/visual_gen/bench_cold_ring.py`:
- Around line 47-60: Ensure benchmark cleanup runs when generation fails by
placing the warmup and iteration loops in bench_cold_ring.py within a
try/finally and keeping vg.shutdown() in the finally block. Apply the same
structure in _bench_pipeline_one in examples/visual_gen/bench_nccl_ub.py, with
vg.shutdown() guaranteed in finally; preserve the existing benchmark behavior
otherwise.
- Around line 1-3: Add the standard NVIDIA SPDX copyright header at the
beginning of the new file, matching the header format and current copyright year
used by the sibling bench_nccl_ub.py file, while preserving the existing shebang
and module docstring.
- Line 3: Update the imports at the top of the module to use separate import
statements for os, statistics, time, json, and argparse, ordered according to
the repository’s configured linter conventions.

In `@tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py`:
- Around line 188-231: Update the registrar’s self._handles entries used by
register() and deregister() to retain a strong reference to each registered
tensor alongside its NCCL handle, preventing the address from being reused while
registration remains active. Adjust existing-handle checks, NCCL deregistration,
and deregister_all() to unpack and use the stored handle while preserving
successful registration counting and cleanup behavior.
- Line 1: Update the CUMEM NCCL version requirement consistently in
nccl_ub_reg.py and the related configuration description in args.py: lower the
documented and enforced minimum to NCCL 2.20 to match the required deadlock fix,
or explicitly document 2.21 as an intentional safety margin if retaining the
current gate.

---

Nitpick comments:
In `@examples/visual_gen/bench_cold_ring.py`:
- Line 42: Update the VisualGen setup and result-output handling in the
benchmark entrypoint to replace the hardcoded model and results paths with
configurable CLI arguments, following the existing args pattern used by --tag,
--warmup, and --iters. Provide sensible defaults while allowing callers to
override both paths without editing source.

In `@examples/visual_gen/bench_nccl_ub.py`:
- Around line 61-71: Move the statistics import from inside _stats to the
module-level import section alongside json, os, and time, while leaving the
function’s statistical calculations unchanged.
- Around line 30-71: Replace the typing.Dict and typing.List imports and all
corresponding annotations, including _timed_iters, _stats, and
_bench_collective_one, with Python 3.10 built-in dict and list generics while
preserving the existing type parameters.

In `@tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py`:
- Around line 129-135: Remove the redundant local import ctypes from the pointer
retrieval block, and reuse the existing module-level ctypes import when
configuring PyCapsule_GetPointer in the surrounding function.
- Around line 209-236: Add the missing type annotations in the registration
class: annotate register_all’s tensors parameter with its appropriate tensor
collection type, and add -> None to deregister, deregister_all, and __del__.
Preserve the existing behavior and register_all’s integer return annotation.
- Line 29: Replace the typing.Optional import and all Optional[...] annotations
in nccl_ub_reg.py with the equivalent X | None syntax, including the annotations
near the referenced lines. Preserve the existing types and annotation behavior
while removing the now-unused import.
- Line 137: Replace broad Exception handlers in _extract_comm_ptr and __del__
with the narrowest exception types raised by their introspection and cleanup
operations. Preserve _extract_comm_ptr’s best-effort fallback behavior and
__del__’s teardown-safe swallowing of expected cleanup failures, while allowing
unexpected exceptions to propagate.
🪄 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: bee095fd-a4a3-4873-a53e-d51fb6c801a6

📥 Commits

Reviewing files that changed from the base of the PR and between 2341c70 and d2f6cdd.

📒 Files selected for processing (8)
  • docs/source/models/visual-generation.md
  • examples/visual_gen/bench_cold_ring.py
  • examples/visual_gen/bench_nccl_ub.py
  • examples/visual_gen/serve/configs/flux1_nccl_ub.yml
  • examples/visual_gen/serve/configs/wan21_nccl_ub.yml
  • tensorrt_llm/_torch/visual_gen/executor.py
  • tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py
  • tensorrt_llm/visual_gen/args.py

Comment thread examples/visual_gen/bench_cold_ring.py Outdated
Comment on lines +1 to +3
#!/usr/bin/env python3
"""Cold-start ring-4 test — each run is a fresh process with no prior ring-4 calls."""
import os, statistics, time, json, argparse

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 NVIDIA copyright header.

This new file has no SPDX/copyright header, unlike its sibling bench_nccl_ub.py (lines 2-3), which includes SPDX-FileCopyrightText and SPDX-License-Identifier.

As per coding guidelines, "New files must include the NVIDIA copyright header; modified files must have the copyright year updated."

📄 Proposed fix
 #!/usr/bin/env python3
+# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
 """Cold-start ring-4 test — each run is a fresh process with no prior ring-4 calls."""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/usr/bin/env python3
"""Cold-start ring-4 test — each run is a fresh process with no prior ring-4 calls."""
import os, statistics, time, json, argparse
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Cold-start ring-4 test — each run is a fresh process with no prior ring-4 calls."""
import os, statistics, time, json, argparse
🤖 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 `@examples/visual_gen/bench_cold_ring.py` around lines 1 - 3, Add the standard
NVIDIA SPDX copyright header at the beginning of the new file, matching the
header format and current copyright year used by the sibling bench_nccl_ub.py
file, while preserving the existing shebang and module docstring.

Source: Coding guidelines

Comment thread examples/visual_gen/bench_cold_ring.py Outdated
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Cold-start ring-4 test — each run is a fresh process with no prior ring-4 calls."""
import os, statistics, time, json, argparse

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

Combine imports onto one line — flagged by pycodestyle E401.

import os, statistics, time, json, argparse places multiple modules on a single import statement, which pycodestyle's E401 rule flags. Ruff in this repo enables the E rule set.

As per coding guidelines, "Order imports according to the configured linter."

🔧 Proposed fix
-import os, statistics, time, json, argparse
+import argparse
+import json
+import os
+import statistics
+import time
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import os, statistics, time, json, argparse
import argparse
import json
import os
import statistics
import time
🤖 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 `@examples/visual_gen/bench_cold_ring.py` at line 3, Update the imports at the
top of the module to use separate import statements for os, statistics, time,
json, and argparse, ordered according to the repository’s configured linter
conventions.

Source: Coding guidelines

Comment thread examples/visual_gen/bench_cold_ring.py Outdated
Comment on lines +47 to +60
for i in range(a.warmup):
print(f" warmup {i+1}/{a.warmup}...")
vg.generate(inputs=PROMPTS[i % len(PROMPTS)], params=VisualGenParams(**gen_kwargs))
print(f" warmup {i+1} done")

times = []
for i in range(a.iters):
t0 = time.perf_counter()
vg.generate(inputs=PROMPTS[i % len(PROMPTS)], params=VisualGenParams(**gen_kwargs))
elapsed = time.perf_counter() - t0
times.append(elapsed)
print(f" iter {i+1}/{a.iters}: {elapsed:.2f}s")

vg.shutdown()

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Worker process/GPU memory can leak if generate() raises before shutdown(). Both benchmark scripts call vg.generate() in a plain loop and only reach vg.shutdown() afterward; if any iteration raises, the executor's worker process(es) and GPU memory are never released, requiring manual cleanup before the next run on a shared node.

  • examples/visual_gen/bench_cold_ring.py#L47-L60: wrap the warmup/iteration loops and vg.shutdown() (line 60) in a try/finally so shutdown() always runs.
  • examples/visual_gen/bench_nccl_ub.py#L172-L182: wrap the warmup/iteration loops in _bench_pipeline_one and vg.shutdown() (line 181) in a try/finally so shutdown() always runs.
📍 Affects 2 files
  • examples/visual_gen/bench_cold_ring.py#L47-L60 (this comment)
  • examples/visual_gen/bench_nccl_ub.py#L172-L182
🤖 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 `@examples/visual_gen/bench_cold_ring.py` around lines 47 - 60, Ensure
benchmark cleanup runs when generation fails by placing the warmup and iteration
loops in bench_cold_ring.py within a try/finally and keeping vg.shutdown() in
the finally block. Apply the same structure in _bench_pipeline_one in
examples/visual_gen/bench_nccl_ub.py, with vg.shutdown() guaranteed in finally;
preserve the existing benchmark behavior otherwise.

@@ -0,0 +1,236 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

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

🧩 Analysis chain

🌐 Web query:

When was NCCL_CUMEM_ENABLE introduced in NCCL, and which NCCL version fixed CUMEM-related hangs?

💡 Result:

The environment variable NCCL_CUMEM_ENABLE was introduced in NCCL 2.18 [1]. While it was disabled by default in that version, it became auto-enabled by default starting in NCCL 2.19 if the system supports it [1]. Regarding CUMEM-related hangs, users encountered deadlock scenarios during communicator connection establishment (specifically reported in Alltoall operations) when NCCL_CUMEM_ENABLE was enabled (the default) in NCCL 2.19 [2][3]. Setting NCCL_CUMEM_ENABLE=0 was the recommended workaround for this issue [2][3]. The fix for these deadlocks was included in NCCL 2.20 [3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n-- nccl_ub_reg.py --\n'
sed -n '1,140p' tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py

printf '\n-- args.py excerpt --\n'
sed -n '220,260p' tensorrt_llm/visual_gen/args.py

Repository: NVIDIA/TensorRT-LLM

Length of output: 1924


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [
    Path("tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py"),
    Path("tensorrt_llm/visual_gen/args.py"),
]:
    print(f"\n== {path} ==")
    for i, line in enumerate(path.read_text().splitlines(), start=1):
        if path.name == "nccl_ub_reg.py" and i <= 130:
            print(f"{i:4d}: {line}")
        elif path.name == "args.py" and 225 <= i <= 255:
            print(f"{i:4d}: {line}")
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 1924


Align the CUMEM floor with the actual NCCL support level
tensorrt_llm/_torch/visual_gen/nccl_ub_reg.py and tensorrt_llm/visual_gen/args.py both say CUMEM needs NCCL ≥ 2.21, but NCCL_CUMEM_ENABLE exists since 2.18, is auto-enabled from 2.19, and the deadlock fix landed in 2.20. If 2.21 is just a safety margin, say so explicitly; otherwise lower the gate/description to 2.20.

🤖 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/visual_gen/nccl_ub_reg.py` at line 1, Update the CUMEM
NCCL version requirement consistently in nccl_ub_reg.py and the related
configuration description in args.py: lower the documented and enforced minimum
to NCCL 2.20 to match the required deadlock fix, or explicitly document 2.21 as
an intentional safety margin if retaining the current gate.

Comment on lines +188 to +231
def register(self, tensor: torch.Tensor) -> bool:
"""Register a single tensor. Returns True on success."""
if not self.available:
return False
key = tensor.data_ptr()
if key in self._handles:
return True # already registered
handle = ctypes.c_void_p(None)
ret = self._lib.ncclCommRegister(
ctypes.c_void_p(self._comm_ptr),
ctypes.c_void_p(key),
ctypes.c_size_t(tensor.nbytes),
ctypes.byref(handle),
)
if ret == 0: # ncclSuccess
self._handles[key] = handle
logger.debug(f"Registered tensor @{key:#x} ({tensor.nbytes} B)")
return True
logger.warning(f"ncclCommRegister returned error {ret} for tensor @{key:#x}")
return False

def register_all(self, tensors) -> int:
"""Register a list of tensors. Returns the number successfully registered."""
return sum(self.register(t) for t in tensors)

def deregister(self, tensor: torch.Tensor):
key = tensor.data_ptr()
if key in self._handles:
self._lib.ncclCommDeregister(
ctypes.c_void_p(self._comm_ptr),
self._handles.pop(key),
)
logger.debug(f"Deregistered tensor @{key:#x}")

def deregister_all(self):
if not self.available or self._lib is None:
return
for key, handle in list(self._handles.items()):
self._lib.ncclCommDeregister(
ctypes.c_void_p(self._comm_ptr),
handle,
)
self._handles.clear()

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Registrar dict keyed by raw data_ptr() can go stale if a tensor is freed and its address reused.

register()/deregister() key self._handles purely by tensor.data_ptr(). If a registered tensor is freed (without deregister() being called) and PyTorch's allocator later reuses the same address for a different, differently-sized tensor, register() on the new tensor will see key in self._handles and silently skip re-registration — leaving NCCL's registration for that address bound to the old size/tensor. The registrar holds no reference to the tensor to keep it alive, and there's no size check to catch a mismatch. The documented intended usage (persistent latent buffer reused every step) largely avoids this, but nothing enforces it.

🛡️ Proposed mitigation
-        key = tensor.data_ptr()
-        if key in self._handles:
-            return True  # already registered
+        key = tensor.data_ptr()
+        if key in self._handles:
+            cached_tensor, _ = self._handles[key]
+            if cached_tensor.nbytes != tensor.nbytes:
+                logger.warning(f"Address {key:`#x`} reused with a different size; re-registering")
+            else:
+                return True  # already registered

Also consider storing (handle, tensor) (keeping a strong reference) in self._handles to prevent premature reuse of the address while registered.

🤖 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/visual_gen/nccl_ub_reg.py` around lines 188 - 231, Update
the registrar’s self._handles entries used by register() and deregister() to
retain a strong reference to each registered tensor alongside its NCCL handle,
preventing the address from being reused while registration remains active.
Adjust existing-handle checks, NCCL deregistration, and deregister_all() to
unpack and use the stored handle while preserving successful registration
counting and cleanup behavior.

@daichu-nv
daichu-nv force-pushed the daichu/copy-pr-15013-nccl-buffer-reg branch from d2f6cdd to 3e85c19 Compare July 29, 2026 10:32

Copy link
Copy Markdown
Author

Pushed a rebase/conflict-resolution update on top of current main: 3e85c1963724cf620a288a28b3ffd714b2d63577.

Follow-up on the copied CUMEM vs. explicit-registration comments: this PR currently wires nccl_buffer_reg to enable NCCL_CUMEM_ENABLE=1 before init_process_group; it does not automatically register VisualGen pipeline tensors with ncclCommRegister. I clarified that in the docs and ParallelConfig field description. The internal NCCLBufferRegistrar helper now also checks _comm_ptr() before the older _get_comm(...) path, so manual explicit-registration experiments can use the communicator pointer path noted in the original discussion.

@daichu-nv
daichu-nv force-pushed the daichu/copy-pr-15013-nccl-buffer-reg branch from 3e85c19 to f3147b7 Compare July 29, 2026 10:43

daichu-nv commented Jul 29, 2026

Copy link
Copy Markdown
Author

Update after addressing the reviewer comments and CI:

  • Clarified the scope of this PR: the runtime path sets NCCL_CUMEM_ENABLE=1 before dist.init_process_group(). That enables NCCL's VMM/CUMEM allocation path, but it does not explicitly register VisualGen tensors through ncclCommRegister. The explicit registrar remains an internal helper and is not wired into the pipeline path in this PR.
  • Kept the VisualGen CUMEM runtime gate at NCCL >= 2.21. NCCL_CUMEM_ENABLE exists in earlier NCCL releases, but keeping the original conservative floor avoids expanding the support scope during review. The docs now state that rationale.
  • Fixed the pre-commit failure by using a raw module docstring for the benchmark and applying the formatter changes.
  • Addressed the CodeRabbit comments: VisualGen.shutdown() now runs under try/finally, bench_cold_ring.py accepts model/output paths instead of hard-coded values, statistics moved to module scope, new annotations use built-in generics, the registrar keeps tensor references together with handles, exception handling is narrowed, and the duplicate local ctypes import was removed.

Local changed-file pre-commit passed for the PR files.

…ctives

Signed-off-by: Daisy Chu <daichu@nvidia.com>
@daichu-nv
daichu-nv force-pushed the daichu/copy-pr-15013-nccl-buffer-reg branch from f3147b7 to e642d69 Compare July 29, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants