Skip to content

[Fix] Automatically work around OVPhysX multi-GPU device selection - #7142

Open
hujc7 wants to merge 5 commits into
isaac-sim:developfrom
hujc7:jichuanh/mgpu-virtual-local-rank
Open

[Fix] Automatically work around OVPhysX multi-GPU device selection#7142
hujc7 wants to merge 5 commits into
isaac-sim:developfrom
hujc7:jichuanh/mgpu-virtual-local-rank

Conversation

@hujc7

@hujc7 hujc7 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

1. Summary

  • Multi-GPU training that selects OVPhysX hangs on any --num_gpus >= 2 when an RTX renderer
    shares the process. The launchers now work around it automatically — no flag to discover.
  • Workaround only. The real fix is tracked as nvbug 6573426 and lands in ovphysx 0.5.11.
    This repo pins ovphysx==0.5.10 and no published build carries the fix yet.
  • Self-removing: once the pin moves past 0.5.10, delete the detection.

2. Root cause (external)

OVPhysX's direct OVStage attach path does not apply the requested active_cuda_gpus before PhysX
creates its CUDA context. With an RTX renderer in the same process a CUDA context is already
current, so PhysX auto-selects that device. Every rank above local rank 0 then gets a device-0
binding while its tensors live on its own device:

RuntimeError: Failed to read tensor binding:
  read_tensor_binding: device mismatch: binding expects GPU (device=0), tensor is GPU (device=2)

Those ranks die and rank 0 blocks in the NCCL bootstrap. Only local_rank > 0 is affected, which
is why single-GPU runs are fine and why this long presented as an NCCL or topology problem.

3. Fix

When the forwarded presets select OVPhysX, the launcher passes torchrun's --virtual_local_rank.
That sets LOCAL_RANK=0 for every worker and rewrites CUDA_VISIBLE_DEVICES so each sees only its
assigned GPU as device 0, making PhysX's default correct on every rank.

Detection matches the physics backend alone, not an OVPhysX/OVRTX pair. The renderer has no
stable name at launcher time: renderer=rtx is a placeholder the worker resolves to OVRTX only
once it knows no Kit runtime is needed. A pair match reads {ovphysx, rtx}, misses, and lets that
configuration hang — arm 6 below is exactly that case. The cost is engaging the workaround for
OVPhysX with a renderer that does not need it, where it is inert.

No device-selection code changed: AppLauncher already derives the device from LOCAL_RANK
bounded by the visible device count, which is the invariant virtual mode preserves. The
--local_ranks_filter 0 default still works, because torchrun applies it agent-side against the
real local rank.

There is no flag. A temporary workaround should not add public CLI surface, because removing
that surface later needs a deprecation and migration path — for an option whose only correct value
is the one the launcher already infers. With none, retiring this is a deletion. Both launcher
preamble messages are flushed so they precede the workers' inherited-fd output in a redirected log.

4. Limits

  • One worker per GPU.
  • Every worker reports LOCAL_RANK=0; use the global rank to name per-rank files or logs.
    rsl_rl keys init_process_group and its process identity on RANK; skrl does not read
    LOCAL_RANK. The benchmark path only range-checks 0 <= local_rank < local_world_size.
  • Multi-node was not validated.
  • Applied automatically whenever OVPhysX is selected; there is no per-run opt-out.

5. Validation

An 8-GPU node, 8x L40. IsaacLab 0469d0e5cee + this change. ovphysx 0.5.10,
ovrtx 0.4.1.364340, ovstage 0.1.1.355824, torch 2.11.0+cu128. Isaac-Cartpole-Camera-Direct,
64 envs, 3 iterations. Topology: GPU0-3 one PCIe switch (NUMA 0), GPU4-7 the other (NUMA 1).

Arm GPUs CUDA_VISIBLE_DEVICES Link rc Training time Traceback Device mismatch
control, workaround disabled 1 4 0,1,2,3 PIX 1 none 4 3
auto 4 0,1,2,3 PIX 0 15.61 s 0 0
auto, reversed 4 7,6,5,4 PIX 0 14.84 s 0 0
auto, cross-switch 4 0,1,4,5 SYS 0 9.53 s 0 0
auto, cross-switch reversed 4 5,4,1,0 SYS 0 9.85 s 0 0
physics=ovphysx renderer=rtx 4 0,1,2,3 PIX 0 8.86 s 0 0
auto 8 all both 0 125.06 s 0 0

