Skip to content

[Bug]: Split narrowphase on GPU skips GJK when requires_grad=True, producing zero convex-convex contacts #2865

@EntangledQuantum

Description

@EntangledQuantum

Bug Description

On GPU backends, Genesis enables split narrowphase whenever the scene contains non-box/plane convex–convex pairs (collider._use_split_narrowphase == True). In this path, contact0 correctly detects overlap and enqueues pairs into the GJK work queue, but _func_multicontact_run_detection never runs GJK (or differentiable GJK) when requires_grad=True.

The narrowphase path (func_convex_convex_contact) correctly branches on requires_grad and calls diff_gjk.func_gjk_contact plus func_add_diff_contact_input. The split multicontact path does not — GJK is guarded by if qd.static(not static_rigid_sim_config.requires_grad) with no else branch.

Symptoms:

  • Broadphase works (n_broad_pairs increases when bodies overlap).
  • Robot–object pairs appear in narrowphase_work_queues GJK queue.
  • multicontact_gjk_state.is_col stays 0 and n_contacts == 0 for those pairs.
  • Objects pass through each other (“ghost” collision) despite visual overlap.
  • Same scene with requires_grad=False produces contacts normally.

Secondary issue: _func_multicontact_gjk_full writes contacts via func_set_contact but never calls func_add_diff_contact_input, so collider_state.diff_contact_input is never populated on the split path (even when forward contacts exist without grad). This breaks the differentiable backward pass (func_narrow_phase_diff_convex_vs_convex.grad).

Affected code:

        if qd.static(collider_static_config.ccd_algorithm != CCD_ALGORITHM_CODE.MJ_MPR):
            if use_gjk:
                if qd.static(not static_rigid_sim_config.requires_grad):
                    gjk.func_gjk_contact(...)
                    ...
                    used_gjk = True
                # MISSING: else branch with diff_gjk.func_gjk_contact(...) when requires_grad=True

Compare with the working monolithic path at narrowphase.py ~L1234–1299, which calls diff_gjk.func_gjk_contact and func_add_diff_contact_input when requires_grad=True.

Instrumentation note: Reading collider._gjk._gjk_state is misleading on split narrowphase — contact0 uses _contact0_gjk_state and multicontact uses _multicontact_gjk_state.

Steps to Reproduce

  1. Use Genesis on a CUDA GPU with a scene that triggers split narrowphase (any convex non-box/plane pair, e.g. Franka finger BOX geoms vs USD/MESH peg).
  2. Build a rigid scene with:
    • sim_options.requires_grad=True
    • rigid_options.use_gjk_collision=True
    • rigid_options.enable_multi_contact=True
    • rigid_options.box_box_detection=False (required when requires_grad=True)
  3. Place a dynamic convex object (e.g. peg geom type MESH, idx 47) in forced overlap with robot finger collision geoms (e.g. geoms 11, 12).
  4. Run collision detection and inspect split-narrowphase state after contact0, before multicontact pass 1:
collider = scene.sim.rigid_solver.collider
wq = collider._collider_state.narrowphase_work_queues
mc_gjk = collider._multicontact_gjk_state

After contact0: pairs are enqueued

gjk_size = int(wq.gjk_queue_size.to_numpy()[0])
print(list(zip(wq.gjk_i_ga.to_numpy()[:gjk_size], wq.gjk_i_gb.to_numpy()[:gjk_size])))

e.g. [(12, 47), (11, 47)]

collider.detection()
print("multicontact is_col:", (mc_gjk.is_col.to_numpy() == 1).sum())
print("n_contacts:", collider._collider_state.n_contacts.to_numpy()[0])
print("robot-peg contacts:", len(robot.get_contacts(with_entity=peg)["position"]))

  1. Repeat step 4 with requires_grad=False on the same scene/pose.

Minimal repro script (in this repo):

scripts/utils/debug_split_narrowphase.py

conda run -n genesis python scripts/utils/debug_split_narrowphase.py

Observed results (RTX 5090, Genesis 1.0.0):

Metric requires_grad=True requires_grad=False
use_split_narrowphase True True
GJK queue (robot–peg) (12,47), (11,47) (12,47), (11,47)
multicontact_gjk.is_col true 0 2
n_contacts 0 2
robot.get_contacts(with_entity=peg) 0 2

Expected Behavior

When requires_grad=True on GPU with split narrowphase active:

  1. _func_multicontact_run_detection should call diff_gjk.func_gjk_contact (mirroring func_convex_convex_contact) instead of skipping GJK entirely.
  2. On collision, func_add_diff_contact_input should populate collider_state.diff_contact_input so forward contacts and backward gradients are consistent.
  3. Robot–peg (and any similar convex–convex) pairs enqueued to the GJK queue should produce n_contacts > 0 and physical contact response, same as requires_grad=False.

Screenshots/Videos

