Skip to content

[None][fix] Fall back to NCCL when CUDA IPC handles cannot be exchanged - #17034

Open
pjdurden wants to merge 3 commits into
NVIDIA:mainfrom
pjdurden:fix/16899
Open

[None][fix] Fall back to NCCL when CUDA IPC handles cannot be exchanged#17034
pjdurden wants to merge 3 commits into
NVIDIA:mainfrom
pjdurden:fix/16899

Conversation

@pjdurden

@pjdurden pjdurden commented Jul 30, 2026

Copy link
Copy Markdown

Fall back to NCCL when CUDA IPC handles cannot be exchanged

Fixes #16899

1. Root cause

The reporter's follow-up (v1.3.0rc22) pins the failure precisely:

AllReduce.__init__
  → get_allreduce_workspace
    → CustomAllReduceHelper.allocate_allreduce_fusion_workspace
      → IpcMemory.__init__
        → IpcMemory.open_ipc_memory
          → cudaIpcOpenMemHandle  ← cudaErrorInvalidDevice (101)
RuntimeError: CUDA Runtime API error: <cudaError_t.cudaErrorInvalidDevice: 101>

and notes that can_access_peer() returned True on the same machine.

can_access_peer() (tensorrt_llm/_ipc_utils.py:39) only calls
cudaDeviceCanAccessPeer. That answers "can GPU A address GPU B's memory", which
is necessary but not sufficient for CUDA IPC: exporting and importing an IPC
memory handle is a separate capability that also fails on GPUs without CUDA IPC
support and when the ranks do not share an IPC namespace. On this system
cudaDeviceCanAccessPeer succeeds while cudaIpcOpenMemHandle does not.

Because can_access_peer() said True, IpcMemory was constructed with
open_ipc=True, and open_ipc_memory() passed the raw CUDA error straight to
_raise_if_error(). That unhandled RuntimeError aborts engine initialization.

This is a hard crash for a situation the runtime already fully supports. The
non-IPC path exists and works: IpcMemory(open_ipc=False) leaves all pointers
null, and on the C++ side AllReduceOp::ifFallbackToNCCL
(cpp/tensorrt_llm/thop/allreduceOp.cpp:1436) already routes every all-reduce to
NCCL when P2P/NVLink is absent, which is exactly this hardware (RTX 6000D is a
PCIe part with no NVLink, so mIsNVLINKSupported is false and the custom kernels
would never have been selected anyway). Only the eager Python-side workspace
allocation stood in the way.

One secondary note that matches the report: TRTLLM_DISABLE_CUSTOM_ALLREDUCE=1
had no effect because no such env var exists anywhere in the tree (grep returns
nothing). The supported knob is allreduce_strategy: NCCL in the LLM args, which
skips the workspace allocation entirely. That is a valid workaround, but users
should not need it.

2. The fix

Degrade to the already-supported NCCL path instead of crashing.

tensorrt_llm/_ipc_utils.py. IpcMemory.open_ipc_memory no longer raises
when IPC handles cannot be exchanged. It returns None, and IpcMemory.__init__
reacts by setting open_ipc = False and ipc_failed = True, leaving the
pointers null. Three details matter:

  • Collective agreement. The decision is allgathered across the TP group, so
    either every rank gets IPC buffers or none does. Without this, a rank that
    succeeded would keep using buffers its peers never mapped, and the subsequent
    tp_allgather calls could desync.
  • No garbage handles. A rank that cannot export contributes None to the
    handle allgather rather than an uninitialized cudaIpcMemHandle_t, so no rank
    ever passes an uninitialized handle to cudaIpcOpenMemHandle. That one
    collective carries both the payload and the agreement on the export step, so
    the total is still two collectives, as before.
  • Cleanup. Handles opened before the failure are closed and the local buffer
    is freed, so the fallback does not leak device memory.

cudaMalloc/cudaMemset failures still raise. Those are genuine errors, not a
capability gap.

tensorrt_llm/_torch/distributed/allreduce_helper.py. Gate
lamport_initialize() on lamport_buffers.open_ipc rather than on
is_p2p_supported. Those two can now disagree, and calling lamport_initialize
with a null local_ptr would be an illegal memory access.

tensorrt_llm/_torch/distributed/ops.py. get_allreduce_workspace returns
the workspace together with an ipc_failed flag, and AllReduce.__init__
downgrades the strategy to NCCL only when that flag is set. The flag
distinguishes the new failure mode (P2P reported, IPC handles unusable) from a
workspace that holds no IPC buffers by design, which is the ordinary inter-node
TP case. That distinction is load-bearing: reacting to the latter would rewrite
the strategy before the MNNVL block and silently disable mnnvl_allreduce on
multi-node NVLink systems. The LOWPRECISION workspace allocation moved below
the check so initialize_static_lowprecision_buffers is not called on a null
workspace.

SYMM_MEM is unaffected: AllReduce.forward tries self.symm_mem_allreduce
before consulting self.strategy, so the downgrade only changes the fallback
path.

Fused ops that have no NCCL path. MoEAllReduce and MiniMaxAllReduceRMS
hand the workspace to custom kernels that reinterpret it as void**, and neither
has a non-IPC alternative, so the strategy downgrade above does not cover them.
They now raise an explicit error when ipc_failed is set, rather than
dereferencing the null peer pointers inside the kernel. That is the same
init-time failure those paths had before this PR, with a message that says why.

3. Files changed

File Change
tensorrt_llm/_ipc_utils.py open_ipc_memory returns Optional[...]; collective IPC-failure agreement without exchanging uninitialized handles; cleanup; IpcMemory.ipc_failed; single warning on the group
tensorrt_llm/_torch/distributed/allreduce_helper.py gate lamport_initialize on the actual IPC state
tensorrt_llm/_torch/distributed/ops.py get_allreduce_workspace returns (workspace, ipc_failed); strategy downgrade to NCCL on real IPC failure only; reorder LOWPRECISION allocation; explicit error for MoEAllReduce / MiniMaxAllReduceRMS
tests/unittest/_torch/distributed/test_ipc_memory_fallback.py new, 11 tests, CUDA runtime and collectives stubbed

4. Reported validation on a second platform

The reporter of #16899 applied the first revision of this patch on 8x RTX 5090
(PCIe, no NVLink), which is a different platform from the RTX 6000D in the
original report:

  • Before: crash at engine init, cudaErrorInvalidDevice: 101 from
    cudaIpcOpenMemHandle, on all 8 ranks.
  • After: falls back to NCCL, engine initializes, warm-up and a 100-request
    benchmark run to completion.

So the failure is not specific to the RTX 6000D, it affects PCIe-only parts
generally. That run predates the changes in section 2 that narrow the downgrade
and rework the handle exchange.

5. Risk and uncertainty

  • I could not reproduce on the reporter's hardware. No RTX 6000D, and this
    environment has neither GPUs nor torch. The root cause is derived from the
    reporter's traceback plus reading the code; the specific reason
    cudaIpcOpenMemHandle returns 101 on that GPU is not established. The fix is
    deliberately agnostic to that reason, it handles "IPC handles cannot be
    exchanged" however it arises.
  • Behaviour change: what used to be a fatal error is now a warning plus a
    slower but correct NCCL path. That is the right trade-off here, since the
    runtime already treats non-P2P topologies this way, but it does mean a
    genuinely broken IPC setup degrades instead of failing loudly. The warning is
    emitted once per process, from open_ipc_memory where the condition is
    detected, and on every rank of the group rather than only on the ones that
    failed locally.
  • Why FP8 worked for the reporter is not established. Their FP8 runs did not
    hit this initialization path; I have no evidence about the mechanism and make
    no claim about it.
  • No extra collective. The IPC-failure agreement rides along with the handle
    allgather that already existed, plus the one that covers the open step. Two
    collectives per IpcMemory construction, unchanged.
  • Not addressed (out of scope): can_access_peer() still reports True on
    such systems, so self.is_p2p_supported in modeling_deepseekv3.py,
    modeling_glm.py, modeling_qwen3_moe.py and friends remains optimistic. A
    complete fix would also plumb the IPC verdict into
    AllReduceOp::setGroupTopology (cpp/tensorrt_llm/thop/allreduceOp.cpp) so
    that C++ and Python agree from the start; the strategy downgrade above covers
    the same ground from the Python side without touching C++.
  • The reporter's original 0.21 symptom (BF16 fails at concurrency 4 but works at
    concurrency 1) is a different failure from the rc22 init crash addressed here.
    Their rc22 report supersedes it, and that is what this fix targets.