The control failed with three device mismatch errors on exactly local_rank 1, 2 and 3, and used
the same renderer as the passing arms — the only variable between them is the workaround. Arms 2-7
pass no virtual-local-rank flag; detection fired on its own each time. Arm 6's banner reports
Renderer rtx (ovrtx) with zero Isaac Sim in the process, confirming the pair-match gap is real.

A sampled re-run confirmed physical placement rather than inferring it: 7,6,5,4 occupied
GPUs 4-7 and 5,4,1,0 occupied GPUs 0,1,4,5.

6. Test

test_virtual_local_rank_follows_the_ovphysx_preset is parametrized over the affected and
unaffected preset combinations, including physics=ovphysx renderer=rtx, and asserts the preset
tokens still reach the worker verbatim. Companion tests cover the bare-flag forwarding to torchrun
and confirm the launcher exposes no opt-in option of its own.

Footnotes

  1. The control was produced with an opt-out flag that this PR's final revision removes, so it is
    not reproducible from the shipped CLI. The failure it demonstrates reproduces on any pre-change build.

Multi-GPU training with presets=ovphysx,ovrtx hangs on any --num_gpus >= 2:
OVPhysX does not apply the requested active_cuda_gpus before PhysX creates its
CUDA context, so with OVRTX in the process PhysX auto-selects the already
current device. Rank 1 gets a device-0 binding for device-1 tensors and dies,
and rank 0 then blocks forever in the NCCL bootstrap.

The flag forwards torchrun's --virtual_local_rank, which gives each worker one
GPU as cuda:0, making PhysX's default correct for every rank. Device selection
needed no change: AppLauncher already derives the device from LOCAL_RANK bounded
by the visible device count, which is the invariant virtual mode preserves.

This is a workaround for nvbug 6573426, fixed in ovphysx 0.5.11; remove it once
the pin moves past 0.5.10.
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Aug 17, 2026
hujc7 added 2 commits August 19, 2026 10:56
The multi-GPU launchers now give every worker a single GPU as cuda:0 whenever the
forwarded presets select OVPhysX, instead of requiring the caller to know about
--virtual_local_rank and pass it.

Detection matches the physics backend alone rather than an OVPhysX/OVRTX pair,
because the renderer has no stable name at launcher time: renderer=rtx is a
placeholder the worker resolves to OVRTX only once it knows no Kit runtime is
needed. A pair match reads {ovphysx, rtx}, misses, and lets that configuration
hang. The cost is engaging the workaround for OVPhysX with a renderer that does
not need it, where it is inert: device selection is bounded by the visible device
count, and rank identity keys on the global rank.

--no_virtual_local_rank opts out and --virtual_local_rank forces it on. Both
launcher preamble messages are flushed so they precede the workers' inherited-fd
output in a redirected log.

Remove once the ovphysx pin moves past 0.5.10, where nvbug 6573426 is fixed.
@hujc7 hujc7 changed the title [Fix] Add --virtual_local_rank to work around OVPhysX multi-GPU device selection [Fix] Automatically work around OVPhysX multi-GPU device selection Aug 19, 2026
hujc7 added 2 commits August 19, 2026 11:07
The workaround is now inferred from the presets alone; --virtual_local_rank and
--no_virtual_local_rank are gone.

A flag would be public CLI surface that outlives the defect it exists for: removing it
after the ovphysx pin moves past 0.5.10 would need a deprecation and migration path,
for an option whose only correct value is the one the launcher can already infer. With
no flag, retiring the workaround is a deletion.

The two detection helpers collapse into one, since with no explicit choice there is
nothing to prefer over the preset scan.
Typed selectors are validated against their cfg type, so renderer= can never carry a
physics backend and scanning it was dead. The match is on the raw name, which a future
ovphysx alias would bypass; noted rather than resolved, since the alias table lives in
isaaclab_tasks and this code is deleted at ovphysx 0.5.11.
@hujc7
hujc7 marked this pull request as ready for review August 19, 2026 18:15
@hujc7
hujc7 requested a review from a team August 19, 2026 18:15

@isaaclab-review-bot isaaclab-review-bot 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.

Isaac Lab Review Bot

The automatic OVPhysX detection and torchrun flag placement are supported by focused tests, but the new detection helper writes an informational banner to stdout while building the command, breaking OVPhysX dry-run output.

  • Design and architecture: Inferring the temporary workaround from forwarded OVPhysX preset tokens avoids adding a launcher option. However, _use_virtual_local_rank mixes detection with output, making the shared command builder observably impure.
  • API: Preset tokens remain forwarded verbatim and no launcher option is added. The --dry_run output contract regresses for OVPhysX runs because stdout no longer contains only the shell-parsable command.
  • Implementation: The bare --virtual_local_rank flag is correctly placed in torchrun's arguments rather than the worker arguments. Move the informational banner outside command construction and emit it only after the dry-run path, or send it to stderr, so dry-run stdout remains machine-readable.

Minor fixes needed. Posted 1 actionable finding inline.

Automated review; human maintainers own approval decisions.

selected.update(name.strip() for name in arg[len(prefix) :].split(","))
if VIRTUAL_LOCAL_RANK_PRESET not in selected:
return False
print(

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.

🟡 Warning · Implementation — Info banner breaks dry-run command output

_use_virtual_local_rank prints from inside build_launch_command, which run_multigpu_cli calls before the --dry_run branch. With an OVPhysX preset, dry-run stdout becomes the [INFO] banner followed by the command, so callers that shell-parse or eval that output no longer get the exact command the launcher documents. Keep detection side-effect free and emit the banner from run_multigpu_cli after the dry-run return, or send it to stderr.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR automatically enables torchrun virtual-local-rank isolation when raw launcher selectors contain OVPhysX, working around OVPhysX 0.5.10 device selection in multi-GPU runs. It also documents the behavior and adds launcher tests, but currently misses the existing physics=physx automatic path that resolves to OVPhysX during kitless execution.

  • Adds raw preset inspection and automatic --virtual_local_rank forwarding.
  • Flushes the launcher preamble before worker output.
  • Adds coverage for literal OVPhysX selector combinations and flag forwarding.
  • Adds a changelog entry describing the temporary workaround and rank semantics.

Confidence Score: 4/5

The PR should not merge until automatic physics=physx selection is recognized when it resolves to OVPhysX, because that supported multi-GPU configuration still reaches the original device-mismatch hang.

Literal-token detection covers explicit OVPhysX selections but diverges from worker-side backend resolution for kitless physics=physx renderer=rtx, leaving OVPhysX and OVRTX running without the required isolation.

Files Needing Attention: source/isaaclab/isaaclab/cli/multigpu.py and source/isaaclab/test/benchmark/test_multigpu_launcher.py

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/cli/multigpu.py Adds automatic virtual-local-rank forwarding, but literal selector matching misses the supported physx automatic route to OVPhysX.
source/isaaclab/test/benchmark/test_multigpu_launcher.py Covers literal OVPhysX combinations and bare-flag forwarding but omits the physics=physx renderer=rtx auto-resolution case.
source/isaaclab/changelog.d/jichuanh-mgpu-virtual-local-rank.minor.rst Documents the workaround, its automatic activation, and the resulting per-worker rank/device semantics.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Forwarded physics and renderer selectors] --> B{Literal ovphysx token?}
    B -- Yes --> C[Append --virtual_local_rank]
    B -- No: physics=physx --> D[Launch without workaround]
    D --> E[Worker resolves kitless PhysxAutoCfg]
    E --> F[OVPhysX plus OVRTX]
    F --> G[Device mismatch and distributed hang]
    C --> H[One visible GPU per worker as cuda:0]
Loading

Reviews (1): Last reviewed commit: "Match the OVPhysX preset only where it c..." | Re-trigger Greptile

Comment on lines +304 to +305
selected.update(name.strip() for name in arg[len(prefix) :].split(","))
if VIRTUAL_LOCAL_RANK_PRESET not in selected:

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.

P1 Automatic PhysX selection bypasses workaround

When a kitless multi-GPU launch uses physics=physx renderer=rtx, the worker resolves these selectors to OVPhysX and OVRTX, but this literal ovphysx check does not add --virtual_local_rank, causing nonzero ranks to hit the device mismatch and leaving rank 0 hanging during NCCL bootstrap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant