Mayo commited on
Commit
35eca3a
·
unverified ·
1 Parent(s): 6cf4597

fix: fallback to cpu if nvidia driver doesn't support cuda 13.1

Browse files
Cargo.lock CHANGED
@@ -3869,6 +3869,7 @@ dependencies = [
3869
  "koharu-rpc",
3870
  "koharu-runtime",
3871
  "koharu-types",
 
3872
  "once_cell",
3873
  "rfd",
3874
  "serde_json",
 
3869
  "koharu-rpc",
3870
  "koharu-runtime",
3871
  "koharu-types",
3872
+ "libloading 0.9.0",
3873
  "once_cell",
3874
  "rfd",
3875
  "serde_json",
koharu/Cargo.toml CHANGED
@@ -40,6 +40,7 @@ tauri-plugin-updater = { workspace = true }
40
  tokio = { workspace = true }
41
  tracing = { workspace = true }
42
  tracing-subscriber = { workspace = true }
 
43
 
44
  [target.'cfg(target_os = "windows")'.dependencies]
45
  windows = { workspace = true }
 
40
  tokio = { workspace = true }
41
  tracing = { workspace = true }
42
  tracing-subscriber = { workspace = true }
43
+ libloading = { workspace = true }
44
 
45
  [target.'cfg(target_os = "windows")'.dependencies]
46
  windows = { workspace = true }
koharu/src/app.rs CHANGED
@@ -112,8 +112,52 @@ async fn prefetch() -> Result<()> {
112
  Ok(())
113
  }
114
 
115
- async fn build_resources(cpu: bool) -> Result<AppResources> {
116
- if cuda_is_available() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  ensure_dylibs(LIB_ROOT.to_path_buf())
118
  .await
119
  .context("Failed to ensure dynamic libraries")?;
@@ -188,7 +232,7 @@ pub async fn run() -> Result<()> {
188
  }
189
  });
190
  shared
191
- .get_or_try_init(|| async { build_resources(cpu).await })
192
  .await?;
193
  tokio::signal::ctrl_c().await?;
194
  return Ok(());
@@ -219,7 +263,7 @@ pub async fn run() -> Result<()> {
219
  .ok();
220
 
221
  shared
222
- .get_or_try_init(|| async { build_resources(cpu).await })
223
  .await
224
  .expect("failed to build app resources");
225
 
 
112
  Ok(())
113
  }
114
 
115
+ fn warning(headless: bool, title: &str, description: &str) {
116
+ tracing::warn!("{description}");
117
+
118
+ if headless {
119
+ return;
120
+ }
121
+
122
+ MessageDialog::new()
123
+ .set_level(rfd::MessageLevel::Warning)
124
+ .set_title(title)
125
+ .set_description(description)
126
+ .show();
127
+ }
128
+
129
+ async fn build_resources(cpu: bool, headless: bool) -> Result<AppResources> {
130
+ let mut cpu = cpu;
131
+
132
+ if !cpu && cuda_is_available() {
133
+ match crate::nvidia::driver_version() {
134
+ Ok(version) if version.supports_cuda_13_1() => {
135
+ tracing::info!("NVIDIA driver reports CUDA {version} support");
136
+ }
137
+ Ok(version) => {
138
+ warning(
139
+ headless,
140
+ "NVIDIA Driver Update Recommended",
141
+ &format!(
142
+ "Your NVIDIA driver only supports CUDA {version}. Koharu will fall back to CPU. Please update your NVIDIA driver to a version that supports CUDA 13.1 or newer to enable GPU acceleration."
143
+ ),
144
+ );
145
+ cpu = true;
146
+ }
147
+ Err(err) => {
148
+ warning(
149
+ headless,
150
+ "NVIDIA Driver Check Failed",
151
+ &format!(
152
+ "Koharu could not verify NVIDIA driver support for CUDA 13.1: {err:#}. Koharu will fall back to CPU. Please update your NVIDIA driver to a version that supports CUDA 13.1 or newer to enable GPU acceleration."
153
+ ),
154
+ );
155
+ cpu = true;
156
+ }
157
+ }
158
+ }
159
+
160
+ if !cpu && cuda_is_available() {
161
  ensure_dylibs(LIB_ROOT.to_path_buf())
162
  .await
163
  .context("Failed to ensure dynamic libraries")?;
 
232
  }
233
  });
234
  shared
235
+ .get_or_try_init(|| async { build_resources(cpu, headless).await })
236
  .await?;
237
  tokio::signal::ctrl_c().await?;
238
  return Ok(());
 
263
  .ok();
264
 
265
  shared
266
+ .get_or_try_init(|| async { build_resources(cpu, headless).await })
267
  .await
