1#[derive(Debug, Clone, PartialEq, Eq)]
5#[non_exhaustive]
6pub struct DeviceInfo {
7 pub name: String,
9 pub architecture: String,
11 pub generation: Option<u32>,
14 pub class: Option<char>,
17 pub recommended_working_set: u64,
19 pub max_threads_per_threadgroup: usize,
21 pub physical_memory: u64,
24}
25
26#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
28pub(crate) fn parse_architecture(arch: &str) -> (Option<u32>, Option<char>) {
29 let Some(rest) = arch.strip_prefix("applegpu_g") else {
30 return (None, None);
31 };
32 let digits = rest.chars().take_while(char::is_ascii_digit).count();
33 let generation = rest[..digits].parse().ok();
34 let class = rest[digits..]
35 .chars()
36 .next()
37 .filter(char::is_ascii_lowercase);
38 (generation, class)
39}
40
41#[cfg(test)]
42mod tests {
43 use super::parse_architecture;
44
45 #[test]
46 fn parses_apple_gpu_architectures() {
47 assert_eq!(parse_architecture("applegpu_g16s"), (Some(16), Some('s')));
48 assert_eq!(parse_architecture("applegpu_g17p"), (Some(17), Some('p')));
49 assert_eq!(parse_architecture("applegpu_g13"), (Some(13), None));
50 assert_eq!(parse_architecture("Paravirtual"), (None, None));
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56#[non_exhaustive]
57pub enum NaxReason {
58 Override,
60 Os,
62 Generation,
65 NoLibrary,
67 Unmeasured,
70 PipelineError,
72 NoDevice,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78#[non_exhaustive]
79pub enum NaxStatus {
80 Active {
84 forced: bool,
86 },
87 Off(NaxReason),
89}
90
91impl NaxStatus {
92 pub fn is_active(&self) -> bool {
94 matches!(self, Self::Active { .. })
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
101pub(crate) enum NaxOverride {
102 Auto,
103 On,
104 Off,
105}
106
107#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
108impl NaxOverride {
109 pub(crate) fn from_env() -> Self {
111 match std::env::var("NUVEC_NAX") {
112 Err(_) => Self::Auto,
113 Ok(v) => match v.to_ascii_lowercase().as_str() {
114 "" | "auto" => Self::Auto,
115 "on" | "1" => Self::On,
116 "off" | "0" => Self::Off,
117 other => {
118 eprintln!("nuvec: ignoring NUVEC_NAX={other:?} (auto, on or off)");
119 Self::Auto
120 }
121 },
122 }
123 }
124}
125
126#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
127pub(crate) fn nax_eligible(generation: Option<u32>, class: Option<char>, os: (u32, u32)) -> bool {
130 let min = if class == Some('p') { 18 } else { 17 };
131 os >= (26, 2) && generation.is_some_and(|g| g >= min)
132}
133
134#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
135pub(crate) fn nax_status(
138 generation: Option<u32>,
139 class: Option<char>,
140 os: (u32, u32),
141 overriding: NaxOverride,
142 library: bool,
143) -> NaxStatus {
144 let eligible = nax_eligible(generation, class, os);
145 match overriding {
146 NaxOverride::Off => NaxStatus::Off(NaxReason::Override),
147 _ if os < (26, 2) => NaxStatus::Off(NaxReason::Os),
148 _ if !library => NaxStatus::Off(NaxReason::NoLibrary),
149 NaxOverride::On => NaxStatus::Active { forced: !eligible },
150 NaxOverride::Auto if eligible => NaxStatus::Off(NaxReason::Unmeasured),
151 NaxOverride::Auto => NaxStatus::Off(NaxReason::Generation),
152 }
153}
154
155#[cfg(test)]
156mod nax_tests {
157 use super::*;
158
159 #[test]
160 fn the_nax_decision_follows_mlx_and_the_override() {
161 use NaxOverride::{Auto, Off, On};
162 let off = |r| NaxStatus::Off(r);
163 for generation in [None, Some(13), Some(16), Some(17), Some(18)] {
165 for class in ['g', 's', 'd', 'p', 'c'] {
166 for os in [(15, 6), (26, 0), (26, 1), (26, 2), (26, 6), (27, 0)] {
167 let min = if class == 'p' { 18 } else { 17 };
168 let eligible = os >= (26, 2) && generation.is_some_and(|g| g >= min);
169 assert_eq!(nax_eligible(generation, Some(class), os), eligible);
170 let auto = nax_status(generation, Some(class), os, Auto, true);
171 let want = match (os >= (26, 2), eligible) {
172 (false, _) => off(NaxReason::Os),
173 (true, true) => off(NaxReason::Unmeasured),
174 (true, false) => off(NaxReason::Generation),
175 };
176 assert_eq!(auto, want, "{generation:?} {class} {os:?}");
177 let on = nax_status(generation, Some(class), os, On, true);
178 let want = match os >= (26, 2) {
179 true => NaxStatus::Active { forced: !eligible },
180 false => off(NaxReason::Os),
181 };
182 assert_eq!(on, want);
183 assert_eq!(
184 nax_status(generation, Some(class), os, Off, true),
185 off(NaxReason::Override)
186 );
187 }
188 }
189 }
190 assert_eq!(
192 nax_status(Some(17), Some('s'), (26, 2), On, false),
193 off(NaxReason::NoLibrary)
194 );
195 }
196}
197
198pub fn physical_memory() -> u64 {
201 #[cfg(target_os = "macos")]
202 return objc2_foundation::NSProcessInfo::processInfo().physicalMemory();
203 #[cfg(not(target_os = "macos"))]
204 std::fs::read_to_string("/proc/meminfo")
205 .ok()
206 .and_then(|s| {
207 let line = s.lines().find(|l| l.starts_with("MemTotal:"))?;
208 let kib: u64 = line.split_whitespace().nth(1)?.parse().ok()?;
209 Some(kib * 1024)
210 })
211 .unwrap_or(0)
212}