offline_intelligence/engine_management/
analyzer.rs1use crate::model_runtime::platform_detector::{HardwareCapabilities, Platform, HardwareArchitecture};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::OnceLock;
10use tracing::{debug, info};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct HardwareProfile {
15 pub platform: Platform,
16 pub architecture: HardwareArchitecture,
17 pub cpu_cores: u32,
18 pub total_memory_gb: f32,
19 pub available_memory_gb: f32,
20 pub gpu_info: Option<GPUInfo>,
21 pub acceleration_support: HashMap<String, bool>,
22 pub system_info: SystemInfo,
23}
24
25static PROFILE_CACHE: OnceLock<HardwareProfile> = OnceLock::new();
27
28impl HardwareProfile {
29 pub async fn analyze(capabilities: &HardwareCapabilities) -> Result<Self, Box<dyn std::error::Error>> {
31 if let Some(cached) = PROFILE_CACHE.get() {
33 return Ok(cached.clone());
34 }
35
36 let analyzer = HardwareAnalyzer::new(capabilities.clone());
37 let profile = analyzer.get_hardware_profile();
38
39 let _ = PROFILE_CACHE.set(profile.clone());
41
42 Ok(profile)
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct GPUInfo {
49 pub vendor: String,
50 pub model: String,
51 pub memory_gb: f32,
52 pub compute_capability: Option<String>,
53 pub driver_version: Option<String>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SystemInfo {
59 pub os_name: String,
60 pub os_version: String,
61 pub kernel_version: Option<String>,
62}
63
64pub struct HardwareAnalyzer {
66 capabilities: HardwareCapabilities,
67}
68
69impl HardwareAnalyzer {
70 pub fn new(capabilities: HardwareCapabilities) -> Self {
71 Self { capabilities }
72 }
73
74 pub fn get_hardware_profile(&self) -> HardwareProfile {
76 let system_info = self.detect_system_info();
77 let gpu_info = self.detect_gpu_info();
78 let memory_info = self.detect_memory_info();
79 let acceleration_support = self.detect_acceleration_support(&gpu_info);
80
81 HardwareProfile {
82 platform: self.capabilities.platform.clone(),
83 architecture: self.capabilities.architecture.clone(),
84 cpu_cores: self.detect_cpu_cores(),
85 total_memory_gb: memory_info.total_gb,
86 available_memory_gb: memory_info.available_gb,
87 gpu_info,
88 acceleration_support,
89 system_info,
90 }
91 }
92
93 fn detect_cpu_cores(&self) -> u32 {
95 let logical_cores = num_cpus::get() as u32;
96 debug!("Detected {} logical CPU cores", logical_cores);
97 logical_cores
98 }
99
100 fn detect_memory_info(&self) -> MemoryInfo {
102 let mut system = sysinfo::System::new_all();
103 system.refresh_memory();
104
105 let total_bytes = system.total_memory();
106 let available_bytes = system.available_memory();
107
108 let total_gb = total_bytes as f32 / (1024.0 * 1024.0 * 1024.0);
109 let available_gb = available_bytes as f32 / (1024.0 * 1024.0 * 1024.0);
110
111 debug!("Memory: {:.1}GB total, {:.1}GB available", total_gb, available_gb);
112
113 MemoryInfo {
114 total_gb,
115 available_gb,
116 }
117 }
118
119 fn detect_gpu_info(&self) -> Option<GPUInfo> {
121 match &self.capabilities.platform {
122 Platform::Windows => self.detect_windows_gpu(),
123 Platform::Linux => self.detect_linux_gpu(),
124 Platform::MacOS => self.detect_macos_gpu(),
125 }
126 }
127
128 fn detect_windows_gpu(&self) -> Option<GPUInfo> {
130 #[cfg(target_os = "windows")]
131 {
132 use std::process::{Command, Stdio};
133 use std::os::windows::process::CommandExt;
134
135 let child = Command::new("powershell")
139 .args([
140 "-NoProfile", "-NonInteractive", "-Command",
141 "Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name"
142 ])
143 .stdout(Stdio::piped())
144 .stderr(Stdio::null())
145 .creation_flags(0x08000000) .spawn();
147
148 if let Ok(mut process) = child {
149 let start = std::time::Instant::now();
150 loop {
151 match process.try_wait() {
152 Ok(Some(status)) => {
153 if status.success() {
154 if let Ok(output) = process.wait_with_output() {
155 let stdout = String::from_utf8_lossy(&output.stdout);
156 for line in stdout.lines() {
157 let trimmed = line.trim();
158 if trimmed.contains("NVIDIA") {
159 return Some(GPUInfo {
160 vendor: "NVIDIA".to_string(),
161 model: trimmed.to_string(),
162 memory_gb: 0.0,
163 compute_capability: None,
164 driver_version: None,
165 });
166 }
167 }
168 }
169 }
170 break;
171 }
172 Ok(None) => {
173 if start.elapsed() > std::time::Duration::from_secs(10) {
174 let _ = process.kill();
175 let _ = process.wait();
176 debug!("GPU detection via PowerShell timed out");
177 break;
178 }
179 std::thread::sleep(std::time::Duration::from_millis(100));
180 }
181 Err(_) => break,
182 }
183 }
184 }
185 }
186
187 if self.capabilities.has_cuda {
189 Some(GPUInfo {
190 vendor: "NVIDIA".to_string(),
191 model: "Unknown CUDA GPU".to_string(),
192 memory_gb: 0.0,
193 compute_capability: None,
194 driver_version: None,
195 })
196 } else {
197 None
198 }
199 }
200
201 fn detect_linux_gpu(&self) -> Option<GPUInfo> {
203 use std::process::{Command, Stdio};
204
205 let child = Command::new("lspci")
207 .stdout(Stdio::piped())
208 .stderr(Stdio::null())
209 .spawn();
210
211 if let Ok(mut process) = child {
212 let start = std::time::Instant::now();
213 loop {
214 match process.try_wait() {
215 Ok(Some(_)) => {
216 if let Ok(output) = process.wait_with_output() {
217 let stdout = String::from_utf8_lossy(&output.stdout);
218 for line in stdout.lines() {
219 if line.contains("VGA compatible controller") || line.contains("3D controller") {
220 if line.contains("NVIDIA") {
221 return Some(GPUInfo {
222 vendor: "NVIDIA".to_string(),
223 model: line.split(": ").nth(1).unwrap_or("Unknown").to_string(),
224 memory_gb: 0.0,
225 compute_capability: None,
226 driver_version: None,
227 });
228 } else if line.contains("AMD") || line.contains("ATI") {
229 return Some(GPUInfo {
230 vendor: "AMD".to_string(),
231 model: line.split(": ").nth(1).unwrap_or("Unknown").to_string(),
232 memory_gb: 0.0,
233 compute_capability: None,
234 driver_version: None,
235 });
236 }
237 }
238 }
239 }
240 break;
241 }
242 Ok(None) => {
243 if start.elapsed() > std::time::Duration::from_secs(5) {
244 let _ = process.kill();
245 let _ = process.wait();
246 debug!("lspci GPU detection timed out");
247 break;
248 }
249 std::thread::sleep(std::time::Duration::from_millis(50));
250 }
251 Err(_) => break,
252 }
253 }
254 }
255
256 None
257 }
258
259 fn detect_macos_gpu(&self) -> Option<GPUInfo> {
261 #[cfg(target_os = "macos")]
262 {
263 if self.capabilities.has_metal {
266 return Some(GPUInfo {
267 vendor: "Apple".to_string(),
268 model: "Integrated GPU".to_string(),
269 memory_gb: 0.0,
270 compute_capability: Some("Metal".to_string()),
271 driver_version: None,
272 });
273 }
274 }
275
276 None
277 }
278
279 fn detect_acceleration_support(&self, _gpu_info: &Option<GPUInfo>) -> HashMap<String, bool> {
281 let mut support = HashMap::new();
282
283 support.insert("cpu".to_string(), true);
285
286 match &self.capabilities.platform {
288 Platform::Windows => {
289 support.insert("cuda".to_string(), self.capabilities.has_cuda);
290 support.insert("directml".to_string(), true); }
292 Platform::MacOS => {
293 support.insert("metal".to_string(), self.capabilities.has_metal);
294 }
295 Platform::Linux => {
296 support.insert("cuda".to_string(), self.capabilities.has_cuda);
297 support.insert("vulkan".to_string(), self.capabilities.has_vulkan);
298 support.insert("rocm".to_string(), self.detect_rocm_support());
300 }
301 }
302
303 support.insert("opencl".to_string(), self.detect_opencl_support());
305
306 debug!("Acceleration support: {:?}", support);
307 support
308 }
309
310 fn detect_rocm_support(&self) -> bool {
312 #[cfg(target_os = "linux")]
313 {
314 use std::path::Path;
315 Path::new("/opt/rocm").exists() || Path::new("/usr/lib/rocm").exists()
317 }
318 #[cfg(not(target_os = "linux"))]
319 {
320 false
321 }
322 }
323
324 fn detect_opencl_support(&self) -> bool {
326 match &self.capabilities.platform {
328 Platform::Windows => {
329 std::path::Path::new("C:\\Windows\\System32\\OpenCL.dll").exists()
331 }
332 Platform::Linux => {
333 std::path::Path::new("/usr/lib/libOpenCL.so").exists() ||
334 std::path::Path::new("/usr/lib/x86_64-linux-gnu/libOpenCL.so").exists()
335 }
336 Platform::MacOS => {
337 true
339 }
340 }
341 }
342
343 fn detect_system_info(&self) -> SystemInfo {
345 SystemInfo {
346 os_name: std::env::consts::OS.to_string(),
347 os_version: "Unknown".to_string(),
348 kernel_version: None,
349 }
350 }
351
352 pub fn get_engine_recommendations(&self) -> Vec<EngineRecommendation> {
354 let profile = self.get_hardware_profile();
355 let mut recommendations = Vec::new();
356
357 recommendations.push(EngineRecommendation {
359 engine_type: "CPU".to_string(),
360 priority: 1,
361 reason: "Universal compatibility".to_string(),
362 estimated_performance: self.estimate_cpu_performance(&profile),
363 });
364
365 if profile.acceleration_support.get("cuda").copied().unwrap_or(false) {
367 recommendations.push(EngineRecommendation {
368 engine_type: "CUDA".to_string(),
369 priority: 2,
370 reason: "NVIDIA GPU acceleration available".to_string(),
371 estimated_performance: self.estimate_cuda_performance(&profile),
372 });
373 }
374
375 if profile.acceleration_support.get("metal").copied().unwrap_or(false) {
376 recommendations.push(EngineRecommendation {
377 engine_type: "Metal".to_string(),
378 priority: 2,
379 reason: "Apple Silicon GPU acceleration".to_string(),
380 estimated_performance: self.estimate_metal_performance(&profile),
381 });
382 }
383
384 if profile.acceleration_support.get("vulkan").copied().unwrap_or(false) {
385 recommendations.push(EngineRecommendation {
386 engine_type: "Vulkan".to_string(),
387 priority: 3,
388 reason: "Cross-platform GPU acceleration".to_string(),
389 estimated_performance: self.estimate_vulkan_performance(&profile),
390 });
391 }
392
393 recommendations.sort_by(|a, b| a.priority.cmp(&b.priority));
395 recommendations
396 }
397
398 fn estimate_cpu_performance(&self, profile: &HardwareProfile) -> PerformanceEstimate {
400 let core_multiplier = profile.cpu_cores as f32;
401 let memory_multiplier = (profile.available_memory_gb / 8.0).min(4.0);
402 let base_score = 50.0;
403
404 PerformanceEstimate {
405 inference_speed: base_score * core_multiplier * memory_multiplier * 0.1,
406 memory_efficiency: (profile.available_memory_gb / profile.total_memory_gb) * 100.0,
407 power_efficiency: 70.0, }
409 }
410
411 fn estimate_cuda_performance(&self, profile: &HardwareProfile) -> PerformanceEstimate {
413 if let Some(gpu) = &profile.gpu_info {
414 let memory_score = gpu.memory_gb * 10.0; let base_score = 80.0;
416
417 PerformanceEstimate {
418 inference_speed: base_score + memory_score,
419 memory_efficiency: 90.0, power_efficiency: 85.0, }
422 } else {
423 PerformanceEstimate {
424 inference_speed: 0.0,
425 memory_efficiency: 0.0,
426 power_efficiency: 0.0,
427 }
428 }
429 }
430
431 fn estimate_metal_performance(&self, _profile: &HardwareProfile) -> PerformanceEstimate {
433 PerformanceEstimate {
435 inference_speed: 75.0,
436 memory_efficiency: 95.0, power_efficiency: 90.0, }
439 }
440
441 fn estimate_vulkan_performance(&self, _profile: &HardwareProfile) -> PerformanceEstimate {
443 PerformanceEstimate {
445 inference_speed: 70.0,
446 memory_efficiency: 85.0,
447 power_efficiency: 80.0,
448 }
449 }
450}
451
452#[derive(Debug)]
454struct MemoryInfo {
455 total_gb: f32,
456 available_gb: f32,
457}
458
459#[derive(Debug, serde::Serialize, serde::Deserialize)]
461pub struct EngineRecommendation {
462 pub engine_type: String,
463 pub priority: u32,
464 pub reason: String,
465 pub estimated_performance: PerformanceEstimate,
466}
467
468#[derive(Debug, serde::Serialize, serde::Deserialize)]
470pub struct PerformanceEstimate {
471 pub inference_speed: f32, pub memory_efficiency: f32, pub power_efficiency: f32, }