6. How I verified it

Constraints: no GPU and no torch in this environment, so pytest cannot import
tensorrt_llm. The pytest file was not executed. Instead:

  1. Logic verified against the real edited source. A standalone harness (not
    committed) loads the actual tensorrt_llm/_ipc_utils.py via importlib with
    the CUDA runtime, Distributed, logger and Mapping stubbed, and runs six
    scenarios. All assertions pass:
    • IPC works: open_ipc=True, ipc_failed=False, non-null pointers, one
      handle opened, two collectives, no warning.
    • cudaIpcOpenMemHandle returns cudaErrorInvalidDevice(101), the reported
      failure: no exception, ipc_failed=True, all pointers null, local buffer
      freed, warning names the failing call.
    • Local rank succeeds but a peer failed to open: this rank also falls back,
      opened handles closed, buffer freed, and it still warns rather than
      degrading silently.
    • cudaIpcGetMemHandle fails locally: no handle is ever opened, buffer freed.
    • A peer failed to export: cudaIpcOpenMemHandle is never called, so no
      uninitialized handle reaches the driver.
    • IPC never requested, and TP larger than one node: open_ipc=False with
      ipc_failed=False, no allocation, no collective. This is the case that must
      not disturb strategy selection.
  2. Lint and format, matching this repo's split toolchain, with ruff pinned
    to the v0.9.4 this repo's pre-commit uses:
    • ruff check and ruff format --diff clean on _ipc_utils.py,
      allreduce_helper.py and the new test (non-legacy files).
    • yapf --diff and isort --diff clean on ops.py, which is in
      legacy-files.txt, and scripts/legacy_utils.py lint-precommit reports
      nothing on it.

Not verified: end-to-end trtllm-bench --tp 8 on affected hardware, and that
NCCL throughput is acceptable for this workload. Both need the reporter's
machine. The new tests are in tests/unittest/_torch/distributed, which #16498
moved from l0_dgx_h100.yml to the CPU-only l0_cpu.yml on 2026-08-04, so CI
runs them with zero GPUs. Everything there passes without a GPU; the three tests
that build an AllReduce skip if the trtllm custom ops are not registered, and
test_lamport_skipped skips because the workspace helper it drives allocates
device tensors. The CUDA stubbing is unittest.mock.patch scoped to a context
manager and touches no real CUDA or MPI state, matching how
test_safe_mpi_comm.py in the same directory already patches.

Rebased onto main on 2026-08-05 (was 162 behind, now 0). Clean rebase: none of
the three source files had upstream commits since the branch point.

Dev Engineer Review

  • IPC initialization now coordinates fallback across tensor-parallel ranks.
  • Failed handle opens and allocated buffers are cleaned up.
  • Lamport initialization now requires usable IPC.
  • AllReduce falls back to NCCL only after a genuine IPC exchange failure.
  • Allocation and memset errors remain fatal.
  • Fused paths without NCCL alternatives now raise explicit errors.
  • The updated get_allreduce_workspace return type is consistent with its callers.
  • No configuration or test-list files changed.
  • GPU-based pytest and end-to-end validation were not available. Static checks and a standalone logic harness passed.

QA Engineer Review

  • Added 11 tests in tests/unittest/_torch/distributed/test_ipc_memory_fallback.py.
  • Coverage includes successful IPC, local and peer export/open failures, cleanup, collective fallback agreement, inter-node tensor parallelism, strategy selection, NCCL downgrade, fused-operation errors, MNNVL preservation, and Lamport initialization.
  • The tests are not listed in tests/integration/test_lists/, test-db/, or qa/.
  • Verdict: sufficient for the added IPC fallback logic. GPU-based integration coverage remains unavailable.

Copilot AI review requested due to automatic review settings July 30, 2026 00:10
@pjdurden
pjdurden requested review from a team as code owners July 30, 2026 00:10
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

CUDA IPC initialization now coordinates failures across tensor-parallel ranks, cleans up partial resources, and reports unavailable IPC. Allreduce initialization selects NCCL or raises an error based on IPC availability. Tests cover success, failure, fallback, and cleanup paths.

Changes

CUDA IPC fallback

Layer / File(s) Summary
IPC failure contract and cleanup
tensorrt_llm/_ipc_utils.py
IpcMemory returns an optional result, synchronizes IPC failures across ranks, cleans up handles and allocations, and disables IPC when setup fails.
Allreduce workspace fallback
tensorrt_llm/_torch/distributed/ops.py, tensorrt_llm/_torch/distributed/allreduce_helper.py
Allreduce detects failed IPC workspaces, selects NCCL when supported, rejects fused operations without an IPC workspace, and skips Lamport initialization for unopened IPC.
IPC fallback validation
tests/unittest/_torch/distributed/test_ipc_memory_fallback.py
CUDA and collective fakes test IPC setup, export and import failures, intentional IPC absence, strategy changes, errors, and cleanup.

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

Suggested reviewers: bowenfu, schetlur-nv

Sequence Diagram(s)

sequenceDiagram
  participant AllReduce
  participant IpcMemory
  participant TPCollective
  participant NCCL
  AllReduce->>IpcMemory: initialize IPC workspace
  IpcMemory->>TPCollective: exchange IPC failure status
  TPCollective-->>IpcMemory: return TP-wide status
  IpcMemory-->>AllReduce: return workspace and failure flag
  AllReduce->>NCCL: select NCCL when IPC failed
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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
Linked Issues check ✅ Passed The changes address issue #16899 by handling CUDA IPC exchange failures and using the supported NCCL fallback path.
Out of Scope Changes check ✅ Passed The source changes and tests are directly related to CUDA IPC fallback, all-reduce strategy handling, cleanup, and error reporting.
Title check ✅ Passed The title clearly summarizes the primary change and follows the repository's required [None][fix] format.
Description check ✅ Passed The description clearly explains the root cause, solution, risks, validation, and test coverage, despite using different section 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: 1

🧹 Nitpick comments (2)
tests/unittest/_torch/distributed/test_ipc_memory_fallback.py (1)

74-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Expand coverage for the remaining fallback contracts.

Coverage summary — insufficient. The three added tests cover successful IPC setup, local import failure, and peer-reported failure. CI/QA test-list membership cannot be verified because no tests/integration/test_lists/test-db/ or tests/integration/test_lists/qa/ file was provided.

  • Make FakeCudart.cudaIpcGetMemHandle() configurable and test its new dummy-handle/cleanup path.
  • In test_local_ipc_open_failure_falls_back_instead_of_raising, use FakeDist(TP_SIZE) so a local failure alone drives agreement; peer_ipc_ok=False currently masks failures to propagate the local error.
  • Add mocked coverage that unavailable IPC downgrades AllReduce to NCCL with workspace is None, and that lamport_initialize() is skipped.

As per path instructions, test changes require coverage and test-list status review.

🤖 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/distributed/test_ipc_memory_fallback.py` around lines
74 - 157, Expand the IPC fallback tests around FakeCudart.cudaIpcGetMemHandle,
making its result configurable and covering the dummy-handle and cleanup
behavior. Update test_local_ipc_open_failure_falls_back_instead_of_raising to
use FakeDist(TP_SIZE), then add mocked coverage verifying unavailable IPC
downgrades AllReduce to NCCL with workspace=None and skips lamport_initialize();
also review coverage and test-list status for these test changes.

Source: Path instructions

tensorrt_llm/_ipc_utils.py (1)

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

Use built-in generics in open_ipc_memory

Replace Optional[Tuple[List[int], int]] with tuple[list[int], int] | None. List is still needed by the other annotations in this file, so the typing import can’t be dropped yet.

🤖 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/_ipc_utils.py` at line 18, Update the return annotation of
open_ipc_memory to use the built-in generic form tuple[list[int], int] | None
instead of Optional[Tuple[List[int], int]]. Retain the existing typing imports
because List remains required by other annotations in the file.

Sources: Coding guidelines, Learnings

🤖 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/_ipc_utils.py`:
- Around line 161-163: Update the CUDA IPC warning in the logger.warning call to
use the f-string conversion flag for ipc_error instead of explicitly calling
repr(), resolving Ruff RUF010 while preserving the existing message.

---

Nitpick comments:
In `@tensorrt_llm/_ipc_utils.py`:
- Line 18: Update the return annotation of open_ipc_memory to use the built-in
generic form tuple[list[int], int] | None instead of Optional[Tuple[List[int],
int]]. Retain the existing typing imports because List remains required by other
annotations in the file.

In `@tests/unittest/_torch/distributed/test_ipc_memory_fallback.py`:
- Around line 74-157: Expand the IPC fallback tests around
FakeCudart.cudaIpcGetMemHandle, making its result configurable and covering the
dummy-handle and cleanup behavior. Update
test_local_ipc_open_failure_falls_back_instead_of_raising to use
FakeDist(TP_SIZE), then add mocked coverage verifying unavailable IPC downgrades
AllReduce to NCCL with workspace=None and skips lamport_initialize(); also
review coverage and test-list status for these test changes.
🪄 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: ab9b4213-0365-4477-a4b7-557a34646fe2

📥 Commits

Reviewing files that changed from the base of the PR and between 960530b and 0c410e3.

📒 Files selected for processing (4)
  • tensorrt_llm/_ipc_utils.py
  • tensorrt_llm/_torch/distributed/allreduce_helper.py
  • tensorrt_llm/_torch/distributed/ops.py
  • tests/unittest/_torch/distributed/test_ipc_memory_fallback.py

Comment thread tensorrt_llm/_ipc_utils.py Outdated

Copilot AI 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.

Pull request overview

This PR addresses a Tensor Parallel initialization failure where CUDA IPC handle import/export can fail (e.g., cudaIpcOpenMemHandle returning cudaErrorInvalidDevice) even when cudaDeviceCanAccessPeer reports P2P capability, by degrading to the already-supported NCCL path instead of aborting engine initialization.

Changes:

  • Make IpcMemory.open_ipc_memory() return None on CUDA IPC handle exchange failures (with TP-wide agreement + cleanup), and have IpcMemory.__init__ disable IPC (null pointers) instead of raising.
  • Gate Lamport buffer initialization on lamport_buffers.open_ipc rather than the optimistic P2P check.
  • In AllReduce.__init__, detect workspaces that have no usable IPC buffers and downgrade the strategy to NCCL; add a focused unit test module with stubbed CUDA runtime / distributed collectives.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tensorrt_llm/_ipc_utils.py Make CUDA IPC failures non-fatal by returning None and cleaning up; disables IPC state in IpcMemory on failure.
tensorrt_llm/_torch/distributed/allreduce_helper.py Prevent lamport_initialize() from running when IPC buffers are not actually available.
tensorrt_llm/_torch/distributed/ops.py Add IPC-workspace detection and force NCCL fallback when IPC buffers are missing; reorder LOWPRECISION allocation to avoid null-workspace usage.
tests/unittest/_torch/distributed/test_ipc_memory_fallback.py Add stubbed unit tests covering IPC-success, local IPC-open failure, and peer IPC failure agreement behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tensorrt_llm/_torch/distributed/ops.py Outdated
Comment on lines +781 to +785
self.workspace = get_allreduce_workspace(self.mapping)
# Every custom all-reduce kernel reads the peers' IPC buffers. When
# CUDA IPC is unavailable those pointers are null, so NCCL is the
# only strategy that can run. See allreduce_workspace_has_ipc.
if not allreduce_workspace_has_ipc(self.mapping):
Comment thread tensorrt_llm/_ipc_utils.py Outdated
Comment on lines +159 to +163
if not all(dist.tp_allgather(ipc_error is None)):
if ipc_error is not None:
logger.warning(
f"CUDA IPC is not usable on this system: {repr(ipc_error)}. "
"Custom all-reduce kernels are disabled, falling back to NCCL."
@pjdurden

Copy link
Copy Markdown
Author

@BowenFu @schetlur-nv @JunyiXu-nv @allisonlim-nv could one of you take a look when you get a chance?

The reporter of #16899 applied the patch on 8x RTX 5090 (PCIe, no NVLink), a different platform from the RTX 6000D in the original report:

Before: crash at engine init, cudaErrorInvalidDevice: 101 from cudaIpcOpenMemHandle, all 8 ranks.
After: falls back to NCCL, engine initializes, warmup and a 100 request benchmark run to completion.

So the failure is not specific to the RTX 6000D, it affects PCIe-only parts generally.

Also pushed one more commit for Ruff nit.

@bo-nv

bo-nv commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

/bot run

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

The _ipc_utils.py fix is the right one and the collective-agreement + cleanup details are handled carefully. Two things before merge.

The ops.py downgrade is broader than the bug. allreduce_workspace_has_ipc() is False whenever can_access_peer() is False — which includes the entirely normal inter-node TP case (can_access_peer returns False on a node-rank mismatch, and IpcMemory.__init__ already forces open_ipc=False when tp_size > gpus_per_node). See the inline comment at [ops.py:785](https://github.com/NVIDIA/TensorRT-LLM/pull/17034/files#diff-8343f10ef2d25d7245cbff50bea93ad47fd865db34a1e3c1d116ae57f7ab7ac1R785); as written this disables MNNVL on multi-node NVLink systems. The _ipc_utils + allreduce_helper changes alone already fix #16899; if you keep the ops.py guard, it should trigger only on the new failure mode (P2P reported, IPC unusable) and run after the MNNVL block.

Test coverage stops at _ipc_utils. The three new tests are good, but nothing covers the two behavioral changes that can break working hardware: the AllReduce.__init__ strategy downgrade and the lamport_initialize gating. A test asserting that an MNNVL-eligible mapping still gets mnnvl_allreduce set would have caught the issue above. tests/unittest/_torch/distributed is enrolled as a directory in l0_dgx_h100.yml, so the new file will run — worth confirming the cudart patching is safe alongside the real-GPU tests in that stage.

Description nits: the last paragraph is cut off mid-sentence ("trading a "), and the claim that FP8 "worked because it fits in fewer bytes, not because it dodges IPC" contradicts the following clause about not hitting the init path. Also, MoEAllReduce and MiniMaxAllReduceRMS still take the workspace unconditionally, so the "defense in depth" argument doesn't cover them — worth saying so explicitly rather than implying full coverage.

Comment thread tensorrt_llm/_torch/distributed/ops.py Outdated
# Every custom all-reduce kernel reads the peers' IPC buffers. When
# CUDA IPC is unavailable those pointers are null, so NCCL is the
# only strategy that can run. See allreduce_workspace_has_ipc.
if not allreduce_workspace_has_ipc(self.mapping):

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.

This fires far more often than the bug it targets. allreduce_workspace_has_ipc() is False for any workspace built with is_p2p_supported=False, and can_access_peer() returns False on a plain node-rank mismatch ([_ipc_utils.py:46](https://github.com/NVIDIA/TensorRT-LLM/pull/17034/files#diff-58a9acafed7455cb039511131ef7c8fb87d8d39200b75e733c93914b25e26eecR46)) — plus IpcMemory.__init__ forces open_ipc=False when tp_size > gpus_per_node. So on any multi-node TP config this rewrites self.strategy to NCCL, and the MNNVL block right below ([ops.py:795](https://github.com/NVIDIA/TensorRT-LLM/pull/17034/files#diff-8343f10ef2d25d7245cbff50bea93ad47fd865db34a1e3c1d116ae57f7ab7ac1R795)) is keyed on self.strategy in (AUTO, MNNVL) — so mnnvl_allreduce is never constructed. That's a silent perf regression on GB200-class multi-node NVLink, which is exactly the topology MNNVL exists for.

Two fixes, either works: (a) only downgrade when the new failure mode occurred — i.e. can_access_peer(mapping) was True but open_ipc came back False — so systems that never had P2P keep today's behavior; or (b) move this block below the MNNVL init and skip it when self.mnnvl_allreduce is not None. (a) is narrower and matches the PR's stated scope.

@pjdurden pjdurden Aug 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Went with (a). IpcMemory tracks ipc_failed separately from open_ipc now, set only when IPC was requested and the group fits in one node but the exchange still failed. can_access_peer False, node-rank mismatch, tp_size > gpus_per_node all leave it False, so multi-node TP does not touch self.strategy and the MNNVL block runs like before.

Added test_mnnvl_kept_without_p2p for it.

Comment thread tensorrt_llm/_torch/distributed/ops.py Outdated
# CUDA IPC is unavailable those pointers are null, so NCCL is the
# only strategy that can run. See allreduce_workspace_has_ipc.
if not allreduce_workspace_has_ipc(self.mapping):
logger.warning(

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.

AllReduce is instantiated per decoder layer (often several per layer), so on an affected system this prints a warning ~100+ times per rank. Use logger.warning_once(..., key=...), or better, emit the message once from open_ipc_memory where the condition is actually detected.

@pjdurden pjdurden Aug 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Moved it. warning_once with a key, out of open_ipc_memory where the condition is detected, so one line per process instead of one per AllReduce. Left a logger.debug at the strategy site that names what got downgraded.

Comment thread tensorrt_llm/_ipc_utils.py Outdated
ipc_error = None if error == cudart.cudaError_t.cudaSuccess else error
if ipc_error is not None:
# Exchange a dummy handle to keep the collective below symmetric.
local_handle = cudart.cudaIpcMemHandle_t()

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.

Exchanging an all-zero cudaIpcMemHandle_t means every other rank calls cudaIpcOpenMemHandle on garbage. It probably just returns an error you catch below, but you're feeding an uninitialized handle to the driver to preserve collective symmetry when a simpler option exists: allgather the cudaIpcGetMemHandle success flag first and skip the handle exchange + open loop entirely if any rank failed. Same number of collectives (you need a second agreement allgather for open failures either way), no garbage handles.

@pjdurden pjdurden Aug 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Dropped the dummy handle. A rank that cannot export sends None in the handle allgather, and everyone bails before the open loop if any entry is None. That collective carries the payload and the export agreement together, so still two collectives, second one covers open failures.

Comment thread tensorrt_llm/_ipc_utils.py Outdated
peer_ptrs.append(ptr)
opened_ptrs.append(ptr)

if not all(dist.tp_allgather(ipc_error is None)):

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.

Only ranks that failed locally log; ranks that succeeded and are falling back because a peer failed degrade silently. Since the group decision is what matters operationally, log unconditionally in this branch (with the peer-driven case worded differently), so a user reading rank 0's log can tell why custom all-reduce is off.

@pjdurden pjdurden Aug 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Both branches log now, through a shared disable_ipc(). Message is either failed here with or failed on a peer rank of the TP group.

Comment thread tensorrt_llm/_torch/distributed/ops.py Outdated
still cannot be imported. In that case the workspace pointers are null and only
NCCL can be used. get_allreduce_workspace must have been called first.
"""
allreduce_workspaces = getattr(_thread_local,

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.

getattr without a default raises AttributeError if get_allreduce_workspace hasn't run for this pp_rank — the docstring notes the precondition, but an unguarded getattr/[mapping] turns a misuse into an opaque traceback. Cheap fix: take the workspace tuple as an argument, or have get_allreduce_workspace return both the tensor and the flag so the invariant is structural rather than documented.

@pjdurden pjdurden Aug 5, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

get_allreduce_workspace returns (workspace, ipc_failed) and computes the flag when it fills the thread-local, so no second reader and no bare getattr. MoEAllReduce and MiniMaxAllReduceRMS go through _require_ipc_workspace() which raises, they have no NCCL path.

pjdurden added a commit to pjdurden/TensorRT-LLM that referenced this pull request Aug 5, 2026
Addresses review feedback on NVIDIA#17034.

allreduce_workspace_has_ipc() was False for any workspace built without P2P,
which includes ordinary inter-node TP, so it rewrote the strategy to NCCL
before the MNNVL block and mnnvl_allreduce was never constructed on multi-node
NVLink systems. IpcMemory now records ipc_failed separately from open_ipc, and
only that failure mode, P2P reported but handles unusable, downgrades the
strategy. Configurations that never had P2P keep the behaviour they had.

get_allreduce_workspace returns the flag alongside the workspace, so a caller
cannot read the thread-local state before it has been populated.

The fallback is reported once, from open_ipc_memory where it is detected, and
on every rank of the group rather than only on the ones that failed locally.
AllReduce is constructed per decoder layer, so it no longer warns per instance.

A rank that cannot export its handle contributes None to the allgather instead
of an uninitialized cudaIpcMemHandle_t, so no rank imports a garbage handle.
The number of collectives is unchanged.

MoEAllReduce and MiniMaxAllReduceRMS have no NCCL path, so they now raise an
actionable error instead of letting the kernel dereference null peer pointers.

Tests cover the strategy downgrade, MNNVL still being selected when the
workspace has no IPC buffers by design, and the lamport_initialize gating.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@pjdurden pjdurden changed the title [Bug]: TensorRT-LLM 1.3.0rc18 Llama-3-70B inference failure on 8x NVIDIA RTX 6000D [None][fix] Fall back to NCCL when CUDA IPC handles cannot be exchanged Aug 5, 2026
@pjdurden

pjdurden commented Aug 5, 2026

Copy link
Copy Markdown
Author

All five inline points addressed. Pushed as a separate commit on top of the original fix so the delta reads on its own, then re-signed the branch to clear DCO, it was failing on the first commit.

ops.py downgrade: went with (a). IpcMemory tracks ipc_failed separately from open_ipc, set only when IPC was requested and possible but the exchange failed. can_access_peer returning False leaves it False, so inter-node TP keeps current behaviour and still reaches the MNNVL block. get_allreduce_workspace returns (workspace, ipc_failed), which also kills the bare getattr at ops.py:62.

Tests are 11 now. New: test_mnnvl_kept_without_p2p (the guard for the above), test_downgrade_to_nccl, test_lamport_skipped which drives the real allocate_allreduce_fusion_workspace and asserts lamport_initialize is not called, test_moe_allreduce_raises, test_peer_export_fails, plus two for ipc_failed staying False.

On the cudart patching in that stage: every patch is unittest.mock.patch scoped to a with block on module attributes, nothing survives the test, and the fallback frees its fake pointers through the fake runtime before the patch unwinds. test_safe_mpi_comm.py in the same dir already patches this way. Only test_lamport_skipped touches real CUDA, it is gated on torch.cuda.is_available() because the helper allocates device tensors.

MoEAllReduce and MiniMaxAllReduceRMS: you are right that defense in depth overclaimed. Neither has a non-IPC path, so they raise explicitly when ipc_failed is set. Same init-time failure they had before this PR, just with a message instead of a null deref in the kernel. Called out in the description now.

Description rewritten. Truncated paragraph gone, FP8 claim dropped, I have no evidence for the mechanism there, only that those runs did not hit this path. Title follows [None][fix] now.

Same caveat as before: no GPU or torch here so pytest did not run. The _ipc_utils logic is covered by a standalone harness that loads the real edited module with the CUDA runtime and collectives stubbed, six scenarios, all pass. Section 6 of the description lists them. Rest needs CI.

DCO is green. Can you kick off another /bot run?

@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/_torch/distributed/test_ipc_memory_fallback.py (1)

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

Test coverage summary: insufficient.

Added tests cover IPC success, local and peer IPC failures, intentional IPC absence, MNNVL preservation, NCCL fallback, MoE failure, and Lamport initialization.

Add a direct MiniMaxAllReduceRMS construction test with ipc_failed=True. The production change now routes this class through _require_ipc_workspace(), but no test verifies that integration.

Test-list membership is not applicable to this review because no tests/integration/test_lists/ file was supplied. Run pytest tests/unittest/ after the update.

As per path instructions, test-code changes must include a coverage verdict.

🤖 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/distributed/test_ipc_memory_fallback.py` around lines
271 - 302, The tests lack direct coverage for MiniMaxAllReduceRMS when IPC is
unavailable. Add a unit test that constructs MiniMaxAllReduceRMS with
ipc_failed=True and verifies the production path raises the expected
requires-CUDA-IPC error through _require_ipc_workspace(); then run pytest
tests/unittest/ and include a coverage verdict for the added test.

