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

PR head: 5205aa49 Baseline: origin/main @ 31b06fe0 Device: cpu (Intel Xeon Icelake, 16 cores) Scenario: 75 shapes/world, 2,775 filter pairs/world Best of 3 runs per point Data: 2026-07-02

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.

6.50xreplicate + finalize speedup at 2048 worlds (11.7 s → 1.8 s)
-41%peak RSS at 2048 worlds (2.84 GB → 1.67 GB)
13xfaster mesh content hash at ≥66k vertices
0contact/excluded pair-count deviations vs baseline

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

Worldsmain replicate+finalize sPR replicate+finalize sSpeedupmain peak RSS MBPR peak RSS MBmain excluded-array sPR excluded-array s
320.1360.0294.65x4184010.0480.117
1280.4730.1004.74x5414700.2760.733
5122.3110.7163.23x1,0177261.6303.872
10245.0680.9505.34x1,6321,0463.9345.110
204811.7211.8046.50x2,8391,67210.38111.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 pathMaterialized fallbackDelta
replicate s0.4810.896-46.3%
finalize s2.2664.751-52.3%
excluded-array build s3.8742.562+51.2%
total incl. excluded array s6.6218.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.

Device: cuda:0 Baseline commit: 2c242002 Current commit: ba838612 World counts: 1, 10, 100, 512, 1024, 2048, 4096, 8192 Repeats: 1 child process per point Data: 2026-06-15 16:35:16 -0700
7robot scenes completed on both branches
56world-count points per branch
-71.2%aggregate 8192-world finalize time
33.2% to 78.8%8192-world per-scene improvement range

Interactive all-in-one plot

8192-world summary

Sceneorigin/main mscurrent msDeltaSpeedup
robot_allegro_hand10,274.364,450.54-56.7%2.31x
robot_anymal_d23,184.564,922.76-78.8%4.71x
robot_cartpole794.30530.74-33.2%1.50x
robot_g177,283.2418,212.30-76.4%4.24x
robot_h120,631.517,179.53-65.2%2.87x
robot_panda_hydro16,155.236,778.28-58.0%2.38x
robot_ur109,645.193,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.

Generated: 2026-06-16T05:53:38+00:00 Newton main: 2c24200200b1 PR 3187: 172dbda45f6a Isaac Lab: a59603d2eaf2 Preset: newton_mjwarp CPU cap: 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.

TaskEnvsMain env creation sPR env creation sEnv speedupScene speedupSimulation-start speedupFirst-step delta
Cartpole10246.5846.8030.97x0.90x0.99x+0.1%
Ant10247.8337.9680.98x0.93x1.02x-32.1%
Humanoid10249.2089.2880.99x0.92x1.06x-15.4%
G1102416.07212.2031.32x1.01x2.10x+8.3%
Franka Reach102461.9888.6777.14x7.38x12.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.