Image Image Image Image

Relevant log output

[Genesis] [02:11:23] [INFO] ╭───────────────────────────────────────────────╮
[Genesis] [02:11:23] [INFO] │┈┉┈┉┈┉┈┉┈┉┈┉┈┉┈┉┈┉┈ Genesis ┈┉┈┉┈┉┈┉┈┉┈┉┈┉┈┉┈┉┈│
[Genesis] [02:11:23] [INFO] ╰───────────────────────────────────────────────╯
[Genesis] [02:11:23] [INFO] Running on [NVIDIA GeForce RTX 5090] with backend gs.cuda. Device memory: 31.33 GB.
[Genesis] [02:11:23] [INFO] 🚀 Genesis initialized. 🔖 version: 1.0.0, 🎨 theme: dark, 🌱 seed: None, 🐛 debug: False, 📏 precision: 32, 🔥 performance: False, 💬 verbose: INFO
[Genesis] [02:11:25] [INFO] Scene <38e308c> created.
[Genesis] [02:11:25] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 0, uid: <52b7e81>, morph: <gs.morphs.Plane>, material: <gs.materials.Rigid>.
[Genesis] [02:11:25] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 1, uid: <406b17c>, morph: <gs.morphs.MJCF(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/genesis-world/genesis/assets/xml/franka_emika_panda/panda.xml')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:25] [WARNING] (MJCF) Approximating tendon by joint actuator for `finger_joint1`
[Genesis] [02:11:25] [WARNING] (MJCF) Approximating tendon by joint actuator for `finger_joint2`
[Genesis] [02:11:25] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 2, uid: <a19cefc>, morph: <gs.morphs.USD(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_hole_8mm.usd')(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_hole_8mm.usd', prim_path='None')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:25] [INFO] Convex hull is not accurate enough for collision detection (0.941). Falling back to more expensive convex decomposition (see FileMorph options).
[Genesis] [02:11:25] [INFO] Baked assets detected and used: /home/cognitron/.cache/genesis/usd/bake/592ee24f0b0034901c2eea5b7c0bb91e1e71cf9afbace4c4c846dedc679ceef0/factory_peg_8mm.usd
[Genesis] [02:11:25] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 3, uid: <68edd52>, morph: <gs.morphs.USD(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_peg_8mm.usd')(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_peg_8mm.usd', prim_path='None')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:25] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 4, uid: <f06c089>, morph: <gs.morphs.Mesh(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/genesis-world/genesis/assets/meshes/axis.obj')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:25] [INFO] Building scene <38e308c>...
[Genesis] [02:11:25] [INFO] Mass is not specified and collision geoms can not be found for link 'axis_obj_baselink'. Using visual geoms to compute inertial properties.
[Genesis] [02:11:25] [WARNING] Neutral robot position (qpos0) exceeds joint limits.
[Genesis] [02:11:26] [WARNING] Filtered out geometry pairs causing self-collision for the neutral configuration (qpos0): (6, 11), (7, 11), (14, 21), (15, 20), (16, 23), (17, 22). Consider tuning Morph option 'decompose_robot_error_threshold' or specify dedicated collision meshes. This behavior can be disabled by setting Morph option 'enable_neutral_collision=True'.
[Genesis] [02:11:26] [INFO] Compiling simulation kernels...
[Genesis] [02:11:27] [INFO] Building visualizer...

========================================================================
RUN: requires_grad=True mesh peg
========================================================================
  backend=2  requires_grad=True
  use_split_narrowphase=True
  peg geom idx=47  type=6
  n_broad_pairs=4
  n_contacts (collider)=0
  n_diff_contact_input.valid=0
  robot.get_contacts(with_entity=peg)=0
  peg total contacts=0
  WRONG collider._gjk._gjk_state is_col true count=0

  --- after_contact0_before_mc_pass1 ---
    gjk_queue_size: 2
    mpr_queue_size: 0
    gjk_pairs_peg: [(12, 47), (11, 47)]
    gjk_pairs_robot_peg: [(12, 47), (11, 47)]

  --- after_contact0_before_mc_pass1 ---
    contact0_is_col_true: 0
    contact0_is_col_sample: [0]
    multicontact_is_col_true: 0
    multicontact_is_col_sample: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

  --- after_mc_pass1 ---
    contact0_is_col_true: 0
    contact0_is_col_sample: [0]
    multicontact_is_col_true: 0
    multicontact_is_col_sample: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

  --- after_mc_pass2 ---
    contact0_is_col_true: 0
    contact0_is_col_sample: [0]
    multicontact_is_col_true: 0
    multicontact_is_col_sample: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[Genesis] [02:11:29] [INFO] Scene <e380b9e> created.
[Genesis] [02:11:29] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 0, uid: <e1b14cd>, morph: <gs.morphs.Plane>, material: <gs.materials.Rigid>.
[Genesis] [02:11:29] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 1, uid: <21b7181>, morph: <gs.morphs.MJCF(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/genesis-world/genesis/assets/xml/franka_emika_panda/panda.xml')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:29] [WARNING] (MJCF) Approximating tendon by joint actuator for `finger_joint1`
[Genesis] [02:11:29] [WARNING] (MJCF) Approximating tendon by joint actuator for `finger_joint2`
[Genesis] [02:11:29] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 2, uid: <9cbd7ca>, morph: <gs.morphs.USD(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_hole_8mm.usd')(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_hole_8mm.usd', prim_path='None')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:29] [INFO] Convex hull is not accurate enough for collision detection (0.941). Falling back to more expensive convex decomposition (see FileMorph options).
[Genesis] [02:11:29] [INFO] Baked assets detected and used: /home/cognitron/.cache/genesis/usd/bake/592ee24f0b0034901c2eea5b7c0bb91e1e71cf9afbace4c4c846dedc679ceef0/factory_peg_8mm.usd
[Genesis] [02:11:29] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 3, uid: <9572e62>, morph: <gs.morphs.USD(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_peg_8mm.usd')(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/assests/factory_peg_8mm.usd', prim_path='None')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:29] [INFO] Adding <gs.engine.entities.RigidEntity>. idx: 4, uid: <afa6776>, morph: <gs.morphs.Mesh(file='/home/cognitron/Projects/Physics_Projects/Cognitron_MultiRobot_MVP/genesis-world/genesis/assets/meshes/axis.obj')>, material: <gs.materials.Rigid>.
[Genesis] [02:11:29] [INFO] Building scene <e380b9e>...
[Genesis] [02:11:29] [INFO] Mass is not specified and collision geoms can not be found for link 'axis_obj_baselink'. Using visual geoms to compute inertial properties.
[Genesis] [02:11:29] [WARNING] Neutral robot position (qpos0) exceeds joint limits.
[Genesis] [02:11:30] [WARNING] Filtered out geometry pairs causing self-collision for the neutral configuration (qpos0): (6, 11), (7, 11), (14, 21), (15, 20), (16, 23), (17, 22). Consider tuning Morph option 'decompose_robot_error_threshold' or specify dedicated collision meshes. This behavior can be disabled by setting Morph option 'enable_neutral_collision=True'.
[Genesis] [02:11:30] [INFO] Compiling simulation kernels...
[Genesis] [02:11:48] [INFO] Building visualizer...

