Skip to content

Skip PhysX replicator for fully-heterogeneous 1:1 layouts (fixes mGPU double-free)#6559

Closed
ooctipus wants to merge 2 commits into
isaac-sim:developfrom
ooctipus:octi/fix-physx-hetero-mgpu-double-free
Closed

Skip PhysX replicator for fully-heterogeneous 1:1 layouts (fixes mGPU double-free)#6559
ooctipus wants to merge 2 commits into
isaac-sim:developfrom
ooctipus:octi/fix-physx-hetero-mgpu-double-free

Conversation

@ooctipus

Copy link
Copy Markdown
Collaborator

Summary

Fixes intermittent double free or corruption (SIGABRT) in PhysxReplicateContext.replicate() when running fully-heterogeneous scenes (one variant per env) across multiple GPUs. Stacks onto #6550.

Root cause: For a fully-heterogeneous 1:1 layout (N variants → N envs, each source mapped only to its own env), replicate() calls rep.replicate() N times — once per source with a single self-target each. This causes intermittent native heap corruption under mGPU. The source prims are already in their correct env positions; no cross-env replication is needed. Each call has PhysX-internal per-call allocations that apparently sum to a problematic total across concurrent processes.

Fix: Check whether all queued rows are self-only (target_envs == (own_env_id,)). When true, skip register_replicator entirely — PhysX parses the source prims directly from the stage, which is correct and avoids the crash.

