Store preferences by model profile
This commit is contained in:
@@ -4,6 +4,12 @@ use std::sync::RwLock;
|
||||
#[derive(Clone)]
|
||||
pub(super) struct PreferenceDraft {
|
||||
pub(super) model: ModelChoice,
|
||||
pub(super) default_reasoning_mode: ReasoningMode,
|
||||
pub(super) generation_model: ModelChoice,
|
||||
pub(super) generation_reasoning_mode: ReasoningMode,
|
||||
pub(super) acceleration_model: ModelChoice,
|
||||
generation_profiles: BTreeMap<ModelChoice, BTreeMap<ReasoningMode, GenerationPreferences>>,
|
||||
model_profiles: BTreeMap<ModelChoice, ModelPreferences>,
|
||||
pub(super) default_permission_mode: PermissionMode,
|
||||
pub(super) legacy_mtp_enabled: bool,
|
||||
pub(super) dspark_enabled: bool,
|
||||
@@ -28,7 +34,6 @@ pub(super) struct PreferenceDraft {
|
||||
pub(super) top_p: String,
|
||||
pub(super) min_p: String,
|
||||
pub(super) seed: String,
|
||||
pub(super) reasoning_mode: ReasoningMode,
|
||||
pub(super) cpu_threads: String,
|
||||
pub(super) power_percent: String,
|
||||
pub(super) prefill_chunk: String,
|
||||
@@ -58,12 +63,19 @@ pub(super) struct PreferenceDraft {
|
||||
|
||||
impl PreferenceDraft {
|
||||
pub(super) fn from_saved(config: &Config) -> Self {
|
||||
let generation = &config.generation;
|
||||
let runtime = &config.runtime;
|
||||
let default_reasoning_mode = config.reasoning_mode(config.model);
|
||||
let generation = config.generation_for(config.model, default_reasoning_mode);
|
||||
let runtime = config.runtime_for(config.model);
|
||||
let execution = &runtime.execution;
|
||||
let speculative = &runtime.speculative;
|
||||
Self {
|
||||
model: config.model,
|
||||
default_reasoning_mode,
|
||||
generation_model: config.model,
|
||||
generation_reasoning_mode: default_reasoning_mode,
|
||||
acceleration_model: config.model,
|
||||
generation_profiles: config.generation_profiles.clone(),
|
||||
model_profiles: config.model_profiles.clone(),
|
||||
default_permission_mode: config.default_permission_mode,
|
||||
legacy_mtp_enabled: speculative.legacy_mtp_enabled,
|
||||
dspark_enabled: speculative.dspark_enabled,
|
||||
@@ -83,12 +95,11 @@ impl PreferenceDraft {
|
||||
git_ignore_blank_lines: config.git.ignore_blank_lines,
|
||||
context_tokens: generation.context_tokens.to_string(),
|
||||
max_generated_tokens: generation.max_generated_tokens.to_string(),
|
||||
system_prompt: text_editor::Content::with_text(&generation.system_prompt),
|
||||
system_prompt: text_editor::Content::with_text(&config.system_prompt),
|
||||
temperature: optional_string(generation.temperature),
|
||||
top_p: optional_string(generation.top_p),
|
||||
min_p: optional_string(generation.min_p),
|
||||
seed: optional_string(generation.seed),
|
||||
reasoning_mode: generation.reasoning_mode,
|
||||
cpu_threads: optional_string(execution.cpu_threads),
|
||||
power_percent: optional_string(execution.power_percent),
|
||||
prefill_chunk: optional_string(execution.prefill_chunk),
|
||||
@@ -137,7 +148,7 @@ impl PreferenceDraft {
|
||||
top_p: parse_optional_f32("Top-p", &self.top_p)?,
|
||||
min_p: parse_optional_f32("Min-p", &self.min_p)?,
|
||||
seed: parse_optional_u64("Seed", &self.seed)?,
|
||||
reasoning_mode: self.reasoning_mode,
|
||||
reasoning_mode: self.generation_reasoning_mode,
|
||||
};
|
||||
preferences.validate()?;
|
||||
Ok(preferences)
|
||||
@@ -159,6 +170,39 @@ impl PreferenceDraft {
|
||||
*self = Self::from_saved(&Config::default());
|
||||
}
|
||||
|
||||
fn store_generation(&mut self) -> Result<(), String> {
|
||||
let generation = self.generation()?;
|
||||
self.generation_profiles
|
||||
.entry(self.generation_model)
|
||||
.or_default()
|
||||
.insert(self.generation_reasoning_mode, generation);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn load_generation(&mut self, model: ModelChoice, mode: ReasoningMode) {
|
||||
let mut generation = self
|
||||
.generation_profiles
|
||||
.get(&model)
|
||||
.and_then(|profiles| profiles.get(&mode))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
generation.reasoning_mode = mode;
|
||||
self.generation_model = model;
|
||||
self.generation_reasoning_mode = mode;
|
||||
self.context_tokens = generation.context_tokens.to_string();
|
||||
self.max_generated_tokens = generation.max_generated_tokens.to_string();
|
||||
self.temperature = optional_string(generation.temperature);
|
||||
self.top_p = optional_string(generation.top_p);
|
||||
self.min_p = optional_string(generation.min_p);
|
||||
self.seed = optional_string(generation.seed);
|
||||
}
|
||||
|
||||
fn select_generation(&mut self, model: ModelChoice, mode: ReasoningMode) -> Result<(), String> {
|
||||
self.store_generation()?;
|
||||
self.load_generation(model, mode);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn execution(&self) -> Result<ExecutionPreferences, String> {
|
||||
Ok(ExecutionPreferences {
|
||||
cpu_threads: parse_optional_u32("CPU helper threads", &self.cpu_threads)?,
|
||||
@@ -185,20 +229,92 @@ impl PreferenceDraft {
|
||||
})
|
||||
}
|
||||
|
||||
fn acceleration(&self) -> Result<(SpeculativePreferences, SsdPreferences), String> {
|
||||
let speculative = self.speculative()?;
|
||||
let ssd = SsdPreferences {
|
||||
enabled: self.ssd_streaming,
|
||||
cold: self.ssd_streaming_cold,
|
||||
cache: parse_streaming_cache(&self.ssd_cache)?,
|
||||
full_layers: parse_optional_u32("SSD full-layer count", &self.ssd_full_layers)?,
|
||||
preload_experts: parse_optional_u32("SSD preload experts", &self.ssd_preload_experts)?,
|
||||
};
|
||||
speculative.validate(self.acceleration_model)?;
|
||||
ssd.validate(self.acceleration_model)?;
|
||||
Ok((speculative, ssd))
|
||||
}
|
||||
|
||||
fn store_acceleration(&mut self) -> Result<(), String> {
|
||||
let (speculative, ssd) = self.acceleration()?;
|
||||
let profile = self
|
||||
.model_profiles
|
||||
.entry(self.acceleration_model)
|
||||
.or_default();
|
||||
profile.speculative = speculative;
|
||||
profile.ssd = ssd;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_acceleration(&mut self, model: ModelChoice) {
|
||||
let profile = self.model_profiles.get(&model).cloned().unwrap_or_default();
|
||||
let speculative = profile.speculative;
|
||||
let ssd = profile.ssd;
|
||||
self.acceleration_model = model;
|
||||
self.legacy_mtp_enabled = speculative.legacy_mtp_enabled;
|
||||
self.dspark_enabled = speculative.dspark_enabled;
|
||||
self.mtp_draft_tokens = speculative.mtp_draft_tokens.to_string();
|
||||
self.mtp_margin = speculative.mtp_margin.to_string();
|
||||
self.glm_mtp = speculative.glm_mtp;
|
||||
self.glm_mtp_timing = speculative.glm_mtp_timing;
|
||||
self.dspark_confidence_threshold = optional_string(speculative.dspark_confidence_threshold);
|
||||
self.dspark_strict = speculative.dspark_strict;
|
||||
self.ssd_streaming = ssd.enabled;
|
||||
self.ssd_streaming_cold = ssd.cold;
|
||||
self.ssd_cache = optional_string(ssd.cache);
|
||||
self.ssd_full_layers = optional_string(ssd.full_layers);
|
||||
self.ssd_preload_experts = optional_string(ssd.preload_experts);
|
||||
}
|
||||
|
||||
fn select_acceleration(&mut self, model: ModelChoice) -> Result<(), String> {
|
||||
self.store_acceleration()?;
|
||||
self.load_acceleration(model);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn effective_for(
|
||||
&self,
|
||||
model: ModelChoice,
|
||||
mode: ReasoningMode,
|
||||
) -> Result<crate::settings::EffectiveSettings, String> {
|
||||
let mut draft = self.clone();
|
||||
draft.store_generation()?;
|
||||
draft.store_acceleration()?;
|
||||
let mut generation = draft
|
||||
.generation_profiles
|
||||
.get(&model)
|
||||
.and_then(|profiles| profiles.get(&mode))
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
generation.system_prompt = draft.system_prompt.text();
|
||||
generation.reasoning_mode = mode;
|
||||
let mut runtime = draft.runtime()?;
|
||||
if let Some(profile) = draft.model_profiles.get(&model) {
|
||||
runtime.speculative = profile.speculative.clone();
|
||||
runtime.ssd = profile.ssd.clone();
|
||||
}
|
||||
crate::settings::effective_settings(model, &generation, &runtime, &models_path())
|
||||
}
|
||||
|
||||
pub(super) fn reasoning_mode_for(&self, model: ModelChoice) -> ReasoningMode {
|
||||
self.model_profiles
|
||||
.get(&model)
|
||||
.map_or(ReasoningMode::default(), |profile| profile.reasoning_mode)
|
||||
}
|
||||
|
||||
pub(super) fn runtime(&self) -> Result<RuntimePreferences, String> {
|
||||
Ok(RuntimePreferences {
|
||||
execution: self.execution()?,
|
||||
speculative: self.speculative()?,
|
||||
ssd: SsdPreferences {
|
||||
enabled: self.ssd_streaming,
|
||||
cold: self.ssd_streaming_cold,
|
||||
cache: parse_streaming_cache(&self.ssd_cache)?,
|
||||
full_layers: parse_optional_u32("SSD full-layer count", &self.ssd_full_layers)?,
|
||||
preload_experts: parse_optional_u32(
|
||||
"SSD preload experts",
|
||||
&self.ssd_preload_experts,
|
||||
)?,
|
||||
},
|
||||
speculative: SpeculativePreferences::default(),
|
||||
ssd: SsdPreferences::default(),
|
||||
steering: SteeringPreferences {
|
||||
file: optional_text(&self.directional_steering_file),
|
||||
ffn_scale: parse_optional_f32(
|
||||
@@ -386,13 +502,14 @@ impl App {
|
||||
self.preference_error = Some("Endpoint port must be a whole number.".into());
|
||||
return;
|
||||
};
|
||||
let generation = match self.preference_draft.generation() {
|
||||
Ok(generation) => generation,
|
||||
Err(error) => {
|
||||
self.preference_error = Some(error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(error) = self.preference_draft.store_generation() {
|
||||
self.preference_error = Some(error);
|
||||
return;
|
||||
}
|
||||
if let Err(error) = self.preference_draft.store_acceleration() {
|
||||
self.preference_error = Some(error);
|
||||
return;
|
||||
}
|
||||
let runtime = match self.preference_draft.runtime() {
|
||||
Ok(runtime) => runtime,
|
||||
Err(error) => {
|
||||
@@ -407,6 +524,11 @@ impl App {
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.preference_draft
|
||||
.model_profiles
|
||||
.entry(self.preference_draft.model)
|
||||
.or_default()
|
||||
.reasoning_mode = self.preference_draft.default_reasoning_mode;
|
||||
let config = Config {
|
||||
model: self.preference_draft.model,
|
||||
default_permission_mode: self.preference_draft.default_permission_mode,
|
||||
@@ -421,7 +543,9 @@ impl App {
|
||||
enabled: self.preference_draft.dev_brain_enabled,
|
||||
vault_path: optional_text(&self.preference_draft.dev_brain_vault_path),
|
||||
},
|
||||
generation,
|
||||
system_prompt: self.preference_draft.system_prompt.text(),
|
||||
generation_profiles: self.preference_draft.generation_profiles.clone(),
|
||||
model_profiles: self.preference_draft.model_profiles.clone(),
|
||||
runtime,
|
||||
git,
|
||||
interface: self.config.interface.clone(),
|
||||
@@ -482,6 +606,11 @@ impl App {
|
||||
return;
|
||||
}
|
||||
self.config = config;
|
||||
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
self.agent_tools = None;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
if dev_brain_changed {
|
||||
self.invalidate_dev_brain_context();
|
||||
@@ -507,33 +636,65 @@ impl App {
|
||||
) -> Result<Message, Task<Message>> {
|
||||
match message {
|
||||
Message::PreferenceModelChanged(model) => {
|
||||
self.preference_draft.model = model;
|
||||
if !model.supports_dspark() {
|
||||
self.preference_draft.legacy_mtp_enabled = false;
|
||||
self.preference_draft.dspark_enabled = false;
|
||||
self.preference_draft.dspark_confidence_threshold.clear();
|
||||
self.preference_draft.dspark_strict = false;
|
||||
if let Err(error) = self.preference_draft.store_generation() {
|
||||
self.preference_error = Some(error);
|
||||
return Err(Task::none());
|
||||
}
|
||||
if let Err(error) = self.preference_draft.store_acceleration() {
|
||||
self.preference_error = Some(error);
|
||||
return Err(Task::none());
|
||||
}
|
||||
let mode = self.preference_draft.reasoning_mode_for(model);
|
||||
self.preference_draft.model = model;
|
||||
self.preference_draft.default_reasoning_mode = mode;
|
||||
self.preference_draft.load_generation(model, mode);
|
||||
self.preference_draft.load_acceleration(model);
|
||||
if model == ModelChoice::Glm52 {
|
||||
self.preference_draft.power_percent.clear();
|
||||
self.preference_draft.prefill_chunk.clear();
|
||||
self.preference_draft.directional_steering_file.clear();
|
||||
self.preference_draft.directional_steering_ffn.clear();
|
||||
self.preference_draft.directional_steering_attn.clear();
|
||||
} else {
|
||||
self.preference_draft.glm_mtp = false;
|
||||
self.preference_draft.glm_mtp_timing = false;
|
||||
self.preference_draft.ssd_full_layers.clear();
|
||||
}
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceDefaultReasoningChanged(mode) => {
|
||||
if self.preference_draft.generation_model == self.preference_draft.model
|
||||
&& let Err(error) = self
|
||||
.preference_draft
|
||||
.select_generation(self.preference_draft.model, mode)
|
||||
{
|
||||
self.preference_error = Some(error);
|
||||
return Err(Task::none());
|
||||
}
|
||||
self.preference_draft.default_reasoning_mode = mode;
|
||||
self.preference_draft
|
||||
.model_profiles
|
||||
.entry(self.preference_draft.model)
|
||||
.or_default()
|
||||
.reasoning_mode = mode;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceGenerationModelChanged(model) => {
|
||||
let mode = self.preference_draft.reasoning_mode_for(model);
|
||||
self.preference_error = self.preference_draft.select_generation(model, mode).err();
|
||||
}
|
||||
Message::PreferenceGenerationReasoningChanged(mode) => {
|
||||
self.preference_error = self
|
||||
.preference_draft
|
||||
.select_generation(self.preference_draft.generation_model, mode)
|
||||
.err();
|
||||
}
|
||||
Message::PreferenceAccelerationModelChanged(model) => {
|
||||
self.preference_error = self.preference_draft.select_acceleration(model).err();
|
||||
}
|
||||
Message::PreferencePermissionModeChanged(mode) => {
|
||||
self.preference_draft.default_permission_mode = mode;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceLegacyMtpChanged(enabled) => {
|
||||
self.preference_draft.legacy_mtp_enabled =
|
||||
self.preference_draft.model.supports_dspark() && enabled;
|
||||
self.preference_draft.acceleration_model.supports_dspark() && enabled;
|
||||
if self.preference_draft.legacy_mtp_enabled {
|
||||
self.preference_draft.dspark_enabled = false;
|
||||
self.preference_draft.dspark_confidence_threshold.clear();
|
||||
@@ -543,7 +704,7 @@ impl App {
|
||||
}
|
||||
Message::PreferenceDsparkChanged(enabled) => {
|
||||
self.preference_draft.dspark_enabled =
|
||||
self.preference_draft.model.supports_dspark() && enabled;
|
||||
self.preference_draft.acceleration_model.supports_dspark() && enabled;
|
||||
if !self.preference_draft.dspark_enabled {
|
||||
self.preference_draft.dspark_confidence_threshold.clear();
|
||||
self.preference_draft.dspark_strict = false;
|
||||
@@ -675,10 +836,6 @@ impl App {
|
||||
self.preference_draft.seed = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceReasoningChanged(value) => {
|
||||
self.preference_draft.reasoning_mode = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceCpuThreadsChanged(value) => {
|
||||
self.preference_draft.cpu_threads = value;
|
||||
self.preference_error = None;
|
||||
@@ -709,7 +866,7 @@ impl App {
|
||||
}
|
||||
Message::PreferenceGlmMtpChanged(value) => {
|
||||
self.preference_draft.glm_mtp =
|
||||
self.preference_draft.model == ModelChoice::Glm52 && value;
|
||||
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
|
||||
if !self.preference_draft.glm_mtp {
|
||||
self.preference_draft.glm_mtp_timing = false;
|
||||
}
|
||||
@@ -717,7 +874,7 @@ impl App {
|
||||
}
|
||||
Message::PreferenceGlmMtpTimingChanged(value) => {
|
||||
self.preference_draft.glm_mtp_timing =
|
||||
self.preference_draft.model == ModelChoice::Glm52 && value;
|
||||
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
|
||||
if self.preference_draft.glm_mtp_timing {
|
||||
self.preference_draft.glm_mtp = true;
|
||||
}
|
||||
@@ -725,7 +882,7 @@ impl App {
|
||||
}
|
||||
Message::PreferenceDsparkConfidenceChanged(value) => {
|
||||
self.preference_draft.dspark_confidence_threshold = value;
|
||||
if self.preference_draft.model.supports_dspark()
|
||||
if self.preference_draft.acceleration_model.supports_dspark()
|
||||
&& !self
|
||||
.preference_draft
|
||||
.dspark_confidence_threshold
|
||||
@@ -739,7 +896,7 @@ impl App {
|
||||
}
|
||||
Message::PreferenceDsparkStrictChanged(value) => {
|
||||
self.preference_draft.dspark_strict =
|
||||
self.preference_draft.model.supports_dspark() && value;
|
||||
self.preference_draft.acceleration_model.supports_dspark() && value;
|
||||
if self.preference_draft.dspark_strict {
|
||||
self.preference_draft.dspark_enabled = true;
|
||||
self.preference_draft.legacy_mtp_enabled = false;
|
||||
@@ -759,7 +916,7 @@ impl App {
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceSsdFullLayersChanged(value) => {
|
||||
if self.preference_draft.model == ModelChoice::Glm52 {
|
||||
if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
|
||||
self.preference_draft.ssd_full_layers = value;
|
||||
}
|
||||
self.preference_error = None;
|
||||
@@ -842,6 +999,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selectors_rehydrate_their_generation_and_model_profiles() {
|
||||
let mut draft = PreferenceDraft::from_saved(&Config::default());
|
||||
draft.context_tokens = "123".into();
|
||||
draft.ssd_streaming = true;
|
||||
|
||||
draft
|
||||
.select_generation(ModelChoice::Glm52, ReasoningMode::Direct)
|
||||
.unwrap();
|
||||
assert_eq!(draft.context_tokens, "32768");
|
||||
draft.context_tokens = "456".into();
|
||||
draft
|
||||
.select_generation(ModelChoice::DeepSeekV4Flash, ReasoningMode::High)
|
||||
.unwrap();
|
||||
assert_eq!(draft.context_tokens, "123");
|
||||
|
||||
draft.select_acceleration(ModelChoice::Glm52).unwrap();
|
||||
assert!(!draft.ssd_streaming);
|
||||
draft
|
||||
.select_acceleration(ModelChoice::DeepSeekV4Flash)
|
||||
.unwrap();
|
||||
assert!(draft.ssd_streaming);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_config_updates_after_lock_poisoning() {
|
||||
let runtime = Arc::new(RwLock::new(Config::default()));
|
||||
|
||||
Reference in New Issue
Block a user