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

@@ -8,6 +8,274 @@ pub(crate) const REASONING_MODES: [ReasoningMode; 3] = [
];
const THINK_MAX_MIN_CONTEXT: i32 = 393_216;
const MAX_CPU_THREADS: u32 = 32;
const MAX_MTP_DRAFT_TOKENS: i32 = 16;
pub(crate) const GIB: u64 = 1024 * 1024 * 1024;
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct SpeculativePreferences {
pub(crate) mtp_draft_tokens: i32,
pub(crate) mtp_margin: f32,
pub(crate) glm_mtp: bool,
pub(crate) glm_mtp_timing: bool,
pub(crate) dspark_enabled: bool,
pub(crate) dspark_confidence_threshold: Option<f32>,
pub(crate) dspark_strict: bool,
}
impl Default for SpeculativePreferences {
fn default() -> Self {
Self {
mtp_draft_tokens: 1,
mtp_margin: 3.0,
glm_mtp: false,
glm_mtp_timing: false,
dspark_enabled: false,
dspark_confidence_threshold: None,
dspark_strict: false,
}
}
}
impl SpeculativePreferences {
pub(crate) fn validate(&self, model: ModelChoice) -> Result<(), String> {
if self.mtp_draft_tokens <= 0 {
return Err("MTP draft tokens must be a positive whole number.".into());
}
validate_float("MTP margin", self.mtp_margin, 0.0, 1000.0)?;
if self.glm_mtp_timing && !self.glm_mtp {
return Err("GLM MTP timing requires GLM MTP.".into());
}
if model != ModelChoice::Glm52 && (self.glm_mtp || self.glm_mtp_timing) {
return Err("GLM MTP is available only for GLM 5.2.".into());
}
if self.dspark_enabled && !model.supports_dspark() {
return Err("DSpark is not available for the selected model.".into());
}
if (self.dspark_confidence_threshold.is_some() || self.dspark_strict)
&& !self.dspark_enabled
{
return Err("DSpark tuning requires DSpark to be enabled.".into());
}
if let Some(threshold) = self.dspark_confidence_threshold {
validate_float("DSpark confidence", threshold, 0.0, 1.0)?;
}
Ok(())
}
pub(crate) fn engine_settings(&self) -> EngineSpeculativeSettings {
EngineSpeculativeSettings {
mtp_draft_tokens: self.mtp_draft_tokens.min(MAX_MTP_DRAFT_TOKENS),
mtp_margin: self.mtp_margin,
glm_mtp: self.glm_mtp,
glm_mtp_timing: self.glm_mtp_timing,
dspark: self.dspark_enabled,
dspark_confidence_threshold: self.dspark_confidence_threshold.unwrap_or(0.9),
dspark_confidence_threshold_set: self.dspark_confidence_threshold.is_some(),
dspark_strict: self.dspark_strict,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct EngineSpeculativeSettings {
pub(crate) mtp_draft_tokens: i32,
pub(crate) mtp_margin: f32,
pub(crate) glm_mtp: bool,
pub(crate) glm_mtp_timing: bool,
pub(crate) dspark: bool,
pub(crate) dspark_confidence_threshold: f32,
pub(crate) dspark_confidence_threshold_set: bool,
pub(crate) dspark_strict: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum StreamingCacheBudget {
Experts(u32),
Gib(u64),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct SsdPreferences {
pub(crate) enabled: bool,
pub(crate) cold: bool,
pub(crate) cache: Option<StreamingCacheBudget>,
pub(crate) full_layers: Option<u32>,
pub(crate) preload_experts: Option<u32>,
}
impl SsdPreferences {
pub(crate) fn validate(&self, model: ModelChoice) -> Result<(), String> {
if self.preload_experts == Some(0) {
return Err("SSD preload experts must be a positive whole number.".into());
}
if self
.preload_experts
.is_some_and(|experts| experts > i32::MAX as u32)
{
return Err("SSD preload experts is too large.".into());
}
if self
.full_layers
.is_some_and(|layers| layers > i32::MAX as u32)
{
return Err("SSD full-layer count is too large.".into());
}
if self.full_layers.is_some_and(|layers| layers > 0) && model != ModelChoice::Glm52 {
return Err("Fully resident SSD layers are available only for GLM 5.2.".into());
}
if let Some(StreamingCacheBudget::Gib(gib)) = self.cache {
validate_gib("SSD cache budget", gib)?;
}
Ok(())
}
pub(crate) fn engine_settings(&self) -> EngineSsdSettings {
let (cache_experts, cache_bytes) = match self.cache {
None => (0, 0),
Some(StreamingCacheBudget::Experts(experts)) => (experts, 0),
Some(StreamingCacheBudget::Gib(gib)) => (0, gib * GIB),
};
EngineSsdSettings {
enabled: self.enabled,
cold: self.cold,
cache_experts,
cache_bytes,
full_layers: self.full_layers.unwrap_or(0),
full_layers_set: self.full_layers.is_some(),
preload_experts: self.preload_experts.unwrap_or(0),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct EngineSsdSettings {
pub(crate) enabled: bool,
pub(crate) cold: bool,
pub(crate) cache_experts: u32,
pub(crate) cache_bytes: u64,
pub(crate) full_layers: u32,
pub(crate) full_layers_set: bool,
pub(crate) preload_experts: u32,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct SteeringPreferences {
pub(crate) file: Option<String>,
pub(crate) ffn_scale: Option<f32>,
pub(crate) attention_scale: Option<f32>,
}
impl SteeringPreferences {
pub(crate) fn validate(&self, model: ModelChoice) -> Result<(), String> {
validate_optional_float("Directional FFN scale", self.ffn_scale, -100.0, 100.0)?;
validate_optional_float(
"Directional attention scale",
self.attention_scale,
-100.0,
100.0,
)?;
if self.file.is_some() && model == ModelChoice::Glm52 {
return Err("Directional steering is not supported for GLM 5.2.".into());
}
if self.file.is_none()
&& (self.ffn_scale.is_some_and(|scale| scale != 0.0)
|| self.attention_scale.is_some_and(|scale| scale != 0.0))
{
return Err("Directional steering scales require a direction-vector file.".into());
}
Ok(())
}
pub(crate) fn engine_settings(&self) -> EngineSteeringSettings {
let no_explicit_scale = self.ffn_scale.is_none() && self.attention_scale.is_none();
EngineSteeringSettings {
file: self.file.clone(),
ffn_scale: self
.ffn_scale
.unwrap_or(if self.file.is_some() && no_explicit_scale {
1.0
} else {
0.0
}),
attention_scale: self.attention_scale.unwrap_or(0.0),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct EngineSteeringSettings {
pub(crate) file: Option<String>,
pub(crate) ffn_scale: f32,
pub(crate) attention_scale: f32,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct DiagnosticPreferences {
pub(crate) simulated_used_memory_gib: Option<u64>,
pub(crate) expert_profile_path: Option<String>,
}
impl DiagnosticPreferences {
pub(crate) fn validate(&self) -> Result<(), String> {
if let Some(gib) = self.simulated_used_memory_gib {
validate_gib("Simulated used memory", gib)?;
}
Ok(())
}
pub(crate) fn engine_settings(&self) -> EngineDiagnosticSettings {
EngineDiagnosticSettings {
simulated_used_memory_bytes: self.simulated_used_memory_gib.unwrap_or(0) * GIB,
expert_profile_path: self.expert_profile_path.clone(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct EngineDiagnosticSettings {
pub(crate) simulated_used_memory_bytes: u64,
pub(crate) expert_profile_path: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct RuntimePreferences {
pub(crate) execution: ExecutionPreferences,
pub(crate) speculative: SpeculativePreferences,
pub(crate) ssd: SsdPreferences,
pub(crate) steering: SteeringPreferences,
pub(crate) diagnostics: DiagnosticPreferences,
}
impl RuntimePreferences {
pub(crate) fn engine_settings(&self, model: ModelChoice) -> Result<EngineSettings, String> {
self.execution.validate(model)?;
self.speculative.validate(model)?;
self.ssd.validate(model)?;
self.steering.validate(model)?;
self.diagnostics.validate()?;
if self.ssd.enabled && self.speculative.dspark_enabled {
return Err("SSD streaming is not compatible with DSpark support.".into());
}
Ok(EngineSettings {
model,
execution: self.execution.engine_settings(),
speculative: self.speculative.engine_settings(),
ssd: self.ssd.engine_settings(),
steering: self.steering.engine_settings(),
diagnostics: self.diagnostics.engine_settings(),
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct EngineSettings {
pub(crate) model: ModelChoice,
pub(crate) execution: EngineExecutionSettings,
pub(crate) speculative: EngineSpeculativeSettings,
pub(crate) ssd: EngineSsdSettings,
pub(crate) steering: EngineSteeringSettings,
pub(crate) diagnostics: EngineDiagnosticSettings,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct ExecutionPreferences {
@@ -151,9 +419,9 @@ impl GenerationPreferences {
Ok(())
}
pub(crate) fn effective(&self, model: ModelChoice) -> EffectiveGenerationSettings {
pub(crate) fn turn_settings(&self, model: ModelChoice) -> TurnSettings {
let glm = model == ModelChoice::Glm52;
EffectiveGenerationSettings {
TurnSettings {
context_tokens: self.context_tokens,
max_generated_tokens: self.max_generated_tokens,
system_prompt: self.system_prompt.clone(),
@@ -178,14 +446,28 @@ fn validate_optional_float(
minimum: f32,
maximum: f32,
) -> Result<(), String> {
if value.is_some_and(|value| !value.is_finite() || !(minimum..=maximum).contains(&value)) {
if let Some(value) = value {
validate_float(name, value, minimum, maximum)?;
}
Ok(())
}
fn validate_float(name: &str, value: f32, minimum: f32, maximum: f32) -> Result<(), String> {
if !value.is_finite() || !(minimum..=maximum).contains(&value) {
return Err(format!("{name} must be between {minimum} and {maximum}."));
}
Ok(())
}
fn validate_gib(name: &str, gib: u64) -> Result<(), String> {
if gib == 0 || gib > u64::MAX / GIB {
return Err(format!("{name} must be a positive whole GiB value."));
}
Ok(())
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct EffectiveGenerationSettings {
pub(crate) struct TurnSettings {
pub(crate) context_tokens: i32,
pub(crate) max_generated_tokens: i32,
pub(crate) system_prompt: String,
@@ -199,17 +481,18 @@ pub(crate) struct EffectiveGenerationSettings {
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
#[test]
fn effective_settings_preserve_unset_model_defaults() {
let defaults = GenerationPreferences::default();
let deepseek = defaults.effective(ModelChoice::DeepSeekV4Flash);
let deepseek = defaults.turn_settings(ModelChoice::DeepSeekV4Flash);
assert_eq!(
(deepseek.temperature, deepseek.top_p, deepseek.min_p),
(1.0, 1.0, 0.05)
);
let glm = defaults.effective(ModelChoice::Glm52);
let glm = defaults.turn_settings(ModelChoice::Glm52);
assert_eq!((glm.temperature, glm.top_p, glm.min_p), (1.0, 0.95, 0.0));
let explicit = GenerationPreferences {
@@ -218,7 +501,7 @@ mod tests {
reasoning_mode: ReasoningMode::Max,
..defaults
};
let effective = explicit.effective(ModelChoice::Glm52);
let effective = explicit.turn_settings(ModelChoice::Glm52);
assert_eq!((effective.top_p, effective.min_p), (0.4, 0.2));
assert_eq!(effective.reasoning_mode, ReasoningMode::High);
}
@@ -245,4 +528,189 @@ mod tests {
assert!(tuned.validate(ModelChoice::Glm52).is_err());
assert_eq!(tuned.engine_settings().cpu_threads, MAX_CPU_THREADS);
}
#[test]
fn speculative_settings_match_ds4_defaults_and_dependencies() {
let defaults = SpeculativePreferences::default();
let engine = defaults.engine_settings();
assert_eq!((engine.mtp_draft_tokens, engine.mtp_margin), (1, 3.0));
assert_eq!(engine.dspark_confidence_threshold, 0.9);
assert!(!engine.dspark_confidence_threshold_set);
let tuned = SpeculativePreferences {
mtp_draft_tokens: 20,
dspark_enabled: true,
dspark_confidence_threshold: Some(0.7),
dspark_strict: true,
..defaults
};
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash).is_ok());
assert!(tuned.validate(ModelChoice::Glm52).is_err());
assert_eq!(tuned.engine_settings().mtp_draft_tokens, 16);
let glm = SpeculativePreferences {
glm_mtp: true,
glm_mtp_timing: true,
..SpeculativePreferences::default()
};
assert!(glm.validate(ModelChoice::Glm52).is_ok());
assert!(glm.validate(ModelChoice::DeepSeekV4Pro).is_err());
}
#[test]
fn runtime_settings_preserve_automatic_values_and_cross_field_rules() {
let runtime = RuntimePreferences {
ssd: SsdPreferences {
enabled: true,
cache: Some(StreamingCacheBudget::Gib(64)),
full_layers: Some(0),
..SsdPreferences::default()
},
steering: SteeringPreferences {
file: Some("direction.gguf".into()),
..SteeringPreferences::default()
},
diagnostics: DiagnosticPreferences {
simulated_used_memory_gib: Some(8),
..DiagnosticPreferences::default()
},
..RuntimePreferences::default()
};
let engine = runtime
.engine_settings(ModelChoice::DeepSeekV4Flash)
.unwrap();
assert_eq!(engine.ssd.cache_bytes, 64 * GIB);
assert!(engine.ssd.full_layers_set);
assert_eq!(engine.ssd.full_layers, 0);
assert_eq!(engine.steering.ffn_scale, 1.0);
assert_eq!(engine.diagnostics.simulated_used_memory_bytes, 8 * GIB);
let incompatible = RuntimePreferences {
speculative: SpeculativePreferences {
dspark_enabled: true,
..SpeculativePreferences::default()
},
..runtime
};
assert!(
incompatible
.engine_settings(ModelChoice::DeepSeekV4Flash)
.is_err()
);
}
#[test]
fn every_ds4_cli_option_is_mapped_or_explicitly_not_a_preference() {
const PREFERENCES: &[&str] = &[
"--ctx",
"--dir-steering-attn",
"--dir-steering-ffn",
"--dir-steering-file",
"--dspark",
"--dspark-confidence",
"--dspark-strict",
"--expert-profile",
"--glm-mtp",
"--glm-mtp-timing",
"--min-p",
"--model",
"--mtp",
"--mtp-draft",
"--mtp-margin",
"--nothink",
"--power",
"--prefill-chunk",
"--quality",
"--seed",
"--simulate-used-memory",
"--ssd-streaming",
"--ssd-streaming-cache-experts",
"--ssd-streaming-cold",
"--ssd-streaming-full-layers",
"--ssd-streaming-preload-experts",
"--system",
"--temp",
"--think",
"--think-max",
"--threads",
"--tokens",
"--top-p",
"--warm-weights",
];
const NON_PREFERENCES: &[&str] = &[
"--backend",
"--coordinator",
"--cpu",
"--cuda",
"--cuda-tensor-parallel",
"--debug",
"--debug-hash",
"--decode-consistency",
"--dist-activation-bits",
"--dist-prefill-chunk",
"--dist-prefill-window",
"--dist-replay-check",
"--dump-logits",
"--dump-logprobs",
"--dump-tokens",
"--first-token-test",
"--gpu-devices",
"--gpu-vram",
"--head-test",
"--help",
"--imatrix-dataset",
"--imatrix-max-prompts",
"--imatrix-max-tokens",
"--imatrix-out",
"--inspect",
"--layers",
"--listen",
"--logprobs-top-k",
"--metal",
"--metal-graph-full-test",
"--metal-graph-generate",
"--metal-graph-prompt-test",
"--metal-graph-test",
"--perplexity-file",
"--prompt",
"--prompt-file",
"--raw",
"--raw-prompt",
"--rdma-device",
"--rdma-gid-index",
"--rocm",
"--role",
"--server",
"--tensor-parallel",
"--tensor-parallel-token-prefill",
"--transport",
];
let source = [
include_str!("../../ds4/ds4_cli.c"),
include_str!("../../ds4/ds4_tp.c"),
include_str!("../../ds4/ds4_distributed.c"),
]
.join("\n");
let actual = option_branches(&source);
let classified = PREFERENCES
.iter()
.chain(NON_PREFERENCES)
.copied()
.collect::<BTreeSet<_>>();
assert_eq!(actual, classified);
assert!(
PREFERENCES
.iter()
.all(|option| !NON_PREFERENCES.contains(option))
);
}
fn option_branches(source: &str) -> BTreeSet<&str> {
source
.split("!strcmp(arg, \"")
.skip(1)
.filter_map(|tail| tail.split('"').next())
.filter(|option| option.starts_with("--"))
.collect()
}
}