ModelBuilder replication performance
Newton PR #3187 replaces the per-world materialization of replicated collision-filter pairs with a compact block representation and reworks mesh content hashing for geometry deduplication. This page tracks measured results as the branch evolves: the current branch-head benchmarks first, then the earlier GPU world-count sweep and Isaac Lab startup comparison.
Current branch head: compact filters and mesh hashing
Headline: Replicate + finalize is 4.6x–6.5x faster than origin/main across 32–2048 worlds, and peak RSS drops 41% at 2048 worlds. Contact-pair and excluded-pair counts match the baseline exactly at every point. The one regression axis: building the sorted excluded-pair array for the opt-in nxn/sap broad phases is up to 2.7x slower, a one-time cost at CollisionPipeline construction that does not affect the default explicit broad phase.
Why the shape changed
Dense self-collision filters (URDF imports with enable_self_collisions=False) grow as shapes² per robot; materializing them per world made add_builder() and finalize() both O(worlds × pairs) in time and memory. The branch stores one filter template per replicated block plus a shape offset, and find_shape_contact_pairs() computes contact pairs once per unique world layout and replays them with an offset. Baseline finalize also grows superlinearly past ~1024 worlds (set churn and GC pressure on millions of tuples), which is why the speedup widens with world count. The block representation stays internal: ModelBuilder.shape_collision_filter_pairs still reads as a plain list, and reading or mutating it simply falls back to the materialized path.
Replication sweep
| Worlds | main replicate+finalize s | PR replicate+finalize s | Speedup | main peak RSS MB | PR peak RSS MB | main excluded-array s | PR excluded-array s |
|---|---|---|---|---|---|---|---|
| 32 | 0.136 | 0.029 | 4.65x | 418 | 401 | 0.048 | 0.117 |
| 128 | 0.473 | 0.100 | 4.74x | 541 | 470 | 0.276 | 0.733 |
| 512 | 2.311 | 0.716 | 3.23x | 1,017 | 726 | 1.630 | 3.872 |
| 1024 | 5.068 | 0.950 | 5.34x | 1,632 | 1,046 | 3.934 | 5.110 |
| 2048 | 11.721 | 1.804 | 6.50x | 2,839 | 1,672 | 10.381 | 11.490 |
Tradeoffs
Excluded-pair array for nxn/sap. The compact representation defers pair expansion, so Model.shape_collision_filter_pairs_for_broad_phase() expands, dedups, and sorts millions of pairs on demand (1.1x–2.7x the cost of sorting the baseline’s already-materialized set). This is paid once per CollisionPipeline and only in the opt-in modes; the default explicit broad phase consumes shape_contact_pairs directly and never builds this array. Emitting pre-sorted pairs per block instead of a global set is the obvious follow-up if this matters for a workload.
Compatibility path halves the win but stays correct. Reading the public builder.shape_collision_filter_pairs list before finalize materializes the compact storage. At 720 worlds this costs 46% on build and 52% on finalize relative to the compact path, yet still produces identical pair counts — the optimization degrades gracefully rather than changing behavior. Mutating Model.shape_collision_filter_pairs after finalize is now a DeprecationWarning, since post-finalize edits never rebuilt shape_contact_pairs anyway.
| Metric (720 worlds, PR head) | Compact path | Materialized fallback | Delta |
|---|---|---|---|
| replicate s | 0.481 | 0.896 | -46.3% |
| finalize s | 2.266 | 4.751 | -52.3% |
| excluded-array build s | 3.874 | 2.562 | +51.2% |
| total incl. excluded array s | 6.621 | 8.209 | -19.3% |
Mesh content hashing
Geometry dedup in finalize() keys finalized meshes by content hash with an object-identity fast path. The old hash flattened every vertex and index into Python tuples; the new one streams length-framed buffers through SHA-256, which fixes cross-buffer ambiguity (equal concatenated bytes from different vertex/index splits no longer collide), is deterministic across processes, and is 3x–14x faster, widening with mesh size. On 300 replicated box meshes, finalize with one shared Mesh object (identity path) takes 0.093 s vs 0.123 s for 300 distinct-but-equal objects; both dedup to a single finalized source.
Raw data
World-count sweep on robot examples (GPU snapshot)
Earlier full-example sweep at branch commit ba838612 comparing origin-main with modelbuilder-compact-filters on seven robot examples. The compact collision-filter path wins most at high world counts.
Interactive all-in-one plot
8192-world summary
| Scene | origin/main ms | current ms | Delta | Speedup |
|---|---|---|---|---|
robot_allegro_hand | 10,274.36 | 4,450.54 | -56.7% | 2.31x |
robot_anymal_d | 23,184.56 | 4,922.76 | -78.8% | 4.71x |
robot_cartpole | 794.30 | 530.74 | -33.2% | 1.50x |
robot_g1 | 77,283.24 | 18,212.30 | -76.4% | 4.24x |
robot_h1 | 20,631.51 | 7,179.53 | -65.2% | 2.87x |
robot_panda_hydro | 16,155.23 | 6,778.28 | -58.0% | 2.38x |
robot_ur10 | 9,645.19 | 3,430.83 | -64.4% | 2.81x |
GPU sweep source data
Each point reports the last measured ModelBuilder.finalize() call for that example. For robot_panda_hydro, that is the replicated-scene finalize.
Isaac Lab Newton PR 3187 startup
Newton PR #3187 was benchmarked against Newton origin/main in an Isaac Lab worktree using presets=newton_mjwarp, the summary benchmark backend, and editable Newton installs verified before launch. Every sample in this section was rerun at 1024 envs under a 2-logical-CPU affinity cap with taskset -c 0,1.
Headline: At 1024 envs under the CPU cap, PR 3187 is near parity on Cartpole, Ant, and Humanoid, improves G1 env creation by 1.32x, and improves Franka Reach env creation by 7.14x.
| Task | Envs | Main env creation s | PR env creation s | Env speedup | Scene speedup | Simulation-start speedup | First-step delta |
|---|---|---|---|---|---|---|---|
| Cartpole | 1024 | 6.584 | 6.803 | 0.97x | 0.90x | 0.99x | +0.1% |
| Ant | 1024 | 7.833 | 7.968 | 0.98x | 0.93x | 1.02x | -32.1% |
| Humanoid | 1024 | 9.208 | 9.288 | 0.99x | 0.92x | 1.06x | -15.4% |
| G1 | 1024 | 16.072 | 12.203 | 1.32x | 1.01x | 2.10x | +8.3% |
| Franka Reach | 1024 | 61.988 | 8.677 | 7.14x | 7.38x | 12.12x | -16.1% |
All five task/revision pairs use 1024 envs. The Franka row uses Isaac-Reach-Franka; Isaac-Lift-Cube-Franka does not expose the newton_mjwarp preset in this Isaac Lab checkout. Normalized data and raw reports are kept with this published page.