CUDA is bound at link time, so a `cuda`-enabled binary cannot start without CUDA installed
Hi — downstream consumer here: arama, a local-first desktop app using candle-core, candle-nn, candle-transformers 0.11 for CLIP and wav2vec2.
We'd like to ship one binary to end users, using the GPU when present and falling back to CPU otherwise. Our device selection already does that:
Device::new_cuda(0).unwrap_or(Device::new_metal(0).unwrap_or(Device::Cpu))But it never runs on a machine without CUDA, because the process fails in the OS loader before main. Two separate things cause that, and we couldn't find a way around either from a consumer's Cargo.toml — though we may well have missed one:
1. candle-core hardcodes cudarc's dynamic-linking.
# candle-core-0.11.0/Cargo.toml
[dependencies.cudarc]
features = [ …, "cuda-version-from-build-system", "dynamic-linking" ]
default-features = falsecudarc 0.19 offers dynamic-loading, and includes fallback-dynamic-loading in its own defaults — candle disables defaults and picks link-time binding.
2. candle-kernels' build.rs links cudart unconditionally.
// candle-kernels-0.11.0/build.rs
println!("cargo:rustc-link-lib=dylib=cudart"); // no feature/cfg guardThis one is why patching (1) alone isn't enough — a build with cudarc switched to loading still fails with unable to find library -lcudart. It's attached to the statically compiled MoE kernels; the normal PTX/NVRTC path doesn't need it.
What would help us
Offered as suggestions rather than requests — you'll have context we don't:
candle-core: some way to select cudarc's runtime-loading mode, with default behaviour unchanged.candle-kernels: gating the MoE static build (and itscudartlink) behind a feature, so consumers who don't use MoE don't get a load-time CUDA dependency for kernels they never call.
What we tried locally
With both changed locally, a probe binary built with cuda:
$ readelf -d probe | grep NEEDED
[libgcc_s.so.1] [libc.so.6] [ld-linux-x86-64.so.2]No CUDA in the dynamic dependency list — so it starts anywhere. And CUDA still works: on an RTX 5060 Ti / CUDA 13.3, Device::new_cuda(0) returned a real device and a 512×512 matmul executed on the GPU through the loading path.
We haven't opened a PR because the shape is yours to choose — particularly for (2), where our local change removes the MoE build rather than gating it, which suits us but wouldn't suit everyone. Happy to send patches, the probe crate, or full build logs, and happy to open a PR against whatever design you prefer.
If this isn't something you want to change, that's genuinely fine — knowing either way just lets us choose between carrying local patches and shipping two binaries. Thanks for candle.
Source: huggingface/candle