Sources: Coding guidelines, 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/distributed/ops.py`:
- Around line 69-81: Update _require_ipc_workspace to distinguish IPC usability
from ipc_failed, and reject workspaces when IPC is intentionally unavailable as
well as when initialization fails; do not return a workspace containing null
peer pointers to MoEAllReduce or MiniMaxAllReduceRMS. Preserve the upstream
contract that intentional IPC absence leaves ipc_failed false, and add
inter-node coverage for both IPC-only fused operations.

In `@tests/unittest/_torch/distributed/test_ipc_memory_fallback.py`:
- Around line 140-143: Update _run so the IpcMemory created with open_ipc=True
is explicitly cleaned up while cudart_patch and dist_patch remain active, then
set its open_ipc attribute to False before returning it to prevent
IpcMemory.__del__ from performing a second cleanup.

---

Nitpick comments:
In `@tests/unittest/_torch/distributed/test_ipc_memory_fallback.py`:
- Around line 271-302: The tests lack direct coverage for MiniMaxAllReduceRMS
when IPC is unavailable. Add a unit test that constructs MiniMaxAllReduceRMS
with ipc_failed=True and verifies the production path raises the expected
requires-CUDA-IPC error through _require_ipc_workspace(); then run pytest
tests/unittest/ and include a coverage verdict for the added test.
🪄 Autofix

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: f1588ceb-fd00-410f-8baf-31aafa0793da

📥 Commits

Reviewing files that changed from the base of the PR and between 1dfb7b1 and 7866550.

📒 Files selected for processing (4)
  • tensorrt_llm/_ipc_utils.py
  • tensorrt_llm/_torch/distributed/allreduce_helper.py
  • tensorrt_llm/_torch/distributed/ops.py
  • tests/unittest/_torch/distributed/test_ipc_memory_fallback.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tensorrt_llm/_torch/distributed/allreduce_helper.py
  • tensorrt_llm/_ipc_utils.py

Comment on lines +69 to +81
def _require_ipc_workspace(mapping: Mapping, op_name: str) -> torch.LongTensor:
"""Workspace for fused ops that reinterpret it as `void**` and have no NCCL path.

AllReduce degrades to NCCL when CUDA IPC turns out to be unusable, but these
kernels cannot: they would dereference the null peer pointers. Fail here with an
actionable message rather than in the kernel with an illegal memory access.
"""
workspace, ipc_failed = get_allreduce_workspace(mapping)
if ipc_failed:
raise RuntimeError(
f"{op_name} requires CUDA IPC, which is unavailable on this system, "
"and it has no NCCL fallback.")
return workspace

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 | 🟠 Major | 🏗️ Heavy lift

Reject workspaces with no usable IPC buffers.

ipc_failed is false when IPC was intentionally unavailable, such as inter-node TP. In that state, IpcMemory still serializes null peer pointers. Lines 76-81 return this workspace to MoEAllReduce and MiniMaxAllReduceRMS, and their fused kernels can dereference the null pointers.

Track IPC usability separately from IPC failure. Make _require_ipc_workspace() reject both states. Add inter-node tests for both IPC-only fused operations.

Based on the supplied upstream contract, intentionally absent IPC keeps ipc_failed=False and leaves peer pointers null.

🤖 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/distributed/ops.py` around lines 69 - 81, Update
_require_ipc_workspace to distinguish IPC usability from ipc_failed, and reject
workspaces when IPC is intentionally unavailable as well as when initialization
fails; do not return a workspace containing null peer pointers to MoEAllReduce
or MiniMaxAllReduceRMS. Preserve the upstream contract that intentional IPC
absence leaves ipc_failed false, and add inter-node coverage for both IPC-only
fused operations.

