Mayo commited on
fix: fallback to cpu if nvidia driver doesn't support cuda 13.1
Browse files- Cargo.lock +1 -0
- koharu/Cargo.toml +1 -0
- koharu/src/app.rs +48 -4
- koharu/src/lib.rs +1 -0
- koharu/src/nvidia.rs +103 -0
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 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|