Skip to content

fix(qdp-core): link cudart locally and stub FFI when toolkit is absent - #1321

Merged
ryankert01 merged 3 commits into
mainfrom
qdp-core-cudart-linkage
Jun 26, 2026
Merged

fix(qdp-core): link cudart locally and stub FFI when toolkit is absent #1321
ryankert01 merged 3 commits into
mainfrom
qdp-core-cudart-linkage

Conversation

@andrewmusselman

@andrewmusselman andrewmusselman commented May 17, 2026

Copy link
Copy Markdown
Contributor

Closes #1318 .

qdp-core/build.rs (extended; it already existed for protoc) now:

  • probes for nvcc with the same logic as qdp-kernels/build.rs,
  • emits cargo:rustc-link-search=native=$CUDA_PATH/lib64 and
    cargo:rustc-link-lib=cudart when found,
  • emits cargo:rustc-cfg=qdp_no_cuda when not found, with a clear
    cargo:warning pointing at the toolkit install,
  • respects QDP_NO_CUDA=1 for explicit forcing (matching qdp-kernels).
    qdp-core/src/gpu/cuda_ffi.rs wraps the extern "C" block in
    #[cfg(not(qdp_no_cuda))] and adds matching #[cfg(qdp_no_cuda)] mod no_cuda_stubs { ... } with pub(crate) unsafe fn stubs for all 14
    declared functions. Each stub returns 999 — the same sentinel
    qdp-kernels uses for its kernel-launcher stubs — so existing caller
    error paths (if ret != 0 { return Err(...) }) surface a clean runtime
    error if anyone calls a CUDA function on a no-toolkit build, instead of
    failing at link time. The whole stub module is wrapped in a single
    #[allow(non_snake_case)] since the originals are camelCase to match
    the real CUDA Runtime API.

Cross-crate behaviour after this PR:

environment qdp-kernels qdp-core result
toolkit installed links cudart links cudart normal GPU build
driver only / macOS / CI stub launchers stub Runtime API links; runtime err 999
QDP_NO_CUDA=1 stub launchers stub Runtime API links; runtime err 999

Verified on Linux + CUDA 12.4: cargo build --workspace --tests --exclude qdp-python succeeds both with and without QDP_NO_CUDA=1;
make test_rust runs full integration tests on the GPU; 12 lint
warnings introduced by the stubs are silenced.

@ryankert01

ryankert01 commented May 19, 2026

Copy link
Copy Markdown
Member

Like the high-level idea:

  • With CUDA toolkit installed: normal GPU-enabled build.
  • Without CUDA toolkit: build still links successfully, but CUDA calls return a controlled runtime error.
  • With QDP_NO_CUDA=1: same forced no-CUDA behavior.

@ryankert01 ryankert01 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.

LGTM. Two small things worth doing. Also, needs to fix the pre-commit.

Comment thread qdp/qdp-core/build.rs
/// This function:
/// * emits `cargo:rustc-link-lib=cudart` and the appropriate
/// `cargo:rustc-link-search` path when nvcc is found, and
/// * emits `cargo:rustc-cfg=qdp_no_cuda` when it is not, gating the

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.

output().is_ok() is true even if nvcc exits non-zero — a half-installed nvcc would set has_cuda = true and fall through to a link error. Suggest .map(|o| o.status.success()).unwrap_or(false). Same idiom in qdp-kernels/build.rs:177; fix both so they can't disagree.

Comment thread qdp/qdp-core/build.rs
///
/// `qdp-core` declares CUDA Runtime API extern symbols in `src/gpu/cuda_ffi.rs`
/// (cudaHostAlloc, cudaMemGetInfo, cudaEventCreateWithFlags, ...). Those symbols
/// must be resolved at link time, which requires `libcudart` from the CUDA

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.

Missing cargo:rerun-if-env-changed=PATH. The whole decision hinges on nvcc-on-PATH, so installing CUDA after a stub build won't re-trigger the script until cargo clean. Same gap in qdp-kernels.

@ryankert01 ryankert01 changed the title qdp-core: own the cudart link directive; gate extern block on qdp_no_cuda feat(qdp-core): own the cudart link directive; gate extern block on qdp_no_cuda May 19, 2026
@ryankert01 ryankert01 changed the title feat(qdp-core): own the cudart link directive; gate extern block on qdp_no_cuda fix(qdp-core): link cudart locally and stub FFI when toolkit is absent May 19, 2026
@ryankert01
ryankert01 force-pushed the qdp-core-cudart-linkage branch from 059cb3d to 1ff2f68 Compare June 22, 2026 17:14
@ryankert01 ryankert01 assigned ryankert01 and unassigned ryankert01 Jun 22, 2026
@ryankert01
ryankert01 force-pushed the qdp-core-cudart-linkage branch from 1ff2f68 to 8b1f340 Compare June 26, 2026 18:05
Address review feedback on the CUDA-detection probe shared by qdp-core
and qdp-kernels build scripts:

- output().is_ok() was true even when nvcc exited non-zero, so a
  half-installed toolkit would set has_cuda = true and fall through to a
  link error. Use .map(|o| o.status.success()).unwrap_or(false) in both
  scripts so the two probes can't disagree.