268
  .expect("failed to build app resources");
269
 
koharu/src/lib.rs CHANGED
@@ -1,5 +1,6 @@
1
  pub mod app;
2
  pub mod assets;
 
3
  pub mod version;
4
  #[cfg(target_os = "windows")]
5
  pub mod windows;
 
1
  pub mod app;
2
  pub mod assets;
3
+ pub mod nvidia;
4
  pub mod version;
5
  #[cfg(target_os = "windows")]
6
  pub mod windows;
koharu/src/nvidia.rs ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::fmt;
2
+
3
+ use anyhow::{Context, Result};
4
+
5
+ const CUDA_SUCCESS: i32 = 0;
6
+ const CUDA_13_1_DRIVER_VERSION: i32 = 13010;
7
+
8
+ type CuInit = unsafe extern "C" fn(flags: u32) -> i32;
9
+ type CuDriverGetVersion = unsafe extern "C" fn(driver_version: *mut i32) -> i32;
10
+
11
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12
+ pub struct CudaDriverVersion {
13
+ raw: i32,
14
+ }
15
+
16
+ impl CudaDriverVersion {
17
+ pub const fn from_raw(raw: i32) -> Self {
18
+ Self { raw }
19
+ }
20
+
21
+ pub const fn raw(self) -> i32 {
22
+ self.raw
23
+ }
24
+
25
+ pub const fn major(self) -> i32 {
26
+ self.raw / 1000
27
+ }
28
+
29
+ pub const fn minor(self) -> i32 {
30
+ (self.raw % 1000) / 10
31
+ }
32
+
33
+ pub const fn supports_cuda_13_1(self) -> bool {
34
+ self.raw >= CUDA_13_1_DRIVER_VERSION
35
+ }
36
+ }
37
+
38
+ impl fmt::Display for CudaDriverVersion {
39
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40
+ write!(f, "{}.{}", self.major(), self.minor())
41
+ }
42
+ }
43
+
44
+ pub fn supports_cuda_13_1() -> Result<bool> {
45
+ Ok(driver_version()?.supports_cuda_13_1())
46
+ }
47
+
48
+ pub fn driver_version() -> Result<CudaDriverVersion> {
49
+ let lib_name = if cfg!(target_os = "windows") {
50
+ "nvcuda.dll"
51
+ } else {
52
+ "libcuda.so"
53
+ };
54
+
55
+ unsafe {
56
+ let library = libloading::Library::new(lib_name)
57
+ .with_context(|| format!("Failed to load NVIDIA driver library {lib_name}"))?;
58
+ let cu_init = *library
59
+ .get::<CuInit>(b"cuInit\0")
60
+ .context("Failed to load cuInit from NVIDIA driver")?;
61
+ let cu_driver_get_version = *library
62
+ .get::<CuDriverGetVersion>(b"cuDriverGetVersion\0")
63
+ .context("Failed to load cuDriverGetVersion from NVIDIA driver")?;
64
+
65
+ let init_status = cu_init(0);
66
+ ensure_cuda_success(init_status, "cuInit")?;
67
+
68
+ let mut raw = 0;
69
+ let version_status = cu_driver_get_version(&mut raw);
70
+ ensure_cuda_success(version_status, "cuDriverGetVersion")?;
71
+
72
+ Ok(CudaDriverVersion::from_raw(raw))
73
+ }
74
+ }
75
+
76
+ fn ensure_cuda_success(code: i32, op: &str) -> Result<()> {
77
+ if code == CUDA_SUCCESS {
78
+ return Ok(());
79
+ }
80
+
81
+ anyhow::bail!("{op} failed with CUDA driver error code {code}")
82
+ }
83
+
84
+ #[cfg(test)]
85
+ mod tests {
86
+ use super::*;
87
+
88
+ #[test]
89
+ fn parses_major_minor_from_driver_version() {
90
+ let version = CudaDriverVersion::from_raw(13010);
91
+ assert_eq!(version.major(), 13);
92
+ assert_eq!(version.minor(), 1);
93
+ assert_eq!(version.to_string(), "13.1");
94
+ }
95
+
96
+ #[test]
97
+ fn checks_cuda_13_1_threshold() {
98
+ assert!(CudaDriverVersion::from_raw(13010).supports_cuda_13_1());
99
+ assert!(CudaDriverVersion::from_raw(13020).supports_cuda_13_1());
100
+ assert!(!CudaDriverVersion::from_raw(13000).supports_cuda_13_1());
101
+ assert!(!CudaDriverVersion::from_raw(12080).supports_cuda_13_1());
102
+ }
103
+ }