Move preferences from the database to a YAML config file

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Georg Bauer
2026-07-26 09:50:39 +02:00
parent 3f2c42513f
commit 5d441038bc
19 changed files with 571 additions and 964 deletions

View File

@@ -1,4 +1,5 @@
use crate::model::{self, EngineArtifacts, ModelChoice};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::Path;
@@ -17,7 +18,8 @@ const DEFAULT_KV_MIN_TOKENS: u32 = 512;
const DEFAULT_KV_COLD_MAX_TOKENS: u32 = 30_000;
const DEFAULT_KV_CONTINUED_INTERVAL_TOKENS: u32 = 10_000;
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct SpeculativePreferences {
pub(crate) mtp_draft_tokens: i32,
pub(crate) mtp_margin: f32,
@@ -94,13 +96,70 @@ pub(crate) struct EngineSpeculativeSettings {
pub(crate) dspark_strict: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
/// An expert count, or a whole GiB budget. Written as `4` or `64GB`, the same
/// spelling the preferences window accepts.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(into = "String", try_from = "String")]
pub(crate) enum StreamingCacheBudget {
Experts(u32),
Gib(u64),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
impl fmt::Display for StreamingCacheBudget {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Experts(experts) => write!(formatter, "{experts}"),
Self::Gib(gib) => write!(formatter, "{gib}GB"),
}
}
}
impl From<StreamingCacheBudget> for String {
fn from(budget: StreamingCacheBudget) -> Self {
budget.to_string()
}
}
impl TryFrom<String> for StreamingCacheBudget {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.parse()
}
}
impl std::str::FromStr for StreamingCacheBudget {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let value = value.trim();
let gib = value
.get(value.len().saturating_sub(2)..)
.is_some_and(|suffix| suffix.eq_ignore_ascii_case("gb"));
let digits = if gib {
&value[..value.len() - 2]
} else {
value
};
let number = (!digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit()))
.then(|| digits.parse::<u64>().ok())
.flatten()
.filter(|number| *number > 0);
match number {
Some(number) if gib && number <= u64::MAX / GIB => Ok(Self::Gib(number)),
Some(number) if !gib && number <= u64::from(u32::MAX) => {
Ok(Self::Experts(number as u32))
}
_ => Err(
"SSD cache budget must be a positive expert count or whole GiB value such as 64GB."
.into(),
),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct SsdPreferences {
pub(crate) enabled: bool,
pub(crate) cold: bool,
@@ -166,7 +225,8 @@ pub(crate) struct EngineSsdSettings {
/// Disk KV cache management, mirroring ds4's `--kv-disk-space-mb` budget and
/// its store-side gates. Unset values keep the DS4 defaults.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct KvCachePreferences {
pub(crate) budget_gib: Option<u64>,
pub(crate) min_tokens: Option<u32>,
@@ -219,7 +279,8 @@ impl KvCacheSettings {
}
}
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct SteeringPreferences {
pub(crate) file: Option<String>,
pub(crate) ffn_scale: Option<f32>,
@@ -270,7 +331,8 @@ pub(crate) struct EngineSteeringSettings {
pub(crate) attention_scale: f32,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct DiagnosticPreferences {
pub(crate) simulated_used_memory_gib: Option<u64>,
pub(crate) expert_profile_path: Option<String>,
@@ -298,7 +360,8 @@ pub(crate) struct EngineDiagnosticSettings {
pub(crate) expert_profile_path: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct RuntimePreferences {
pub(crate) execution: ExecutionPreferences,
pub(crate) speculative: SpeculativePreferences,
@@ -354,7 +417,8 @@ pub(crate) struct EngineSettings {
pub(crate) diagnostics: EngineDiagnosticSettings,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct ExecutionPreferences {
pub(crate) cpu_threads: Option<u32>,
pub(crate) power_percent: Option<u8>,
@@ -420,28 +484,16 @@ pub(crate) struct EngineExecutionSettings {
pub(crate) warm_weights: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum ReasoningMode {
#[serde(rename = "none")]
Direct,
#[default]
High,
Max,
}
impl ReasoningMode {
pub(crate) fn id(self) -> &'static str {
match self {
Self::Direct => "none",
Self::High => "high",
Self::Max => "max",
}
}
pub(crate) fn from_id(id: &str) -> Option<Self> {
REASONING_MODES.into_iter().find(|mode| mode.id() == id)
}
}
impl fmt::Display for ReasoningMode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
@@ -452,7 +504,8 @@ impl fmt::Display for ReasoningMode {
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct GenerationPreferences {
pub(crate) context_tokens: i32,
pub(crate) max_generated_tokens: i32,