========================================================================
RUN: requires_grad=False mesh peg
========================================================================
  backend=2  requires_grad=False
  use_split_narrowphase=True
  peg geom idx=47  type=6
  n_broad_pairs=5
  n_contacts (collider)=2
  n_diff_contact_input.valid=0
  robot.get_contacts(with_entity=peg)=2
  peg total contacts=2
  WRONG collider._gjk._gjk_state is_col true count=0

  --- after_contact0_before_mc_pass1 ---
    gjk_queue_size: 2
    mpr_queue_size: 0
    gjk_pairs_peg: [(12, 47), (11, 47)]
    gjk_pairs_robot_peg: [(12, 47), (11, 47)]

  --- after_contact0_before_mc_pass1 ---
    contact0_is_col_true: 0
    contact0_is_col_sample: [0]
    multicontact_is_col_true: 0
    multicontact_is_col_sample: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

  --- after_mc_pass1 ---
    contact0_is_col_true: 0
    contact0_is_col_sample: [0]
    multicontact_is_col_true: 2
    multicontact_is_col_sample: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

  --- after_mc_pass2 ---
    contact0_is_col_true: 0
    contact0_is_col_sample: [0]
    multicontact_is_col_true: 2
    multicontact_is_col_sample: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

========================================================================
COMPARISON (grad vs no-grad)
========================================================================
  n_contacts: grad=0  no-grad=2
  n_diff_contact_input_valid: grad=0  no-grad=0
  n_robot_peg_contacts: grad=0  no-grad=2
[Genesis] [02:11:50] [INFO] 💤 Exiting Genesis and caching compiled kernels...

Environment

Field Value
OS Ubuntu 24.04.4 LTS (kernel 6.17.0-29-generic)
GPU/CPU NVIDIA GeForce RTX 5090
GPU driver 580.159.04
CUDA / CUDA toolkit CUDA 13.3 (compiler build cuda_13.3.r13.3/compiler.37862127_0)
Genesis release 1.0.0
Commit ID 6007f263fd2c76586c65eb787de7bd0b0c67aee9 (v1.0.0-4-g6007f263)
Backend gs.cuda

Release version or Commit ID

6007f26
v1.0.0-4-g6007f263

Additional Context

i am stuck on this for hours . i had a peg in the hole project with differtiable gradients enabled the robot and the peg (USD object) simply become ghost to each other even though they are colliding with the ground.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions