Store preferences by model profile

This commit is contained in:
Georg Bauer
2026-08-02 12:38:21 +02:00
parent 69c83e7059
commit e52efb80b0
13 changed files with 606 additions and 196 deletions

View File

@@ -524,12 +524,10 @@ impl App {
return;
}
let model = self.config.model;
let effective = crate::settings::effective_settings(
model,
&self.config.generation,
&self.config.runtime,
&models_path(),
);
let generation = self.config.active_generation();
let runtime = self.config.runtime_for(model);
let effective =
crate::settings::effective_settings(model, &generation, &runtime, &models_path());
let mut effective = match effective {
Ok(settings) => settings,
Err(error) => {
@@ -728,8 +726,8 @@ impl App {
if self.config.a2ui_enabled {
reminders.push(crate::a2ui::SYSTEM_PROMPT.to_owned());
}
if !self.config.generation.system_prompt.trim().is_empty() {
reminders.push(self.config.generation.system_prompt.clone());
if !self.config.system_prompt.trim().is_empty() {
reminders.push(self.config.system_prompt.clone());
}
if let Some(agents) = self.session_agents_prompt() {
reminders.push(agents.to_owned());
@@ -1239,7 +1237,8 @@ impl App {
.find(|project| project.project.id == project_id)
.map(|project| PathBuf::from(&project.project.path))
.ok_or_else(|| "The active project is unavailable.".to_owned())?;
let mut tools = crate::agent::Tools::new(&root, self.config.generation.context_tokens)?;
let mut tools =
crate::agent::Tools::new(&root, self.config.active_generation().context_tokens)?;
if self.config.dev_brain.enabled {
let projects = self
.projects
@@ -1254,10 +1253,12 @@ impl App {
let approval_mode = match self.permission_mode {
PermissionMode::Heuristic => crate::agent::ShellApprovalMode::Heuristic,
PermissionMode::Ai => {
let generation = self.config.active_generation();
let runtime = self.config.runtime_for(self.config.model);
let effective = crate::settings::effective_settings(
self.config.model,
&self.config.generation,
&self.config.runtime,
&generation,
&runtime,
&models_path(),
)?;
let service = self
@@ -1290,12 +1291,10 @@ impl App {
.ok_or_else(|| "The active session is unavailable.".to_owned())?;
self.skip_compaction_once = false;
let model = self.config.model;
let mut effective = crate::settings::effective_settings(
model,
&self.config.generation,
&self.config.runtime,
&models_path(),
)?;
let generation = self.config.active_generation();
let runtime = self.config.runtime_for(model);
let mut effective =
crate::settings::effective_settings(model, &generation, &runtime, &models_path())?;
effective.turn.system_prompt = self.chat_system_prompt(
model,
&effective.turn.system_prompt,
@@ -1395,12 +1394,10 @@ impl App {
);
}
let model = self.config.model;
let mut effective = crate::settings::effective_settings(
model,
&self.config.generation,
&self.config.runtime,
&models_path(),
)?;
let generation = self.config.active_generation();
let runtime = self.config.runtime_for(model);
let mut effective =
crate::settings::effective_settings(model, &generation, &runtime, &models_path())?;
effective.turn.system_prompt = self.chat_system_prompt(
model,
&effective.turn.system_prompt,
@@ -1548,12 +1545,10 @@ impl App {
reason: &str,
) -> Result<(), String> {
let model = self.config.model;
let mut effective = crate::settings::effective_settings(
model,
&self.config.generation,
&self.config.runtime,
&models_path(),
)?;
let generation = self.config.active_generation();
let runtime = self.config.runtime_for(model);
let mut effective =
crate::settings::effective_settings(model, &generation, &runtime, &models_path())?;
effective.turn.system_prompt = self.chat_system_prompt(
model,
&effective.turn.system_prompt,
@@ -1788,12 +1783,10 @@ impl App {
return Err("A title is already being generated.".into());
}
let model = self.config.model;
let mut effective = crate::settings::effective_settings(
model,
&self.config.generation,
&self.config.runtime,
&models_path(),
)?;
let generation = self.config.active_generation();
let runtime = self.config.runtime_for(model);
let mut effective =
crate::settings::effective_settings(model, &generation, &runtime, &models_path())?;
let database = self
.database
.as_mut()

View File

@@ -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()));

View File

@@ -180,7 +180,7 @@ impl App {
self.queued_inputs.clear();
self.system_prompt_seen_at = 0;
self.context_used = 0;
self.context_limit = self.config.generation.context_tokens.max(0) as u32;
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
self.tokens_per_second = None;
self.error = None;
}

View File

@@ -307,12 +307,12 @@ impl App {
.text_size(12)
.padding([2, 6])
});
let reasoning_mode = self.config.generation.effective_reasoning_mode();
let reasoning_mode = self.config.reasoning_mode(self.config.model);
let reasoning_control: Element<'_, Message> = if self.active_chat_count() > 0 {
text(reasoning_mode.to_string()).size(12).into()
} else {
pick_list(
self.config.generation.supported_reasoning_modes(),
self.config.supported_reasoning_modes(self.config.model),
Some(reasoning_mode),
Message::ReasoningModeChanged,
)

View File

@@ -5,7 +5,7 @@ impl App {
pub(super) fn preferences_panel(&self) -> Element<'_, Message> {
let legacy_mtp_toggle: Option<fn(bool) -> Message> = self
.preference_draft
.model
.acceleration_model
.supports_dspark()
.then_some(Message::PreferenceLegacyMtpChanged);
let legacy_mtp = hint(
@@ -16,7 +16,7 @@ impl App {
);
let dspark_toggle: Option<fn(bool) -> Message> = self
.preference_draft
.model
.acceleration_model
.supports_dspark()
.then_some(Message::PreferenceDsparkChanged);
let dspark = hint(
@@ -25,33 +25,44 @@ impl App {
.on_toggle_maybe(dspark_toggle),
"Speculative decoding with the managed DSpark draft artifact: a small model proposes tokens that the main model verifies in one pass. Usually a large speedup; the target model may also stream routed experts from SSD.",
);
let glm_mtp_toggle: Option<fn(bool) -> Message> = (self.preference_draft.model
== ModelChoice::Glm52)
.then_some(Message::PreferenceGlmMtpChanged);
let glm_mtp_timing_toggle: Option<fn(bool) -> Message> = (self.preference_draft.model
== ModelChoice::Glm52)
.then_some(Message::PreferenceGlmMtpTimingChanged);
let glm_mtp_toggle: Option<fn(bool) -> Message> =
(self.preference_draft.acceleration_model == ModelChoice::Glm52)
.then_some(Message::PreferenceGlmMtpChanged);
let glm_mtp_timing_toggle: Option<fn(bool) -> Message> =
(self.preference_draft.acceleration_model == ModelChoice::Glm52)
.then_some(Message::PreferenceGlmMtpTimingChanged);
let dspark_strict_toggle: Option<fn(bool) -> Message> = self
.preference_draft
.model
.acceleration_model
.supports_dspark()
.then_some(Message::PreferenceDsparkStrictChanged);
let effective = self
.preference_draft
.generation()
.and_then(|generation| {
self.preference_draft.runtime().and_then(|runtime| {
crate::settings::effective_settings(
self.preference_draft.model,
&generation,
&runtime,
&models_path(),
)
})
})
.effective_for(
self.preference_draft.model,
self.preference_draft.default_reasoning_mode,
)
.ok();
let generation_effective = self
.preference_draft
.effective_for(
self.preference_draft.generation_model,
self.preference_draft.generation_reasoning_mode,
)
.ok();
let acceleration_effective = self
.preference_draft
.effective_for(
self.preference_draft.acceleration_model,
self.preference_draft
.reasoning_mode_for(self.preference_draft.acceleration_model),
)
.ok();
let engine = effective.as_ref().map(|settings| &settings.engine);
let turn = effective.as_ref().map(|settings| &settings.turn);
let turn = generation_effective.as_ref().map(|settings| &settings.turn);
let acceleration_engine = acceleration_effective
.as_ref()
.map(|settings| &settings.engine);
let mut power = text_input("100", &self.preference_draft.power_percent);
let mut prefill = text_input("Automatic", &self.preference_draft.prefill_chunk);
let mut ssd_full_layers = text_input("Automatic", &self.preference_draft.ssd_full_layers);
@@ -72,10 +83,11 @@ impl App {
steering_file = steering_file.on_input(Message::PreferenceSteeringFileChanged);
steering_ffn = steering_ffn.on_input(Message::PreferenceSteeringFfnChanged);
steering_attn = steering_attn.on_input(Message::PreferenceSteeringAttnChanged);
} else {
}
if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
ssd_full_layers = ssd_full_layers.on_input(Message::PreferenceSsdFullLayersChanged);
}
if self.preference_draft.model.supports_dspark() {
if self.preference_draft.acceleration_model.supports_dspark() {
dspark_confidence =
dspark_confidence.on_input(Message::PreferenceDsparkConfidenceChanged);
}
@@ -93,6 +105,20 @@ impl App {
.width(Length::Fill),
"Which local weights the Metal engine loads for chats and for the HTTP endpoint. The choice decides which accelerations below apply, and the matching artifacts must be downloaded in the model manager.",
),
row![
hint(
text("Default thinking mode").size(13).width(Length::Fill),
"Thinking mode selected when this model becomes active. Chat can switch it for later turns.",
),
pick_list(
&REASONING_MODES[..],
Some(self.preference_draft.default_reasoning_mode),
Message::PreferenceDefaultReasoningChanged,
)
.width(240),
]
.spacing(12)
.align_y(Alignment::Center),
text(format!(
"Main: {}{}",
engine.map_or_else(
@@ -282,10 +308,49 @@ impl App {
]
.spacing(10),
);
let prompt_group = preference_group(
PreferenceSection::Prompt,
"PROMPT",
column![
hint(
text("System prompt").size(13),
"Standing instruction sent before every conversation: persona, tone, house rules. Leave it empty to send no system message at all.",
),
text_editor(&self.preference_draft.system_prompt)
.placeholder("Additional system instructions…")
.height(140)
.on_action(Message::PreferenceSystemPromptAction)
.padding(9),
text("The system prompt is shared by every model and thinking mode.").size(12),
]
.spacing(10),
);
let generation_group = preference_group(
PreferenceSection::Generation,
"GENERATION",
column![
row![
text("Model").size(13).width(Length::Fill),
pick_list(
&MODEL_CHOICES[..],
Some(self.preference_draft.generation_model),
Message::PreferenceGenerationModelChanged,
)
.width(400),
]
.spacing(12)
.align_y(Alignment::Center),
row![
text("Thinking mode").size(13).width(Length::Fill),
pick_list(
&REASONING_MODES[..],
Some(self.preference_draft.generation_reasoning_mode),
Message::PreferenceGenerationReasoningChanged,
)
.width(240),
]
.spacing(12)
.align_y(Alignment::Center),
preference_input_row(
"Context tokens",
"Size of the window the model can see: system prompt, history, the new question and the answer all have to fit. Larger windows allow longer sessions but reserve much more memory for the key-value cache.",
@@ -298,17 +363,7 @@ impl App {
text_input("50000", &self.preference_draft.max_generated_tokens)
.on_input(Message::PreferenceMaxTokensChanged),
),
hint(
text("System prompt").size(13),
"Standing instruction sent before every conversation: persona, tone, house rules. Leave it empty to send no system message at all.",
),
text_editor(&self.preference_draft.system_prompt)
.placeholder("Additional system instructions…")
.height(140)
.on_action(Message::PreferenceSystemPromptAction)
.padding(9),
Space::new().height(4),
text("SAMPLING & REASONING").size(11).color(muted_text()),
text("SAMPLING").size(11).color(muted_text()),
preference_input_row(
"Temperature",
"How adventurous token choice is. Near 0 the model repeats the most likely continuation, which suits code and extraction; higher values invent more and drift more.",
@@ -333,20 +388,6 @@ impl App {
text_input("Random", &self.preference_draft.seed)
.on_input(Message::PreferenceSeedChanged),
),
row![
hint(
text("Reasoning").size(13).width(Length::Fill),
"How much hidden thinking precedes the answer. Direct skips it and replies fastest, Thinking is the balanced default, Think Max reasons longest and needs at least 393216 context tokens.",
),
pick_list(
&REASONING_MODES[..],
Some(self.preference_draft.reasoning_mode),
Message::PreferenceReasoningChanged,
)
.width(240),
]
.spacing(12)
.align_y(Alignment::Center),
text("Blank sampling values retain DS4's model-family defaults. Think Max needs at least 393216 context tokens.")
.size(12),
text(turn.map_or_else(
@@ -428,6 +469,17 @@ impl App {
PreferenceSection::Acceleration,
"ACCELERATION & MEMORY",
column![
row![
text("Model").size(13).width(Length::Fill),
pick_list(
&MODEL_CHOICES[..],
Some(self.preference_draft.acceleration_model),
Message::PreferenceAccelerationModelChanged,
)
.width(400),
]
.spacing(12)
.align_y(Alignment::Center),
text("SPECULATIVE DECODING").size(11).color(muted_text()),
preference_input_row(
"MTP draft tokens",
@@ -466,15 +518,15 @@ impl App {
.on_toggle_maybe(dspark_strict_toggle),
"Lets the draft model only propose, never decide: every token is sampled by the full model. Gives up some of the speedup in exchange for output identical to non-speculative decoding.",
),
text(if self.preference_draft.model.supports_dspark() {
text(if self.preference_draft.acceleration_model.supports_dspark() {
"Legacy MTP and DSpark use separate managed support artifacts; entering a DSpark threshold or enabling strict mode selects DSpark."
} else if self.preference_draft.model == ModelChoice::Glm52 {
} else if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
"GLM MTP is integrated; DSpark is unavailable for this model."
} else {
"No managed MTP support artifact is available for this model."
})
.size(12),
text(engine.as_ref().map_or_else(
text(acceleration_engine.map_or_else(
|| "Effective speculative settings will appear after valid values are entered."
.to_owned(),
|engine| {
@@ -527,7 +579,7 @@ impl App {
),
text("A blank full-layer value is automatic; an explicit 0 disables fully resident GLM layers. Flash legacy MTP and DSpark support weights remain resident when target experts stream.")
.size(12),
text(engine.as_ref().map_or_else(
text(acceleration_engine.map_or_else(
|| "Effective SSD settings will appear after valid values are entered."
.to_owned(),
|engine| {
@@ -680,6 +732,7 @@ impl App {
endpoint_group,
dev_brain_group,
git_group,
prompt_group,
generation_group,
execution_group,
acceleration_group,