Comment on lines +140 to +143
def _run(mapping, cudart, dist, open_ipc=True):
cudart_patch, dist_patch = _patched(cudart, dist)
with cudart_patch, dist_patch:
return IpcMemory(mapping, 1 << 20, open_ipc)

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

Clean up the fake IPC allocation before the patches exit.

_run() returns an IpcMemory with open_ipc=True after it restores _ipc_utils.cudart. When the successful test releases that object, IpcMemory.__del__() can call the real CUDA runtime with fake pointers.

Keep the patches active through explicit IPC cleanup. Then set open_ipc to False to prevent a second cleanup in __del__.

🤖 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/distributed/test_ipc_memory_fallback.py` around lines
140 - 143, Update _run so the IpcMemory created with open_ipc=True is explicitly
cleaned up while cudart_patch and dist_patch remain active, then set its
open_ipc attribute to False before returning it to prevent IpcMemory.__del__
from performing a second cleanup.

pjdurden added a commit to pjdurden/TensorRT-LLM that referenced this pull request Aug 5, 2026
Addresses review feedback on NVIDIA#17034.

allreduce_workspace_has_ipc() was False for any workspace built without P2P,
which includes ordinary inter-node TP, so it rewrote the strategy to NCCL
before the MNNVL block and mnnvl_allreduce was never constructed on multi-node
NVLink systems. IpcMemory now records ipc_failed separately from open_ipc, and
only that failure mode, P2P reported but handles unusable, downgrades the
strategy. Configurations that never had P2P keep the behaviour they had.

get_allreduce_workspace returns the flag alongside the workspace, so a caller
cannot read the thread-local state before it has been populated.

The fallback is reported once, from open_ipc_memory where it is detected, and
on every rank of the group rather than only on the ones that failed locally.
AllReduce is constructed per decoder layer, so it no longer warns per instance.

A rank that cannot export its handle contributes None to the allgather instead
of an uninitialized cudaIpcMemHandle_t, so no rank imports a garbage handle.
The number of collectives is unchanged.

MoEAllReduce and MiniMaxAllReduceRMS have no NCCL path, so they now raise an
actionable error instead of letting the kernel dereference null peer pointers.

Tests cover the strategy downgrade, MNNVL still being selected when the
workspace has no IPC buffers by design, and the lamport_initialize gating.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

pjdurden added a commit to pjdurden/TensorRT-LLM that referenced this pull request Aug 5, 2026
Addresses review feedback on NVIDIA#17034.

allreduce_workspace_has_ipc() was False for any workspace built without P2P,
which includes ordinary inter-node TP, so it rewrote the strategy to NCCL
before the MNNVL block and mnnvl_allreduce was never constructed on multi-node
NVLink systems. IpcMemory now records ipc_failed separately from open_ipc, and
only that failure mode, P2P reported but handles unusable, downgrades the
strategy. Configurations that never had P2P keep the behaviour they had.

get_allreduce_workspace returns the flag alongside the workspace, so a caller
cannot read the thread-local state before it has been populated.

The fallback is reported once, from open_ipc_memory where it is detected, and
on every rank of the group rather than only on the ones that failed locally.
AllReduce is constructed per decoder layer, so it no longer warns per instance.

A rank that cannot export its handle contributes None to the allgather instead
of an uninitialized cudaIpcMemHandle_t, so no rank imports a garbage handle.
The number of collectives is unchanged.

MoEAllReduce and MiniMaxAllReduceRMS have no NCCL path, so they now raise an
actionable error instead of letting the kernel dereference null peer pointers.

Tests cover the strategy downgrade, MNNVL still being selected when the
workspace has no IPC buffers by design, and the lamport_initialize gating.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
@pjdurden

pjdurden commented Aug 5, 2026

Copy link
Copy Markdown
Author

Rebased onto main. It was 162 behind, now 0, and it was a clean rebase, none of _ipc_utils.py, ops.py or allreduce_helper.py had upstream commits since my base. All 3 commits signed off, DCO green.

One thing that changed under us and is worth flagging, since your review note assumed otherwise: #16498 moved tests/unittest/_torch/distributed out of l0_dgx_h100.yml and into l0_cpu.yml on 08-04. So the new file runs with zero GPUs, not on the H100 stage. Two consequences:

  • The AllReduce tests now actually execute in pre-merge CI, which is better coverage than before. I guarded the three that build an AllReduce behind a skipif on the trtllm custom ops being registered, because AllReduce.__init__ resolves torch.ops.trtllm.allreduce before any of the logic under test.
  • test_lamport_skipped needs a GPU (the workspace helper allocates device tensors) so it will skip on that stage. Say the word if you would rather I add an explicit entry somewhere GPU-backed instead.

On the cudart patching question, that also gets simpler: nothing in the file touches a real GPU on the CPU stage, and every patch is a unittest.mock.patch scoped to a with block on module attributes.

Separately, checking the MNNVL interaction more closely: is_mnnvl() requires mapping.is_multi_node(), and ipc_failed can only be set when can_access_peer() returned True, which requires single-node. So the two are mutually exclusive in production and option (a) fully covers the case you raised, no reachable config where the downgrade hides MNNVL. The one exception is the TLLM_TEST_MNNVL=1 bypass, which forces is_mnnvl true on a single node. If you want that covered too I can move the block below the MNNVL init as in your option (b), but it looked like unnecessary complexity for a test-only env var.

@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

🤖 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/_torch/distributed/test_ipc_memory_fallback.py`:
- Around line 92-98: Update FakeCudart.cudaMalloc and FakeCudart.cudaMemset to
support configurable CUDA failures, then add tests covering each RuntimeError
path and verifying errors propagate without falling back from IPC to NCCL. Keep
the existing success behavior unchanged and exercise the failure configuration
through the relevant distributed IPC test flows.
- Around line 46-51: Update _has_allreduce_op to catch only AttributeError when
accessing torch.ops.trtllm.allreduce, allowing unrelated setup failures to
propagate while preserving the false result for a missing operator.
🪄 Autofix

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: 39b3239f-8583-45ba-9fca-ce8317e5b2d7

