Skip to content

Harden host-side sparse index width and allocation safety - #2822

Draft
LwhJesse wants to merge 1 commit into
su2code:developfrom
LwhJesse:fix/host-sparse-index-overflow
Draft

Harden host-side sparse index width and allocation safety#2822
LwhJesse wants to merge 1 commit into
su2code:developfrom
LwhJesse:fix/host-sparse-index-overflow

Conversation

@LwhJesse

@LwhJesse LwhJesse commented May 25, 2026

Copy link
Copy Markdown

Proposed Changes

Introduce su2_index_t as the host-side sparse index type:

using su2_index_t = std::uint64_t;

Use it through the host sparse-pattern path, including graph-toolbox aliases, geometry sparse-pattern accessors, CSysMatrix LDU metadata, CSysVector dimensions, and PaStiX sparse input handling.

Add checked arithmetic for sparse storage sizes and checked casts at external boundaries. CUDA device sparse indices remain su2uint; host indices are checked before upload.

This prevents silent overflow or truncation in large host-side sparse matrices. On LLP64 platforms, unsigned long is 32-bit, so products such as nnz_blocks * nVar * nEqn can wrap before allocation. For example, a 5 x 5 double block matrix crosses the 32-bit unsigned limit at:

171,798,692 * 25 = 4,294,967,300 entries
4,294,967,300 * 8 bytes = 34,359,738,400 bytes

The affected paths now fail explicitly instead of allocating with wrapped sizes.

Related Work

Keeps the LDU/custom-kernel direction discussed in #2838. This does not replace the current CUDA sparse backend with cuSPARSE.

Validation:

  • git diff --check
  • uvx --python 3.10 pre-commit run -a -v
  • ninja -C build
  • ninja -C build-verify-cuda
  • ninja -C build-verify-pastix
  • ninja -C build-harness-omp-cpu
  • periodic2d OpenMP regression, SU2_CFD -t 2, passed at iteration 1400 within 1e-4
  • fea_topology/quick_start/settings.cfg, SU2_CFD, completed with Exit Success

PR Checklist

  • I am submitting my contribution to the develop branch.
  • My contribution generates no new compiler warnings (try with --warnlevel=3 when using meson).
  • My contribution is commented and consistent with SU2 style (https://su2code.github.io/docs_v7/Style-Guide/).
  • I used the pre-commit hook to prevent dirty commits and used pre-commit run --all to format old commits.
  • I have added a test case that demonstrates my contribution, if necessary.
  • I have updated appropriate documentation (Tutorials, Docs Page, config_template.cpp), if necessary.

@LwhJesse

LwhJesse commented May 27, 2026

Copy link
Copy Markdown
Author

After #2816, I think the follow-up direction needs a policy decision before I move #2822 or the resource-cache draft further.

I currently have two related follow-ups:

The next steps depend on which CUDA sparse-backend direction the project prefers. I see four practical routes:

  1. Modern cuSPARSE-only route: keep the generic cusparseSpMV() BSR path from #2816, require CUDA/cuSPARSE 13.1+ for that backend, add a clear configure/build guard for unsupported CUDA/cuSPARSE 13.0 or older versions, and move both host and device sparse indices to fixed 64-bit.

  2. Transitional host-side hardening route: keep #2822 limited to host-side sparse-index hardening. Host indices become fixed 64-bit, while the current CUDA boundary keeps its existing index type with checked conversion.

  3. Split-backend compatibility route: keep the modern generic cuSPARSE BSR path for CUDA/cuSPARSE 13.1+, but add a CUDA/cuSPARSE 13.0-or-older fallback. That fallback could be either the legacy int32 BSR API, or a corrected custom CUDA kernel.

  4. Full custom-kernel route: move away from the generic cuSPARSE BSR path and maintain a corrected SU2 custom CUDA matvec kernel instead. This would avoid the CUDA/cuSPARSE 13.1+ requirement, but it would also change the direction of the #2816-based resource-cache draft.

My technical preference is route 1, because it gives the cleanest correctness story and the cleanest future GPU-resident Krylov path. If that is too large as the immediate next step, route 2 is a reasonable limited scope for #2822.

Could the maintainers confirm which direction they prefer before I mark #2822 ready or move the #2816-based resource-cache draft into the main project?

@pcarruscag

pcarruscag commented May 30, 2026

Copy link
Copy Markdown
Member

I think both are going too deep into the software engineering weeds.
I would see an end-to-end linear solve on GPUs, product, preconditioner, vector-vector operations, before we start worrying about these portability and caching aspects.

pcarruscag added a commit that referenced this pull request Aug 2, 2026
## Proposed Changes

This PR adds an end-to-end CUDA linear solve path: the Krylov solvers
keep their host control flow, and `CSysVector` operations, the SpMV and
the Jacobi preconditioner are dispatched to CUDA kernels when
`ENABLE_CUDA=YES`.

Transfers are explicit and owned by the object responsible for the data,
with no coherency or dirty-flag tracking in `CSysVector` / `CSysMatrix`:
- `CSysMatrixVectorProduct` uploads the matrix on construction
- the preconditioner uploads its data in `Build()`
- `CSysSolve` uploads `b` and `x` and downloads `x` in
`HandleTemporariesIn/Out`, which is also where device evaluation is
switched on and off
- preconditioners without a device implementation (ILU, LU-SGS, Linelet,
PaStiX) download the input, apply on the host, and upload the result

A solve is therefore fully device resident for Identity and Jacobi
preconditioners: **2 uploads and 1 download per linear system**,
independent of the Krylov subspace size.

Implementation notes:
- `cuBLAS` for `dot` / `norm`, custom kernels for the block-LDU SpMV,
the Jacobi apply, and `CSysVector` expression assignment
- `CSysVector` operands are captured in expressions by value, so an
arbitrary expression tree is trivially copyable into the assignment
kernel; the required expression shapes are explicitly instantiated in
`CSysVectorGPU.cu`, consistent with how `CSysMatrix` is instantiated
- device work is issued by a single thread with the OpenMP team
synchronized around it, so the GPU path is usable from inside the
existing parallel regions; this is internal to the linear algebra layer
- the CUDA translation units are only linked into the primal libraries,
since they cannot be compiled with the CoDiPack defines; device dispatch
is compiled out of the AD builds
- adds `LINEAR_SOLVER_PREC= NONE` (identity)

## Related Work

Follows the review direction in #2822 (show a working end-to-end GPU
linear solve before splitting out infrastructure) and the implementation
preferences in #2816.

