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}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76#[non_exhaustive]
77pub enum NaxStatus {
78 Active {
82 forced: bool,
84 },
85 Off(NaxReason),
87}
88
89impl NaxStatus {
90 pub fn is_active(&self) -> bool {
92 matches!(self, Self::Active { .. })
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
99pub(crate) enum NaxOverride {
100 Auto,
101 On,
102 Off,
103}
104
105#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
106impl NaxOverride {
107 pub(crate) fn from_env() -> Self {
109 match std::env::var("NUVEC_NAX") {
110 Err(_) => Self::Auto,
111 Ok(v) => match v.to_ascii_lowercase().as_str() {
112 "" | "auto" => Self::Auto,
113 "on" | "1" => Self::On,
114 "off" | "0" => Self::Off,
115 other => {
116 eprintln!("nuvec: ignoring NUVEC_NAX={other:?} (auto, on or off)");
117 Self::Auto
118 }
119 },
120 }
121 }
122}
123
124#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
125pub(crate) fn nax_eligible(generation: Option<u32>, class: Option<char>, os: (u32, u32)) -> bool {
128 let min = if class == Some('p') { 18 } else { 17 };
129 os >= (26, 2) && generation.is_some_and(|g| g >= min)
130}
131
132#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
133pub(crate) fn nax_status(
136 generation: Option<u32>,
137 class: Option<char>,
138 os: (u32, u32),
139 overriding: NaxOverride,
140 library: bool,
141) -> NaxStatus {
142 let eligible = nax_eligible(generation, class, os);
143 match overriding {
144 NaxOverride::Off => NaxStatus::Off(NaxReason::Override),
145 _ if os < (26, 2) => NaxStatus::Off(NaxReason::Os),
146 _ if !library => NaxStatus::Off(NaxReason::NoLibrary),
147 NaxOverride::On => NaxStatus::Active { forced: !eligible },
148 NaxOverride::Auto if eligible => NaxStatus::Off(NaxReason::Unmeasured),
149 NaxOverride::Auto => NaxStatus::Off(NaxReason::Generation),
150 }
151}
152
153#[cfg(test)]
154mod nax_tests {
155 use super::*;
156
157 #[test]
158 fn the_nax_decision_follows_mlx_and_the_override() {
159 use NaxOverride::{Auto, Off, On};
160 let off = |r| NaxStatus::Off(r);
161 for generation in [None, Some(13), Some(16), Some(17), Some(18)] {
163 for class in ['g', 's', 'd', 'p', 'c'] {
164 for os in [(15, 6), (26, 0), (26, 1), (26, 2), (26, 6), (27, 0)] {
165 let min = if class == 'p' { 18 } else { 17 };
166 let eligible = os >= (26, 2) && generation.is_some_and(|g| g >= min);
167 assert_eq!(nax_eligible(generation, Some(class), os), eligible);
168 let auto = nax_status(generation, Some(class), os, Auto, true);
169 let want = match (os >= (26, 2), eligible) {
170 (false, _) => off(NaxReason::Os),
171 (true, true) => off(NaxReason::Unmeasured),
172 (true, false) => off(NaxReason::Generation),
173 };
174 assert_eq!(auto, want, "{generation:?} {class} {os:?}");
175 let on = nax_status(generation, Some(class), os, On, true);
176 let want = match os >= (26, 2) {
177 true => NaxStatus::Active { forced: !eligible },
178 false => off(NaxReason::Os),
179 };
180 assert_eq!(on, want);
181 assert_eq!(
182 nax_status(generation, Some(class), os, Off, true),
183 off(NaxReason::Override)
184 );
185 }
186 }
187 }
188 assert_eq!(
190 nax_status(Some(17), Some('s'), (26, 2), On, false),
191 off(NaxReason::NoLibrary)
192 );
193 }
194}