Skip to content

perf(rbd): skip inactive joint-limit constraint rows#14

Open
haixuanTao wants to merge 1 commit into
dimforge:mainfrom
haixuanTao:perf/skip-inactive-joint-limits
Open

perf(rbd): skip inactive joint-limit constraint rows#14
haixuanTao wants to merge 1 commit into
dimforge:mainfrom
haixuanTao:perf/skip-inactive-joint-limits

Conversation

@haixuanTao

Copy link
Copy Markdown
Contributor

What

emit_limit_constraint always emits a kind = 1 row and computes its O(ndofs) compute_constraint_column LU back-solve — even when the joint sits strictly inside its bounds. In that state the row has zero positional bias and both impulse clamps at 0 (max_neg_impulse = max_pos_impulse = 0), so it can never apply any impulse: it is dead weight in every PGS sweep, re-paid every substep.

This PR skips emission when !min_enabled && !max_enabled. The slot stays kind = 0 (pre-zeroed by the discovery walk), which the PGS sweeps already skip (if cons.kind == 0 { continue }). Constraints are rebuilt from current coordinates each substep, so a joint reaching its bound re-emits on the next substep exactly as before — result-identical by construction (the skipped rows could only ever contribute a zero impulse).

Why it matters

For articulated robots with limits on every joint, essentially all limit rows are inactive on every substep of normal operation (a standing/walking robot lives well inside its joint ranges). The joint-constraint work then scales as #joints × O(ndofs) back-solves per substep for nothing.

Measured on a downstream biped-RL training stack (RTX 5090, 2,048–8,192 parallel envs, limits on all joints, profiled with Nsight Systems):

  • the joint-constraint kernel was the feat: update deps + release v0.2.0 #1 GPU kernel for a 25-joint humanoid (~28 % of total GPU time);
  • with the skip: +16 % end-to-end training throughput on a full-body Unitree G1, +5 % on a 12-DOF biped;
  • training statistics bit-identical to the unpatched build.

The win grows with joint count, so it compounds for exactly the robots where the multibody solver is most loaded.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Dbpu5NhqXK2Xt3YzUbJRM

emit_limit_constraint always emitted a kind=1 row and computed its
O(ndofs) compute_constraint_column LU back-solve, even when the joint
sits strictly inside its bounds - where the row has zero positional
bias AND both impulse clamps at 0, so it can never apply any impulse.
Every PGS sweep then carried #limited-joints dead rows per multibody
per substep.

Skip emission when !min_enabled && !max_enabled: the slot stays kind=0
(pre-zeroed by the discovery walk), which the PGS sweeps already skip.
Constraints are rebuilt from current coords each substep, so a joint
reaching its bound re-emits on the next substep exactly as before -
result-identical by construction.

Measured on a downstream biped-RL training stack (RTX 5090, 2048-8192
parallel envs, joint limits on all joints): the joint-constraint
kernel was the top GPU kernel for a 25-joint humanoid (~28% of GPU
time - O(joints) rows x O(ndofs) back-solve each, every substep).
With the skip: full-body Unitree G1 training throughput +16%, 12-DOF
biped +5%, with bit-identical training statistics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Dbpu5NhqXK2Xt3YzUbJRM
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