use serde::{Deserialize, Serialize};
#[cfg(any(target_os = "macos", target_os = "windows"))]
use std::process::Command;
use sysinfo::System;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HardwareTier {
Tier8GB,
Tier16GB,
Tier32GB,
Tier64GBPlus,
}
impl HardwareTier {
pub fn from_bytes(bytes: u64) -> Self {
let gib = bytes / (1024 * 1024 * 1024);
if gib <= 10 {
HardwareTier::Tier8GB
} else if gib <= 20 {
HardwareTier::Tier16GB
} else if gib <= 40 {
HardwareTier::Tier32GB
} else {
HardwareTier::Tier64GBPlus
}
}
pub fn display_name(&self) -> &'static str {
match self {
HardwareTier::Tier8GB => "8 GB Tier (Lightweight / Thin-and-Light)",
HardwareTier::Tier16GB => "16 GB Tier (Standard Developer Laptop)",
HardwareTier::Tier32GB => "24–36 GB Tier (High-End Pro / Max Laptop)",
HardwareTier::Tier64GBPlus => "64 GB+ Tier (Workstation / Studio)",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareProfile {
pub total_memory_bytes: u64,
pub total_memory_gb: f64,
pub chip_name: String,
pub tier: HardwareTier,
pub recommended_model: &'static str,
pub concise_model: &'static str,
pub recommended_size: &'static str,
pub recommended_quant: &'static str,
pub disk_size: &'static str,
pub recommended_context: usize,
pub recommended_context_display: &'static str,
pub recommended_launch_command: String,
pub ollama_launch_command: String,
pub ram_footprint: &'static str,
}
impl HardwareProfile {
pub fn detect() -> Self {
let total_memory_bytes = detect_total_memory().unwrap_or(16 * 1024 * 1024 * 1024);
let chip_name = detect_chip_name().unwrap_or_else(|| "Standard CPU".to_string());
Self::from_specs(total_memory_bytes, chip_name)
}
pub fn from_specs(total_memory_bytes: u64, chip_name: String) -> Self {
let chip_name = crate::ui::sanitize_title(&chip_name);
let total_memory_gb = total_memory_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
let tier = HardwareTier::from_bytes(total_memory_bytes);
let (
recommended_model,
concise_model,
recommended_size,
recommended_quant,
disk_size,
recommended_context,
recommended_context_display,
model_file,
ollama_model,
ram_footprint,
) = match tier {
HardwareTier::Tier8GB => (
"ggml-org/gemma-4-E4B-it-GGUF:Q8_0",
"gemma-4",
"E4B",
"Q8_0",
"~8.6 GB",
8_192,
"8k ctx",
"gemma-4-E4B-it-Q8_0.gguf",
"gemma2:9b",
"~8.9 GB",
),
HardwareTier::Tier16GB => (
"ggml-org/gemma-4-E4B-it-GGUF:Q8_0",
"gemma-4",
"E4B",
"Q8_0",
"~8.6 GB",
32_768,
"32k ctx",
"gemma-4-E4B-it-Q8_0.gguf",
"gemma2:9b",
"~9.4 GB",
),
HardwareTier::Tier32GB => (
"ggml-org/gemma-4-E4B-it-GGUF:Q8_0",
"gemma-4",
"E4B",
"Q8_0",
"~8.6 GB",
65_536,
"64k ctx",
"gemma-4-E4B-it-Q8_0.gguf",
"gemma2:9b",
"~10 GB",
),
HardwareTier::Tier64GBPlus => (
"ggml-org/gemma-4-26B-A4B-it-GGUF:Q8_0",
"gemma-4",
"26B-A4B",
"Q8_0",
"~28.0 GB",
131_072,
"128k ctx",
"gemma-4-26B-A4B-it-Q8_0.gguf",
"gemma2:27b",
"~30–32 GB",
),
};
let recommended_launch_command = format!(
"llama-server -m {} --alias {} -c {} --port 9931",
model_file, recommended_model, recommended_context
);
let ollama_launch_command = format!(
"ollama run {} --ctx-size {}",
ollama_model, recommended_context
);
Self {
total_memory_bytes,
total_memory_gb,
chip_name,
tier,
recommended_model,
concise_model,
recommended_size,
recommended_quant,
disk_size,
recommended_context,
recommended_context_display,
recommended_launch_command,
ollama_launch_command,
ram_footprint,
}
}
pub fn llama_gui_selection(&self) -> String {
format!(
"{} | {} | {} | {}",
self.concise_model,
self.recommended_size,
self.recommended_quant,
self.recommended_context_display
)
}
pub fn llama_gui_badge_summary(&self) -> String {
format!(
"{} [{}] [{}] ({})",
self.concise_model,
self.recommended_size,
self.recommended_quant,
self.recommended_context_display
)
}
pub fn one_pass_token_ceiling(&self) -> usize {
self.recommended_context * 7 / 10
}
pub fn to_json_value(&self) -> serde_json::Value {
serde_json::json!({
"device": self.chip_name,
"total_memory_bytes": self.total_memory_bytes,
"total_memory_gb": self.total_memory_gb,
"tier": self.tier.display_name(),
"recommended_family": "Gemma 4",
"recommended_model": self.recommended_model,
"concise_model": self.concise_model,
"recommended_size": self.recommended_size,
"recommended_quant": self.recommended_quant,
"disk_size": self.disk_size,
"recommended_context": self.recommended_context,
"recommended_context_display": self.recommended_context_display,
"recommended_launch_command": self.recommended_launch_command,
"ollama_launch_command": self.ollama_launch_command,
"ram_footprint": self.ram_footprint,
"one_pass_token_ceiling": self.one_pass_token_ceiling(),
"llama_gui_selection": self.llama_gui_selection(),
})
}
}
pub fn format_context_tokens(tokens: usize) -> String {
if tokens >= 1024 && tokens.is_multiple_of(1024) {
format!("{}k ctx", tokens / 1024)
} else if tokens >= 1000 {
format!("{}k ctx", tokens / 1000)
} else {
format!("{} ctx", tokens)
}
}
fn detect_total_memory() -> Option<u64> {
if let Ok(mut sys) = System::new() {
sys.refresh_memory();
let mem = sys.total_memory();
if mem > 0 {
return Some(mem);
}
}
#[cfg(target_os = "macos")]
{
if let Ok(output) = Command::new("sysctl").args(["-n", "hw.memsize"]).output()
&& output.status.success()
&& let Ok(text) = std::str::from_utf8(&output.stdout)
&& let Ok(bytes) = text.trim().parse::<u64>()
{
return Some(bytes);
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if let Some(rest) = line.strip_prefix("MemTotal:") {
let parts: Vec<&str> = rest.split_whitespace().collect();
if let Some(kb_str) = parts.first()
&& let Ok(kb) = kb_str.parse::<u64>()
{
return Some(kb * 1024);
}
}
}
}
}
#[cfg(target_os = "windows")]
{
if let Ok(output) = Command::new("powershell")
.args([
"-NoProfile",
"-Command",
"(Get-CimInstance Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum",
])
.output()
&& output.status.success()
&& let Ok(text) = std::str::from_utf8(&output.stdout)
&& let Ok(bytes) = text.trim().parse::<u64>()
{
return Some(bytes);
}
}
None
}
fn detect_chip_name() -> Option<String> {
if let Ok(mut sys) = System::new() {
sys.refresh_cpu_all();
if let Some(cpu) = sys.cpus().first() {
let brand = cpu.brand().trim();
if !brand.is_empty() {
return Some(brand.to_string());
}
}
}
#[cfg(target_os = "macos")]
{
if let Ok(output) = Command::new("sysctl")
.args(["-n", "machdep.cpu.brand_string"])
.output()
&& output.status.success()
&& let Ok(text) = std::str::from_utf8(&output.stdout)
{
let clean = text.trim();
if !clean.is_empty() {
return Some(clean.to_string());
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/cpuinfo") {
for line in content.lines() {
if let Some(rest) = line.strip_prefix("model name")
&& let Some(val) = rest.split(':').nth(1)
{
let clean = val.trim();
if !clean.is_empty() {
return Some(clean.to_string());
}
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hardware_tier_classification() {
assert_eq!(
HardwareTier::from_bytes(8 * 1024 * 1024 * 1024),
HardwareTier::Tier8GB
);
assert_eq!(
HardwareTier::from_bytes(16 * 1024 * 1024 * 1024),
HardwareTier::Tier16GB
);
assert_eq!(
HardwareTier::from_bytes(24 * 1024 * 1024 * 1024),
HardwareTier::Tier32GB
);
assert_eq!(
HardwareTier::from_bytes(64 * 1024 * 1024 * 1024),
HardwareTier::Tier64GBPlus
);
}
#[test]
fn test_profile_from_specs() {
let profile = HardwareProfile::from_specs(16 * 1024 * 1024 * 1024, "Apple M3".to_string());
assert_eq!(profile.tier, HardwareTier::Tier16GB);
assert_eq!(
profile.recommended_model,
"ggml-org/gemma-4-E4B-it-GGUF:Q8_0"
);
assert_eq!(profile.concise_model, "gemma-4");
assert_eq!(profile.recommended_size, "E4B");
assert_eq!(profile.recommended_quant, "Q8_0");
assert_eq!(profile.disk_size, "~8.6 GB");
assert_eq!(profile.recommended_context, 32_768);
assert_eq!(profile.recommended_context_display, "32k ctx");
assert_eq!(profile.ram_footprint, "~9.4 GB");
assert!(profile.recommended_launch_command.contains("-c 32768"));
assert!(profile.recommended_launch_command.contains("gemma-4-E4B"));
assert!(
profile
.ollama_launch_command
.contains("ollama run gemma2:9b --ctx-size 32768")
);
assert_eq!(profile.one_pass_token_ceiling(), 32_768 * 7 / 10);
assert_eq!(
profile.llama_gui_selection(),
"gemma-4 | E4B | Q8_0 | 32k ctx"
);
assert_eq!(
profile.llama_gui_badge_summary(),
"gemma-4 [E4B] [Q8_0] (32k ctx)"
);
}
#[test]
fn test_profile_64gb_specs() {
let profile =
HardwareProfile::from_specs(64 * 1024 * 1024 * 1024, "Apple M3 Max".to_string());
assert_eq!(profile.tier, HardwareTier::Tier64GBPlus);
assert_eq!(
profile.recommended_model,
"ggml-org/gemma-4-26B-A4B-it-GGUF:Q8_0"
);
assert_eq!(profile.concise_model, "gemma-4");
assert_eq!(profile.recommended_size, "26B-A4B");
assert_eq!(profile.recommended_quant, "Q8_0");
assert_eq!(profile.disk_size, "~28.0 GB");
assert_eq!(profile.recommended_context, 131_072);
assert_eq!(profile.recommended_context_display, "128k ctx");
assert!(profile.recommended_launch_command.contains("26B-A4B"));
assert_eq!(
profile.llama_gui_selection(),
"gemma-4 | 26B-A4B | Q8_0 | 128k ctx"
);
}
#[test]
fn test_format_context_tokens() {
assert_eq!(format_context_tokens(8192), "8k ctx");
assert_eq!(format_context_tokens(32768), "32k ctx");
assert_eq!(format_context_tokens(65536), "64k ctx");
assert_eq!(format_context_tokens(131072), "128k ctx");
assert_eq!(format_context_tokens(4000), "4k ctx");
assert_eq!(format_context_tokens(512), "512 ctx");
}
#[test]
fn test_hardware_detect_runs_without_panic() {
let profile = HardwareProfile::detect();
assert!(profile.total_memory_bytes > 0);
assert!(!profile.chip_name.is_empty());
}
#[test]
fn test_sysinfo_detection() {
let mem = detect_total_memory();
assert!(mem.is_some_and(|m| m > 0));
let chip = detect_chip_name();
assert!(chip.is_some_and(|c| !c.is_empty()));
}
}