Skip to content

Fix CUDA initialization race on Windows with CUDA 13 - #114

Merged
ZedongPeng merged 1 commit into
MIT-Lu-Lab:mainfrom
LucasBoTang:fix/gpu-init-race
Oct 27, 2026
Merged

ZedongPeng merged 1 commit into
MIT-Lu-Lab:mainfrom
LucasBoTang:fix/gpu-init-race

Conversation

@LucasBoTang

Copy link
Copy Markdown
Collaborator

On Windows with CUDA 13, the first solve sometimes fails with:

** On entry to cusparseCreate(): CUDA context cannot be initialized
cuSPARSE Error at .../src/solver.cu:404: CUSPARSE_STATUS_NOT_INITIALIZED

cupdlpx_gpu_init_async() calls cudaFree(0) on a background thread to warm up the GPU. If preprocessing is fast, the solver thread calls cusparseCreate() before that warmup has finished, and on Windows with CUDA 13 this call can fail.

Why only this setup? On Windows, CUDA 13 programs no longer use their own copy of the CUDA runtime. They use a shared one that comes with the GPU driver (nvcudart_hybrid64.dll), and the CUDA 13 build of cuSPARSE uses it too. So the warmup thread and cusparseCreate() end up in the same runtime at the same time. A second cudaFree(0) would wait for the warmup to finish, but cusparseCreate() fails instead. I can't see why, since that code is closed source.

With CUDA 12, the program and cuSPARSE each have their own runtime, so the two threads never touch the same one. Linux doesn't use the shared driver runtime either. That is why the comment on cupdlpx_gpu_init_async(), which says other threads wait for the warmup, was true until now.

I tested this with a small program: start cudaFree(0) on a background thread, then call cusparseCreate() on the main thread. Each row is 40 runs, each in a new process, on an RTX 5080 with driver 610.60. It only fails when both the runtime and cuSPARSE come from CUDA 13 on Windows.

Platform CUDA runtime cuSPARSE Failures
Windows 12.4 12.4 0/40
Windows 12.9 12.9 0/40
Windows 12.9 from CUDA 13.4 0/40
Windows 13.4 from CUDA 12.9 0/40
Windows 13.4 13.4 14/40
Linux (WSL) 13.3 13.3 0/40

The fix adds CUDA_CHECK(cudaFree(0)) on the solver thread right before the cuSPARSE and cuBLAS handles are created. The solver thread now waits for the GPU to be ready before using it. The warmup still runs in the background while the file is parsed and presolved, so we keep that speedup. The extra call runs once per solve. I also updated the comment on cupdlpx_gpu_init_async().

To reproduce on 724894e, open an x64 MSVC developer shell with a conda environment that has CUDA 13.4 (conda-forge cuda-version=13.4), CMake and Ninja. The /Zc:preprocessor flag is needed to build with CUDA 13 on MSVC until #113 is merged.

git clone https://github.com/MIT-Lu-Lab/cuPDLPx.git cupdlpx-init-repro
cd cupdlpx-init-repro
git checkout 724894e

$env:CUDA_PATH = Join-Path $env:CONDA_PREFIX "Library"
$env:CUDACXX = Join-Path $env:CUDA_PATH "bin/nvcc.exe"
$env:CMAKE_GENERATOR_PLATFORM = ""
$env:CMAKE_GENERATOR_TOOLSET = ""
$env:CUDAFLAGS = "-Xcompiler=/Zc:preprocessor"
cmake -S . -B build -G Ninja `
  -DCMAKE_BUILD_TYPE=Release `
  -DCMAKE_CUDA_ARCHITECTURES=120 `
  -DCUPDLPX_BUILD_TESTS=ON `
  -DCUPDLPX_BUILD_PYTHON=OFF
cmake --build build --target test_interface
$env:PATH = "$PWD/build/_deps/pslp-build;$env:CUDA_PATH/bin;$env:PATH"
ctest --test-dir build --repeat until-fail:20 --output-on-failure

Each repeat runs in a new process. On my machine, test_interface failed with the error above in 27 of 40 runs on 724894e. With this fix, it passed all 40.

With the fix, the small test program also passed 40 of 40 runs. Together with the reduction workspace fix, which will be a separate PR, all 107 Python tests passed 5 times in a row on CUDA 13.4 with pytest's default output capture, and also passed on CUDA 12.9. I have not tested HIP.

@ZedongPeng
ZedongPeng merged commit c8bf0e5 into MIT-Lu-Lab:main Oct 27, 2026
18 checks passed
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