Remove deprecated Flash preview and legacy MTP

This commit is contained in:
Georg Bauer
2026-08-31 08:04:20 +02:00
parent 7f88e23884
commit 0fa15bb68b
24 changed files with 246 additions and 1200 deletions

View File

@@ -3812,7 +3812,7 @@ mod tests {
#[test]
fn prompts_and_parsers_expose_the_reference_tool_set() {
let prompt = system_prompt(ModelChoice::DeepSeekV4Flash, "extra", false);
let prompt = system_prompt(ModelChoice::DeepSeekV4Flash0731, "extra", false);
for name in [
"google_search",
"visit_page",
@@ -3830,11 +3830,11 @@ mod tests {
}
assert!(prompt.ends_with("extra"));
assert!(
system_prompt_reminder(ModelChoice::DeepSeekV4Flash, false)
system_prompt_reminder(ModelChoice::DeepSeekV4Flash0731, false)
.contains("[System prompt reminder follows.]")
);
assert!(!prompt.contains("dev_brain_search"));
let dev_brain_prompt = system_prompt(ModelChoice::DeepSeekV4Flash, "", true);
let dev_brain_prompt = system_prompt(ModelChoice::DeepSeekV4Flash0731, "", true);
for name in ["dev_brain_info", "dev_brain_search", "dev_brain_validate"] {
assert!(dev_brain_prompt.contains(name));
}
@@ -3851,12 +3851,12 @@ mod tests {
assert_eq!(calls[0].arguments["path"], "src/main.rs");
let dsml = "done<DSMLtool_calls><DSMLinvoke name=\"read\"><DSMLparameter name=\"path\" string=\"true\">src/main.rs</DSMLparameter></DSMLinvoke></DSMLtool_calls>";
let (visible, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, dsml).unwrap();
let (visible, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, dsml).unwrap();
assert_eq!(visible, "done");
assert_eq!(calls[0].arguments["path"], "src/main.rs");
assert!(
parse_tool_calls(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
"<DSMLtool_calls><DSMLinvoke name=\"read\">"
)
.is_err()
@@ -4073,12 +4073,12 @@ mod tests {
assert!(validate_tool_call(&calls[0]).is_ok());
let dsml = "<DSMLtool_calls><DSMLinvoke name=\"read\"><DSMLparameter name=\"path\" string=\"true\">README.md</DSMLparameter><DSMLparameter name=\"whole\" string=\"false\">true</DSMLparameter></DSMLinvoke></DSMLtool_calls>";
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, dsml).unwrap();
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, dsml).unwrap();
assert_eq!(calls[0].arguments["whole"], true);
assert!(validate_tool_call(&calls[0]).is_ok());
let quoted = "<DSMLtool_calls><DSMLinvoke name=\"read\"><DSMLparameter name=\"path\" string=\"true\">README.md</DSMLparameter><DSMLparameter name=\"max_lines\" string=\"true\">20</DSMLparameter></DSMLinvoke></DSMLtool_calls>";
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, quoted).unwrap();
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, quoted).unwrap();
assert!(
validation_error(&calls[0])
.render()
@@ -4101,7 +4101,7 @@ mod tests {
#[test]
fn ralph_report_schemas_are_child_only_and_parse_for_both_models() {
for model in [ModelChoice::DeepSeekV4Flash, ModelChoice::Glm52] {
for model in [ModelChoice::DeepSeekV4Flash0731, ModelChoice::Glm52] {
let parent = system_prompt(model, "", false);
let child = ralph_system_prompt(model, "", false);
assert!(parent.contains("\"name\":\"ralph\""));
@@ -4118,7 +4118,7 @@ mod tests {
);
let dsml = "<DSMLtool_calls><DSMLinvoke name=\"ralph_report\"><DSMLparameter name=\"status\" string=\"true\">continue</DSMLparameter><DSMLparameter name=\"summary\" string=\"true\">Inspected the failing test.</DSMLparameter><DSMLparameter name=\"evidence\" string=\"true\">failure reproduced</DSMLparameter><DSMLparameter name=\"next_steps\" string=\"true\">Fix the shared parser.</DSMLparameter><DSMLparameter name=\"blocker\" string=\"true\"></DSMLparameter></DSMLinvoke></DSMLtool_calls>";
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, dsml).unwrap();
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, dsml).unwrap();
assert_eq!(
ralph_report(&calls[0]).unwrap().status,
RalphStatus::Continue
@@ -4700,7 +4700,7 @@ mod tests {
<DSMLinvoke name="read"><DSMLparameter name="path" string="true">README.md</DSMLparameter></DSMLinvoke>
</DSMLtool_calls>"#;
let cards = stored_tool_cards(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
assistant,
None,
&[Some("Only reads the working directory.".into()), None],

View File

@@ -374,7 +374,6 @@ pub(crate) enum Message {
PreferenceGenerationReasoningChanged(ReasoningMode),
PreferenceAccelerationModelChanged(ModelChoice),
PreferencePermissionModeChanged(PermissionMode),
PreferenceLegacyMtpChanged(bool),
PreferenceDsparkChanged(bool),
PreferenceTimeoutChanged(String),
PreferenceA2uiChanged(bool),
@@ -418,8 +417,6 @@ pub(crate) enum Message {
PreferencePrefillChunkChanged(String),
PreferenceQualityChanged(bool),
PreferenceWarmWeightsChanged(bool),
PreferenceMtpDraftChanged(String),
PreferenceMtpMarginChanged(String),
PreferenceGlmMtpChanged(bool),
PreferenceGlmMtpTimingChanged(bool),
PreferenceDsparkConfidenceChanged(String),
@@ -2807,7 +2804,6 @@ mod tests {
shortcut(tab, keyboard::Modifiers::SHIFT),
Some(Message::FocusPrevious)
));
assert!(ModelChoice::DeepSeekV4Flash.supports_dspark());
assert!(ModelChoice::DeepSeekV4Flash0731.supports_dspark());
assert!(!ModelChoice::DeepSeekV4Pro.supports_dspark());
assert!(!ModelChoice::Glm52.supports_dspark());
@@ -2960,7 +2956,7 @@ EOF</DSMLparameter></DSMLinvoke>
assistant.tool_approval_reasons = vec![Some("This only prints text.".into())];
let exported = export_markdown(
"A chat",
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&[
message(false, false, true, "private"),
message(true, false, false, "hello"),

View File

@@ -2612,11 +2612,11 @@ mod tests {
</DSMLtool_calls>"#;
let malformed = assistant(Some(call), "");
assert!(has_misplaced_tool_call(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&malformed
));
assert!(!has_misplaced_tool_call(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&assistant(Some(call), "I was only discussing this call.")
));
assert!(is_empty_response(&assistant(Some(""), "")));

View File

@@ -11,7 +11,6 @@ pub(super) struct PreferenceDraft {
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,
pub(super) idle_timeout_minutes: String,
pub(super) a2ui_enabled: bool,
@@ -39,8 +38,6 @@ pub(super) struct PreferenceDraft {
pub(super) prefill_chunk: String,
pub(super) quality: bool,
pub(super) warm_weights: bool,
pub(super) mtp_draft_tokens: String,
pub(super) mtp_margin: String,
pub(super) glm_mtp: bool,
pub(super) glm_mtp_timing: bool,
pub(super) dspark_confidence_threshold: String,
@@ -78,7 +75,6 @@ impl PreferenceDraft {
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,
idle_timeout_minutes: config.idle_timeout_minutes.to_string(),
a2ui_enabled: config.a2ui_enabled,
@@ -106,8 +102,6 @@ impl PreferenceDraft {
prefill_chunk: optional_string(execution.prefill_chunk),
quality: execution.quality,
warm_weights: execution.warm_weights,
mtp_draft_tokens: speculative.mtp_draft_tokens.to_string(),
mtp_margin: speculative.mtp_margin.to_string(),
glm_mtp: speculative.glm_mtp,
glm_mtp_timing: speculative.glm_mtp_timing,
dspark_confidence_threshold: optional_string(speculative.dspark_confidence_threshold),
@@ -217,9 +211,6 @@ impl PreferenceDraft {
pub(super) fn speculative(&self) -> Result<SpeculativePreferences, String> {
Ok(SpeculativePreferences {
mtp_draft_tokens: parse_positive_i32("MTP draft tokens", &self.mtp_draft_tokens)?,
mtp_margin: parse_f32("MTP margin", &self.mtp_margin)?,
legacy_mtp_enabled: self.legacy_mtp_enabled,
glm_mtp: self.glm_mtp,
glm_mtp_timing: self.glm_mtp_timing,
dspark_enabled: self.dspark_enabled,
@@ -262,10 +253,7 @@ impl PreferenceDraft {
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);
@@ -374,13 +362,6 @@ fn parse_optional_f32(name: &str, value: &str) -> Result<Option<f32>, String> {
}
}
fn parse_f32(name: &str, value: &str) -> Result<f32, String> {
value
.trim()
.parse()
.map_err(|_| format!("{name} must be a number."))
}
fn parse_optional_u64(name: &str, value: &str) -> Result<Option<u64>, String> {
let value = value.trim();
if value.is_empty() {
@@ -715,20 +696,6 @@ impl App {
self.preference_draft.default_permission_mode = mode;
self.preference_error = None;
}
Message::PreferenceLegacyMtpChanged(enabled) => {
self.preference_draft.legacy_mtp_enabled = self
.preference_draft
.acceleration_model
.supports_legacy_mtp()
&& enabled;
if self.preference_draft.legacy_mtp_enabled {
self.preference_draft.dspark_enabled = false;
self.preference_draft.dspark_confidence_threshold.clear();
self.preference_draft.dspark_strict = false;
self.preference_draft.dspark_exact_sampling = false;
}
self.preference_error = None;
}
Message::PreferenceDsparkChanged(enabled) => {
self.preference_draft.dspark_enabled =
self.preference_draft.acceleration_model.supports_dspark() && enabled;
@@ -736,8 +703,6 @@ impl App {
self.preference_draft.dspark_confidence_threshold.clear();
self.preference_draft.dspark_strict = false;
self.preference_draft.dspark_exact_sampling = false;
} else {
self.preference_draft.legacy_mtp_enabled = false;
}
self.preference_error = None;
}
@@ -884,14 +849,6 @@ impl App {
self.preference_draft.warm_weights = value;
self.preference_error = None;
}
Message::PreferenceMtpDraftChanged(value) => {
self.preference_draft.mtp_draft_tokens = value;
self.preference_error = None;
}
Message::PreferenceMtpMarginChanged(value) => {
self.preference_draft.mtp_margin = value;
self.preference_error = None;
}
Message::PreferenceGlmMtpChanged(value) => {
self.preference_draft.glm_mtp =
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
@@ -918,7 +875,6 @@ impl App {
.is_empty()
{
self.preference_draft.dspark_enabled = true;
self.preference_draft.legacy_mtp_enabled = false;
}
self.preference_error = None;
}
@@ -927,7 +883,6 @@ impl App {
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;
}
self.preference_error = None;
}
@@ -936,7 +891,6 @@ impl App {
self.preference_draft.acceleration_model.supports_dspark() && value;
if self.preference_draft.dspark_exact_sampling {
self.preference_draft.dspark_enabled = true;
self.preference_draft.legacy_mtp_enabled = false;
}
self.preference_error = None;
}

View File

@@ -3,17 +3,6 @@ use iced::widget::column;
impl App {
pub(super) fn preferences_panel(&self) -> Element<'_, Message> {
let legacy_mtp_toggle: Option<fn(bool) -> Message> = self
.preference_draft
.acceleration_model
.supports_legacy_mtp()
.then_some(Message::PreferenceLegacyMtpChanged);
let legacy_mtp = hint(
toggle(self.preference_draft.legacy_mtp_enabled)
.label("Enable legacy MTP for this model")
.on_toggle_maybe(legacy_mtp_toggle),
"Uses the managed one-stage MTP support GGUF. The target model verifies every drafted token; it is mutually exclusive with DSpark.",
);
let dspark_toggle: Option<fn(bool) -> Message> = self
.preference_draft
.acceleration_model
@@ -131,7 +120,7 @@ impl App {
|engine| engine.artifacts.model.display().to_string(),
),
engine
.and_then(|engine| engine.artifacts.mtp.as_ref())
.and_then(|engine| engine.artifacts.support.as_ref())
.map_or_else(String::new, |path| format!(
" • support: {}",
path.display()
@@ -602,18 +591,6 @@ impl App {
.spacing(12)
.align_y(Alignment::Center),
text("SPECULATIVE DECODING").size(11).color(muted_text()),
preference_input_row(
"MTP draft tokens",
"How many tokens the multi-token-prediction head guesses ahead for the main model to check in a single pass. More drafting pays off on predictable text and is wasted work on surprising text; the engine caps it at 16.",
text_input("1", &self.preference_draft.mtp_draft_tokens)
.on_input(Message::PreferenceMtpDraftChanged),
),
preference_input_row(
"MTP verifier margin",
"How much more likely the main model must find a drafted token before accepting it. A high margin accepts few drafts and stays close to plain decoding; a low one accepts more and rolls back more often.",
text_input("3", &self.preference_draft.mtp_margin)
.on_input(Message::PreferenceMtpMarginChanged),
),
hint(
toggle(self.preference_draft.glm_mtp)
.label("Enable integrated GLM MTP")
@@ -626,7 +603,6 @@ impl App {
.on_toggle_maybe(glm_mtp_timing_toggle),
"Records per-stage timings of the speculative path to the log, to show where the acceleration actually goes. A diagnostic aid that costs a little throughput.",
),
legacy_mtp,
dspark,
preference_input_row(
"DSpark confidence threshold",
@@ -646,11 +622,11 @@ impl App {
"For non-zero temperatures, applies DS4's exact acceptance and corrected rejection sampling. Off uses the faster opportunistic mode: sample a boundary token, then accept DSpark tokens only while they match the target's greedy path.",
),
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."
"DeepSeek V4 Flash 0731 uses its managed DSpark support artifact."
} 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."
"No speculative-decoding support is available for this model."
})
.size(12),
text(acceleration_engine.map_or_else(
@@ -659,10 +635,7 @@ impl App {
|engine| {
let settings = engine.speculative;
format!(
"Engine: MTP draft {} • margin {} • legacy MTP {} GLM MTP {} • timing {} • DSpark {} • confidence {}{} • target-only {} • exact sampling {}",
settings.mtp_draft_tokens,
settings.mtp_margin,
if self.preference_draft.legacy_mtp_enabled { "on" } else { "off" },
"Engine: GLM MTP {} • timing {} • DSpark {} • confidence {}{} • target-only {} • exact sampling {}",
if settings.glm_mtp { "on" } else { "off" },
if settings.glm_mtp_timing { "on" } else { "off" },
if settings.dspark { "on" } else { "off" },
@@ -705,7 +678,7 @@ impl App {
text_input("Automatic", &self.preference_draft.ssd_preload_experts)
.on_input(Message::PreferenceSsdPreloadChanged),
),
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.")
text("A blank full-layer value is automatic; an explicit 0 disables fully resident GLM layers. DSpark support weights remain resident when target experts stream.")
.size(12),
text(acceleration_engine.map_or_else(
|| "Effective SSD settings will appear after valid values are entered."

View File

@@ -367,7 +367,6 @@ impl App {
metric_row(
"Mode",
match stats.speculative_mode {
1 => "Legacy MTP",
2 => "DSpark",
3 => "GLM MTP",
_ => "Off",
@@ -857,7 +856,7 @@ mod tests {
let summary = SessionStats::from_messages(
&[user, assistant, tool, compaction, system],
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
);
assert_eq!(

View File

@@ -300,6 +300,7 @@ impl Config {
let mut value: Value = serde_norway::from_str(&text)
.map_err(|error| format!("Could not read {}: {error}", path.display()))?;
drop_legacy_model_settings(&mut value);
let migrated = migrate_deprecated_flash(&mut value);
let dspark_explicit = deepseek_0731_dspark_is_explicit(&value);
let mut config: Self = serde_norway::from_value(value)
.map_err(|error| format!("Could not read {}: {error}", path.display()))?;
@@ -313,6 +314,9 @@ impl Config {
.dspark_enabled = true;
}
config.validate()?;
if migrated {
config.save(path)?;
}
Ok(config)
}
@@ -451,6 +455,45 @@ fn drop_legacy_model_settings(value: &mut Value) {
}
}
fn migrate_deprecated_flash(value: &mut Value) -> bool {
let Value::Mapping(root) = value else {
return false;
};
let old = Value::String("deepseek-v4-flash".into());
let current = Value::String(ModelChoice::DeepSeekV4Flash0731.id().into());
let mut migrated = false;
if root.get(Value::String("model".into())) == Some(&old) {
root.insert(Value::String("model".into()), current.clone());
migrated = true;
}
for section in ["generation_profiles", "model_profiles"] {
let Some(Value::Mapping(profiles)) = root.get_mut(Value::String(section.into())) else {
continue;
};
if let Some(profile) = profiles.remove(&old) {
if !profiles.contains_key(&current) {
profiles.insert(current.clone(), profile);
}
migrated = true;
}
}
if let Some(Value::Mapping(profiles)) = root.get_mut(Value::String("model_profiles".into())) {
for profile in profiles.values_mut() {
let Some(speculative) = profile
.as_mapping_mut()
.and_then(|profile| profile.get_mut(Value::String("speculative".into())))
.and_then(Value::as_mapping_mut)
else {
continue;
};
for field in ["legacy_mtp_enabled", "mtp_draft_tokens", "mtp_margin"] {
migrated |= speculative.remove(Value::String(field.into())).is_some();
}
}
}
migrated
}
/// Drops every value that still matches the default, so the file lists only what
/// the user actually set. Mappings that end up empty disappear with their key.
fn without_defaults(value: Value, defaults: &Value) -> Option<Value> {
@@ -601,4 +644,37 @@ mod tests {
// A missing file is simply the defaults.
assert_eq!(Config::load(&path).unwrap(), Config::default());
}
#[test]
fn deprecated_flash_config_migrates_and_persists_only_0731() {
let directory =
std::env::temp_dir().join(format!("ds4-config-preview-{}", std::process::id()));
let path = directory.join("config.yaml");
fs::create_dir_all(&directory).unwrap();
fs::write(
&path,
"model: deepseek-v4-flash\ngeneration_profiles:\n deepseek-v4-flash:\n none:\n max_generated_tokens: 123\nmodel_profiles:\n deepseek-v4-flash:\n speculative:\n legacy_mtp_enabled: true\n mtp_draft_tokens: 4\n mtp_margin: 2\n dspark_enabled: false\n",
)
.unwrap();
let config = Config::load(&path).unwrap();
assert_eq!(config.model, ModelChoice::DeepSeekV4Flash0731);
assert_eq!(
config
.generation_profiles
.get(&ModelChoice::DeepSeekV4Flash0731)
.unwrap()
.get(&ReasoningMode::Direct)
.unwrap()
.max_generated_tokens,
123
);
assert!(!config.runtime_for(config.model).speculative.dspark_enabled);
let persisted = fs::read_to_string(&path).unwrap();
assert!(!persisted.contains("deepseek-v4-flash:"));
assert!(!persisted.contains("legacy_mtp"));
assert!(!persisted.contains("mtp_draft_tokens"));
assert!(persisted.contains("deepseek-v4-flash-0731"));
fs::remove_dir_all(&directory).unwrap();
}
}

View File

@@ -109,8 +109,8 @@ struct Shape {
original_context: u64,
}
const FLASH: Shape = Shape {
model: ModelChoice::DeepSeekV4Flash,
const DEEPSEEK_BASE: Shape = Shape {
model: ModelChoice::DeepSeekV4Flash0731,
family: ModelFamily::DeepSeek,
layers: 43,
embd: 4096,
@@ -152,10 +152,7 @@ const FLASH: Shape = Shape {
original_context: 65_536,
};
const FLASH_0731: Shape = Shape {
model: ModelChoice::DeepSeekV4Flash0731,
..FLASH
};
const FLASH_0731: Shape = Shape { ..DEEPSEEK_BASE };
const PRO: Shape = Shape {
model: ModelChoice::DeepSeekV4Pro,
@@ -168,7 +165,7 @@ const PRO: Shape = Shape {
ff_expert: 3072,
indexer_top_k: 1024,
expert_weight_scale: 2.5,
..FLASH
..DEEPSEEK_BASE
};
const GLM: Shape = Shape {
@@ -236,7 +233,6 @@ impl Model {
pub(crate) fn open(settings: &EngineSettings) -> Result<Self, String> {
validate_engine_artifacts(
settings.model,
settings.artifacts.mtp.is_some() && !settings.speculative.dspark,
settings.speculative.dspark,
&settings.artifacts,
)?;
@@ -244,7 +240,7 @@ impl Model {
if settings.execution.warm_weights {
model.main.warm()?;
}
if let Some(path) = &settings.artifacts.mtp {
if let Some(path) = &settings.artifacts.support {
let support = Gguf::open(path)?;
let kind = validate_support(&support, &model.shape)?;
if settings.execution.warm_weights {
@@ -1958,13 +1954,12 @@ mod sampling_tests {
fn metal_executes_real_flash_token() {
configure_metal_sources().unwrap();
let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash,
false,
ModelChoice::DeepSeekV4Flash0731,
false,
&crate::app::models_path(),
)
.model;
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
let tokens = model.render_prompt(
"You are a helpful assistant",
"Hello",
@@ -2008,7 +2003,7 @@ mod sampling_tests {
.0;
drop(executor);
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
let mut restored = metal::Executor::open(model, 32_768, false, 0).unwrap();
assert!(restored.load_checkpoint(&checkpoint, &mut |_| {}).unwrap());
assert_eq!(restored.position(), tokens.len() as u32);

File diff suppressed because it is too large Load Diff

View File

@@ -301,10 +301,6 @@ impl DeepSeekExecutor {
self.tokens = tokens;
self.logits = logits;
self.checkpoint_tag = checkpoint_tag;
if let Some(mtp) = &mut self.legacy_mtp {
mtp.draft_token = None;
mtp.raw_rows = 0;
}
if let Some(dspark) = &mut self.dspark {
dspark.capture_mask = 0;
dspark.cache_start = 0;

View File

@@ -398,8 +398,6 @@ impl GlmExecutor {
quality,
ssd,
EngineSpeculativeSettings {
mtp_draft_tokens: 1,
mtp_margin: 3.0,
glm_mtp: false,
glm_mtp_timing: false,
dspark: false,
@@ -3243,8 +3241,7 @@ mod tests {
use crate::settings::EngineSsdSettings;
fn installed_glm_path() -> std::path::PathBuf {
crate::model::engine_artifacts(ModelChoice::Glm52, false, false, &crate::app::models_path())
.model
crate::model::engine_artifacts(ModelChoice::Glm52, false, &crate::app::models_path()).model
}
#[test]
@@ -3433,8 +3430,6 @@ mod tests {
preload_experts: 0,
},
EngineSpeculativeSettings {
mtp_draft_tokens: 2,
mtp_margin: 3.0,
glm_mtp: enabled,
glm_mtp_timing: false,
dspark: false,

View File

@@ -288,9 +288,10 @@ mod tests {
std::process::id(),
std::thread::current().name().unwrap_or("test")
));
let mut profile = ExpertProfile::new(path.to_str(), ModelChoice::DeepSeekV4Flash, 1, 8, 2)
.unwrap()
.unwrap();
let mut profile =
ExpertProfile::new(path.to_str(), ModelChoice::DeepSeekV4Flash0731, 1, 8, 2)
.unwrap()
.unwrap();
profile
.record_row(0, 10, &[1, 2], &[0.6, 0.4], false)
.unwrap();

View File

@@ -8,7 +8,6 @@ pub(crate) fn validate_model_artifact(
if support {
let model = Gguf::open(path)?;
let shape = match expected {
ModelChoice::DeepSeekV4Flash => FLASH,
ModelChoice::DeepSeekV4Flash0731 => FLASH_0731,
ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 => {
return Err(format!("{expected} does not use an external support GGUF"));
@@ -38,7 +37,6 @@ pub(crate) fn validate_model_artifact(
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum SupportKind {
LegacyMtp,
DSpark,
}
@@ -106,122 +104,17 @@ pub(super) fn dspark_config(model: &Gguf) -> Result<DsparkConfig, String> {
}
pub(super) fn validate_support(model: &Gguf, shape: &Shape) -> Result<SupportKind, String> {
if model.tensors.contains_key("mtp.0.e_proj.weight")
&& model.tensors.contains_key("mtp.0.h_proj.weight")
&& model.tensors.contains_key("mtp.0.hc_head_base.weight")
{
validate_legacy_mtp(model, shape)?;
Ok(SupportKind::LegacyMtp)
} else if model.metadata.contains_key("deepseek4.dspark.block_size")
if model.metadata.contains_key("deepseek4.dspark.block_size")
|| model.metadata.contains_key("deepseek4.dspark_block_size")
|| model.metadata.contains_key("dspark.block_size")
{
validate_dspark(model, shape)?;
Ok(SupportKind::DSpark)
} else {
Err("support GGUF is neither legacy MTP nor DSpark".into())
Err("support GGUF is not a DSpark artifact".into())
}
}
fn validate_legacy_mtp(model: &Gguf, shape: &Shape) -> Result<(), String> {
if shape.model != ModelChoice::DeepSeekV4Flash {
return Err("legacy MTP support is available only for DeepSeek V4 Flash".into());
}
let prefix = "mtp.0";
let hc_dim = shape.embd * shape.hc;
let hc_mix = 2 * shape.hc + shape.hc * shape.hc;
let q_dim = shape.heads * shape.head_dim;
let output_low = shape.out_groups * shape.lora_o;
for (suffix, types, dims) in [
("hc_head_base.weight", &[F32][..], vec![shape.hc]),
("hc_head_fn.weight", PLAIN, vec![hc_dim, shape.hc]),
("hc_head_scale.weight", &[F32][..], vec![1]),
("e_proj.weight", &[Q8_0][..], vec![shape.embd, shape.embd]),
("h_proj.weight", &[Q8_0][..], vec![shape.embd, shape.embd]),
("enorm.weight", &[F32][..], vec![shape.embd]),
("hnorm.weight", &[F32][..], vec![shape.embd]),
("norm.weight", &[F32][..], vec![shape.embd]),
("hc_attn_fn.weight", PLAIN, vec![hc_dim, hc_mix]),
("hc_attn_scale.weight", &[F32][..], vec![3]),
("hc_attn_base.weight", &[F32][..], vec![hc_mix]),
("attn_norm.weight", &[F32][..], vec![shape.embd]),
(
"attn_q_a.weight",
&[Q8_0][..],
vec![shape.embd, shape.lora_q],
),
("attn_q_a_norm.weight", &[F32][..], vec![shape.lora_q]),
("attn_q_b.weight", &[Q8_0][..], vec![shape.lora_q, q_dim]),
(
"attn_kv.weight",
&[Q8_0][..],
vec![shape.embd, shape.head_dim],
),
("attn_kv_a_norm.weight", &[F32][..], vec![shape.head_dim]),
("attn_sinks.weight", &[F32][..], vec![shape.heads]),
(
"attn_output_a.weight",
&[Q8_0][..],
vec![
shape.head_dim * (shape.heads / shape.out_groups),
output_low,
],
),
(
"attn_output_b.weight",
&[Q8_0][..],
vec![output_low, shape.embd],
),
("hc_ffn_fn.weight", PLAIN, vec![hc_dim, hc_mix]),
("hc_ffn_scale.weight", &[F32][..], vec![3]),
("hc_ffn_base.weight", &[F32][..], vec![hc_mix]),
("ffn_norm.weight", &[F32][..], vec![shape.embd]),
(
"ffn_gate_inp.weight",
PLAIN,
vec![shape.embd, shape.experts],
),
("exp_probs_b.bias", &[F32][..], vec![shape.experts]),
(
"ffn_gate_exps.weight",
ROUTED,
vec![shape.embd, shape.ff_expert, shape.experts],
),
(
"ffn_up_exps.weight",
ROUTED,
vec![shape.embd, shape.ff_expert, shape.experts],
),
(
"ffn_down_exps.weight",
ROUTED,
vec![shape.ff_expert, shape.embd, shape.experts],
),
(
"ffn_gate_shexp.weight",
&[Q8_0][..],
vec![shape.embd, shape.ff_expert],
),
(
"ffn_up_shexp.weight",
&[Q8_0][..],
vec![shape.embd, shape.ff_expert],
),
(
"ffn_down_shexp.weight",
&[Q8_0][..],
vec![shape.ff_expert, shape.embd],
),
] {
expect(model, &format!("{prefix}.{suffix}"), types, &dims)?;
}
same_type(
model,
"mtp.0.ffn_gate_exps.weight",
"mtp.0.ffn_up_exps.weight",
)
}
pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape, String> {
let family = if model.bytes("general.architecture").ok() == Some(b"glm-dsa") {
ModelFamily::Glm
@@ -232,7 +125,7 @@ pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape
ModelFamily::Glm => GLM,
ModelFamily::DeepSeek => match (model.u32("deepseek4.block_count")?, expected) {
(43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731,
(43, _) => FLASH,
(43, _) => FLASH_0731,
(61, _) => PRO,
(layers, _) => return Err(format!("unsupported DeepSeek layer count: {layers}")),
},
@@ -766,10 +659,7 @@ fn validate_glm_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
}
pub(super) fn validate_dspark(model: &Gguf, shape: &Shape) -> Result<(), String> {
if !matches!(
shape.model,
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731
) {
if !matches!(shape.model, ModelChoice::DeepSeekV4Flash0731) {
return Err("DSpark support is available only for DeepSeek V4 Flash".into());
}
let DsparkConfig {
@@ -1004,18 +894,14 @@ fn float_eq(actual: f32, expected: f32) -> bool {
fn compression_ratio(shape: &Shape, layer: u32) -> u32 {
match shape.model {
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0,
ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0,
ModelChoice::DeepSeekV4Pro if layer < 2 => 128,
ModelChoice::DeepSeekV4Flash
| ModelChoice::DeepSeekV4Flash0731
| ModelChoice::DeepSeekV4Pro
ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro
if layer.is_multiple_of(2) =>
{
4
}
ModelChoice::DeepSeekV4Flash
| ModelChoice::DeepSeekV4Flash0731
| ModelChoice::DeepSeekV4Pro => 128,
ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro => 128,
ModelChoice::Glm52 => 0,
}
}
@@ -1054,8 +940,7 @@ mod tests {
#[test]
fn installed_ds4_fixture_opens_and_renders_a_prompt() {
let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash,
false,
ModelChoice::DeepSeekV4Flash0731,
false,
&crate::app::models_path(),
)
@@ -1063,9 +948,9 @@ mod tests {
if !path.exists() {
return;
}
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
let summary = model.summary();
assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash);
assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash0731);
assert_eq!(summary.vocabulary_size, 129_280);
assert_eq!(
model.tokenize("Hello, world! 1234\nint café = 7;\n中文テスト"),
@@ -1262,33 +1147,14 @@ mod tests {
#[test]
fn installed_dspark_fixture_passes_the_target_layout() {
let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash,
false,
ModelChoice::DeepSeekV4Flash0731,
true,
&crate::app::models_path(),
)
.mtp
.support
.unwrap();
if path.exists() {
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash, true).unwrap();
}
}
#[test]
fn installed_legacy_mtp_fixture_passes_the_target_layout() {
let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash,
true,
false,
&crate::app::models_path(),
)
.mtp
.unwrap();
if path.exists() {
assert_eq!(
validate_support(&Gguf::open(&path).unwrap(), &FLASH).unwrap(),
SupportKind::LegacyMtp
);
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash0731, true).unwrap();
}
}
}

View File

@@ -844,7 +844,6 @@ fn source(value: u8) -> WorkSource {
fn model_code(model: ModelChoice) -> u8 {
match model {
ModelChoice::DeepSeekV4Flash => 1,
ModelChoice::DeepSeekV4Pro => 2,
ModelChoice::Glm52 => 3,
ModelChoice::DeepSeekV4Flash0731 => 4,
@@ -1027,6 +1026,12 @@ fn cache_usage(root: &Path) -> CacheUsage {
mod tests {
use super::*;
#[test]
fn removed_preview_metric_code_stays_decodable() {
assert_eq!(model_name(1), "DeepSeek V4 Flash");
assert_eq!(model_code(ModelChoice::DeepSeekV4Flash0731), 4);
}
#[test]
fn snapshots_track_runtime_and_server_counters() {
let metrics = Metrics::new(Path::new("/path/that/does/not/exist"));

View File

@@ -9,18 +9,14 @@ use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
pub(crate) const MODEL_CHOICES: [ModelChoice; 4] = [
pub(crate) const MODEL_CHOICES: [ModelChoice; 3] = [
ModelChoice::DeepSeekV4Flash0731,
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Pro,
ModelChoice::Glm52,
];
pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 7] = [
pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 4] = [
ManagedArtifactId::DeepSeekV4Flash0731,
ManagedArtifactId::DeepSeekV4Flash0731Dspark,
ManagedArtifactId::DeepSeekV4Flash,
ManagedArtifactId::DeepSeekV4FlashMtp,
ManagedArtifactId::DeepSeekV4FlashDspark,
ManagedArtifactId::DeepSeekV4Pro,
ManagedArtifactId::Glm52,
];
@@ -28,30 +24,6 @@ pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 7] = [
const DEEPSEEK_REPOSITORY: &str = "antirez/deepseek-v4-gguf";
const GLM_REPOSITORY: &str = "antirez/glm-5.2-gguf";
const FLASH: Artifact = Artifact {
label: "DeepSeek V4 Flash model",
file_name: "DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf",
repository: DEEPSEEK_REPOSITORY,
size: 86_720_111_488,
sha256: "efc7ed607ff27076e3e501fc3fefefa33c0ed8cf1eff483a2b7fdc0c2e616668",
support: Some(false),
};
const FLASH_DSPARK: Artifact = Artifact {
label: "DSpark support",
file_name: "DeepSeek-V4-Flash-DSpark-support.gguf",
repository: DEEPSEEK_REPOSITORY,
size: 5_989_114_272,
sha256: "8b3adf5942bec22ae2ea867cd7079cf13530ba83ffcffaf00f5de48664a1a34e",
support: Some(true),
};
const FLASH_MTP: Artifact = Artifact {
label: "Legacy MTP support",
file_name: "DeepSeek-V4-Flash-MTP-Q4K-Q8_0-F32.gguf",
repository: DEEPSEEK_REPOSITORY,
size: 3_807_602_400,
sha256: "afd481ee689dce9037f70f39085fcdae5a5b096d521cdad43b19fa52bf8f4083",
support: Some(true),
};
const FLASH_0731: Artifact = Artifact {
label: "DeepSeek V4 Flash 0731 model",
file_name: "DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-0731.gguf",
@@ -87,8 +59,6 @@ const GLM: Artifact = Artifact {
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) enum ModelChoice {
#[serde(rename = "deepseek-v4-flash")]
DeepSeekV4Flash,
#[default]
#[serde(rename = "deepseek-v4-flash-0731")]
DeepSeekV4Flash0731,
@@ -101,7 +71,6 @@ pub(crate) enum ModelChoice {
impl ModelChoice {
pub(crate) fn id(self) -> &'static str {
match self {
Self::DeepSeekV4Flash => "deepseek-v4-flash",
Self::DeepSeekV4Flash0731 => "deepseek-v4-flash-0731",
Self::DeepSeekV4Pro => "deepseek-v4-pro",
Self::Glm52 => "glm-5.2",
@@ -112,17 +81,12 @@ impl ModelChoice {
MODEL_CHOICES.into_iter().find(|model| model.id() == id)
}
pub(crate) fn supports_legacy_mtp(self) -> bool {
self == Self::DeepSeekV4Flash
}
pub(crate) fn supports_dspark(self) -> bool {
matches!(self, Self::DeepSeekV4Flash | Self::DeepSeekV4Flash0731)
self == Self::DeepSeekV4Flash0731
}
fn main_artifact(self) -> &'static Artifact {
match self {
Self::DeepSeekV4Flash => &FLASH,
Self::DeepSeekV4Flash0731 => &FLASH_0731,
Self::DeepSeekV4Pro => &PRO,
Self::Glm52 => &GLM,
@@ -131,21 +95,15 @@ impl ModelChoice {
fn dspark_artifact(self) -> Option<&'static Artifact> {
match self {
Self::DeepSeekV4Flash => Some(&FLASH_DSPARK),
Self::DeepSeekV4Flash0731 => Some(&FLASH_0731_DSPARK),
Self::DeepSeekV4Pro | Self::Glm52 => None,
}
}
#[cfg(test)]
fn artifacts(
self,
legacy_mtp_enabled: bool,
dspark_enabled: bool,
) -> impl Iterator<Item = &'static Artifact> {
fn artifacts(self, dspark_enabled: bool) -> impl Iterator<Item = &'static Artifact> {
[
Some(self.main_artifact()),
(self.supports_legacy_mtp() && legacy_mtp_enabled).then_some(&FLASH_MTP),
dspark_enabled.then(|| self.dspark_artifact()).flatten(),
]
.into_iter()
@@ -156,20 +114,17 @@ impl ModelChoice {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct EngineArtifacts {
pub(crate) model: PathBuf,
pub(crate) mtp: Option<PathBuf>,
pub(crate) support: Option<PathBuf>,
}
pub(crate) fn engine_artifacts(
model: ModelChoice,
legacy_mtp_enabled: bool,
dspark_enabled: bool,
models_path: &Path,
) -> EngineArtifacts {
EngineArtifacts {
model: model.main_artifact().path(model, models_path),
mtp: if model.supports_legacy_mtp() && legacy_mtp_enabled {
Some(FLASH_MTP.path(model, models_path))
} else if dspark_enabled {
support: if dspark_enabled {
model
.dspark_artifact()
.map(|artifact| artifact.path(model, models_path))
@@ -181,30 +136,21 @@ pub(crate) fn engine_artifacts(
pub(crate) fn validate_engine_artifacts(
model: ModelChoice,
legacy_mtp_enabled: bool,
dspark_enabled: bool,
artifacts: &EngineArtifacts,
) -> Result<(), String> {
if legacy_mtp_enabled && dspark_enabled {
return Err("Legacy MTP and DSpark cannot be enabled together".into());
}
if legacy_mtp_enabled && !model.supports_legacy_mtp() {
return Err(format!("Legacy MTP is not compatible with {model}"));
}
if dspark_enabled && !model.supports_dspark() {
return Err(format!("DSpark is not compatible with {model}"));
}
model
.main_artifact()
.validate_installed_path(&artifacts.model)?;
let expected_support = if legacy_mtp_enabled {
model.supports_legacy_mtp().then_some(&FLASH_MTP)
} else if dspark_enabled {
let expected_support = if dspark_enabled {
model.dspark_artifact()
} else {
None
};
match (expected_support, artifacts.mtp.as_deref()) {
match (expected_support, artifacts.support.as_deref()) {
(Some(expected), Some(path)) => expected.validate_installed_path(path),
(None, None) => Ok(()),
(Some(_), None) => Err(format!("{model} is missing its required support GGUF")),
@@ -217,9 +163,6 @@ pub(crate) fn validate_engine_artifacts(
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ManagedArtifactId {
DeepSeekV4Flash,
DeepSeekV4FlashMtp,
DeepSeekV4FlashDspark,
DeepSeekV4Flash0731,
DeepSeekV4Flash0731Dspark,
DeepSeekV4Pro,
@@ -229,9 +172,6 @@ pub(crate) enum ManagedArtifactId {
impl ManagedArtifactId {
pub(crate) fn model(self) -> ModelChoice {
match self {
Self::DeepSeekV4Flash | Self::DeepSeekV4FlashMtp | Self::DeepSeekV4FlashDspark => {
ModelChoice::DeepSeekV4Flash
}
Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Flash0731Dspark => {
ModelChoice::DeepSeekV4Flash0731
}
@@ -242,9 +182,6 @@ impl ManagedArtifactId {
fn artifact(self) -> &'static Artifact {
match self {
Self::DeepSeekV4Flash => &FLASH,
Self::DeepSeekV4FlashMtp => &FLASH_MTP,
Self::DeepSeekV4FlashDspark => &FLASH_DSPARK,
Self::DeepSeekV4Flash0731 => &FLASH_0731,
Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK,
Self::DeepSeekV4Pro => &PRO,
@@ -340,7 +277,6 @@ pub(crate) enum DownloadOutcome {
impl fmt::Display for ModelChoice {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::DeepSeekV4Flash => "DeepSeek V4 Flash (deprecated preview)",
Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731",
Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813",
Self::Glm52 => "GLM 5.2",

View File

@@ -330,50 +330,33 @@ mod tests {
Some(ModelChoice::DeepSeekV4Flash0731)
);
assert!(ModelChoice::from_id("unknown").is_none());
assert_eq!(
ModelChoice::DeepSeekV4Flash.main_artifact().size,
86_720_111_488
);
assert_eq!(MODEL_CHOICES.len(), 3);
assert_eq!(MANAGED_ARTIFACTS.len(), 4);
assert_eq!(ModelChoice::Glm52.main_artifact().size, 211_075_856_448);
assert_eq!(
ModelChoice::DeepSeekV4Flash0731.main_artifact().size,
86_720_111_488
);
assert_eq!(
ModelChoice::DeepSeekV4Flash.artifacts(true, true).count(),
3
);
assert_eq!(ModelChoice::Glm52.artifacts(true, true).count(), 1);
assert_eq!(
ModelChoice::DeepSeekV4Flash0731
.artifacts(true, true)
.count(),
2
);
assert_eq!(ModelChoice::DeepSeekV4Flash0731.artifacts(true).count(), 2);
assert_eq!(ModelChoice::Glm52.artifacts(true).count(), 1);
let id = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let models_path = std::env::temp_dir().join(format!("ds4-server-models-{id}"));
let engine = engine_artifacts(ModelChoice::DeepSeekV4Flash, false, true, &models_path);
let engine = engine_artifacts(ModelChoice::DeepSeekV4Flash0731, true, &models_path);
assert_eq!(
engine.model.file_name(),
Some(std::ffi::OsStr::new(FLASH.file_name))
Some(std::ffi::OsStr::new(FLASH_0731.file_name))
);
assert_eq!(
engine.mtp.as_deref().and_then(Path::file_name),
Some(std::ffi::OsStr::new(FLASH_DSPARK.file_name))
);
let flash_0731 =
engine_artifacts(ModelChoice::DeepSeekV4Flash0731, false, true, &models_path);
assert_eq!(
flash_0731.mtp.as_deref().and_then(Path::file_name),
engine.support.as_deref().and_then(Path::file_name),
Some(std::ffi::OsStr::new(FLASH_0731_DSPARK.file_name))
);
assert!(
engine_artifacts(ModelChoice::DeepSeekV4Flash0731, true, false, &models_path)
.mtp
engine_artifacts(ModelChoice::DeepSeekV4Flash0731, false, &models_path)
.support
.is_none()
);
let empty = Artifact {
@@ -384,18 +367,20 @@ mod tests {
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
support: None,
};
let partial = empty.partial_path(ModelChoice::DeepSeekV4Flash, &models_path);
let partial = empty.partial_path(ModelChoice::DeepSeekV4Flash0731, &models_path);
fs::create_dir_all(partial.parent().unwrap()).unwrap();
fs::write(&partial, []).unwrap();
download_artifact(ModelChoice::DeepSeekV4Flash, &empty, &models_path).unwrap();
assert!(empty.is_installed(ModelChoice::DeepSeekV4Flash, &models_path));
download_artifact(ModelChoice::DeepSeekV4Flash0731, &empty, &models_path).unwrap();
assert!(empty.is_installed(ModelChoice::DeepSeekV4Flash0731, &models_path));
assert!(!partial.exists());
assert_eq!(
fs::read_to_string(empty.verification_path(ModelChoice::DeepSeekV4Flash, &models_path))
.unwrap(),
fs::read_to_string(
empty.verification_path(ModelChoice::DeepSeekV4Flash0731, &models_path)
)
.unwrap(),
empty.sha256
);
let installed = empty.path(ModelChoice::DeepSeekV4Flash, &models_path);
let installed = empty.path(ModelChoice::DeepSeekV4Flash0731, &models_path);
assert!(empty.validate_installed_path(&installed).is_ok());
let wrong_name = installed.with_file_name("wrong-checkpoint.gguf");
fs::write(&wrong_name, []).unwrap();
@@ -405,10 +390,9 @@ mod tests {
validate_engine_artifacts(
ModelChoice::DeepSeekV4Flash0731,
true,
false,
&EngineArtifacts {
model: installed.clone(),
mtp: None,
support: None,
},
)
.is_err()
@@ -416,11 +400,10 @@ mod tests {
assert!(
validate_engine_artifacts(
ModelChoice::DeepSeekV4Pro,
false,
true,
&EngineArtifacts {
model: installed,
mtp: None,
support: None,
},
)
.is_err()
@@ -452,7 +435,7 @@ mod tests {
verify(
&path,
&artifact,
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&AtomicBool::new(false),
&verified_bytes,
)
@@ -472,7 +455,7 @@ mod tests {
.unwrap()
.as_nanos()
));
let id = ManagedArtifactId::DeepSeekV4Flash;
let id = ManagedArtifactId::DeepSeekV4Flash0731;
let partial = id.artifact().partial_path(id.model(), &models_path);
fs::create_dir_all(partial.parent().unwrap()).unwrap();
fs::write(&partial, b"part").unwrap();
@@ -611,14 +594,14 @@ mod tests {
sha256: "unused",
support: None,
};
let partial = artifact.partial_path(ModelChoice::DeepSeekV4Flash, &directory);
let partial = artifact.partial_path(ModelChoice::DeepSeekV4Flash0731, &directory);
fs::create_dir_all(partial.parent().unwrap()).unwrap();
fs::write(&partial, b"part").unwrap();
let cancel = AtomicBool::new(true);
assert_eq!(
download_artifact_with_cancel(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&artifact,
&directory,
&cancel,

View File

@@ -546,7 +546,7 @@ fn model_json(id: &str, model: ModelChoice, context: i32, default_tokens: i32) -
fn model_alias(id: &str) -> Option<ModelChoice> {
match id {
"deepseek-chat" | "deepseek-reasoner" => Some(ModelChoice::DeepSeekV4Flash),
"deepseek-chat" | "deepseek-reasoner" => Some(ModelChoice::DeepSeekV4Flash0731),
"glm-5.2-chat"
| "glm-5.2-no-think"
| "glm-5.2-nothink"
@@ -929,7 +929,7 @@ mod tests {
let request = ResponseOptions {
protocol: Protocol::Chat,
reasoning_summary: false,
model_id: "deepseek-v4-flash".into(),
model_id: "deepseek-v4-flash-0731".into(),
stream: true,
include_usage: false,
has_tools: true,
@@ -1002,9 +1002,17 @@ mod tests {
#[test]
fn model_metadata_uses_the_requested_alias_and_default_token_limit() {
assert_eq!(
model_alias("deepseek-chat"),
Some(ModelChoice::DeepSeekV4Flash0731)
);
assert_eq!(
model_alias("deepseek-reasoner"),
Some(ModelChoice::DeepSeekV4Flash0731)
);
let model = model_json(
"deepseek-reasoner",
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
32_768,
50_000,
);

View File

@@ -11,7 +11,6 @@ pub(crate) const REASONING_MODES: [ReasoningMode; 3] = [
const STANDARD_REASONING_MODES: [ReasoningMode; 2] = [ReasoningMode::High, 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;
/// DS4 disk KV cache defaults, from `ds4_kvstore.h` and `--kv-disk-space-mb`.
pub(crate) const DEFAULT_KV_BUDGET_GIB: u64 = 4;
@@ -19,12 +18,9 @@ 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, Deserialize, PartialEq, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct SpeculativePreferences {
pub(crate) mtp_draft_tokens: i32,
pub(crate) mtp_margin: f32,
pub(crate) legacy_mtp_enabled: bool,
pub(crate) glm_mtp: bool,
pub(crate) glm_mtp_timing: bool,
pub(crate) dspark_enabled: bool,
@@ -33,28 +29,8 @@ pub(crate) struct SpeculativePreferences {
pub(crate) dspark_exact_sampling: bool,
}
impl Default for SpeculativePreferences {
fn default() -> Self {
Self {
mtp_draft_tokens: 1,
mtp_margin: 3.0,
legacy_mtp_enabled: false,
glm_mtp: false,
glm_mtp_timing: false,
dspark_enabled: false,
dspark_confidence_threshold: None,
dspark_strict: false,
dspark_exact_sampling: 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());
}
@@ -64,12 +40,6 @@ impl SpeculativePreferences {
if self.dspark_enabled && !model.supports_dspark() {
return Err("DSpark is not available for the selected model.".into());
}
if self.legacy_mtp_enabled && !model.supports_legacy_mtp() {
return Err("Legacy MTP is not available for the selected model.".into());
}
if self.legacy_mtp_enabled && self.dspark_enabled {
return Err("Legacy MTP and DSpark use different support artifacts.".into());
}
if (self.dspark_confidence_threshold.is_some()
|| self.dspark_strict
|| self.dspark_exact_sampling)
@@ -85,8 +55,6 @@ impl SpeculativePreferences {
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,
@@ -100,8 +68,6 @@ impl SpeculativePreferences {
#[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,
@@ -415,12 +381,7 @@ impl RuntimePreferences {
self.validate(model)?;
Ok(EngineSettings {
model,
artifacts: model::engine_artifacts(
model,
self.speculative.legacy_mtp_enabled,
self.speculative.dspark_enabled,
models_path,
),
artifacts: model::engine_artifacts(model, self.speculative.dspark_enabled, models_path),
context_tokens,
execution: self.execution.engine_settings(),
speculative: self.speculative.engine_settings(),
@@ -698,7 +659,7 @@ mod tests {
assert_eq!(defaults.system_prompt, DEFAULT_SYSTEM_PROMPT);
assert!(defaults.system_prompt.contains("\n\nGuidelines:\n-"));
let cache = KvCachePreferences::default().settings();
let deepseek = defaults.turn_settings(ModelChoice::DeepSeekV4Flash, cache);
let deepseek = defaults.turn_settings(ModelChoice::DeepSeekV4Flash0731, cache);
assert_eq!(
(deepseek.temperature, deepseek.top_p, deepseek.min_p),
(1.0, 1.0, 0.05)
@@ -773,7 +734,7 @@ mod tests {
};
assert!(
unsupported_threads
.validate(ModelChoice::DeepSeekV4Flash)
.validate(ModelChoice::DeepSeekV4Flash0731)
.is_err()
);
@@ -782,7 +743,7 @@ mod tests {
prefill_chunk: Some(4096),
..ExecutionPreferences::default()
};
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash).is_ok());
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash0731).is_ok());
assert!(tuned.validate(ModelChoice::Glm52).is_err());
}
@@ -790,21 +751,18 @@ mod tests {
fn speculative_settings_match_acceleration_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.8);
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,
dspark_exact_sampling: true,
..defaults
};
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash).is_ok());
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash0731).is_ok());
assert!(tuned.validate(ModelChoice::Glm52).is_err());
assert_eq!(tuned.engine_settings().mtp_draft_tokens, 16);
let glm = SpeculativePreferences {
glm_mtp: true,
@@ -814,21 +772,6 @@ mod tests {
assert!(glm.validate(ModelChoice::Glm52).is_ok());
assert!(glm.validate(ModelChoice::DeepSeekV4Pro).is_err());
let legacy = SpeculativePreferences {
legacy_mtp_enabled: true,
..SpeculativePreferences::default()
};
assert!(legacy.validate(ModelChoice::DeepSeekV4Flash).is_ok());
assert!(legacy.validate(ModelChoice::DeepSeekV4Flash0731).is_err());
assert!(legacy.validate(ModelChoice::DeepSeekV4Pro).is_err());
assert!(
SpeculativePreferences {
dspark_enabled: true,
..legacy
}
.validate(ModelChoice::DeepSeekV4Flash)
.is_err()
);
assert!(
SpeculativePreferences {
dspark_exact_sampling: true,
@@ -892,7 +835,7 @@ mod tests {
..RuntimePreferences::default()
};
let effective = effective_settings(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&GenerationPreferences::default(),
&runtime,
Path::new("/models"),
@@ -900,7 +843,7 @@ mod tests {
.unwrap();
let engine = effective.engine;
assert_eq!(engine.context_tokens, 32_768);
assert!(engine.artifacts.mtp.is_none());
assert!(engine.artifacts.support.is_none());
assert_eq!(engine.ssd.cache_bytes, 64 * GIB);
assert!(engine.ssd.full_layers_set);
assert_eq!(engine.ssd.full_layers, 0);
@@ -914,7 +857,7 @@ mod tests {
},
..runtime
};
assert!(combined.validate(ModelChoice::DeepSeekV4Flash).is_ok());
assert!(combined.validate(ModelChoice::DeepSeekV4Flash0731).is_ok());
}
#[test]
@@ -932,7 +875,7 @@ mod tests {
..RuntimePreferences::default()
};
let effective = effective_settings(
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Flash0731,
&generation,
&runtime,
Path::new("/models"),
@@ -942,9 +885,9 @@ mod tests {
assert_eq!(effective.engine.context_tokens, 65_536);
assert_eq!(
effective.engine.artifacts.model.parent(),
Some(Path::new("/models/deepseek-v4-flash"))
Some(Path::new("/models/deepseek-v4-flash-0731"))
);
assert!(effective.engine.artifacts.mtp.is_some());
assert!(effective.engine.artifacts.support.is_some());
assert_eq!(effective.turn.temperature, 0.25);
}
@@ -967,9 +910,6 @@ mod tests {
"--kv-disk-space-mb",
"--min-p",
"--model",
"--mtp",
"--mtp-draft",
"--mtp-margin",
"--nothink",
"--power",
"--prefill-chunk",