📥 Commits

Reviewing files that changed from the base of the PR and between 1dfb7b1 and d5097de.

📒 Files selected for processing (4)
  • tensorrt_llm/_ipc_utils.py
  • tensorrt_llm/_torch/distributed/allreduce_helper.py
  • tensorrt_llm/_torch/distributed/ops.py
  • tests/unittest/_torch/distributed/test_ipc_memory_fallback.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tensorrt_llm/_torch/distributed/allreduce_helper.py
  • tensorrt_llm/_ipc_utils.py
  • tensorrt_llm/_torch/distributed/ops.py

Comment on lines +46 to +51
def _has_allreduce_op() -> bool:
"""Whether the trtllm custom ops are registered in this build."""
try:
return torch.ops.trtllm.allreduce is not None
except Exception:
return False

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

🌐 Web query:

In the supported PyTorch versions for TensorRT-LLM, which exception does torch.ops..<missing_operator> raise when the operator is not registered?

💡 Result:

When using PyTorch's torch.ops to access an operator that has not been registered, the behavior depends on the specific API implementation used to retrieve the operator, but it typically raises a ValueError or a RuntimeError [1][2][3]. According to the PyTorch source code for operator resolution (e.g., torch/_custom_op/impl.py), the function used to retrieve an operator raises a ValueError with the message: "Could not find the operator . Please make sure you have already registered the operator and (if registered from C++) loaded it via torch.ops.load_library." [3]. This specific error is frequently encountered when an operator is missing or not properly loaded [1]. In other contexts—such as when the dispatcher attempts to resolve a call to an operator that does not exist—PyTorch may raise a RuntimeError, often phrased as "RuntimeError: operator does not exist" or "RuntimeError: No such operator " [4][2][5]. While TensorRT-LLM relies on PyTorch's operator infrastructure, these exceptions are standard PyTorch behaviors triggered when the underlying dispatcher or operator registry cannot resolve the requested operator [3][6]. If you encounter this while using TensorRT-LLM, it usually indicates that the necessary library (e.g., a custom C++ extension or kernel library) has not been loaded or that there is a version mismatch between the environment and the compiled extensions [1][4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target file outline ---'
ast-grep outline tests/unittest/_torch/distributed/test_ipc_memory_fallback.py --match '$_' --view compact 2>/dev/null || true

printf '%s\n' '--- helper and related usage ---'
rg -n -C 5 '_has_allreduce_op|allreduce|pytest\.skip|except ' tests/unittest/_torch/distributed/test_ipc_memory_fallback.py

printf '%s\n' '--- installed PyTorch behavior probe ---'
python3 - <<'PY'
try:
    import torch
except Exception as exc:
    print(f"torch import unavailable: {type(exc).__name__}: {exc}")
else:
    print(f"torch version: {torch.__version__}")
    for namespace, operator in (("trtllm", "allreduce"), ("definitely_missing_namespace", "definitely_missing_operator")):
        try:
            value = getattr(getattr(torch.ops, namespace), operator)
        except BaseException as exc:
            print(f"{namespace}.{operator}: {type(exc).__name__}: {exc}")
        else:
            print(f"{namespace}.{operator}: returned {value!r}")
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 4683


🌐 Web query:

site:github.com/pytorch/pytorch "_OpNamespace" "__getattr__" "has no attribute"

💡 Result:

The error AttributeError: '_OpNamespace' '[namespace]' object has no attribute '[op_name]' is a PyTorch error that occurs when you attempt to access an operator within a namespace (e.g., torch.ops.aten, torch.ops._C) that has not been registered or is otherwise inaccessible [1][2]. This error originates from the _OpNamespace class in PyTorch, which dynamically handles operator lookups [1]. When you call an attribute on a namespace, the getattr method attempts to retrieve the specified operator from the PyTorch C++ operator registry [1][2]. If the operator cannot be found, an AttributeError is raised [1][2]. Common causes for this error include: 1. Unregistered or Missing Custom Operators: If you are using a custom operator (common in projects like vLLM), the underlying C++ extension that registers that operator may not have been correctly built, loaded, or installed [3][4]. 2. Version Mismatches: The operator may have been renamed, moved, or removed in the version of PyTorch you are using, or you may be calling an operator that is not present in your specific PyTorch build [5][6][7]. 3. Loading Issues: For C++ extensions, the library might not be fully loaded before you attempt to access the operator. Ensure that any custom libraries or extensions are initialized before calling their functions [8][9]. 4. Incorrect Namespace or Name: Ensure you are using the correct namespace and operator name. In some cases, extensions might use a different namespace than expected or require specific macro expansion during registration [8][9]. To troubleshoot this: - Verify that the extension or library containing the operator is correctly installed and imported. - Check the PyTorch version compatibility, especially if the code was written for a different version. - If using third-party packages (like vLLM), verify that the package is compatible with your PyTorch environment [3][4]. - If you are developing the custom operator, verify your C++ registration code (e.g., TORCH_LIBRARY) to ensure the namespace is correctly defined [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- PyTorch version constraints in the repository ---'
rg -n -i -C 2 'torch([<>=!~]|[-_ ]version)|pytorch([<>=!~]|[-_ ]version)' \
  pyproject.toml setup.py setup.cfg requirements* docker .github 2>/dev/null | head -200 || true

printf '%s\n' '--- local references to the operator helper or skip marker ---'
rg -n -C 3 '_has_allreduce_op|requires_allreduce_op|allreduce custom op' .

Repository: NVIDIA/TensorRT-LLM

Length of output: 7845


🏁 Script executed:

#!/bin/bash
set -eu

for ref in v2.11.0 main; do
  url="https://raw.githubusercontent.com/pytorch/pytorch/${ref}/torch/_ops.py"
  printf '%s\n' "--- ${ref} ---"
  curl -fsSL "$url" |
    grep -n -A35 -B5 'class _OpNamespace' |
    head -80
done

Repository: NVIDIA/TensorRT-LLM

Length of output: 4248


🏁 Script executed:

#!/bin/bash
set -eu

for ref in v2.11.0 main; do
  printf '%s\n' "--- ${ref} missing-operator branch ---"
  curl -fsSL "https://raw.githubusercontent.com/pytorch/pytorch/${ref}/torch/_ops.py" |
    sed -n '/def __getattr__(self, op_name: str)/,+32p'
done

Repository: NVIDIA/TensorRT-LLM

Length of output: 3278


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- changed files ---'
git diff --name-only

printf '%s\n' '--- target test functions ---'
rg -n '^def test_|^async def test_' tests/unittest/_torch/distributed/test_ipc_memory_fallback.py

printf '%s\n' '--- matching test-list entries ---'
rg -n -i 'ipc_memory_fallback|test_ipc_memory_fallback' tests/integration/test_lists 2>/dev/null || true

printf '%s\n' '--- candidate test-list files ---'
fd -i '.*(yml|yaml|sqlite)$' tests/integration/test_lists 2>/dev/null | head -100

Repository: NVIDIA/TensorRT-LLM

Length of output: 4859


Catch only AttributeError for a missing operator. PyTorch raises AttributeError when torch.ops.trtllm.allreduce is not registered. Broad exception handling can hide unrelated setup failures.

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 50-50: Do not catch blind exception: Exception

(BLE001)

🤖 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/distributed/test_ipc_memory_fallback.py` around lines
46 - 51, Update _has_allreduce_op to catch only AttributeError when accessing
torch.ops.trtllm.allreduce, allowing unrelated setup failures to propagate while
preserving the false result for a missing operator.

Sources: Coding guidelines, Linters/SAST tools

Comment on lines +92 to +98
def cudaMalloc(self, size):
self.next_ptr += size
self.allocated.add(self.next_ptr)
return FakeCudaError.cudaSuccess, self.next_ptr

def cudaMemset(self, ptr, value, size):
return (FakeCudaError.cudaSuccess,)

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
set -euo pipefail

rg -n -C 3 \
  'def cudaMalloc|def cudaMemset|test_.*(malloc|memset)|test_ipc_memory_fallback' \
  tests/unittest/_torch/distributed/test_ipc_memory_fallback.py

rg -n -C 2 \
  'test_ipc_memory_fallback|_torch/distributed' \
  tests/integration/test_lists || true

Repository: NVIDIA/TensorRT-LLM

Length of output: 1175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- file outline ---'
ast-grep outline tests/unittest/_torch/distributed/test_ipc_memory_fallback.py

printf '%s\n' '--- test file ---'
cat -n tests/unittest/_torch/distributed/test_ipc_memory_fallback.py

printf '%s\n' '--- related source and test-list references ---'
rg -n -C 3 \
  'cudaMalloc|cudaMemset|cudaSuccess|ipc.*fallback|NCCL|test_ipc_memory_fallback|unittest/_torch/distributed' \
  tests/unittest/_torch/distributed tests/integration/test_lists

printf '%s\n' '--- applicable QA list files ---'
find tests/integration/test_lists -type f \( -path '*/qa/*' -o -iname '*qa*' \) -print

Repository: NVIDIA/TensorRT-LLM

Length of output: 26052


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- IpcMemory implementation locations ---'
rg -l 'class IpcMemory|def __init__.*IpcMemory|cudaMalloc|cudaMemset' --glob '*.py' .

printf '%s\n' '--- IpcMemory implementation ---'
rg -n -C 8 'class IpcMemory|cudaMalloc|cudaMemset|cudaIpcGetMemHandle|cudaIpcOpenMemHandle' \
  --glob '*.py' .

printf '%s\n' '--- exact test registration across all integration lists ---'
rg -n -C 2 \
  'test_ipc_memory_fallback\.py|unittest/_torch/distributed' \
  tests/integration/test_lists

printf '%s\n' '--- test-list files and changed test paths ---'
git ls-files tests/integration/test_lists

Repository: NVIDIA/TensorRT-LLM

Length of output: 38944


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- implementation and error helper ---'
cat -n tensorrt_llm/_ipc_utils.py | sed -n '1,215p'

printf '%s\n' '--- applicable coding guidance ---'
find . -name CODING_GUIDELINES.md -print
if [ -f CODING_GUIDELINES.md ]; then
    cat CODING_GUIDELINES.md
fi

printf '%s\n' '--- test-list guidance ---'
cat tests/integration/test_lists/test-db/README.md
printf '%s\n' '--- QA guidance ---'
cat tests/integration/test_lists/qa/README.md

printf '%s\n' '--- deterministic AST checks ---'
python3 - <<'PY'
import ast
from pathlib import Path

test_path = Path("tests/unittest/_torch/distributed/test_ipc_memory_fallback.py")
source_path = Path("tensorrt_llm/_ipc_utils.py")

test_tree = ast.parse(test_path.read_text())
test_names = [
    node.name for node in test_tree.body
    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
    and node.name.startswith("test_")
]
print("test_functions:", test_names)

source_tree = ast.parse(source_path.read_text())
ipc_class = next(
    node for node in source_tree.body
    if isinstance(node, ast.ClassDef) and node.name == "IpcMemory"
)
init = next(node for node in ipc_class.body if isinstance(node, ast.FunctionDef)
            and node.name == "__init__")

calls = []
for node in ast.walk(init):
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
        if node.func.attr in {"cudaMalloc", "cudaMemset", "_raise_if_error"}:
            calls.append((node.lineno, node.func.attr))
print("IpcMemory.__init__ relevant calls:", sorted(calls))

registration = Path("tests/integration/test_lists/test-db/l0_cpu.yml").read_text()
print("directory_registration:",
      "unittest/_torch/distributed" in registration)
qa_matches = []
for path in Path("tests/integration/test_lists/qa").rglob("*"):
    if path.is_file() and path.name != ".gitignore":
        text = path.read_text(errors="replace")
        if "test_ipc_memory_fallback" in text:
            qa_matches.append(str(path))
print("qa_matches:", qa_matches)
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

test_path = Path("tests/unittest/_torch/distributed/test_ipc_memory_fallback.py")
tree = ast.parse(test_path.read_text())
tests = [
    node.name for node in tree.body
    if isinstance(node, ast.FunctionDef) and node.name.startswith("test_")
]
print("test_functions:")
for name in tests:
    print(f"  {name}")

source = Path("tensorrt_llm/_ipc_utils.py").read_text()
for needle in (
    "error, local_ptr = cudart.cudaMalloc(aligned_size)",
    "_raise_if_error(error)",
    "_raise_if_error(cudart.cudaMemset(local_ptr, 0, aligned_size)[0])",
):
    print(f"{needle!r}: {needle in source}")

ci_list = Path("tests/integration/test_lists/test-db/l0_cpu.yml").read_text()
print(
    "CI directory registration:",
    "unittest/_torch/distributed" in ci_list,
)

qa_root = Path("tests/integration/test_lists/qa")
matches = [
    str(path) for path in qa_root.rglob("*")
    if path.is_file()
    and path.name != ".gitignore"
    and "test_ipc_memory_fallback" in path.read_text(errors="replace")
]
print("Manual-QA matches:", matches)
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 655


Add CUDA allocation and memset failure tests.

Test coverage summary — insufficient. FakeCudart.cudaMalloc and FakeCudart.cudaMemset always succeed, so their RuntimeError paths are untested. Add configurable failures and tests that confirm errors propagate without IPC-to-NCCL fallback.

Added tests: test_ipc_ok, test_local_open_fails, test_peer_open_fails, test_local_export_fails, test_peer_export_fails, test_ipc_not_requested, test_inter_node_tp, test_mnnvl_kept_without_p2p, test_downgrade_to_nccl, test_moe_allreduce_raises, and test_lamport_skipped. The file is registered through unittest/_torch/distributed in tests/integration/test_lists/test-db/l0_cpu.yml; no matching manual-QA entry exists.

🤖 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/distributed/test_ipc_memory_fallback.py` around lines
92 - 98, Update FakeCudart.cudaMalloc and FakeCudart.cudaMemset to support
configurable CUDA failures, then add tests covering each RuntimeError path and
verifying errors propagate without falling back from IPC to NCCL. Keep the
existing success behavior unchanged and exercise the failure configuration
through the relevant distributed IPC test flows.

Source: Path instructions

@pjdurden

Copy link
Copy Markdown
Author

@brnguyen2 all five points from your review are addressed and pushed, and the branch is rebased onto main (was 162 behind, now 0). the tests are CPU-safe now that #16498 moved them into l0_cpu.yml. anything else you want changed, or is this ready for a /bot run?

@bo-nv

bo-nv commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #68209 [ run ] triggered by Bot. Commit: d5097de Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #68209 [ run ] completed with state FAILURE. Commit: d5097de
/LLM/main/L0_MergeRequest_PR pipeline #55648 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

cudaDeviceCanAccessPeer reporting P2P support does not guarantee that CUDA IPC
handles can be exported and imported. On 8x RTX 6000D, and on PCIe-only parts
generally, cudaIpcOpenMemHandle fails with cudaErrorInvalidDevice and engine
initialization aborted instead of using the NCCL path that already exists.

IpcMemory.open_ipc_memory now returns None on such a failure, after agreeing on
the outcome across the whole TP group and releasing whatever it managed to
allocate, so that either every rank gets IPC buffers or none of them does.
IpcMemory keeps null pointers in that case, and lamport_initialize is gated on
the buffers actually being open.

Fixes NVIDIA#16899

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
Addresses review feedback on NVIDIA#17034.

allreduce_workspace_has_ipc() was False for any workspace built without P2P,
which includes ordinary inter-node TP, so it rewrote the strategy to NCCL
before the MNNVL block and mnnvl_allreduce was never constructed on multi-node
NVLink systems. IpcMemory now records ipc_failed separately from open_ipc, and
only that failure mode, P2P reported but handles unusable, downgrades the
strategy. Configurations that never had P2P keep the behaviour they had.

get_allreduce_workspace returns the flag alongside the workspace, so a caller
cannot read the thread-local state before it has been populated.

The fallback is reported once, from open_ipc_memory where it is detected, and
on every rank of the group rather than only on the ones that failed locally.
AllReduce is constructed per decoder layer, so it no longer warns per instance.

A rank that cannot export its handle contributes None to the allgather instead
of an uninitialized cudaIpcMemHandle_t, so no rank imports a garbage handle.
The number of collectives is unchanged.

MoEAllReduce and MiniMaxAllReduceRMS have no NCCL path, so they now raise an
actionable error instead of letting the kernel dereference null peer pointers.

Tests cover the strategy downgrade, MNNVL still being selected when the
workspace has no IPC buffers by design, and the lamport_initialize gating.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
NVIDIA#16498 moved tests/unittest/_torch/distributed from l0_dgx_h100 to l0_cpu, so
this file now runs with zero GPUs. The three tests that build an AllReduce skip
when the trtllm custom ops are not registered, since AllReduce.__init__ resolves
torch.ops.trtllm.allreduce before any of the logic under test. test_lamport_skipped
still needs a GPU because the workspace helper it drives allocates device tensors.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
@pjdurden

Copy link
Copy Markdown
Author

@bo-nv thanks for running it. The pipeline links are all on the internal network so I cannot see which stage failed, could you share the failing stage or test name?

In the meantime I rebased onto main, it was 484 behind, now 0, and the rebase was clean. Only one upstream commit had touched any of my files since my base, c0e6d79, and that one rewrites AllReduce.forward while this PR only changes AllReduce.__init__, so they do not overlap. All 3 commits are signed off and DCO is green.

What I could check locally against the four changed files, all clean: ruff and ruff-format on _ipc_utils.py, allreduce_helper.py and the new test, yapf and isort on ops.py since it is still in legacy-files. All three callers of get_allreduce_workspace live in ops.py and all were updated for the new tuple return. The new tests only run in l0_cpu, since unittest/_torch/distributed is registered there as a directory.

Could you trigger another run on the rebased head, d0e8d04? If it fails again I would need the stage name to get any further.

@pjdurden

Copy link
Copy Markdown
Author

@bo-nv ping on this one. The pipeline links are internal so I still cannot see what broke. Could you paste the failing stage or test name? Branch is rebased and current, happy to turn a fix around quickly.

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.

[Bug]: Llama-3-70B FP16/BF16 inference fails while FP8 works on TensorRT-LLM 0.21.c-rc0 with 8x RTX 6000D

5 participants