huggingface / huggingface/candle

CUDA is bound at link time, so a `cuda`-enabled binary cannot start without CUDA installed

Open
#3,900 4 comments 2 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
21k
Forks
1.8k
Avg merge
16h 42m
Merged PRs (30d)
25

Description

Hi — downstream consumer here: [arama](https://github.com/nabbisen/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:

```rust
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`.**

```toml
# candle-core-0.11.0/Cargo.toml
[dependencies.cudarc]
features = [ …, "cuda-version-from-build-system", "dynamic-linking" ]
default-features = false
```

`cudarc` 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.**

```rust
// candle-kernels-0.11.0/build.rs
println!("cargo:rustc-link-lib=dylib=cudart"); // no feature/cfg guard
```

This 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 its `cudart` link) 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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with candle-core-0.11.0/Cargo.toml and candle-kernels-0.11.0/build.rs, then inspect how the CUDA and MoE paths are selected and built. Reproduce the issue with the probe binary and compare its readelf -d output. Done means a CUDA-enabled binary starts without CUDA installed while Device::new_cuda(0) and GPU matmul still work when CUDA is available.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
build-system, machine-learning
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.