feat: more preferences
This commit is contained in:
482
src/settings.rs
482
src/settings.rs
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user