Repository navigation
Scale the noslip cost-change guard to float32 rounding - #1964
Merged
adenzler-nvidia merged 1 commit intoNov 13, 2026
Merged
adenzler-nvidia merged 1 commit into
adenzler-nvidia merged 1 commit into
Conversation
The noslip solver restores a contact's old friction forces when an update raises the cost by more than 1e-10, MuJoCo's double-precision threshold. In float32 the cost change of a nearly converged update is below the rounding of the new forces: on an active cone row the gradient is not zero, so one ulp of force moves the cost by about eps * |force * gradient|. The guard then rejects valid updates depending on rounding, and because a rejected update leaves the state unchanged, every later sweep rejects it again and the contact stalls short of MuJoCo's solution. Add that rounding bound to the threshold in the pyramidal and elliptic guards, so only a cost increase above float32 noise restores the old forces.
thowell
approved these changes
Nov 13, 2026
thowell
left a comment
Collaborator
There was a problem hiding this comment.
LGTM! Thanks @adenzler-nvidia -- nice catch and clean fix scaling the noslip cost-change guard to float32 force-rounding noise while preserving zero-force rollback on failed QCQPs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The noslip solver restores a contact's old friction forces when an update raises the cost by more than 1e-10, which is MuJoCo's double-precision threshold. In float32 that is below the rounding of the new forces: on an active cone row the gradient is not zero, so one ulp of force moves the cost by about eps * |force * gradient|, a few 1e-7 in the test below. Near convergence the guard therefore rejects valid updates depending on rounding, and because a rejected update leaves the state unchanged, every later sweep rejects it again and the contact stalls short of MuJoCo's solution. This showed up as a CPU-only failure of
test_noslip43in #1879, which only changes the main solver's rounding.This adds that rounding bound to the threshold in the pyramidal and elliptic guards, with the same 8 * eps factor the line search uses. Evaluating the cost change in float64 does not help, because the noise is in the rounded forces and not in the arithmetic. A failed QCQP returns zero force, for which the bound is zero, so those updates are still restored as before.
The new test sweeps the spin of a sphere on a plane (elliptic, condim 6) over 61 worlds; on main some of these worlds miss MuJoCo's qacc by up to 1.9e-4 relative. In the pyramidal guard the rejected updates I found were only 1-2 ulps, so that change is for consistency and has no test of its own.
humanoid at 8192 worlds on an RTX PRO 6000 Blackwell, main → this PR in steps/s: 2.009M → 2.010M with
noslip_iterations=3, 1.919M → 1.918M with elliptic cones as well, 9.128M → 9.125M with noslip off.