Skip to content

Solve tightly in gradcheck tests rather than loosening atol (fixes #244) - #245

Open
tschm wants to merge 1 commit into
cvxpy:masterfrom
tschm:fix-244-gradcheck-solver-tolerance
Open

tschm wants to merge 1 commit into
cvxpy:masterfrom
tschm:fix-244-gradcheck-solver-tolerance

Conversation

@tschm

@tschm tschm commented Sep 8, 2026

Copy link
Copy Markdown

Fixes #244.

The variable is the tolerance, not the SCS version

scs >= 3.3.0 broke three gradcheck tests. It turns out neither SCS nor the implicit derivative is wrong — both sides of the gradcheck comparison were only accurate to ~1e-4, which is the atol being asserted.

Measured against the closed-form Jacobian of test_dual_gradcheck_vector_equality:

solver tolerance analytic gradient error
SCS default 1.25e-02
eps=1e-10 7.0e-10

Same story for test_sdp: the layer's analytic Jacobian is off by 3.05e-04 at default tolerance and 2.4e-08 at eps=1e-8. So cvxpylayers' derivative machinery is correct, and its accuracy is capped by the forward solve.

The finite difference is worse. At default tolerance its error is pinned at ~3.4e-04 for every step size from 1e-6 to 1e-3 — that is solver noise, not curvature — and only starts scaling with h once the solve is tight:

solver tolerance h=1e-6 h=1e-5 h=1e-4
default 3.35e-04 3.50e-04 3.50e-04
eps=1e-10 8.16e-05 8.24e-06 7.26e-07

3.3.x broke nothing. It stopped over-delivering on the requested tolerance, so the two noise realizations stopped cancelling. The 1.4x margin on 3.2.11 reported in the issue was the noise floor rather than a margin.

The fix

Of the three directions in #244 this is (3): fix the accuracy, don't hide the symptom. No assertion is loosened and no scs bound is added.

  • GRADCHECK_SOLVER_ARGS = {"eps": 1e-10}, passed at layer construction in all 14 diffcp-backed gradcheck tests — not just the three that failed, since every one of them had the same latent exposure.
  • test_sdp additionally takes eps=1e-4 for the finite-difference step. Its optimum is the rank-one projector onto the smallest eigenvector of C, so most entries of dX/dC are near zero and are compared against atol alone; a tight solve alone leaves those sitting exactly at the finite-difference floor.
  • The solver="MOREAU" gradcheck tests are left alone: eps is a diffcp/SCS key and those tests are unaffected.

Margins

From a harness that shrinks atol on each gradcheck test until it fails:

before after, 3.3.1 after, 3.2.11
test_sdp fails 1000x 1000x
test_dual_gradcheck_inequality fails 10,000x 100,000x
test_dual_gradcheck_vector_equality fails >10⁶x >10⁶x
tightest of all 18 gradcheck tests ~1x 1000x 1000x

Verification

  • 87 passed / 40 skipped on both scs==3.3.1 and scs==3.2.11 (baseline: 3 failed / 84 passed on 3.3.1)
  • ruff@0.12.7 check clean on both touched files; ruff format --diff is byte-identical before and after, so the change is format-neutral (tests/test_torch.py is CRLF and already fails format --check on master, along with two other test files — left as-is rather than dragging in ~2700 lines of reformatting)

This should turn test_cvxpylayers in cvxpy's extensions.yml green again.

Upstream

Diagnosis confirmed independently by SCS upstream in cvxgrp/scs#464, which closed with exactly this prescription: "gradcheck tests should pass tight solver tolerances via solver_args, and the SDP check needs either a larger finite-difference step or a comparison against a closed-form derivative." cvxgrp/scs#463 investigated 3.3.0's b-dependent equilibration as the mechanism and ruled it out.

🤖 Generated with Claude Code

Three gradcheck tests failed on scs >= 3.3.0 and passed on 3.2.11. The
cause is not a regression in SCS, cvxpy or the implicit derivative: both
sides of the comparison were only accurate to ~1e-4, which is the atol
being asserted.

Measured against the closed-form Jacobian of the vector-equality case,
the analytic gradient is off by 1.25e-02 at SCS's default tolerance and
by 7.0e-10 at eps=1e-10. The same holds for the SDP (3.05e-04 vs
2.4e-08). So the derivative machinery is correct and its accuracy is
capped by the forward solve. The finite difference is worse still: at
default tolerance its error is pinned at ~3.4e-04 for every step size
from 1e-6 to 1e-3, i.e. pure solver noise, and only scales with h once
the solve is tight.

3.3.x broke nothing. It stopped over-delivering on the requested
tolerance, so the two noise realizations stopped cancelling.

Ask for a solve accurate enough that finite differencing is meaningful,
instead of loosening the assertions or bounding scs:

- GRADCHECK_SOLVER_ARGS = {"eps": 1e-10}, passed at layer construction
  in all 14 diffcp-backed gradcheck tests, not only the three that
  failed - every one of them had the same latent exposure.
- test_sdp also takes eps=1e-4 for the finite-difference step. Its
  optimum is a rank-one projector, so most entries of dX/dC are near
  zero and are compared against atol alone; a tight solve alone leaves
  those at the finite-difference floor.

No assertion is loosened and no version bound is added. The tightest
margin across all 18 gradcheck tests goes from ~1x to 1000x: 87 passed
on both scs 3.3.1 and 3.2.11, against 3 failed / 84 passed before.

The MOREAU-backed gradcheck tests are left alone: eps is a diffcp/SCS
key and those tests are unaffected.

Diagnosis confirmed independently by SCS upstream in cvxgrp/scs#464,
which prescribes this fix. cvxgrp/scs#463 investigated 3.3.0's
b-dependent equilibration as the mechanism and ruled it out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tschm

tschm commented Sep 11, 2026

Copy link
Copy Markdown
Author

@PTNobel please have a look. This seems to be an annoying problem for cvxpy at the moment

@SteveDiamond SteveDiamond left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tighter forward solves and larger SDP finite-difference step are a reasonable fix for solver-noise-sensitive gradchecks. Assertions are preserved and production code is unchanged. Reviewed the diff; I did not rerun the reported local tests, and the historical failing CI job details are no longer available.

This branch has not been deployed

No deployments
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.

gradcheck tests fail with scs >= 3.3.0 (pass on 3.2.11)

2 participants