Skip to content

[Draft/RFC] Native-CUDA (cuda-oxide) backend: device build, fixed-grid dispatch, CUDA-graph capture#13

Draft
haixuanTao wants to merge 4 commits into
dimforge:mainfrom
haixuanTao:feat/cuda-oxide-native
Draft

[Draft/RFC] Native-CUDA (cuda-oxide) backend: device build, fixed-grid dispatch, CUDA-graph capture#13
haixuanTao wants to merge 4 commits into
dimforge:mainfrom
haixuanTao:feat/cuda-oxide-native

Conversation

@haixuanTao

Copy link
Copy Markdown
Contributor

Draft for discussion — nexus running end-to-end on native CUDA (cuda-oxide) from Python, including the multibody solver. Companions: dimforge/khal#9 (device backend + graph-capture runtime) and dimforge/vortx#9 (vortx-shaders device build).

Results (cube-drop + LeRobot bipedal capture demos, RTX 5080 laptop, 640×480)

scene WebGPU CUDA (indirect) CUDA (fixed-grid) CUDA graph
cube drop 92 fps 5.6 16.4 117
LeRobot biped (13-link multibody) 6.9 fps 12.7 28.3

Physics verified against WebGPU (same trajectories; first numerical validation of the multibody solver under cuda-oxide).

The four commits

  1. Device build: nexus_rbd_shaders3d gains the cuda-oxide feature; two shader-code fixes (LU/jacobian helpers generalized over MaybeIndexUnchecked for SmemBuf workgroup memory; panic-free ColBranchless::col_b replacing Mat::col, whose formatted panic can't exist in a kernel). Produces a sm_120 cubin via cuda-oxide → llvm-link(libdevice) → llc → ptxas, consumed through CUDA_OXIDE_SHADERS_PTX_NEXUS_RBD_SHADERS3D.
  2. Fixed-grid dispatch (17 sites): on CUDA every DispatchGrid::Indirect costs a stream synchronize + host count read and forbids graph capture; capacity-based grids replace them (kernels bounds-check the true count, so over-launch is safe). Default ON for CUDA only; NEXUS_FIXED_GRID overrides. 5.6 → 16.4 fps alone.
  3. Radix sort capture-safety: host-exact fixed grids for both sort paths + cached SortUniforms tensors (they were re-allocated every call — cuMemAlloc invalidates a capture).
  4. CUDA-graph capture: NexusPipeline::capture_rbd_graph / replay_rbd_graph (+ Python capture_cuda_graph/replay_cuda_graph) — one cuGraphLaunch replays the whole rbd_steps_per_frame × step sequence. Capture after warmup (records raw buffer addresses); timestamps/auto-resize skipped while replaying.

Known discussion points

  • Cargo wiring is dev-machine-shaped: commit 1 enables the local-path [patch] block (khal/vortx checkouts) because the published khal-std 0.2.1 / vortx-shaders 0.3.0 lack the cuda-oxide feature — cargo metadata fails on a clean checkout until khal#9 / vortx#9 are released. Happy to rework once those land.
  • Blocked on upstream cuda-oxide: NVlabs/cuda-oxide #350, #358, #360 are needed to build the device code; until then the codegen .so comes from haixuanTao/cuda-oxide branch feat/nexus3d-vortx-native-cuda.
  • Graph invalidation on buffer growth is currently the caller's responsibility (capture after warmup); could auto-invalidate from auto_resize_buffers if preferred.

🤖 Generated with Claude Code

haixuanTao and others added 4 commits July 9, 2026 15:22
- Workspace: patch khal/vortx family to the local path checkouts and disable
  khal-std default features (the rust-cuda default pulls cuda_std, which does
  not build under cuda-oxide's -Zbuild-std).
- nexus_rbd_shaders3d: add the cuda-oxide feature (khal-derive keys its
  entry-point codegen off this exact feature name on the shader crate).
- Shader fixes for cuda-oxide, both patterns ported from the nexus-rl fork:
  * generalize the LU / jacobian helpers over MaybeIndexUnchecked so they
    accept SmemBuf-wrapped workgroup memory,
  * ColBranchless::col_b — panic-free Mat2/Mat3 column access (Mat::col
    panics with a formatted message, which GPU kernels cannot carry) — and
    switch all 41 call sites.

With this, build_cuda-style scripts produce a sm_120 cubin from nexus-cuda's
shaders (cuda-oxide .ll -> llvm-link libdevice -> llc -> ptxas), consumed via
CUDA_OXIDE_SHADERS_PTX_NEXUS_RBD_SHADERS3D at host build time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… grids on CUDA

On the CUDA backend every DispatchGrid::Indirect costs a stream synchronize +
device->host count read (a full GPU drain) and invalidates CUDA-graph capture.
Port the nexus-rl fork's fixed-grid mechanism: a dispatch_grid() helper picks a
capacity-based grid when enabled (kernels are dispatched at workgroup
granularity and bounds-check against the true count buffer, so over-launch is
always correct). 17 sites: coloring (5), narrow-phase (3), solver (8),
warmstart (1). Default ON for CUDA, OFF for WebGPU/Metal (native indirect
dispatch is free there); NEXUS_FIXED_GRID=1/0 overrides.

Cube-drop capture demo, CUDA backend: 5.6 -> 16.4 gen-fps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Both sort paths dispatched count/reduce/scan_add/scatter indirectly via
  num_wgs/num_reduce_wgs; compute the same grids host-side from the known
  capacities (exact match to gpu_init_sort_dispatch) as DispatchGrid::Grid.
- The per-pass SortUniforms tensors (and n_sort_flat) were re-allocated every
  sort call; cache them keyed on (max_keys, passes, num_batches, per_batch).
  cuMemAlloc during CUDA-graph capture invalidates the capture, and the churn
  was free to remove anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NexusPipeline::capture_rbd_graph records one frame's rbd_steps_per_frame x
step dispatch sequence into a CUDA graph (executing it once);
replay_rbd_graph replays it with a single cuGraphLaunch. Python:
pipeline.capture_cuda_graph(viewer, state) / pipeline.replay_cuda_graph().

Requires the CUDA backend, stable buffer sizes (capture after warmup; the
graph records raw buffer addresses) and fixed-grid dispatch (the CUDA
default). Timestamps and auto-resize are skipped while capturing/replaying.

Cube-drop capture demo: 16.4 -> 115 gen-fps (vs 92 on WebGPU) — one graph
launch replaces the host-side re-encode of ~40 dispatches per frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant