1use std::ffi::c_int;
2use std::sync::OnceLock;
3
4use anyhow::Context;
5use libloading::{Library, Symbol};
6
7use tract_core::internal::*;
8use tract_core::tract_linalg::block_quant::*;
9use tract_gpu::tensor::DeviceTensor;
10
11use crate::Q40_ROW_PADDING;
12use crate::ops::GgmlQuantQ81Fact;
13
14static CULIBS_MISSING: OnceLock<Option<&'static str>> = OnceLock::new();
15static DEPENDENCIES_OK: OnceLock<()> = OnceLock::new();
16
17#[cfg(feature = "cuda-12000")]
24pub const REQUIRED_CUDA_API: i32 = 12000;
25#[cfg(feature = "cuda-12010")]
26pub const REQUIRED_CUDA_API: i32 = 12010;
27#[cfg(feature = "cuda-12020")]
28pub const REQUIRED_CUDA_API: i32 = 12020;
29#[cfg(feature = "cuda-12030")]
30pub const REQUIRED_CUDA_API: i32 = 12030;
31#[cfg(feature = "cuda-12040")]
32pub const REQUIRED_CUDA_API: i32 = 12040;
33#[cfg(feature = "cuda-12050")]
34pub const REQUIRED_CUDA_API: i32 = 12050;
35#[cfg(feature = "cuda-12060")]
36pub const REQUIRED_CUDA_API: i32 = 12060;
37#[cfg(feature = "cuda-12080")]
38pub const REQUIRED_CUDA_API: i32 = 12080;
39#[cfg(feature = "cuda-12090")]
40pub const REQUIRED_CUDA_API: i32 = 12090;
41#[cfg(feature = "cuda-13000")]
42pub const REQUIRED_CUDA_API: i32 = 13000;
43#[cfg(feature = "cuda-13010")]
44pub const REQUIRED_CUDA_API: i32 = 13010;
45#[cfg(feature = "cuda-13020")]
46pub const REQUIRED_CUDA_API: i32 = 13020;
47
48#[cfg(not(any(
55 feature = "cuda-12000",
56 feature = "cuda-12010",
57 feature = "cuda-12020",
58 feature = "cuda-12030",
59 feature = "cuda-12040",
60 feature = "cuda-12050",
61 feature = "cuda-12060",
62 feature = "cuda-12080",
63 feature = "cuda-12090",
64 feature = "cuda-13000",
65 feature = "cuda-13010",
66 feature = "cuda-13020",
67)))]
68compile_error!(
69 "Tract CUDA backend requires exactly one of the cuda-XXXXX features \
70 (cuda-12000..cuda-13020) to be enabled. Enable it in Cargo features.",
71);
72
73type CuResult = c_int;
75
76type CuInitFn = unsafe extern "C" fn(flags: u32) -> CuResult;
77type CuDriverGetVersionFn = unsafe extern "C" fn(version: *mut c_int) -> CuResult;
78
79#[cfg(target_os = "linux")]
80const CUDA_DRIVER_LIB_CANDIDATES: [&str; 2] = ["libcuda.so.1", "libcuda.so"];
81
82#[cfg(target_os = "windows")]
83const CUDA_DRIVER_LIB_CANDIDATES: [&str; 1] = ["nvcuda.dll"];
84
85#[cfg(not(any(target_os = "linux", target_os = "windows")))]
86const CUDA_DRIVER_LIB_CANDIDATES: [&str; 0] = [];
87
88fn format_cuda_version(v: i32) -> (i32, i32) {
89 (v / 1000, (v % 1000) / 10)
90}
91
92fn load_first_found_cuda_lib() -> TractResult<(&'static str, Library)> {
93 for name in CUDA_DRIVER_LIB_CANDIDATES {
94 match unsafe { Library::new(name) } {
95 Ok(lib) => {
96 log::debug!("tract-cuda: driver candidate {name:?} loaded");
97 return Ok((name, lib));
98 }
99 Err(e) => log::debug!("tract-cuda: driver candidate {name:?} not loadable: {e}"),
100 }
101 }
102
103 bail!(
104 "CUDA driver library not found. Tried: {:?}. \
105 Is an NVIDIA driver installed \
106 (and, in containers, did you run with GPU passthrough)?",
107 CUDA_DRIVER_LIB_CANDIDATES
108 )
109}
110
111fn ensure_cuda_driver_compatible() -> TractResult<()> {
116 let (lib_name, lib) = load_first_found_cuda_lib()?;
118 let (req_major, req_minor) = format_cuda_version(REQUIRED_CUDA_API);
119
120 unsafe {
121 let cu_init: Symbol<CuInitFn> = lib.get(b"cuInit\0").map_err(|e| {
123 format_err!(
124 "CUDA driver library loaded ({lib_name}), but symbol cuInit is missing. \
125 This does not look like a functional NVIDIA driver installation. Details: {e}"
126 )
127 })?;
128
129 let cu_driver_get_version: Symbol<CuDriverGetVersionFn> =
130 lib.get(b"cuDriverGetVersion\0").map_err(|e| {
131 format_err!(
132 "CUDA driver library loaded ({lib_name}), but symbol cuDriverGetVersion is missing. \
133 Driver is too old or installation is corrupted. Details: {e}"
134 )
135 })?;
136
137 let init_res = cu_init(0);
139 if init_res != 0 {
140 bail!(
142 "CUDA driver initialization failed (cuInit returned {}, via {lib_name}). \
143 Possible causes: no CUDA-capable device exposed to this process, \
144 missing /dev/nvidia* nodes (container), insufficient permissions, \
145 or a broken driver install.",
146 init_res
147 );
148 }
149
150 let mut version: c_int = 0;
152 let ver_res = cu_driver_get_version(&mut version as *mut _);
153 if ver_res != 0 {
154 bail!(
155 "cuDriverGetVersion failed (returned {}, via {lib_name}). \
156 NVIDIA driver may be corrupted or not functioning properly.",
157 ver_res
158 );
159 }
160
161 let (found_major, found_minor) = format_cuda_version(version);
162
163 if version < REQUIRED_CUDA_API {
165 bail!(
166 "CUDA driver too old.\n\
167 Driver library: {lib_name}\n\
168 Built with cudarc feature cuda-{} (requires driver API >= {}.{}).\n\
169 Found driver API {}.{}.\n\
170 Fix: upgrade the NVIDIA driver, or rebuild tract-cuda with a lower cuda-XXXXX gate.",
171 REQUIRED_CUDA_API,
172 req_major,
173 req_minor,
174 found_major,
175 found_minor
176 );
177 }
178
179 log::info!(
180 "tract-cuda: CUDA driver {lib_name} OK; API version {found_major}.{found_minor} (built for >= {req_major}.{req_minor})"
181 );
182 }
183
184 Ok(())
185}
186
187fn first_missing_cudarc_culib() -> Option<&'static str> {
190 *CULIBS_MISSING.get_or_init(|| {
191 let probe = |name: &'static str, ok: bool| -> Option<&'static str> {
195 if ok {
196 log::debug!("tract-cuda: cudarc probe for {name:?} succeeded");
197 None
198 } else {
199 log::debug!("tract-cuda: cudarc probe for {name:?} failed");
200 Some(name)
201 }
202 };
203 probe("cuda (driver)", unsafe { cudarc::driver::sys::is_culib_present() })
204 .or_else(|| {
205 probe("cudart (runtime)", unsafe { cudarc::runtime::sys::is_culib_present() })
206 })
207 .or_else(|| probe("nvrtc", unsafe { cudarc::nvrtc::sys::is_culib_present() }))
208 .or_else(|| probe("cublas", unsafe { cudarc::cublas::sys::is_culib_present() }))
209 .or_else(|| probe("cudnn", unsafe { cudarc::cudnn::sys::is_culib_present() }))
210 })
211}
212
213pub fn ensure_cuda_runtime_dependencies(context_msg: &'static str) -> TractResult<()> {
214 if DEPENDENCIES_OK.get().is_some() {
218 return Ok(());
219 }
220
221 ensure_cuda_driver_compatible()
222 .context("CUDA driver validation failed")
223 .context(context_msg)?;
224
225 if let Some(missing) = first_missing_cudarc_culib() {
226 bail!(
227 "{context_msg}: CUDA runtime sub-library {missing:?} could not be dlopen'd. \
228 cudarc searches standard library names derived from the build-time CUDA \
229 version; install the matching package or set LD_LIBRARY_PATH so the \
230 loader can find it. Enable RUST_LOG=tract_cuda=debug to see each probe."
231 );
232 }
233
234 log::info!(
235 "tract-cuda: all required cudarc sub-libraries present (driver, cudart, nvrtc, cublas, cudnn)"
236 );
237
238 let _ = DEPENDENCIES_OK.set(());
239 Ok(())
240}
241
242pub fn get_ggml_q81_fact(t: &DeviceTensor) -> Option<GgmlQuantQ81Fact> {
243 if let DeviceTensor::Owned(t) = t {
244 t.exotic_fact().and_then(|of| of.downcast_ref::<GgmlQuantQ81Fact>()).cloned()
245 } else if let DeviceTensor::ArenaView(t) = t {
246 t.exotic_fact().and_then(|of| of.downcast_ref::<GgmlQuantQ81Fact>()).cloned()
247 } else {
248 None
249 }
250}
251
252pub fn pad_q40(bqs: &BlockQuantStorage, m: usize, k: usize) -> TractResult<BlockQuantStorage> {
253 ensure!(k % 32 == 0);
254
255 let to_pad = k.next_multiple_of(Q40_ROW_PADDING) - k;
256 if to_pad == 0 {
257 return Ok(bqs.clone()); }
259
260 let row_bytes = k * Q4_0.block_bytes() / Q4_0.block_len();
261
262 let pad_quant = Q4_0.quant_f32(&vec![0f32; to_pad])?;
263 let pad_bytes = pad_quant.len();
264
265 let mut new_data = Vec::with_capacity(m * (row_bytes + pad_bytes));
266 let old_bytes = bqs.value().as_bytes();
267
268 for row in 0..m {
269 let start = row * row_bytes;
270 new_data.extend_from_slice(&old_bytes[start..start + row_bytes]);
271 new_data.extend_from_slice(&pad_quant);
272 }
273
274 BlockQuantStorage::new(
275 tract_core::dyn_clone::clone_box(bqs.format()),
276 m,
277 k + to_pad,
278 Arc::new(Blob::from_bytes(&new_data)?),
279 )
280}