diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b167333..aa18bd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,6 +102,60 @@ jobs: pixi r pytest rm -rf build/ + build_gpu: + # Build-only: linking against cuBLAS/cuSPARSE needs the CUDA toolkit but + # not a physical GPU, so this validates the use_gpu build/link path without + # running the solver (running would require GPU hardware). + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + strategy: + fail-fast: false + matrix: + python-version: [ "3.12" ] + + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + - uses: prefix-dev/setup-pixi@v0.10.0 + with: + cache: false + pixi-version: v0.46.0 + - name: Add .pixi/envs/default to the $PATH + run: echo "$(pwd)/.pixi/envs/default/bin" >> $GITHUB_PATH + shell: bash + - name: Install dependencies + run: | + pixi add --platform linux-64 scipy numpy pip pytest meson meson-python \ + openblas python==${{ matrix.python-version }} \ + cuda-version=12.* cuda-nvcc cuda-cudart-dev libcublas-dev libcusparse-dev + - name: Build (use_gpu=true) + run: | + export CUDA_PATH="$(pwd)/.pixi/envs/default" + export CUDA_HOME="$CUDA_PATH" + pixi r python -m pip install -v . --no-build-isolation \ + -Csetup-args=-Duse_gpu=true -Csetup-args=-Dint32=true + - name: Check _scs_gpu is linked against cuBLAS and cuSPARSE + # A shared library links successfully even with undefined symbols, so the + # real regression test is that libcublas/libcusparse are recorded as + # NEEDED entries (the bug fixed in #221 was a missing link, seen only at + # import time as `undefined symbol: cusparseDestroySpMat`). + run: | + # Locate the installed extension in the pixi env's site-packages. + so=$(find "$(pwd)/.pixi/envs/default/lib" -name '_scs_gpu*.so' | head -1) + if [ -z "$so" ]; then + echo "ERROR: _scs_gpu*.so not found in the pixi environment" + find "$(pwd)/.pixi/envs/default/lib" -name '_scs_*.so' || true + exit 1 + fi + echo "Inspecting $so" + readelf -d "$so" + readelf -d "$so" | grep -q 'libcublas' || { echo "ERROR: _scs_gpu not linked against cuBLAS"; exit 1; } + readelf -d "$so" | grep -q 'libcusparse' || { echo "ERROR: _scs_gpu not linked against cuSPARSE"; exit 1; } + echo "OK: _scs_gpu links cuBLAS and cuSPARSE" + build_native_arch: runs-on: ubuntu-latest defaults: diff --git a/meson.build b/meson.build index 1d37946..babd9b7 100644 --- a/meson.build +++ b/meson.build @@ -287,17 +287,18 @@ endif if get_option('use_gpu') gpu_c_args = common_c_args + ['-DPY_GPU=1', '-DINDIRECT=1'] - _deps += cuda_dep cublas_dep = cc.find_library('cublas', required: false) if not cublas_dep.found() cublas_dep = dependency('cublas', required: true) endif - _deps += cublas_dep cusparse_dep = cc.find_library('cusparse', required: false) if not cusparse_dep.found() cusparse_dep = dependency('cusparse', required: true) endif - _deps += cusparse_dep + # Scope the CUDA libraries to this target rather than mutating the shared + # `_deps`, so they don't leak into _scs_mkl / _scs_accelerate / _scs_cudss + # when use_gpu is combined with those backends. + gpu_deps = _deps + [cuda_dep, cublas_dep, cusparse_dep] if get_option('gpu_atrans') gpu_c_args += '-DGPU_TRANSPOSE_MAT=1' endif @@ -310,10 +311,12 @@ if get_option('use_gpu') # In Meson, sources with a `.cu` extension are compiled with nvcc by default. # To compile `.c` files with nvcc, they must be explicitly targeted. # It is strongly recommended to rename CUDA-C files to `.cu`. + # Meson's fs module has no glob(), so these are listed explicitly; keep in + # sync with the .c files under scs_source/linsys/gpu{,/indirect}. 'scs_source/linsys/gpu/gpu.c', 'scs_source/linsys/gpu/indirect/private.c', c_args: gpu_c_args, - dependencies: _deps, + dependencies: gpu_deps, include_directories: [ common_includes, 'scs_source/linsys/gpu/', 'scs_source/linsys/gpu/indirect' ], @@ -382,9 +385,10 @@ if host_machine.system() == 'darwin' endif if get_option('link_cudss') - _deps += cuda_dep cudss_dep = dependency('cudss', required: true) - _deps += cudss_dep + # Scope the CUDA libraries to this target rather than mutating the shared + # `_deps` (see the use_gpu block above). + cudss_deps = _deps + [cuda_dep, cudss_dep] py.extension_module( '_scs_cudss', 'scs/scspy.c', @@ -394,7 +398,7 @@ if get_option('link_cudss') spectral_cone_sources, c_args: common_c_args + ['-DPY_CUDSS'], include_directories: common_includes + ['scs_source/linsys/cudss/direct'], - dependencies: _deps, + dependencies: cudss_deps, install_dir: scs_dir, install: true, )