llm_manager/backend/
hardware.rs1use std::fs;
2use std::path::Path;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Platform {
7 Linux,
8 Windows,
9 Macos,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum GpuVendor {
15 Amd,
16 Nvidia,
17 Intel,
18 Unknown,
19}
20
21pub fn detect_platform() -> Platform {
23 match std::env::consts::OS {
24 "windows" => Platform::Windows,
25 "macos" => Platform::Macos,
26 _ => Platform::Linux,
27 }
28}
29
30pub fn is_arm64() -> bool {
32 cfg!(target_arch = "aarch64")
33}
34
35pub fn platform_name(platform: Platform) -> &'static str {
37 match platform {
38 Platform::Linux => "linux",
39 Platform::Windows => "windows",
40 Platform::Macos => "macos",
41 }
42}
43
44pub fn backend_supported(backend: crate::models::Backend, platform: Platform) -> bool {
46 match platform {
47 Platform::Linux => backend.is_linux(),
48 Platform::Windows => backend.is_windows(),
49 Platform::Macos => backend.is_macos(),
50 }
51}
52
53fn drm_card_paths() -> Vec<std::path::PathBuf> {
55 let drm_path = Path::new("/sys/class/drm");
56 if !drm_path.exists() {
57 return Vec::new();
58 }
59 fs::read_dir(drm_path)
60 .map(|entries| {
61 entries
62 .flatten()
63 .filter(|e| {
64 let n = e.file_name();
65 let s = n.to_string_lossy();
66 s.starts_with("card") && !s.contains('-')
67 })
68 .map(|e| e.path())
69 .collect()
70 })
71 .unwrap_or_default()
72}
73
74pub fn detect_gpu_vendors() -> Vec<GpuVendor> {
77 let mut vendors = Vec::new();
78 for card_path in drm_card_paths() {
79 let vendor_path = card_path.join("device/vendor");
80 if let Ok(vendor_id) = fs::read_to_string(vendor_path) {
81 let vendor_id = vendor_id.trim();
82 let vendor = match vendor_id {
83 "0x1002" => GpuVendor::Amd,
84 "0x10de" => GpuVendor::Nvidia,
85 "0x8086" => GpuVendor::Intel,
86 _ => continue,
87 };
88 if !vendors.contains(&vendor) {
89 vendors.push(vendor);
90 }
91 }
92 }
93
94 if vendors.is_empty() {
95 vendors.push(GpuVendor::Unknown);
96 }
97
98 vendors
99}
100
101pub fn detect_gpu_models() -> Vec<Option<String>> {
104 let card_paths = drm_card_paths();
105 if card_paths.is_empty() {
106 return Vec::new();
107 }
108
109 let amd_gfx_targets = detect_amd_gfx_targets();
110 let mut amd_card_idx: usize = 0;
111 let mut models = Vec::new();
112 for card_path in &card_paths {
113 let vendor_path = card_path.join("device/vendor");
114 if let Ok(vendor_id) = fs::read_to_string(vendor_path) {
115 let vendor_id = vendor_id.trim();
116 let vendor = match vendor_id {
117 "0x1002" => GpuVendor::Amd,
118 "0x10de" => GpuVendor::Nvidia,
119 "0x8086" => GpuVendor::Intel,
120 _ => continue,
121 };
122
123 let vendor_name = match vendor {
124 GpuVendor::Amd => "AMD",
125 GpuVendor::Nvidia => "NVIDIA",
126 GpuVendor::Intel => "Intel",
127 GpuVendor::Unknown => continue,
128 };
129
130 if vendor == GpuVendor::Amd {
131 if let Some(gfx) = amd_gfx_targets.get(amd_card_idx % amd_gfx_targets.len()) {
132 models.push(Some(format!("{} ({})", vendor_name, gfx)));
133 } else {
134 models.push(Some(vendor_name.to_string()));
135 }
136 amd_card_idx += 1;
137 } else {
138 models.push(Some(vendor_name.to_string()));
139 }
140 }
141 }
142
143 models
144}
145
146fn gfx_target_to_string(val: u32) -> Option<String> {
149 if val == 0 {
150 return None;
151 }
152 let major = val / 10000;
153 let minor = (val % 10000) / 100;
154 let stepping = val % 100;
155
156 if stepping > 0 {
157 Some(format!("gfx{}{}{}", major, minor, stepping))
158 } else {
159 Some(format!("gfx{}{}", major, minor))
160 }
161}
162
163pub fn detect_amd_gfx_targets() -> Vec<String> {
167 let kfd_path = Path::new("/sys/class/kfd/kfd/topology/nodes");
168 if !kfd_path.exists() {
169 return Vec::new();
170 }
171
172 let mut targets = Vec::new();
173 if let Ok(entries) = fs::read_dir(kfd_path) {
174 for entry in entries.flatten() {
175 let props_path = entry.path().join("properties");
176 if let Ok(props) = fs::read_to_string(props_path) {
177 for line in props.lines() {
178 if line.starts_with("gfx_target_version")
179 && let Some(val_str) = line.split_whitespace().last()
180 && let Ok(val) = val_str.parse::<u32>()
181 && let Some(gfx) = gfx_target_to_string(val) {
182 if !targets.contains(&gfx) {
183 targets.push(gfx);
184 }
185 break;
186 }
187 }
188 }
189 }
190 }
191 targets
192}
193
194pub fn detect_amd_gfx_target() -> Option<String> {
197 detect_amd_gfx_targets().into_iter().next()
198}
199
200pub fn get_lemonade_gfx_suffix(gfx: &str) -> &'static str {
202 if gfx.starts_with("gfx103") {
203 "gfx103X"
204 } else if gfx.starts_with("gfx110") {
205 "gfx110X"
206 } else if gfx == "gfx1150" {
207 "gfx1150"
208 } else if gfx == "gfx1151" {
209 "gfx1151"
210 } else if gfx.starts_with("gfx120") {
211 "gfx120X"
212 } else {
213 "gfx110X"
215 }
216}