cuDSS issue draft — "sparse solve returns wrong (and non-deterministic) results for symmetric indefinite KKT systems"
Status: ready to file. Target repo: NVIDIA/cudss (issues). Filing checklist at the bottom.
All numbers below are reproducible with the attached MRE (code/cudss_mre.c) and the exported
matrices (code/export_kkt_mm.py + mre_out/iter_*.txt). Raw cross-check logs:
results/gpu01/xcheck.json (jax/spineax path, RTX 3080, cu12) and
results/gpu01/xcheck_a100_patched.log (jax/spineax path, A100, official cu13 wheel).
Title
cudssSolve returns incorrect and non-deterministic results for symmetric indefinite systems (0.8.0.10, both cu12 and cu13 builds)
Summary
A minimal C program that calls the plain cuDSS flow (cudssCreate → cudssExecute(ANALYSIS) →
cudssExecute(FACTORIZATION) → cudssExecute(SOLVE), CUDSS_MTYPE_SYMMETRIC /
CUDSS_MVIEW_UPPER, CSR, double) gets residuals of 0.5–7 in ||Ax-b||_inf on 8 of 15 real
symmetric indefinite KKT matrices of size n=1136 / nnz=5571. LAPACK (numpy.linalg.solve on the
same matrix in the same precision) returns 1e-11 on all 15. Repeated runs of the same binary on
the same input return different residuals every time (e.g. 3.2, 52.3, 43.3, 26.6, 98.1), so the
result is not just inaccurate, it is non-deterministic.
The matrices are the per-iteration KKT systems of an interior-point solver
(z = [x; s; y_c; y_d], symmetric indefinite, inertia roughly [+626, −510]). Some of them are
near-singular (one has a dense-solve residual of ~1e-4 already), but the majority are perfectly
solvable in double precision, and for those the dense reference is at 1e-11.
Environment (both stacks independently reproduce)
|
machine A |
machine B |
| GPU |
A100 80GB PCIe (CC 8.0) |
GeForce RTX 3080 20GB |
| driver |
595.91.07 |
535.309.01 |
| cuDSS |
0.8.0.10 (nvidia-cudss-cu13 pip wheel, linked against nvidia/cu13/lib/libcudss.so.0) |
0.8.0.10 (nvidia-cudss-cu12) |
| CUDA runtime |
13.4.2 (toolkit 12.4 present, unused by the MRE) |
12.6 |
| host |
x86_64 Ubuntu 22.04, gcc 11.4 |
x86_64 WSL2, gcc 11.4 |
The reproducer does not use any language binding — it links cudss.h + libcudss.so.0 directly
and manages all buffers manually, so nothing on our side (JAX, sympy codegen, our own CSR
assembly) is in the loop.
Repro
# build (libs from the nvidia pip wheels; any cuDSS 0.8.0.10 install works)
gcc -O2 -o cudss_mre cudss_mre.c \
-I$VENV/lib/python3.12/site-packages/nvidia/cu13/include \
-I/usr/local/cuda/include \
-L$VENV/lib/python3.12/site-packages/nvidia/cu13/lib \
-l:libcudss.so.0 -l:libcudart.so.13 -lm \
-Wl,-rpath,$VENV/lib/python3.12/site-packages/nvidia/cu13/lib
# one failing system (n=1136, nnz=5571):
./cudss_mre iter_10.txt
# [mre] n=1136 nnz=5571 max_col=1135 b_norm=6.260191e+02
# RESULT n=1136 nnz=5571 ||Ax-b||_inf=1.807062e+01 ||b||_inf=6.145458e+02 rel=2.940484e-02 <-- FAILURE
# determinism: same input, five runs
for i in 1 2 3 4 5; do ./cudss_mre iter_10.txt | grep RESULT; done
# 3.2e+00 / 5.2e+01 / 4.3e+01 / 2.7e+01 / 9.8e+01 (all different)
# reference: dense LAPACK on the same matrix and rhs
python3 -c "import numpy as np; ... " # ||Ax-b||_inf = 4.5e-12
Input format (written by export_kkt_mm.py from the solver's own CSR arrays): n nnz, then
nnz lines of row col val (0-based, upper triangle including diagonal, column indices are
not required to be sorted — see question 3 below), then n right-hand-side values.
Observed (15 exported systems, one solve each)
iter_1 2.268977e+00 FAIL iter_10 (5 runs) 3.2 / 52.3 / 43.3 / 26.6 / 98.1 FAIL
iter_2 9.426486e-10 ok iter_11 3.310241e-11 ok
iter_3 8.441980e-11 ok iter_12 6.407230e-11 ok
iter_4 5.016689e-10 ok iter_13 5.983779e-01 FAIL
iter_5 1.006816e+00 FAIL iter_14 7.709061e-01 FAIL
iter_6 8.308368e-01 FAIL iter_15 4.935396e-01 FAIL
iter_7 1.006560e+00 FAIL
iter_8 7.450581e-09 ok
iter_9 7.250556e-11 ok
Pass rate 7/15. Failing residuals are 12 orders of magnitude above the dense reference.
Additional observation: a single manual iterative-refinement step (compute the residual on the
host, re-solve for the correction with the same factorization) reduces the residual to ~1e-4
for most failing cases (iter_1: 1.9 → 8.2e-5, iter_5: 0.77 → 1.0e-4, iter_13: 0.70 → 2.7e-4),
but makes one case worse (iter_10: 13.6 → 38.3). CUDSS_PHASE_SOLVE includes
CUDSS_PHASE_SOLVE_REFINEMENT in 0.8.0.10, so I would expect the library's own refinement to
cover this; it evidently does not.
Expected
||Ax-b||_inf on the order of eps * cond(A) * ||b||, i.e. what a dense LU gives on the same
inputs (1e-11 … 1e-13 here), and a deterministic answer for a deterministic input.
What would help
- Confirm/deny on your side with the attached MRE + matrices (
iter_*.txt, ~1.5 MB total).
The matrix set covers both "ordinary" and near-singular members of the same family.
- Is there a documented path by which
cudssSolve can silently produce a wrong answer for
CUDSS_MTYPE_SYMMETRIC + CUDSS_MVIEW_UPPER when (a) the matrix is indefinite,
(b) column indices within a row are not sorted, and/or (c) the matrix is near-singular?
Any of these three is present in our set; I have not isolated which one is the trigger.
- The run-to-run variation on identical input suggests a race (library-internal threading?).
Is there a supported way to force single-threaded/deterministic execution to check, other
than rebuilding with a different threading layer?
- Is iterative refinement supposed to run as part of
CUDSS_PHASE_SOLVE, and under what
conditions is it skipped?
Not ruled out on our side
- Our CSR arrays are produced by a code generator (jax2sympy) and are passed exactly as
generated; rowPtr/colInd are validated (0 ≤ idx < n, rowPtr monotone, nnz consistent)
inside the MRE, and the host residual uses a matrix rebuilt from the same arrays, so an
input-construction error would show up as a large residual for the dense solve too — it
does not.
- We have not tried other reorderings (
CUDSS_CONFIG_REORDERING) or the hybrid memory mode.
- Two independent cuDSS builds (cu12 and cu13 wheels) and two different GPUs/drivers show the
same behaviour, which is why I believe this is worth your time rather than a local install
problem.
Attachments
cudss_mre.c — self-contained reproducer (no dependencies beyond cuDSS + CUDA runtime).
iter_1..15.txt — the 15 systems in the format described above.
xcheck.json / xcheck_a100_patched.log — the same failure observed through a Python
binding (for cross-reference only; the C path is the authoritative one).
Filing checklist
cudss_mre_pkg.tar.gz
cuDSS issue draft — "sparse solve returns wrong (and non-deterministic) results for symmetric indefinite KKT systems"
Title
cudssSolvereturns incorrect and non-deterministic results for symmetric indefinite systems (0.8.0.10, both cu12 and cu13 builds)Summary
A minimal C program that calls the plain cuDSS flow (
cudssCreate→cudssExecute(ANALYSIS)→cudssExecute(FACTORIZATION)→cudssExecute(SOLVE),CUDSS_MTYPE_SYMMETRIC/CUDSS_MVIEW_UPPER, CSR, double) gets residuals of 0.5–7 in||Ax-b||_infon 8 of 15 realsymmetric indefinite KKT matrices of size n=1136 / nnz=5571. LAPACK (
numpy.linalg.solveon thesame matrix in the same precision) returns 1e-11 on all 15. Repeated runs of the same binary on
the same input return different residuals every time (e.g. 3.2, 52.3, 43.3, 26.6, 98.1), so the
result is not just inaccurate, it is non-deterministic.
The matrices are the per-iteration KKT systems of an interior-point solver
(
z = [x; s; y_c; y_d], symmetric indefinite, inertia roughly [+626, −510]). Some of them arenear-singular (one has a dense-solve residual of ~1e-4 already), but the majority are perfectly
solvable in double precision, and for those the dense reference is at 1e-11.
Environment (both stacks independently reproduce)
nvidia-cudss-cu13pip wheel, linked againstnvidia/cu13/lib/libcudss.so.0)nvidia-cudss-cu12)The reproducer does not use any language binding — it links
cudss.h+libcudss.so.0directlyand manages all buffers manually, so nothing on our side (JAX, sympy codegen, our own CSR
assembly) is in the loop.
Repro
Input format (written by
export_kkt_mm.pyfrom the solver's own CSR arrays):n nnz, thennnzlines ofrow col val(0-based, upper triangle including diagonal, column indices arenot required to be sorted — see question 3 below), then
nright-hand-side values.Observed (15 exported systems, one solve each)
Pass rate 7/15. Failing residuals are 12 orders of magnitude above the dense reference.
Additional observation: a single manual iterative-refinement step (compute the residual on the
host, re-solve for the correction with the same factorization) reduces the residual to ~1e-4
for most failing cases (iter_1: 1.9 → 8.2e-5, iter_5: 0.77 → 1.0e-4, iter_13: 0.70 → 2.7e-4),
but makes one case worse (iter_10: 13.6 → 38.3).
CUDSS_PHASE_SOLVEincludesCUDSS_PHASE_SOLVE_REFINEMENTin 0.8.0.10, so I would expect the library's own refinement tocover this; it evidently does not.
Expected
||Ax-b||_infon the order ofeps * cond(A) * ||b||, i.e. what a dense LU gives on the sameinputs (1e-11 … 1e-13 here), and a deterministic answer for a deterministic input.
What would help
iter_*.txt, ~1.5 MB total).The matrix set covers both "ordinary" and near-singular members of the same family.
cudssSolvecan silently produce a wrong answer forCUDSS_MTYPE_SYMMETRIC+CUDSS_MVIEW_UPPERwhen (a) the matrix is indefinite,(b) column indices within a row are not sorted, and/or (c) the matrix is near-singular?
Any of these three is present in our set; I have not isolated which one is the trigger.
Is there a supported way to force single-threaded/deterministic execution to check, other
than rebuilding with a different threading layer?
CUDSS_PHASE_SOLVE, and under whatconditions is it skipped?
Not ruled out on our side
generated;
rowPtr/colIndare validated (0 ≤ idx < n, rowPtr monotone, nnz consistent)inside the MRE, and the host residual uses a matrix rebuilt from the same arrays, so an
input-construction error would show up as a large residual for the dense solve too — it
does not.
CUDSS_CONFIG_REORDERING) or the hybrid memory mode.same behaviour, which is why I believe this is worth your time rather than a local install
problem.
Attachments
cudss_mre.c— self-contained reproducer (no dependencies beyond cuDSS + CUDA runtime).iter_1..15.txt— the 15 systems in the format described above.xcheck.json/xcheck_a100_patched.log— the same failure observed through a Pythonbinding (for cross-reference only; the C path is the authoritative one).
Filing checklist
cudss_mre.cand the 15.txtmatrices (or a tarballmre.tar.gz).CUDSS_PHASE_SOLVEincludes the refinement phase and it did not help.returns wrong/non-deterministic results on these inputs" — the numbers speak for themselves.
cudss_mre_pkg.tar.gz