## Validation

- CPU vs GPU on inviscid NACA0012, 25 iterations, RTX 4070 Ti SUPER
(sm_89), for FGMRES + `JACOBI`, FGMRES + `NONE`, FGMRES + `ILU` (host
preconditioner path) and BCGSTAB. Results agree to the printed precision
in double, and to ~6 significant figures in mixed precision. BCGSTAB
agrees exactly once the linear system is converged
(`LINEAR_SOLVER_ERROR=1e-10`); at loose tolerances the two paths diverge
through BCGSTAB's own sensitivity, not a difference in the algebra.
- Mixed, normal and single precision builds all compile and run; device
dispatch confirmed live in each (kernel launch counts track the subspace
size).
- `OMP_NUM_THREADS=1` and `4` give bit-identical results on the GPU
path.
- Builds verified: primal, primal + AD + directdiff, `enable-cuda` +
`with-omp`, mixed / normal / single precision.
- Transfer counts measured per solve: 2 H2D + 1 D2H + 1 matrix upload
for Jacobi and `NONE`, plus one download/upload pair per preconditioner
application for ILU.

Earlier validation of the original design (6 representative cases,
`nsys` / `ncu` profiling) predates the rework of the transfer and
dispatch model and should be repeated.

## PR Checklist

- [x] I am submitting my contribution to the develop branch.
- [x] My contribution generates no new compiler warnings (try with
--warnlevel=3 when using meson).
- [x] My contribution is commented and consistent with SU2 style
(https://su2code.github.io/docs_v7/Style-Guide/).
- [x] I used the pre-commit hook to prevent dirty commits and used
`pre-commit run --all` to format old commits.
- [ ] I have added a test case that demonstrates my contribution, if
necessary.
- [ ] I have updated appropriate documentation (Tutorials, Docs Page,
config_template.cpp), if necessary.

---------

Co-authored-by: Pedro Gomes <pcarruscag@gmail.com>
@LwhJesse
LwhJesse force-pushed the fix/host-sparse-index-overflow branch 2 times, most recently from 9eafcf7 to 0a86312 Compare August 10, 2026 09:41
@LwhJesse
LwhJesse force-pushed the fix/host-sparse-index-overflow branch from 0a86312 to 780448d Compare August 10, 2026 12:31

@pcarruscag pcarruscag left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that the su2uint I introduced addresses this, and we have custom kernels for almost everything now, since we switched to the LDU format.
So this work is probably not needed anymore? What do you think?

@LwhJesse

Copy link
Copy Markdown
Author

I think that the su2uint I introduced addresses this, and we have custom kernels for almost everything now, since we switched to the LDU format. So this work is probably not needed anymore? What do you think?

I checked the current develop again. The specific overflow risk this PR addresses still exists independently of the CUDA sparse backend.

su2uint fixes the sparse pattern index type at 32 bits, but the host-side storage size is still computed from expressions such as nnz_l * nVar * nEqn and passed through unsigned long. On Windows LLP64, unsigned long is 32-bit, so the block pattern itself can fit in 32 bits while the scalar-entry count overflows.

For example, 171,798,692 * 5 * 5 = 4,294,967,300, which is just above UINT32_MAX. That can wrap before allocation even though nnz_l itself is valid.

So the switch to LDU/custom CUDA kernels removes the old cuSPARSE-related concern, but it does not remove this host-side allocation/index-width issue.

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.

2 participants