Controls (from mgpu_repro.zip in #6550):

Condition Before After
Heterogeneous mGPU (4 GPU, 192 variants) 3/4 pass, 1/4 SIGABRT expected 4/4
Heterogeneous 1-GPU 2/2 pass unaffected
Homogeneous mGPU 2/2 pass unaffected

Failure signature (before fix)

Replicate physics     : True
Starting the simulation. This may take a few seconds. Please wait...
double free or corruption (!prev)
torch.distributed.elastic.multiprocessing.errors.ChildFailedError
traceback : Signal 6 (SIGABRT)

🤖 Generated with Claude Code

ooctipus added 2 commits July 15, 2026 22:45
Since the replication-session refactor, InteractiveSceneCfg.replicate_physics
was ignored: scenes configured with replicate_physics=False invoked native
physics replication anyway, silently discarding per-environment USD
differences (identified in isaac-sim#6541).

The flag now lives where the policy executes:

- CloneCfg.replicate_physics is the policy's home; InteractiveScene pipes its
  cfg flag into it and forwards it through ReplicateSession to replicate().
- REPLICATION_QUEUE holds bare asset cfgs: construction only registers which
  cfgs participate (required by ClonePlan.from_env_0 in direct workflows and
  to distinguish clonable assets from lights/terrain).
- replicate() resolves how each cfg clones at dispatch: the cfg's
  cloning_contexts field when set, otherwise the active backend's default
  stack exported as isaaclab_<backend>.cloner.REPLICATION. PhysX pairs
  physics replication with USD clones (collision groups are authored on the
  per-env prims; PhysX has no kitless mode), Newton includes USD only under
  Kit, and OvPhysX replicates alone since its clone replay authors USD.
- With replicate_physics=False, cloning is USD-only: the physics engine
  parses the per-env USD prims directly; an asset whose contexts are all
  physics-based is simply not cloned.

Asset implementations know nothing about cloning beyond registering their
cfg; per-asset overrides are a cfg assignment. The per-backend
queue_*_replication helpers are removed in favor of this single path.
For scenes where every source maps only to its own environment
(one variant per env, fully-heterogeneous 1:1), calling rep.replicate()
once per source with a single self-target intermittently triggers native
heap corruption (double-free / SIGABRT) under mGPU.

Detect this layout by checking that all queued rows are self-only and
skip register_replicator entirely. PhysX then parses the source prims
directly from the stage, which is both correct and safe.

Reproducer: mgpu_repro.zip -- heterogeneous mGPU with 192 variants and
4 GPUs, replicate_physics=True.
@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team labels Jul 16, 2026
@ooctipus

Copy link
Copy Markdown
Collaborator Author

Folding this into #6550 directly.

@ooctipus ooctipus closed this Jul 16, 2026
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes an intermittent SIGABRT (double free or corruption) in PhysxReplicateContext.replicate() triggered by fully-heterogeneous 1:1 layouts (N variants → N envs) under mGPU. It also stacks a significant refactor of the replication queue — replacing per-backend queue_<backend>_replication helpers with a single queue_replication(cfg) call in AssetBase.__init__, and moving the cloning-context decision (physics vs. USD) from asset constructors into a per-cfg cloning_contexts field resolved at dispatch time.

  • Bug fix: PhysxReplicateContext.replicate() now detects when every queued entry is a self-only mapping and returns early, skipping register_replicator so PhysX parses prims from the stage directly.
  • Queue refactor: REPLICATION_QUEUE now holds raw cfgs; backends export a REPLICATION tuple as their default context stack; each cfg may override via the new cloning_contexts field.
  • replicate_physics plumbing: InteractiveSceneCfg.replicate_physics is now correctly piped through CloneCfg and ReplicateSession to replicate(), fixing a regression where the flag was silently ignored.

Confidence Score: 4/5

The mGPU crash fix is targeted and empirically verified; the refactoring is broad but mechanical. The two remaining observations are documentation gaps, not correctness defects.

The self-only detection logic is sound: it correctly identifies all-self-only layouts, clears the queue before any early return, and relies on PhysX parsing the stage directly. Two documentation gaps exist: the physx_replicate docstring still claims rep.replicate() always fires for self-only sources, and the replicate_physics=False attention note omits the OvPhysX silent-no-clone case.

source/isaaclab_physx/isaaclab_physx/cloner/replicate.py (stale docstring on physx_replicate) and source/isaaclab/isaaclab/scene/interactive_scene_cfg.py (missing OvPhysX limitation in the replicate_physics attention note).

Important Files Changed

Filename Overview
source/isaaclab_physx/isaaclab_physx/cloner/replicate.py Core bug fix: adds self-only detection in PhysxReplicateContext.replicate() to skip register_replicator for fully-heterogeneous 1:1 layouts. Queue clear correctly moved before the early-return check. Standalone physx_replicate docstring not updated to reflect the new behaviour.
source/isaaclab/isaaclab/cloner/replicate_session.py Queue refactored from (cfg, BackendCtxCls) pairs to raw cfgs; context resolution moved to dispatch time via cfg.cloning_contexts or dynamic backend REPLICATION import; replicate_physics flag correctly threaded through.
source/isaaclab/isaaclab/assets/asset_base.py Added queue_replication(cfg) in AssetBase.__init__ before cfg.copy(), consolidating registration previously scattered across backend subclasses.
source/isaaclab/isaaclab/assets/asset_base_cfg.py Added cloning_contexts field (None = backend default stack, empty tuple = not cloned). Well-documented.
source/isaaclab/isaaclab/sensors/sensor_base_cfg.py Sets cloning_contexts to USD-only for all sensors. Correct: sensors carry no physics of their own.
source/isaaclab_newton/isaaclab_newton/cloner/replicate.py Replaces queue_newton_physics_replication with a REPLICATION tuple evaluated at import time via has_kit(). Documented constraint: must be imported after AppLauncher starts Kit.
source/isaaclab_ovphysx/isaaclab_ovphysx/cloner/replicate.py Replaces queue_ovphysx_replication with REPLICATION = (OvPhysxReplicateContext,). With replicate_physics=False, OvPhysX assets produce no clones — limitation not documented at scene-cfg level.
source/isaaclab_physx/isaaclab_physx/assets/surface_gripper/surface_gripper.py Correctly adds explicit queue_replication(cfg) since SurfaceGripper skips AssetBase.__init__. Comment explains the reason.
source/isaaclab/isaaclab/scene/interactive_scene.py Correctly pipes replicate_physics from InteractiveSceneCfg into CloneCfg and ReplicateSession.
source/isaaclab/isaaclab/cloner/cloner_cfg.py Adds replicate_physics: bool = True field to CloneCfg. Well-documented.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Asset constructor called] --> B[AssetBase.__init__]
    B --> C[queue_replication cfg]
    C --> D[REPLICATION_QUEUE stores cfg]
    E[ReplicateSession.__exit__] --> F[replicate plan stage replicate_physics]
    F --> G{For each cfg in queue}
    G --> H{cfg.cloning_contexts set?}
    H -- Yes --> I[Use cfg.cloning_contexts]
    H -- No --> J[Load backend REPLICATION tuple]
    I --> K{replicate_physics=False?}
    J --> K
    K -- Yes --> L[Filter out non-USD contexts]
    K -- No --> M[Keep all contexts]
    L --> N[backend_rows union]
    M --> N
    N --> O[For each BackendCtxCls call queue_mapping]
    O --> P[PhysxReplicateContext.replicate]
    P --> Q{Queue empty?}
    Q -- Yes --> R[Return]
    Q -- No --> S[Copy queue then clear]
    S --> T{ALL entries self-only?}
    T -- Yes --> U[Return: PhysX parses stage directly - mGPU crash avoided]
    T -- No --> V[register_replicator + rep.replicate per source]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Asset constructor called] --> B[AssetBase.__init__]
    B --> C[queue_replication cfg]
    C --> D[REPLICATION_QUEUE stores cfg]
    E[ReplicateSession.__exit__] --> F[replicate plan stage replicate_physics]
    F --> G{For each cfg in queue}
    G --> H{cfg.cloning_contexts set?}
    H -- Yes --> I[Use cfg.cloning_contexts]
    H -- No --> J[Load backend REPLICATION tuple]
    I --> K{replicate_physics=False?}
    J --> K
    K -- Yes --> L[Filter out non-USD contexts]
    K -- No --> M[Keep all contexts]
    L --> N[backend_rows union]
    M --> N
    N --> O[For each BackendCtxCls call queue_mapping]
    O --> P[PhysxReplicateContext.replicate]
    P --> Q{Queue empty?}
    Q -- Yes --> R[Return]
    Q -- No --> S[Copy queue then clear]
    S --> T{ALL entries self-only?}
    T -- Yes --> U[Return: PhysX parses stage directly - mGPU crash avoided]
    T -- No --> V[register_replicator + rep.replicate per source]
