Skip to content

sparse solve returns wrong (and non-deterministic) results for symmetric indefinite KKT systems #371

Description

@binxuwang2002-lang

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

  1. 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.
  2. 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.
  3. 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?
  4. 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

  • attach cudss_mre.c and the 15 .txt matrices (or a tarball mre.tar.gz).
  • state versions exactly as in the table above.
  • include the determinism loop output verbatim.
  • mention that CUDSS_PHASE_SOLVE includes the refinement phase and it did not help.
  • do not claim "this is a bug in NVIDIA's library"; claim "the plain documented flow
    returns wrong/non-deterministic results on these inputs" — the numbers speak for themselves.

cudss_mre_pkg.tar.gz

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions