feat: more preferences

This commit is contained in:
Georg Bauer
2026-07-24 16:04:15 +02:00
parent ac464317d4
commit 87dfc01e74
11 changed files with 1317 additions and 57 deletions

View File

@@ -5,7 +5,11 @@ use std::fs;
use std::path::Path;
use crate::schema::{preferences, projects, sessions};
use crate::settings::{ExecutionPreferences, GenerationPreferences, ReasoningMode};
use crate::settings::{
DiagnosticPreferences, ExecutionPreferences, GenerationPreferences, ReasoningMode,
RuntimePreferences, SpeculativePreferences, SsdPreferences, SteeringPreferences,
StreamingCacheBudget,
};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
@@ -30,6 +34,23 @@ pub struct AppPreferences {
pub prefill_chunk: Option<i32>,
pub quality: bool,
pub warm_weights: bool,
pub mtp_draft_tokens: i32,
pub mtp_margin: f32,
pub glm_mtp: bool,
pub glm_mtp_timing: bool,
pub dspark_confidence_threshold: Option<f32>,
pub dspark_strict: bool,
pub ssd_streaming: bool,
pub ssd_streaming_cold: bool,
pub ssd_cache_experts: Option<i64>,
pub ssd_cache_gib: Option<i64>,
pub ssd_full_layers: Option<i32>,
pub ssd_preload_experts: Option<i32>,
pub directional_steering_file: Option<String>,
pub directional_steering_ffn: Option<f32>,
pub directional_steering_attn: Option<f32>,
pub simulated_used_memory_gib: Option<i64>,
pub expert_profile_path: Option<String>,
}
impl Default for AppPreferences {
@@ -52,6 +73,23 @@ impl Default for AppPreferences {
prefill_chunk: None,
quality: false,
warm_weights: false,
mtp_draft_tokens: 1,
mtp_margin: 3.0,
glm_mtp: false,
glm_mtp_timing: false,
dspark_confidence_threshold: None,
dspark_strict: false,
ssd_streaming: false,
ssd_streaming_cold: false,
ssd_cache_experts: None,
ssd_cache_gib: None,
ssd_full_layers: None,
ssd_preload_experts: None,
directional_steering_file: None,
directional_steering_ffn: None,
directional_steering_attn: None,
simulated_used_memory_gib: None,
expert_profile_path: None,
}
}
}
@@ -99,6 +137,65 @@ impl AppPreferences {
warm_weights: self.warm_weights,
})
}
pub(crate) fn speculative(&self) -> SpeculativePreferences {
SpeculativePreferences {
mtp_draft_tokens: self.mtp_draft_tokens,
mtp_margin: self.mtp_margin,
glm_mtp: self.glm_mtp,
glm_mtp_timing: self.glm_mtp_timing,
dspark_enabled: self.dspark_enabled,
dspark_confidence_threshold: self.dspark_confidence_threshold,
dspark_strict: self.dspark_strict,
}
}
pub(crate) fn runtime(&self) -> Result<RuntimePreferences, String> {
let cache = match (self.ssd_cache_experts, self.ssd_cache_gib) {
(None, None) => None,
(Some(experts), None) => Some(StreamingCacheBudget::Experts(
u32::try_from(experts).map_err(|_| "Saved SSD expert count is invalid.")?,
)),
(None, Some(gib)) => Some(StreamingCacheBudget::Gib(
u64::try_from(gib).map_err(|_| "Saved SSD cache budget is invalid.")?,
)),
(Some(_), Some(_)) => {
return Err("Saved SSD cache budget has both count and GiB values.".into());
}
};
Ok(RuntimePreferences {
execution: self.execution()?,
speculative: self.speculative(),
ssd: SsdPreferences {
enabled: self.ssd_streaming,
cold: self.ssd_streaming_cold,
cache,
full_layers: self
.ssd_full_layers
.map(u32::try_from)
.transpose()
.map_err(|_| "Saved SSD full-layer count is invalid.")?,
preload_experts: self
.ssd_preload_experts
.map(u32::try_from)
.transpose()
.map_err(|_| "Saved SSD preload count is invalid.")?,
},
steering: SteeringPreferences {
file: self.directional_steering_file.clone(),
ffn_scale: self.directional_steering_ffn,
attention_scale: self.directional_steering_attn,
},
diagnostics: DiagnosticPreferences {
simulated_used_memory_gib: self
.simulated_used_memory_gib
.map(u64::try_from)
.transpose()
.map_err(|_| "Saved simulated memory is invalid.")?,
expert_profile_path: self.expert_profile_path.clone(),
},
})
}
}
#[derive(AsChangeset)]
@@ -120,6 +217,23 @@ struct PreferenceChanges<'a> {
prefill_chunk: Option<i32>,
quality: bool,
warm_weights: bool,
mtp_draft_tokens: i32,
mtp_margin: f32,
glm_mtp: bool,
glm_mtp_timing: bool,
dspark_confidence_threshold: Option<f32>,
dspark_strict: bool,
ssd_streaming: bool,
ssd_streaming_cold: bool,
ssd_cache_experts: Option<i64>,
ssd_cache_gib: Option<i64>,
ssd_full_layers: Option<i32>,
ssd_preload_experts: Option<i32>,
directional_steering_file: Option<&'a str>,
directional_steering_ffn: Option<f32>,
directional_steering_attn: Option<f32>,
simulated_used_memory_gib: Option<i64>,
expert_profile_path: Option<&'a str>,
}
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
@@ -220,20 +334,29 @@ impl Database {
pub fn update_preferences(
&mut self,
selected_model: &str,
dspark_enabled: bool,
idle_timeout_minutes: i32,
generation: &GenerationPreferences,
execution: &ExecutionPreferences,
runtime: &RuntimePreferences,
) -> Result<AppPreferences, String> {
generation.validate()?;
let model = crate::model::ModelChoice::from_id(selected_model)
.ok_or_else(|| format!("Unsupported model: {selected_model}"))?;
execution.validate(model)?;
runtime.engine_settings(model)?;
let execution = &runtime.execution;
let speculative = &runtime.speculative;
let (ssd_cache_experts, ssd_cache_gib) = match runtime.ssd.cache {
None => (None, None),
Some(StreamingCacheBudget::Experts(experts)) => (Some(i64::from(experts)), None),
Some(StreamingCacheBudget::Gib(gib)) => (
None,
Some(i64::try_from(gib).map_err(|_| "SSD cache budget is too large.")?),
),
};
let seed = generation.seed.map(|seed| seed.to_string());
diesel::update(preferences::table.find(1))
.set(PreferenceChanges {
selected_model,
dspark_enabled,
dspark_enabled: speculative.dspark_enabled,
idle_timeout_minutes,
context_tokens: generation.context_tokens,
max_generated_tokens: generation.max_generated_tokens,
@@ -248,6 +371,26 @@ impl Database {
prefill_chunk: execution.prefill_chunk.map(|value| value as i32),
quality: execution.quality,
warm_weights: execution.warm_weights,
mtp_draft_tokens: speculative.mtp_draft_tokens,
mtp_margin: speculative.mtp_margin,
glm_mtp: speculative.glm_mtp,
glm_mtp_timing: speculative.glm_mtp_timing,
dspark_confidence_threshold: speculative.dspark_confidence_threshold,
dspark_strict: speculative.dspark_strict,
ssd_streaming: runtime.ssd.enabled,
ssd_streaming_cold: runtime.ssd.cold,
ssd_cache_experts,
ssd_cache_gib,
ssd_full_layers: runtime.ssd.full_layers.map(|value| value as i32),
ssd_preload_experts: runtime.ssd.preload_experts.map(|value| value as i32),
directional_steering_file: runtime.steering.file.as_deref(),
directional_steering_ffn: runtime.steering.ffn_scale,
directional_steering_attn: runtime.steering.attention_scale,
simulated_used_memory_gib: runtime
.diagnostics
.simulated_used_memory_gib
.map(|value| value as i64),
expert_profile_path: runtime.diagnostics.expert_profile_path.as_deref(),
})
.returning(AppPreferences::as_returning())
.get_result(&mut self.connection)
@@ -308,15 +451,26 @@ mod tests {
assert!(!preferences.dspark_enabled);
assert_eq!(preferences.idle_timeout_minutes, 10);
let generation = GenerationPreferences::default();
let execution = ExecutionPreferences::default();
let runtime = RuntimePreferences::default();
assert!(
database
.update_preferences("glm-5.2", true, 30, &generation, &execution)
.update_preferences(
"glm-5.2",
30,
&generation,
&RuntimePreferences {
speculative: SpeculativePreferences {
dspark_enabled: true,
..runtime.speculative.clone()
},
..runtime.clone()
}
)
.is_err()
);
assert!(
database
.update_preferences("deepseek-v4-flash", false, 0, &generation, &execution)
.update_preferences("deepseek-v4-flash", 0, &generation, &runtime,)
.is_err()
);
let generation = GenerationPreferences {
@@ -328,10 +482,33 @@ mod tests {
let execution = ExecutionPreferences {
cpu_threads: Some(8),
quality: true,
..execution
..runtime.execution
};
let speculative = SpeculativePreferences {
glm_mtp: true,
glm_mtp_timing: true,
mtp_draft_tokens: 4,
mtp_margin: 2.5,
..runtime.speculative
};
let runtime = RuntimePreferences {
execution,
speculative,
ssd: SsdPreferences {
enabled: true,
cache: Some(StreamingCacheBudget::Gib(64)),
full_layers: Some(0),
preload_experts: Some(32),
..SsdPreferences::default()
},
diagnostics: DiagnosticPreferences {
simulated_used_memory_gib: Some(8),
expert_profile_path: Some("/tmp/experts.json".into()),
},
..RuntimePreferences::default()
};
database
.update_preferences("glm-5.2", false, 30, &generation, &execution)
.update_preferences("glm-5.2", 30, &generation, &runtime)
.unwrap();
let project = database.create_project("DS4", "/tmp/ds4").unwrap();
@@ -354,7 +531,7 @@ mod tests {
assert_eq!(preferences.selected_model, "glm-5.2");
assert_eq!(preferences.idle_timeout_minutes, 30);
assert_eq!(preferences.generation().unwrap(), generation);
assert_eq!(preferences.execution().unwrap(), execution);
assert_eq!(preferences.runtime().unwrap(), runtime);
drop(reopened);
fs::remove_file(path).unwrap();
}