Repository navigation
GPU Change notes #37
justinh2002
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
ISSM GPU Solver — Change Notes
Work to make ISSM run its linear solves on NVIDIA V100 GPUs (NCI Gadi
gpuvoltaqueue) via a CUDA-enabled PETSc. Chronological summary of what waschanged and why.
Files changed / added
externalpackages/petsc/install-3.22-gadi-gpu.shconfigure.sh(gpu step)--with-python=...so Python wrappers buildsrc/m/classes/gpuoptions.pysrc/c/Makefile.amAndersonAccelerator.cppto the buildtest/gpu_kspsolve_test.pytest/run_gpu_kspsolve_test.pbstest/gpu_solver_report.texProblems hit and how they were fixed (in order)
ModuleNotFoundError: IssmConfig_python— GPUconfigurewas missing--with-python, so the compiled Python wrappers (*.so) were never built.Added
--with-python=/apps/intel-python3/.../python3. Wrappers install to$ISSM_DIR/lib, which must be onPYTHONPATH.libk5crypto.so.3: undefined symbol EVP_KDF_ctrl— Intel Python ships anold
libcryptolackingEVP_KDF_ctrl. Fixed at runtime in the PBS scriptby
LD_PRELOAD-ing system OpenSSL 1.1.1k (/lib64/libcrypto.so.1.1,libssl.so.1.1). Not a build change.Tiny problem, GPU slower than CPU — expected. At 50 km (680 DOFs) MUMPS
is sub-second; GPU overhead (CUDA init, CUSPARSE assembly, H2D transfer)
dominates. Built the resolution-sweep benchmark to find the crossover.
GMRES + bjacobi/ILU stalls at 5 km+ — ILU preconditioning quality decays
with mesh refinement. Switched default
pc_typefrombjacobitogamg(algebraic multigrid).
np=4multi-rank → 4 independent rank-0 jobs — root cause:issm.exewas linked against two MPI runtimes at once (
libmpi.so.12MPICH fromGPU PETSc +
libmpi.so.40OpenMPI frommpicxx). Harmless atnp=1, breaksany
np>1. Fixed by rebuilding GPU PETSc against system OpenMPI: removed--download-mpich=1, added--with-cc=mpicc --with-cxx=mpicxx --with-fc=mpif90+ an MPI sanity check in the install script. Requiresrm -rf install-gpu src-gpubefore re-runningsource configure.sh gpu.Link error
undefined reference to AndersonAccelerator::...— after theclean rebuild,
AndersonAccelerator.cppwas missing fromsrc/c/Makefile.am(the file existed and was referenced by
solutionsequence_nonlinear.cppbutnever compiled). Added it to the source list.
GAMG won't converge at 2 km / 1 km — GAMG treated the SSA system as a
scalar matrix. SSA has 2 coupled DOFs/node (vx, vy). Added
mat_block_size = 2so GAMG aggregates by node. Fixed convergence for allresolutions that fit in memory.
np=4on a single GPU is ~4× slower — 4 ranks contend for one V100 andadd MPI overhead inside GAMG with no parallelism gain. Reverted benchmark to
NP = 1. (Multi-rank only helps with one GPU per rank; the MPI fix is stillneeded for that future case.)
2 km / 1 km →
cudaErrorMemoryAllocationinMatProductSymbolic—GAMG's coarse-grid Galerkin product (
MatPtAP) uses CUSPARSE SpGEMM, whosescratch buffers overflow 32 GB at 397k / 1.59M DOFs. Fix is to run the matrix
products on the host (96 GB) while the Krylov iteration stays on the GPU —
but the option name matters: GAMG calls
MatPtAP()directly(
api_user=true), so PETSc reads-matptap_backend_cpu, not the generic-mat_product_algorithm_backend_cpu(which it silently ignored —Option left: ... value: true). Setmatptap_backend_cpu = trueplusmatmatmult_backend_cpu = true(for the inner A·B products of the decomposedPtAP). This cleared the OOM: 2 km then passed at 2.2×.
1 km → true residual stuck at 1.51e-6 (> 1e-6 gate) — not OOM and not
iteration-starved: the residual was bit-identical across
ksp_max_it2000→5000 and
ksp_gmres_restart30→100, proving GMRES was converging to itsrelative tolerance, not exhausting iterations.
ksp_rtol = 1e-10(relativereduction of the preconditioned residual) left the true residual
||KU-F||/||F||at 1.5e-6. Tightenedksp_rtol = 1e-12→ true residual~1.5e-8. 1 km then passed at 3.2×. (Longer
ksp_gmres_restart = 100keptso the extra iterations to reach the tighter tol don't stagnate.)
Multi-GPU (one rank per GPU) — the parallel saga
With single-GPU complete, the next goal was multi-GPU scaling (1 MPI rank per
V100 on a 4-GPU gpuvolta node). PETSc maps rank
r-> GPUr % ndevautomatically (
PETSC_DECIDE, verified incupmdevice.cxx:259), so no bindingwrapper is needed. Getting it to actually converge took a four-step hunt:
CPU MUMPS scaled, every GPU solve failed. First confirmation the OpenMPI
rebuild works at np>1: 4-rank MUMPS sped up (1km: 865→395 s). GPU solves
crashed in
PCGAMGOptimizeProlongator_AGG: "Computed maximum singular valueas zero." (smoothed-aggregation eigenvalue estimate).
pc_gamg_agg_nsmoothssilently ignored. Setting it to integer0hit thetoolkits.pymarshaller bug (if not optionvalue:writes a valueless flagfor falsy values), so PETSc kept its default. Pass option values as
STRINGS (
'0') to survive marshalling. (Latent foot-gun for any option setto integer 0.)
Unsmoothed aggregation then diverged (residual 3–7, worse than the RHS).
A
-use_gpu_aware_mpi 0guess changed nothing (bit-identical residuals), sothat was a red herring — ruling it out mattered.
Isolation diagnostic pinned the real cause (
gpu_mgpu_diagnose.py: onesmall mesh, np=4, several PCs back-to-back in one job). Results:
bjacobi+iluCONVERGED (so the parallel GPU matvec is correct); GAMG withhost-PtAP FAILED but the identical GAMG with GPU-PtAP CONVERGED
(1e-11). So
matptap_backend_cpu=truebuilds a corrupt coarse operator inparallel — the very flag that fixed the single-GPU OOM. The rule:
host-PtAP for single-GPU (memory); GPU-PtAP for multi-GPU (correctness, and
it fits since each rank holds ~1/N of the matrix). Encoded as an NP-based
switch in
gpu_kspsolve_test.py.Multi-GPU benchmark (4× V100, unsmoothed aggregation + GPU-PtAP) — all PASS
Scales sublinearly (~48% efficiency at 1.6M DOFs, growing with size — small
problems are communication-bound). NB the 1→4 comparison mixes a preconditioner
change (multi-GPU used unsmoothed, single-GPU smoothed); a clean strong-scaling
study needs the same config at np=1,2,4.
A100 benchmark and extended investigation
15. Three-column benchmark on A100 (July 2026)
Submitted the same three-solver sweep to the
dgxa100queue (single A100 80GB,ISSM_GPU_PTAP=1). With 80GB HBM2e, the CUSPARSE SpGEMM scratch forMatPtAPfits without OOM (unlike 32GB V100), so GPU-PtAP can run on the device.Result: GPU-PtAP converges on all resolutions up to 1km — but only with
pc_gamg_agg_nsmooths=0(unsmoothed aggregation). Smoothed aggregation(
nsmooths=1) diverges:solver residue too high: 5.38 > 1e-6. Root causeidentified later (see #20).
Hardware speedup improved to 1.40× (vs 1.35× on V100) because GPU-PtAP
eliminates the H2D transfer tax that dominated on V100 (confirmed by nsys: only
~2% of V100 walltime was on GPU with host-PtAP). PBS reported 16% GPU
utilisation with GPU-PtAP vs ~2% with host-PtAP.
16. Solver variants sweep (Chebyshev, BiCGStab, pipelining)
Tested five options combinations on A100 NP=1 at 2km and 1km
(
gpu_speedup_variants_test.py):All within ~6% of each other. No variant breaks through the ~1.40× hardware
ceiling. The CG-eigenestimator fix did not rescue nsmooths=1 (root cause is
different — see #20). cheb+bcgs is marginally best at 1km but the gain is
too small to justify changing the default.
17. Multi-GPU A100 (NP=4)
Ran the benchmark with NP=4 (one rank per A100) on the dgxa100 node.
NP > 1triggers GPU-PtAP + nsmooths=0 automatically ingpu_kspsolve_test.py.Going from NP=1 to NP=4: CPU-GMRES scales 2.1× (375→177s) but GPU-GMRES only
scales 1.5× (267→182s). At NP=4 the GPU barely matches CPU. Reason: with 4
ranks each holding ~400k DOFs, the GMRES global AllReduce (one per iteration)
and halo exchanges dominate. GPU is ~64% utilised but spending most of its time
waiting on MPI rather than computing. PBS: GPU utilisation 64%, GPU memory only
6.35 GB (1/4 of the single-GPU problem).
Single-GPU is the sweet spot for this problem size. Multi-GPU is
communication-limited, not compute-limited.
18. GPU-aware MPI (NP=4)
OpenMPI 4.1.3 on Gadi is compiled with
--with-cuda(smcudaBTL,MCA coll: cuda), so GPU-aware MPI can be enabled by removing-use_gpu_aware_mpi 0. Tried this on NP=4 to eliminate theGPU→CPU→MPI→CPU→GPU round-trip per iteration.
Result: GPU GMRES fails at 2km and 1km (residual 42026 at 2km, 1.43 at 1km).
Small resolutions pass (GPU-PtAP not triggered). The GPU-aware MPI transfers
corrupt the parallel coarse operator assembly during GPU-PtAP. UCX's CUDA IPC
path cannot safely share device pointers across CUDA contexts on different GPUs.
GPU-aware MPI is incompatible with GPU-PtAP at NP>1. Keep
-use_gpu_aware_mpi 0for all multi-GPU runs.19. Pipelined Krylov (NP=4, GPU-aware MPI off)
Pipelined variants (
pipefgmres,pipebcgs) restructure the Krylov recurrenceto overlap AllReduce(k) with SpMV(k+1), hiding the MPI latency.
Pipelining is slightly slower than standard GMRES at NP=4. On a single DGX
node the 4 A100s are connected by NVLink (~600 GB/s), so the AllReduce latency
is already negligible. The pipeline overhead (extra synchronisation checkpoints
in the recurrence) outweighs any latency hiding. Pipelining would only help for
inter-node communication (high-latency Infiniband), not intra-node NVLink.
Note: the CPU-GMRES baseline in this test was inadvertently set to nsmooths=0
(same as GPU), making it appear slower (421s at 1km). The apparent 2.29×
hardware speedup is misleading — it measures GPU vs CPU at the same (weaker)
preconditioner quality, not best-vs-best. The true hardware speedup at NP=4
remains ~0.97× (GPU-nsmooths=0 vs CPU-nsmooths=1).
20. nsmooths=1 GPU-PtAP root cause (CUSPARSE_STATUS_INSUFFICIENT_RESOURCES)
Definitive diagnosis via
gpu_nsmooths_diag.py— five variants at A100 NP=1,sweeping GPU/CPU assignment for MatMatMult (prolongator smoothing: A×P₀) and
MatPtAP (coarse operator: P^T×A×P) independently.
The PETSc error log:
Root cause: with nsmooths=1, the smoothed prolongator P has more non-zeros
than the tentative P₀ (nsmooths=0). The CUSPARSE SpGEMM symbolic phase for
P^T×A×P then exceeds CUSPARSE's internal hardware resource limit (not device
memory — a workspace/occupancy limit in the SpGEMM kernel). This is a hard
CUSPARSE constraint that cannot be worked around via command-line options.
Isolation results:
n1-all-gpu(GPU-MatMul + GPU-PtAP, n=1): FAIL (CUSPARSE crash → residual 5.38)n1-mul-cpu(CPU-MatMul + GPU-PtAP, n=1): FAIL (same CUSPARSE crash in MatPtAP)n1-ptap-cpu(GPU-MatMul + CPU-PtAP, n=1): ~926s at 2km (extremely slow but passes)n1-all-cpu(CPU-MatMul + CPU-PtAP, n=1): 49s at 2km (PASS, only 7% slower than n0-gpu)The fix for n1 is to put both MatMatMul and MatPtAP on CPU. At 2km this adds
only 7% overhead vs n0-gpu because the better preconditioner (nsmooths=1) needs
fewer GMRES iterations. This is essentially the same trade-off as the original
V100 host-PtAP approach: CPU setup overhead offset by fewer Krylov iterations.
The GPU-PtAP improvement and the nsmooths degradation cancel each other out:
both V100-host-PtAP+nsmooths=1 and A100-GPU-PtAP+nsmooths=0 land near 3.07–3.13×
vs MUMPS at 1km. This is a fundamental result: the CUSPARSE SpGEMM workspace
limit prevents smoothed aggregation from running on GPU with large matrices,
and the preconditioner quality loss from nsmooths=0 offsets the GPU-PtAP gain.
Final
gpuoptionsdefaultsThese defaults target single-GPU. For multi-GPU the driver overrides PtAP
to run on the GPU (host-PtAP is broken in parallel):
matptap_backend_cpu=false,matmatmult_backend_cpu=false,mat_product_algorithm_backend_cpu=false. Pass all option values as strings(toolkits.py marshalling drops falsy/zero values written as bare flags).
Benchmark status (single V100, SquareShelf SSA, np=1) — COMPLETE, all PASS
Three-solver sweep isolates the hardware and algorithm contributions separately:
The CPU GMRES+GAMG column uses identical GAMG tuning (
mat_block_size=2,ksp_rtol=1e-12, etc.) with onlyvec_type=standardandmat_type=aij(noCUDA) — so the two rightmost columns change exactly one variable: the device.
Reading the numbers:
iterative O(N) solver beats direct O(N^{3/2}) on the CPU alone. This is the
dominant effect and grows with N.
true GPU contribution — and it will grow further with the next resolution level
as the bandwidth advantage of HBM2 over DRAM compounds.
Correctness: GPU vs CPU-MUMPS
Vxrelative diff ≤ ~4e-11 throughout.Rebuild / run cheatsheet
Note: edits to
gpuoptions.pyor the test script are Python-only — norebuild needed, just resubmit the PBS job. Only C/PETSc/Makefile.am changes
require
source configure.sh gpu.All reactions