- Add cargo:rerun-if-env-changed=PATH so installing the toolkit after a
  stub build re-triggers detection without `cargo clean`.

Also run cargo fmt on the no_cuda_stubs module in cuda_ffi.rs to clear
the pre-commit fmt hook (was failing the test job).
This PR lets _qdp build and import without the CUDA toolkit (stub CUDA
Runtime symbols), so "extension importable" no longer implies a usable
GPU. The conftest auto-skip only checked extension availability, so on a
GPU-less runner the @pytest.mark.gpu tests ran against the stub engine
and aborted the pytest worker (SIGABRT) instead of being skipped.

- conftest: add a torch.cuda.is_available() probe and skip
  @pytest.mark.gpu tests when no device is present, matching the inline
  torch.cuda.is_available() guards already used across the QDP modules.
  The existing "extension missing" skip behaviour is unchanged.
- mark test_synthetic_loader_batch_count @pytest.mark.gpu: it iterates a
  synthetic loader, which encodes on the GPU.

Verified locally: with CUDA hidden the previously-crashing tests skip
cleanly (0 failures across testing/qdp + testing/qdp_python); with a GPU
present they run and pass.
@ryankert01
ryankert01 merged commit 205ce75 into main Jun 26, 2026
8 checks passed
@ryankert01
ryankert01 deleted the qdp-core-cudart-linkage branch June 26, 2026 18:56
ryankert01 added a commit that referenced this pull request Jun 26, 2026
Follow-up to #1321 (addresses #1414). After #1321, `_qdp` can build and
import without the CUDA toolkit (stub runtime), so "extension importable"
no longer implies a usable GPU. The test suite gated GPU tests on
`torch.cuda.is_available()`, a proxy that is wrong on this PR's headline
scenario -- a GPU host with PyTorch but no toolkit, where `_qdp` is a stub
build yet torch still reports a device.

- qdp-core: add `cuda_runtime_available()`, which queries
  `cudaGetDeviceCount` (false in a stub build via the existing 999 sentinel
  stub, and on hosts with no device). Re-exported from the crate root.
- _qdp: expose it as `_qdp.cuda_available()`.
- qumat_qdp: add `is_cuda_available()`, mirroring `is_triton_amd_available()`,
  as the single Python source of truth.
- testing/conftest: gate the `@pytest.mark.gpu` auto-skip on the native
  signal (falling back to torch only if the helper is absent).
- test_fallback: coverage that runs on a stub build too, guarding that
  querying availability returns a bool without aborting.

Verified on GPU (tests run and pass) and with CUDA hidden (tests skip);
full Rust suite, clippy --all-features, ruff, and ty all clean.
ryankert01 added a commit that referenced this pull request Jun 28, 2026
Follow-up to #1321 (addresses #1414). After #1321, `_qdp` can build and
import without the CUDA toolkit (stub runtime), so "extension importable"
no longer implies a usable GPU. The test suite gated GPU tests on
`torch.cuda.is_available()`, a proxy that is wrong on this PR's headline
scenario -- a GPU host with PyTorch but no toolkit, where `_qdp` is a stub
build yet torch still reports a device.

- qdp-core: add `cuda_runtime_available()`, which queries
  `cudaGetDeviceCount` (false in a stub build via the existing 999 sentinel
  stub, and on hosts with no device). Re-exported from the crate root.
- _qdp: expose it as `_qdp.cuda_available()`.
- qumat_qdp: add `is_cuda_available()`, mirroring `is_triton_amd_available()`,
  as the single Python source of truth.
- testing/conftest: gate the `@pytest.mark.gpu` auto-skip on the native
  signal (falling back to torch only if the helper is absent).
- test_fallback: coverage that runs on a stub build too, guarding that
  querying availability returns a bool without aborting.

Verified on GPU (tests run and pass) and with CUDA hidden (tests skip);
full Rust suite, clippy --all-features, ruff, and ty all clean.
400Ping pushed a commit that referenced this pull request Jul 5, 2026
…on it (#1416)

Follow-up to #1321 (addresses #1414). After #1321, `_qdp` can build and
import without the CUDA toolkit (stub runtime), so "extension importable"
no longer implies a usable GPU. The test suite gated GPU tests on
`torch.cuda.is_available()`, a proxy that is wrong on this PR's headline
scenario -- a GPU host with PyTorch but no toolkit, where `_qdp` is a stub
build yet torch still reports a device.

- qdp-core: add `cuda_runtime_available()`, which queries
  `cudaGetDeviceCount` (false in a stub build via the existing 999 sentinel
  stub, and on hosts with no device). Re-exported from the crate root.
- _qdp: expose it as `_qdp.cuda_available()`.
- qumat_qdp: add `is_cuda_available()`, mirroring `is_triton_amd_available()`,
  as the single Python source of truth.
- testing/conftest: gate the `@pytest.mark.gpu` auto-skip on the native
  signal (falling back to torch only if the helper is absent).
- test_fallback: coverage that runs on a stub build too, guarding that
  querying availability returns a bool without aborting.

Verified on GPU (tests run and pass) and with CUDA hidden (tests skip);
full Rust suite, clippy --all-features, ruff, and ty all clean.
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.

[Bug] qdp-core does not own its libcudart link directive; build fails on driver-only systems

2 participants