Loading

Comments Outside Diff (2)

  1. source/isaaclab_physx/isaaclab_physx/cloner/replicate.py, line 154-196 (link)

    P2 Stale docstring after self-only early-exit fix

    physx_replicate's docstring still says "Self-only sources always keep self so that rep.replicate() fires" and "every source must appear in at least one replicate call." Both statements are no longer true: when every queued entry is self-only, PhysxReplicateContext.replicate() now returns before calling register_replicator, so rep.replicate() is never invoked. The docstring should be updated to describe the new invariant — that for all-self-only layouts PhysX reads the prims from the stage directly without a replicate call — otherwise future readers may assume a physics body always requires a rep.replicate() side-effect and inadvertently break the optimization.

  2. source/isaaclab/isaaclab/scene/interactive_scene_cfg.py, line 108-130 (link)

    P2 replicate_physics=False limitation for OvPhysX not documented

    The new attention note mentions Newton as unsupported but is silent on OvPhysX. OvPhysX's REPLICATION = (OvPhysxReplicateContext,) has no USD context; with replicate_physics=False the dispatch in replicate() filters out every non-USD context, leaving an empty list. OvPhysX assets produce no clones at all (no physics bodies, no USD prims for the non-env-0 environments) without any error or warning — a silent correctness failure that is harder to debug than the Newton case.

Reviews (1): Last reviewed commit: "Skip physx replicator for fully-heteroge..." | Re-trigger Greptile

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

Labels

bug Something isn't working documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant