#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DeviceInfo {
pub name: String,
pub architecture: String,
pub generation: Option<u32>,
pub class: Option<char>,
pub recommended_working_set: u64,
pub max_threads_per_threadgroup: usize,
pub physical_memory: u64,
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) fn parse_architecture(arch: &str) -> (Option<u32>, Option<char>) {
let Some(rest) = arch.strip_prefix("applegpu_g") else {
return (None, None);
};
let digits = rest.chars().take_while(char::is_ascii_digit).count();
let generation = rest[..digits].parse().ok();
let class = rest[digits..]
.chars()
.next()
.filter(char::is_ascii_lowercase);
(generation, class)
}
#[cfg(test)]
mod tests {
use super::parse_architecture;
#[test]
fn parses_apple_gpu_architectures() {
assert_eq!(parse_architecture("applegpu_g16s"), (Some(16), Some('s')));
assert_eq!(parse_architecture("applegpu_g17p"), (Some(17), Some('p')));
assert_eq!(parse_architecture("applegpu_g13"), (Some(13), None));
assert_eq!(parse_architecture("Paravirtual"), (None, None));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum NaxReason {
Override,
Os,
Generation,
NoLibrary,
Unmeasured,
PipelineError,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum NaxStatus {
Active {
forced: bool,
},
Off(NaxReason),
}
impl NaxStatus {
pub fn is_active(&self) -> bool {
matches!(self, Self::Active { .. })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) enum NaxOverride {
Auto,
On,
Off,
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
impl NaxOverride {
pub(crate) fn from_env() -> Self {
match std::env::var("NUVEC_NAX") {
Err(_) => Self::Auto,
Ok(v) => match v.to_ascii_lowercase().as_str() {
"" | "auto" => Self::Auto,
"on" | "1" => Self::On,
"off" | "0" => Self::Off,
other => {
eprintln!("nuvec: ignoring NUVEC_NAX={other:?} (auto, on or off)");
Self::Auto
}
},
}
}
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) fn nax_eligible(generation: Option<u32>, class: Option<char>, os: (u32, u32)) -> bool {
let min = if class == Some('p') { 18 } else { 17 };
os >= (26, 2) && generation.is_some_and(|g| g >= min)
}
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) fn nax_status(
generation: Option<u32>,
class: Option<char>,
os: (u32, u32),
overriding: NaxOverride,
library: bool,
) -> NaxStatus {
let eligible = nax_eligible(generation, class, os);
match overriding {
NaxOverride::Off => NaxStatus::Off(NaxReason::Override),
_ if os < (26, 2) => NaxStatus::Off(NaxReason::Os),
_ if !library => NaxStatus::Off(NaxReason::NoLibrary),
NaxOverride::On => NaxStatus::Active { forced: !eligible },
NaxOverride::Auto if eligible => NaxStatus::Off(NaxReason::Unmeasured),
NaxOverride::Auto => NaxStatus::Off(NaxReason::Generation),
}
}
#[cfg(test)]
mod nax_tests {
use super::*;
#[test]
fn the_nax_decision_follows_mlx_and_the_override() {
use NaxOverride::{Auto, Off, On};
let off = |r| NaxStatus::Off(r);
for generation in [None, Some(13), Some(16), Some(17), Some(18)] {
for class in ['g', 's', 'd', 'p', 'c'] {
for os in [(15, 6), (26, 0), (26, 1), (26, 2), (26, 6), (27, 0)] {
let min = if class == 'p' { 18 } else { 17 };
let eligible = os >= (26, 2) && generation.is_some_and(|g| g >= min);
assert_eq!(nax_eligible(generation, Some(class), os), eligible);
let auto = nax_status(generation, Some(class), os, Auto, true);
let want = match (os >= (26, 2), eligible) {
(false, _) => off(NaxReason::Os),
(true, true) => off(NaxReason::Unmeasured),
(true, false) => off(NaxReason::Generation),
};
assert_eq!(auto, want, "{generation:?} {class} {os:?}");
let on = nax_status(generation, Some(class), os, On, true);
let want = match os >= (26, 2) {
true => NaxStatus::Active { forced: !eligible },
false => off(NaxReason::Os),
};
assert_eq!(on, want);
assert_eq!(
nax_status(generation, Some(class), os, Off, true),
off(NaxReason::Override)
);
}
}
}
assert_eq!(
nax_status(Some(17), Some('s'), (26, 2), On, false),
off(NaxReason::NoLibrary)
);
}
}