Skip to content

perf(mb): keep the contact-constraint back-solve column in registers#23

Open
haixuanTao wants to merge 1 commit into
dimforge:mainfrom
haixuanTao:perf/mb-finalize-local-column
Open

perf(mb): keep the contact-constraint back-solve column in registers#23
haixuanTao wants to merge 1 commit into
dimforge:mainfrom
haixuanTao:perf/mb-finalize-local-column

Conversation

@haixuanTao

Copy link
Copy Markdown
Contributor

What

gpu_mb_finalize_contact_constraints back-solves M · column = Jᵀ for every
multibody contact constraint, in place in the global
contact_constraint_columns buffer.

Why it's slow

The dense LU solve (permute + forward-sub + back-sub) read-modify-writes the
column O(n²) times. On the GPU L1 is write-evict for global stores, so each
dependent read in the solve's dependence chain stalls on an L2 round-trip — the
column never stays resident across the chain.

Change

Hold the column in a per-thread local [f32; 64] (registers / L1-backed local
memory), run the solve there, and write it out once. lu_solve_in_place is
#[inline], so it lowers directly onto the local array — no cross-function
storage traffic. Same arithmetic, same iteration order → bit-identical
inv_lhs and contact_constraint_columns. 17-line diff, one file.

Measurement (provenance)

On a downstream fork running a tree-sparse LᵀDL variant of this exact kernel, the
identical change cut the finalize kernel 2.36× (1449 → 615 µs/call; G1
29-DOF, 8192 envs, RTX 5090). Upstream's dense LU does more global RMW per
solve, so the win should be at least as large here. I did not have an
upstream-stock GPU setup to re-measure on — flagging that honestly so a
maintainer can confirm on your bench.

`gpu_mb_finalize_contact_constraints` solved `M·column = Jᵀ` in place in
the GLOBAL `contact_constraint_columns` buffer. The dense LU back-solve
(permute + forward + backward substitution) read-modify-writes that
column O(n²) times, and each dependent read stalls on an L2 round-trip:
GPU L1 is write-evict for global stores, so a write isn't visible to the
next read until it reaches L2. Hold the column in a per-thread local
`[f32; 64]` instead (registers / L1-backed local memory) and write it out
once at the end. `lu_solve_in_place` is `#[inline]`, so it lowers onto the
local array with direct indexing — no cross-function storage traffic.
Same arithmetic and iteration order → bit-identical `inv_lhs` and columns.

Measured on a downstream fork (a tree-sparse LᵀDL variant of this kernel),
the identical change cut the finalize kernel 2.36× (1449→615 µs/call, G1
29-DOF, 8192 envs). Upstream's dense LU does even more global RMW per
solve, so the effect should be at least as large. I did not have an
upstream-stock GPU setup to re-measure on — flagging that honestly.
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