feat(A.1 #7): runtime CPU feature detection (AVX2/FMA/AVX-512/SSE4.2/NEON)
Extends hardware::CpuInfo with a CpuFeatures struct populated via std::is_x86_feature_detected! on x86_64 and an architectural assumption for aarch64 (NEON). Adds has_ggml_baseline() so callers can cheaply ask 'will whisper.cpp / llama.cpp ship a fast path on this CPU?' without knowing the arch-specific rule. The point of #7 is giving the runtime a way to surface a clear "non-AVX2 fallback" warning before the user hits a wall of silent slowness. The banner itself ships in the next commit as part of get_runtime_capabilities (item #1). Tests cover the baseline helper on both x86 and non-x86 targets. Co-authored-by: jars <jakejars@users.noreply.github.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ dist/
|
|||||||
Cargo.lock
|
Cargo.lock
|
||||||
.firecrawl/
|
.firecrawl/
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
.cargo/
|
||||||
|
|||||||
@@ -15,6 +15,70 @@ pub struct SystemProfile {
|
|||||||
pub struct CpuInfo {
|
pub struct CpuInfo {
|
||||||
pub logical_processors: usize,
|
pub logical_processors: usize,
|
||||||
pub brand: String,
|
pub brand: String,
|
||||||
|
pub features: CpuFeatures,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runtime-detected CPU feature flags relevant to the speech-to-text
|
||||||
|
/// and LLM backends Kon ships. All whisper.cpp / llama.cpp / ggml
|
||||||
|
/// kernels degrade roughly two tiers without AVX2, which is why we
|
||||||
|
/// surface it separately: when AVX2 is absent, the UI should warn the
|
||||||
|
/// user that performance will be a fraction of what they would see
|
||||||
|
/// on a contemporary CPU. References:
|
||||||
|
/// - whisper-rs #8, #117 (illegal instruction on pre-AVX2 CPUs)
|
||||||
|
/// - Buzz FAQ (non-AVX2 fallback builds)
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
pub struct CpuFeatures {
|
||||||
|
pub avx2: bool,
|
||||||
|
pub avx512f: bool,
|
||||||
|
pub fma: bool,
|
||||||
|
pub sse4_2: bool,
|
||||||
|
pub neon: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CpuFeatures {
|
||||||
|
/// Whether this CPU has the baseline ggml expects (AVX2 + FMA on
|
||||||
|
/// x86_64, NEON on aarch64). If false, the runtime banner fires.
|
||||||
|
pub fn has_ggml_baseline(&self) -> bool {
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
{
|
||||||
|
return self.avx2 && self.fma;
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
{
|
||||||
|
return self.neon;
|
||||||
|
}
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Probes CPU feature flags via compile-time/runtime CPUID. On x86_64
|
||||||
|
/// we rely on `std::is_x86_feature_detected!`, which lowers to CPUID
|
||||||
|
/// at runtime. On aarch64 we assume NEON (architectural baseline);
|
||||||
|
/// on other targets all flags are false.
|
||||||
|
pub fn probe_cpu_features() -> CpuFeatures {
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
{
|
||||||
|
return CpuFeatures {
|
||||||
|
avx2: std::is_x86_feature_detected!("avx2"),
|
||||||
|
avx512f: std::is_x86_feature_detected!("avx512f"),
|
||||||
|
fma: std::is_x86_feature_detected!("fma"),
|
||||||
|
sse4_2: std::is_x86_feature_detected!("sse4.2"),
|
||||||
|
neon: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
|
{
|
||||||
|
return CpuFeatures {
|
||||||
|
avx2: false,
|
||||||
|
avx512f: false,
|
||||||
|
fma: false,
|
||||||
|
sse4_2: false,
|
||||||
|
neon: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
CpuFeatures::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -64,6 +128,7 @@ fn probe_cpu_from(sys: &System) -> CpuInfo {
|
|||||||
.first()
|
.first()
|
||||||
.map(|c| c.brand().to_string())
|
.map(|c| c.brand().to_string())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
features: probe_cpu_features(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,3 +168,53 @@ pub fn probe_system() -> SystemProfile {
|
|||||||
os: probe_os(),
|
os: probe_os(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_cpu_features_runs_without_panicking() {
|
||||||
|
let _ = probe_cpu_features();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_system_populates_cpu_features() {
|
||||||
|
let profile = probe_system();
|
||||||
|
// The check doesn't assume the runner has AVX2; it just asserts
|
||||||
|
// that the feature probe was actually called and is wired in.
|
||||||
|
let f = profile.cpu.features;
|
||||||
|
assert!(
|
||||||
|
f == f,
|
||||||
|
"CpuFeatures must be PartialEq so the runtime banner can debounce"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ggml_baseline_matches_x86_64_rule() {
|
||||||
|
let features = CpuFeatures {
|
||||||
|
avx2: true,
|
||||||
|
fma: true,
|
||||||
|
..CpuFeatures::default()
|
||||||
|
};
|
||||||
|
// Only actually true on x86_64 — on other arches the helper
|
||||||
|
// returns false, which is equally fine for this test.
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
assert!(features.has_ggml_baseline());
|
||||||
|
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||||
|
assert!(!features.has_ggml_baseline());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ggml_baseline_requires_both_avx2_and_fma() {
|
||||||
|
let features = CpuFeatures {
|
||||||
|
avx2: true,
|
||||||
|
fma: false,
|
||||||
|
..CpuFeatures::default()
|
||||||
|
};
|
||||||
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
|
assert!(!features.has_ggml_baseline());
|
||||||
|
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||||
|
assert!(!features.has_ggml_baseline());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ pub fn rank_recommendations(profile: &SystemProfile) -> Vec<ScoredModel> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::hardware::{CpuInfo, GpuAcceleration, GpuInfo, GpuVendor, Os};
|
use crate::hardware::{CpuFeatures, CpuInfo, GpuAcceleration, GpuInfo, GpuVendor, Os};
|
||||||
|
|
||||||
fn profile_with_ram(ram: Megabytes) -> SystemProfile {
|
fn profile_with_ram(ram: Megabytes) -> SystemProfile {
|
||||||
SystemProfile {
|
SystemProfile {
|
||||||
@@ -93,6 +93,7 @@ mod tests {
|
|||||||
cpu: CpuInfo {
|
cpu: CpuInfo {
|
||||||
logical_processors: 8,
|
logical_processors: 8,
|
||||||
brand: "Test CPU".into(),
|
brand: "Test CPU".into(),
|
||||||
|
features: CpuFeatures::default(),
|
||||||
},
|
},
|
||||||
gpu: None,
|
gpu: None,
|
||||||
os: Os::Windows,
|
os: Os::Windows,
|
||||||
@@ -105,6 +106,7 @@ mod tests {
|
|||||||
cpu: CpuInfo {
|
cpu: CpuInfo {
|
||||||
logical_processors: 8,
|
logical_processors: 8,
|
||||||
brand: "Test CPU".into(),
|
brand: "Test CPU".into(),
|
||||||
|
features: CpuFeatures::default(),
|
||||||
},
|
},
|
||||||
gpu: Some(GpuInfo {
|
gpu: Some(GpuInfo {
|
||||||
vendor: GpuVendor::Nvidia,
|
vendor: GpuVendor::Nvidia,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2313,22 +2313,22 @@
|
|||||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:default",
|
"const": "dialog:default",
|
||||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the ask command without any pre-configured scope.",
|
"description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:allow-ask",
|
"const": "dialog:allow-ask",
|
||||||
"markdownDescription": "Enables the ask command without any pre-configured scope."
|
"markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the confirm command without any pre-configured scope.",
|
"description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:allow-confirm",
|
"const": "dialog:allow-confirm",
|
||||||
"markdownDescription": "Enables the confirm command without any pre-configured scope."
|
"markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the message command without any pre-configured scope.",
|
"description": "Enables the message command without any pre-configured scope.",
|
||||||
@@ -2349,16 +2349,16 @@
|
|||||||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the ask command without any pre-configured scope.",
|
"description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:deny-ask",
|
"const": "dialog:deny-ask",
|
||||||
"markdownDescription": "Denies the ask command without any pre-configured scope."
|
"markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the confirm command without any pre-configured scope.",
|
"description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:deny-confirm",
|
"const": "dialog:deny-confirm",
|
||||||
"markdownDescription": "Denies the confirm command without any pre-configured scope."
|
"markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the message command without any pre-configured scope.",
|
"description": "Denies the message command without any pre-configured scope.",
|
||||||
|
|||||||
@@ -2313,22 +2313,22 @@
|
|||||||
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
"markdownDescription": "Denies the unminimize command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:default",
|
"const": "dialog:default",
|
||||||
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the ask command without any pre-configured scope.",
|
"description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:allow-ask",
|
"const": "dialog:allow-ask",
|
||||||
"markdownDescription": "Enables the ask command without any pre-configured scope."
|
"markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the confirm command without any pre-configured scope.",
|
"description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:allow-confirm",
|
"const": "dialog:allow-confirm",
|
||||||
"markdownDescription": "Enables the confirm command without any pre-configured scope."
|
"markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the message command without any pre-configured scope.",
|
"description": "Enables the message command without any pre-configured scope.",
|
||||||
@@ -2349,16 +2349,16 @@
|
|||||||
"markdownDescription": "Enables the save command without any pre-configured scope."
|
"markdownDescription": "Enables the save command without any pre-configured scope."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the ask command without any pre-configured scope.",
|
"description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:deny-ask",
|
"const": "dialog:deny-ask",
|
||||||
"markdownDescription": "Denies the ask command without any pre-configured scope."
|
"markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the confirm command without any pre-configured scope.",
|
"description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "dialog:deny-confirm",
|
"const": "dialog:deny-confirm",
|
||||||
"markdownDescription": "Denies the confirm command without any pre-configured scope."
|
"markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the message command without any pre-configured scope.",
|
"description": "Denies the message command without any pre-configured scope.",
|
||||||
|
|||||||
Reference in New Issue
Block a user