offline_intelligence/model_runtime/
platform_detector.rs1use std::path::PathBuf;
7use std::sync::OnceLock;
8use tracing::info;
9
10#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
11pub enum Platform {
12 Windows,
13 Linux,
14 MacOS,
15}
16
17#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
18pub enum HardwareArchitecture {
19 X86_64,
20 Aarch64, Other(String),
22}
23
24#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
25pub struct HardwareCapabilities {
26 pub platform: Platform,
27 pub architecture: HardwareArchitecture,
28 pub has_cuda: bool,
29 pub has_metal: bool, pub has_vulkan: bool,
31}
32
33static HARDWARE_CACHE: OnceLock<HardwareCapabilities> = OnceLock::new();
35
36impl Default for HardwareCapabilities {
37 fn default() -> Self {
38 Self::detect()
39 }
40}
41
42impl HardwareCapabilities {
43 pub fn detect() -> Self {
45 if let Some(cached) = HARDWARE_CACHE.get() {
47 return cached.clone();
48 }
49
50 let platform = Self::detect_platform();
52 let architecture = Self::detect_architecture();
53 let has_cuda = Self::detect_cuda_support();
54 let has_metal = Self::detect_metal_support(&architecture);
55 let has_vulkan = Self::detect_vulkan_support();
56
57 info!(
58 "Detected platform: {:?}, architecture: {:?}, CUDA: {}, Metal: {}, Vulkan: {}",
59 platform, architecture, has_cuda, has_metal, has_vulkan
60 );
61
62 let capabilities = Self {
63 platform,
64 architecture,
65 has_cuda,
66 has_metal,
67 has_vulkan,
68 };
69
70 let _ = HARDWARE_CACHE.set(capabilities.clone());
72
73 capabilities
74 }
75
76 fn detect_platform() -> Platform {
77 if cfg!(target_os = "windows") {
78 Platform::Windows
79 } else if cfg!(target_os = "linux") {
80 Platform::Linux
81 } else if cfg!(target_os = "macos") {
82 Platform::MacOS
83 } else {
84 #[cfg(target_os = "windows")]
86 return Platform::Windows;
87 #[cfg(target_os = "linux")]
88 return Platform::Linux;
89 #[cfg(target_os = "macos")]
90 return Platform::MacOS;
91 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
92 return Platform::Linux; }
94 }
95
96 fn detect_architecture() -> HardwareArchitecture {
97 if cfg!(target_arch = "x86_64") {
98 HardwareArchitecture::X86_64
99 } else if cfg!(target_arch = "aarch64") {
100 HardwareArchitecture::Aarch64
101 } else {
102 HardwareArchitecture::Other(std::env::consts::ARCH.to_string())
103 }
104 }
105
106 fn detect_cuda_support() -> bool {
107 use std::process::{Command, Stdio};
110
111 #[cfg(target_os = "windows")]
113 let child = {
114 use std::os::windows::process::CommandExt;
115 Command::new("nvidia-smi")
116 .arg("--query-gpu=name")
117 .arg("--format=csv,noheader,nounits")
118 .stdout(Stdio::piped())
119 .stderr(Stdio::null())
120 .creation_flags(0x08000000) .spawn()
122 };
123
124 #[cfg(not(target_os = "windows"))]
125 let child = Command::new("nvidia-smi")
126 .arg("--query-gpu=name")
127 .arg("--format=csv,noheader,nounits")
128 .stdout(Stdio::piped())
129 .stderr(Stdio::null())
130 .spawn();
131
132 match child {
133 Ok(mut process) => {
134 let start = std::time::Instant::now();
136 loop {
137 match process.try_wait() {
138 Ok(Some(status)) => return status.success(),
139 Ok(None) => {
140 if start.elapsed() > std::time::Duration::from_secs(5) {
141 let _ = process.kill();
142 let _ = process.wait();
143 return false;
144 }
145 std::thread::sleep(std::time::Duration::from_millis(50));
146 }
147 Err(_) => return false,
148 }
149 }
150 }
151 Err(_) => false,
152 }
153 }
154
155 fn detect_metal_support(architecture: &HardwareArchitecture) -> bool {
162 if cfg!(target_os = "macos") {
163 let _ = architecture; true
167 } else {
168 false
169 }
170 }
171
172 fn detect_vulkan_support() -> bool {
173 false
174 }
175
176 pub fn get_runtime_binary_path(&self) -> Option<PathBuf> {
182 let resources_dir = self.get_resources_dir()?;
183
184 let os_folder = match &self.platform {
188 Platform::Windows => "Windows",
189 Platform::MacOS => "MacOS",
190 Platform::Linux => "Linux",
191 };
192 let platform_dir = resources_dir.join(os_folder);
193
194 match &self.platform {
195 Platform::Windows => {
196 if self.has_cuda {
198 Some(
199 platform_dir
200 .join("llama-b6970-bin-win-cuda-12.4-x64")
201 .join("llama-server.exe"),
202 )
203 } else {
204 Some(platform_dir.join("llama-cpu").join("llama-server.exe"))
205 }
206 }
207 Platform::Linux => {
208 None
210 }
211 Platform::MacOS => {
212 if self.has_metal {
216 Some(platform_dir.join("llama-metal").join("llama-server"))
217 } else {
218 Some(platform_dir.join("llama-cpu").join("llama-server"))
219 }
220 }
221 }
222 }
223
224 fn get_resources_dir(&self) -> Option<PathBuf> {
233 if let Ok(current_exe) = std::env::current_exe() {
234 if let Some(exe_dir) = current_exe.parent() {
235 if let Some(contents_dir) = exe_dir.parent() {
240 let candidate = contents_dir.join("Resources").join("bin");
241 if candidate.exists() {
242 return Some(candidate);
243 }
244 }
245
246 for resource_folder in &["Resources", "resources"] {
249 let candidate = exe_dir.join(resource_folder).join("bin");
250 if candidate.exists() {
251 return Some(candidate);
252 }
253 }
254 }
255 }
256
257 for resource_folder in &["Resources", "resources"] {
259 let candidate = std::path::PathBuf::from(resource_folder).join("bin");
260 if candidate.exists() {
261 return Some(candidate);
262 }
263 }
264
265 #[cfg(debug_assertions)]
267 {
268 let dev_candidate = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
269 .join("Resources")
270 .join("bin");
271 if dev_candidate.exists() {
272 return Some(dev_candidate);
273 }
274 }
275
276 None
277 }
278}
279
280impl std::fmt::Display for Platform {
281 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282 match self {
283 Platform::Windows => write!(f, "Windows"),
284 Platform::Linux => write!(f, "Linux"),
285 Platform::MacOS => write!(f, "MacOS"),
286 }
287 }
288}
289
290impl std::fmt::Display for HardwareArchitecture {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 match self {
293 HardwareArchitecture::X86_64 => write!(f, "x86_64"),
294 HardwareArchitecture::Aarch64 => write!(f, "aarch64"),
295 HardwareArchitecture::Other(s) => write!(f, "{}", s),
296 }
297 }
298}