773 lines
25 KiB
Rust
773 lines
25 KiB
Rust
use crate::model::{self, EngineArtifacts, ModelChoice};
|
|
use std::fmt;
|
|
use std::path::Path;
|
|
|
|
pub(crate) const REASONING_MODES: [ReasoningMode; 3] = [
|
|
ReasoningMode::High,
|
|
ReasoningMode::Max,
|
|
ReasoningMode::Direct,
|
|
];
|
|
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 validate(&self, model: ModelChoice) -> Result<(), 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(())
|
|
}
|
|
|
|
fn engine_settings(
|
|
&self,
|
|
model: ModelChoice,
|
|
context_tokens: i32,
|
|
models_path: &Path,
|
|
) -> Result<EngineSettings, String> {
|
|
self.validate(model)?;
|
|
Ok(EngineSettings {
|
|
model,
|
|
artifacts: model::engine_artifacts(model, self.speculative.dspark_enabled, models_path),
|
|
context_tokens,
|
|
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) artifacts: EngineArtifacts,
|
|
pub(crate) context_tokens: i32,
|
|
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 {
|
|
pub(crate) cpu_threads: Option<u32>,
|
|
pub(crate) power_percent: Option<u8>,
|
|
pub(crate) prefill_chunk: Option<u32>,
|
|
pub(crate) quality: bool,
|
|
pub(crate) warm_weights: bool,
|
|
}
|
|
|
|
impl ExecutionPreferences {
|
|
pub(crate) fn validate(&self, model: ModelChoice) -> Result<(), String> {
|
|
if self.cpu_threads == Some(0) {
|
|
return Err("CPU helper threads must be a positive whole number.".into());
|
|
}
|
|
if self
|
|
.cpu_threads
|
|
.is_some_and(|threads| threads > i32::MAX as u32)
|
|
{
|
|
return Err("CPU helper threads is too large.".into());
|
|
}
|
|
if self
|
|
.power_percent
|
|
.is_some_and(|power| !(1..=100).contains(&power))
|
|
{
|
|
return Err("GPU power must be between 1 and 100 percent.".into());
|
|
}
|
|
if self.prefill_chunk == Some(0) {
|
|
return Err("Prefill chunk must be a positive whole number.".into());
|
|
}
|
|
if self
|
|
.prefill_chunk
|
|
.is_some_and(|chunk| chunk > i32::MAX as u32)
|
|
{
|
|
return Err("Prefill chunk is too large.".into());
|
|
}
|
|
if model == ModelChoice::Glm52 {
|
|
if self.power_percent.is_some_and(|power| power != 100) {
|
|
return Err("GLM 5.2 currently requires 100% GPU power.".into());
|
|
}
|
|
if self.prefill_chunk.is_some() {
|
|
return Err("GLM 5.2 selects its prefill chunk automatically.".into());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn engine_settings(&self) -> EngineExecutionSettings {
|
|
EngineExecutionSettings {
|
|
cpu_threads: self.cpu_threads.unwrap_or(0).min(MAX_CPU_THREADS),
|
|
power_percent: self.power_percent.unwrap_or(0),
|
|
prefill_chunk: self.prefill_chunk.unwrap_or(0),
|
|
quality: self.quality,
|
|
warm_weights: self.warm_weights,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
pub(crate) struct EngineExecutionSettings {
|
|
pub(crate) cpu_threads: u32,
|
|
pub(crate) power_percent: u8,
|
|
pub(crate) prefill_chunk: u32,
|
|
pub(crate) quality: bool,
|
|
pub(crate) warm_weights: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
pub(crate) enum ReasoningMode {
|
|
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 {
|
|
Self::Direct => "Direct (no thinking)",
|
|
Self::High => "Thinking",
|
|
Self::Max => "Think Max",
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub(crate) struct GenerationPreferences {
|
|
pub(crate) context_tokens: i32,
|
|
pub(crate) max_generated_tokens: i32,
|
|
pub(crate) system_prompt: String,
|
|
pub(crate) temperature: Option<f32>,
|
|
pub(crate) top_p: Option<f32>,
|
|
pub(crate) min_p: Option<f32>,
|
|
pub(crate) seed: Option<u64>,
|
|
pub(crate) reasoning_mode: ReasoningMode,
|
|
}
|
|
|
|
impl Default for GenerationPreferences {
|
|
fn default() -> Self {
|
|
Self {
|
|
context_tokens: 32_768,
|
|
max_generated_tokens: 50_000,
|
|
system_prompt: "You are a helpful assistant".into(),
|
|
temperature: None,
|
|
top_p: None,
|
|
min_p: None,
|
|
seed: None,
|
|
reasoning_mode: ReasoningMode::High,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GenerationPreferences {
|
|
pub(crate) fn validate(&self) -> Result<(), String> {
|
|
if self.context_tokens <= 0 {
|
|
return Err("Context tokens must be a positive whole number.".into());
|
|
}
|
|
if self.max_generated_tokens <= 0 {
|
|
return Err("Maximum generated tokens must be a positive whole number.".into());
|
|
}
|
|
validate_optional_float("Temperature", self.temperature, 0.0, 100.0)?;
|
|
validate_optional_float("Top-p", self.top_p, 0.0, 1.0)?;
|
|
validate_optional_float("Min-p", self.min_p, 0.0, 1.0)?;
|
|
if self.seed == Some(0) {
|
|
return Err("Seed must be a positive whole number.".into());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn turn_settings(&self, model: ModelChoice) -> TurnSettings {
|
|
let glm = model == ModelChoice::Glm52;
|
|
TurnSettings {
|
|
context_tokens: self.context_tokens,
|
|
max_generated_tokens: self.max_generated_tokens,
|
|
system_prompt: self.system_prompt.clone(),
|
|
temperature: self.temperature.unwrap_or(1.0),
|
|
top_p: self.top_p.unwrap_or(if glm { 0.95 } else { 1.0 }),
|
|
min_p: self.min_p.unwrap_or(if glm { 0.0 } else { 0.05 }),
|
|
top_k: 0,
|
|
stops: Vec::new(),
|
|
seed: self.seed,
|
|
reasoning_mode: if self.reasoning_mode == ReasoningMode::Max
|
|
&& self.context_tokens < THINK_MAX_MIN_CONTEXT
|
|
{
|
|
ReasoningMode::High
|
|
} else {
|
|
self.reasoning_mode
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn validate_optional_float(
|
|
name: &str,
|
|
value: Option<f32>,
|
|
minimum: f32,
|
|
maximum: f32,
|
|
) -> Result<(), String> {
|
|
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 TurnSettings {
|
|
pub(crate) context_tokens: i32,
|
|
pub(crate) max_generated_tokens: i32,
|
|
pub(crate) system_prompt: String,
|
|
pub(crate) temperature: f32,
|
|
pub(crate) top_p: f32,
|
|
pub(crate) min_p: f32,
|
|
pub(crate) top_k: i32,
|
|
pub(crate) stops: Vec<String>,
|
|
pub(crate) seed: Option<u64>,
|
|
pub(crate) reasoning_mode: ReasoningMode,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub(crate) struct EffectiveSettings {
|
|
pub(crate) engine: EngineSettings,
|
|
pub(crate) turn: TurnSettings,
|
|
}
|
|
|
|
pub(crate) fn effective_settings(
|
|
model: ModelChoice,
|
|
generation: &GenerationPreferences,
|
|
runtime: &RuntimePreferences,
|
|
models_path: &Path,
|
|
) -> Result<EffectiveSettings, String> {
|
|
generation.validate()?;
|
|
Ok(EffectiveSettings {
|
|
engine: runtime.engine_settings(model, generation.context_tokens, models_path)?,
|
|
turn: generation.turn_settings(model),
|
|
})
|
|
}
|
|
|
|
#[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.turn_settings(ModelChoice::DeepSeekV4Flash);
|
|
assert_eq!(
|
|
(deepseek.temperature, deepseek.top_p, deepseek.min_p),
|
|
(1.0, 1.0, 0.05)
|
|
);
|
|
|
|
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 {
|
|
top_p: Some(0.4),
|
|
min_p: Some(0.2),
|
|
reasoning_mode: ReasoningMode::Max,
|
|
..defaults
|
|
};
|
|
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);
|
|
}
|
|
|
|
#[test]
|
|
fn execution_settings_keep_ds4_automatic_values_and_glm_rules() {
|
|
let defaults = ExecutionPreferences::default().engine_settings();
|
|
assert_eq!(
|
|
(
|
|
defaults.cpu_threads,
|
|
defaults.power_percent,
|
|
defaults.prefill_chunk
|
|
),
|
|
(0, 0, 0)
|
|
);
|
|
|
|
let tuned = ExecutionPreferences {
|
|
cpu_threads: Some(100),
|
|
power_percent: Some(50),
|
|
prefill_chunk: Some(4096),
|
|
..ExecutionPreferences::default()
|
|
};
|
|
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash).is_ok());
|
|
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 effective = effective_settings(
|
|
ModelChoice::DeepSeekV4Flash,
|
|
&GenerationPreferences::default(),
|
|
&runtime,
|
|
Path::new("/models"),
|
|
)
|
|
.unwrap();
|
|
let engine = effective.engine;
|
|
assert_eq!(engine.context_tokens, 32_768);
|
|
assert!(engine.artifacts.mtp.is_none());
|
|
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.validate(ModelChoice::DeepSeekV4Flash).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn effective_settings_build_one_engine_and_turn_configuration() {
|
|
let generation = GenerationPreferences {
|
|
context_tokens: 65_536,
|
|
temperature: Some(0.25),
|
|
..GenerationPreferences::default()
|
|
};
|
|
let runtime = RuntimePreferences {
|
|
speculative: SpeculativePreferences {
|
|
dspark_enabled: true,
|
|
..SpeculativePreferences::default()
|
|
},
|
|
..RuntimePreferences::default()
|
|
};
|
|
let effective = effective_settings(
|
|
ModelChoice::DeepSeekV4Flash,
|
|
&generation,
|
|
&runtime,
|
|
Path::new("/models"),
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(effective.engine.context_tokens, 65_536);
|
|
assert_eq!(
|
|
effective.engine.artifacts.model.parent(),
|
|
Some(Path::new("/models/deepseek-v4-flash"))
|
|
);
|
|
assert!(effective.engine.artifacts.mtp.is_some());
|
|
assert_eq!(effective.turn.temperature, 0.25);
|
|
}
|
|
|
|
#[test]
|
|
fn captured_ds4_cli_options_are_uniquely_classified() {
|
|
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 classified = PREFERENCES
|
|
.iter()
|
|
.chain(NON_PREFERENCES)
|
|
.copied()
|
|
.collect::<BTreeSet<_>>();
|
|
assert_eq!(classified.len(), PREFERENCES.len() + NON_PREFERENCES.len());
|
|
assert!(
|
|
PREFERENCES
|
|
.iter()
|
|
.all(|option| !NON_PREFERENCES.contains(option))
|
|
);
|
|
}
|
|
}
|