From 847dc755ac27956e3b376df9a04cca6f5618ac31 Mon Sep 17 00:00:00 2001 From: jars Date: Sat, 9 May 2026 11:49:32 +0100 Subject: [PATCH] feat(core): inference_thread_count halves on battery Adds battery-state awareness to the thread-count helper: when probe_power_state() returns OnBattery, chosen is halved before the [MIN, MAX] clamp. Also removes the leading underscore from the workload/gpu_offloaded parameters (they are silenced via let _ = ... until the GPU-clamp task). New test battery_halves_thread_count verifies on_battery <= on_ac and >= MIN when the host has more than MIN physical cores. Restructured to sequential with_override calls (not nested) to avoid re-entrant deadlock on power::TEST_LOCK. Co-Authored-By: Claude Sonnet 4.6 --- crates/core/src/tuning.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/crates/core/src/tuning.rs b/crates/core/src/tuning.rs index 70ec908..aa7a1b7 100644 --- a/crates/core/src/tuning.rs +++ b/crates/core/src/tuning.rs @@ -40,7 +40,7 @@ const GPU_FLOOR_WHISPER: usize = 4; /// 3. on battery → base /= 2. /// 4. gpu_offloaded → base = min(base, gpu_floor(workload)). /// 5. clamp to [MIN_INFERENCE_THREADS, MAX_INFERENCE_THREADS]. -pub fn inference_thread_count(_workload: Workload, _gpu_offloaded: bool) -> usize { +pub fn inference_thread_count(workload: Workload, gpu_offloaded: bool) -> usize { if let Ok(s) = std::env::var("MAGNOTIA_INFERENCE_THREADS") { if let Ok(n) = s.parse::() { if n > 0 { @@ -49,13 +49,17 @@ pub fn inference_thread_count(_workload: Workload, _gpu_offloaded: bool) -> usiz } } let physical = num_cpus::get_physical(); - let chosen = if physical > 0 { + let mut chosen = if physical > 0 { physical } else { std::thread::available_parallelism() .map(|p| p.get()) .unwrap_or(MIN_INFERENCE_THREADS) }; + if power::probe_power_state() == PowerState::OnBattery { + chosen /= 2; + } + let _ = (workload, gpu_offloaded); // GPU clamp added in next task chosen.clamp(MIN_INFERENCE_THREADS, MAX_INFERENCE_THREADS) } @@ -101,4 +105,27 @@ mod tests { fn workload_variants_distinct() { assert_ne!(Workload::Llm, Workload::Whisper); } + + #[test] + fn battery_halves_thread_count() { + with_thread_env_lock(|| { + std::env::remove_var("MAGNOTIA_INFERENCE_THREADS"); + // Measure on battery, then on AC — sequential, not nested, + // to avoid re-entrant deadlock on power::TEST_LOCK. + let on_battery = crate::power::with_override(Some(PowerState::OnBattery), || { + inference_thread_count(Workload::Llm, false) + }); + let on_ac = crate::power::with_override(Some(PowerState::OnAc), || { + inference_thread_count(Workload::Llm, false) + }); + // on_battery should be roughly half of on_ac, clamped to MIN. + if on_ac > MIN_INFERENCE_THREADS { + assert!( + on_battery <= on_ac, + "battery ({on_battery}) should be <= AC ({on_ac})" + ); + assert!(on_battery >= MIN_INFERENCE_THREADS); + } + }); + } }