From 0fa15bb68b4c526c6720bda2bb243ba573c45c88 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Mon, 31 Aug 2026 08:04:20 +0200 Subject: [PATCH] Remove deprecated Flash preview and legacy MTP --- native/metal/ds4.h | 5 - scripts/endpoint_continuation.py | 12 +- scripts/endpoint_parity.py | 6 +- scripts/endpoint_reasoning.py | 6 +- scripts/execution_parity.py | 3 +- scripts/speculative_parity.py | 6 +- src/agent.rs | 20 +- src/app.rs | 6 +- src/app/generation.rs | 4 +- src/app/preferences.rs | 46 -- src/app/view/preferences.rs | 37 +- src/app/view/stats.rs | 3 +- src/config.rs | 76 +++ src/engine.rs | 21 +- src/engine/metal.rs | 769 ++----------------------------- src/engine/metal/checkpoint.rs | 4 - src/engine/metal/glm.rs | 7 +- src/engine/metal/profile.rs | 7 +- src/engine/validation.rs | 160 +------ src/metrics.rs | 7 +- src/model.rs | 80 +--- src/model/transfer.rs | 63 +-- src/server.rs | 14 +- src/settings.rs | 84 +--- 24 files changed, 246 insertions(+), 1200 deletions(-) diff --git a/native/metal/ds4.h b/native/metal/ds4.h index 3c9e066..39e2ec5 100644 --- a/native/metal/ds4.h +++ b/native/metal/ds4.h @@ -126,13 +126,10 @@ typedef struct { typedef struct { const char *model_path; - const char *mtp_path; ds4_backend backend; int n_threads; int context_size; uint32_t prefill_chunk; - int mtp_draft_tokens; - float mtp_margin; float dspark_confidence_threshold; const char *directional_steering_file; const char *expert_profile_path; @@ -451,8 +448,6 @@ int ds4_session_ctx(ds4_session *s); int ds4_session_prefill_cap(ds4_session *s); int ds4_engine_routed_quant_bits(ds4_engine *e); bool ds4_engine_has_output_head(ds4_engine *e); -bool ds4_engine_has_mtp(ds4_engine *e); -int ds4_engine_mtp_draft_tokens(ds4_engine *e); const ds4_tokens *ds4_session_tokens(ds4_session *s); /* Low-level graph slice entry points used by distributed inference. The diff --git a/scripts/endpoint_continuation.py b/scripts/endpoint_continuation.py index ac569a6..4c0c43c 100644 --- a/scripts/endpoint_continuation.py +++ b/scripts/endpoint_continuation.py @@ -45,7 +45,7 @@ def post(path, payload): first = post( "/v1/chat/completions", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [{"role": "user", "content": prompt}], "tools": [chat_tool], "reasoning_effort": "none", @@ -58,7 +58,7 @@ print("chat-first", json.dumps(first["usage"], separators=(",", ":"))) second = post( "/v1/chat/completions", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [ {"role": "user", "content": prompt}, {"role": "assistant", "content": "", "tool_calls": [call]}, @@ -79,7 +79,7 @@ print("chat", json.dumps(second, separators=(",", ":"))) first = post( "/v1/messages", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [{"role": "user", "content": prompt}], "tools": [anthropic_tool], "thinking": {"type": "disabled"}, @@ -92,7 +92,7 @@ print("anthropic-first", json.dumps(first["usage"], separators=(",", ":"))) second = post( "/v1/messages", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [ {"role": "user", "content": prompt}, {"role": "assistant", "content": [call]}, @@ -118,7 +118,7 @@ print("anthropic", json.dumps(second, separators=(",", ":"))) first = post( "/v1/responses", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "input": prompt, "tools": [responses_tool], "reasoning": {"effort": "none"}, @@ -131,7 +131,7 @@ print("responses-first", json.dumps(first["usage"], separators=(",", ":"))) second = post( "/v1/responses", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "input": [ { "type": "message", diff --git a/scripts/endpoint_parity.py b/scripts/endpoint_parity.py index 3006ff6..71b2c08 100644 --- a/scripts/endpoint_parity.py +++ b/scripts/endpoint_parity.py @@ -16,7 +16,7 @@ cases = [ "chat", "/v1/chat/completions", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [ { "role": "user", @@ -42,7 +42,7 @@ cases = [ "anthropic", "/v1/messages", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [ { "role": "user", @@ -65,7 +65,7 @@ cases = [ "responses", "/v1/responses", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "input": "Call echo with text hi. Do not answer normally.", "tools": [ { diff --git a/scripts/endpoint_reasoning.py b/scripts/endpoint_reasoning.py index 8d52115..6c9127a 100644 --- a/scripts/endpoint_reasoning.py +++ b/scripts/endpoint_reasoning.py @@ -11,7 +11,7 @@ cases = [ "chat", "/v1/chat/completions", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [{"role": "user", "content": prompt}], "reasoning_effort": "low", "temperature": 0, @@ -22,7 +22,7 @@ cases = [ "anthropic", "/v1/messages", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [{"role": "user", "content": prompt}], "thinking": {"type": "enabled", "budget_tokens": 64}, "output_config": {"effort": "low"}, @@ -34,7 +34,7 @@ cases = [ "responses", "/v1/responses", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "input": prompt, "reasoning": {"effort": "low", "summary": "auto"}, "temperature": 0, diff --git a/scripts/execution_parity.py b/scripts/execution_parity.py index 5afebae..563e53b 100755 --- a/scripts/execution_parity.py +++ b/scripts/execution_parity.py @@ -12,9 +12,8 @@ HARDWARE_TESTS = ( "flash_resident_and_ssd_streaming_choose_the_same_tokens", "flash_ssd_streaming_maps_batched_prefill_layers", "resident_multi_session_switching_preserves_each_kv_frontier", - "legacy_mtp_runs_a_target_owned_greedy_cycle", "dspark_runs_a_target_owned_greedy_cycle", - "ssd_streaming_supports_legacy_mtp_and_dspark", + "ssd_streaming_supports_dspark", "directional_steering_matches_the_ds4_token_oracle", "resident_and_streamed_glm_match_ds4_decode_oracles", "streamed_glm_uses_ds4_indexed_prefill_for_long_prompts", diff --git a/scripts/speculative_parity.py b/scripts/speculative_parity.py index fa99611..bf086b5 100755 --- a/scripts/speculative_parity.py +++ b/scripts/speculative_parity.py @@ -28,7 +28,7 @@ def chat(base_url, prompt): base_url, "/v1/chat/completions", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [{"role": "user", "content": prompt}], "reasoning_effort": "none", "temperature": 0, @@ -50,7 +50,7 @@ def anthropic(base_url, prompt): base_url, "/v1/messages", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "messages": [{"role": "user", "content": prompt}], "thinking": {"type": "disabled"}, "temperature": 0, @@ -69,7 +69,7 @@ def responses(base_url, prompt): base_url, "/v1/responses", { - "model": "deepseek-v4-flash", + "model": "deepseek-v4-flash-0731", "input": prompt, "reasoning": {"effort": "none"}, "temperature": 0, diff --git a/src/agent.rs b/src/agent.rs index 9629892..c4778de 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -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<|DSML|tool_calls><|DSML|invoke name=\"read\"><|DSML|parameter name=\"path\" string=\"true\">src/main.rs"; - 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, "<|DSML|tool_calls><|DSML|invoke name=\"read\">" ) .is_err() @@ -4073,12 +4073,12 @@ mod tests { assert!(validate_tool_call(&calls[0]).is_ok()); let dsml = "<|DSML|tool_calls><|DSML|invoke name=\"read\"><|DSML|parameter name=\"path\" string=\"true\">README.md<|DSML|parameter name=\"whole\" string=\"false\">true"; - 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 = "<|DSML|tool_calls><|DSML|invoke name=\"read\"><|DSML|parameter name=\"path\" string=\"true\">README.md<|DSML|parameter name=\"max_lines\" string=\"true\">20"; - 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 = "<|DSML|tool_calls><|DSML|invoke name=\"ralph_report\"><|DSML|parameter name=\"status\" string=\"true\">continue<|DSML|parameter name=\"summary\" string=\"true\">Inspected the failing test.<|DSML|parameter name=\"evidence\" string=\"true\">failure reproduced<|DSML|parameter name=\"next_steps\" string=\"true\">Fix the shared parser.<|DSML|parameter name=\"blocker\" string=\"true\">"; - 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 { <|DSML|invoke name="read"><|DSML|parameter name="path" string="true">README.md "#; let cards = stored_tool_cards( - ModelChoice::DeepSeekV4Flash, + ModelChoice::DeepSeekV4Flash0731, assistant, None, &[Some("Only reads the working directory.".into()), None], diff --git a/src/app.rs b/src/app.rs index c41a14c..96af020 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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 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"), diff --git a/src/app/generation.rs b/src/app/generation.rs index 0f1d92e..e56290b 100644 --- a/src/app/generation.rs +++ b/src/app/generation.rs @@ -2612,11 +2612,11 @@ mod tests { "#; 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(""), ""))); diff --git a/src/app/preferences.rs b/src/app/preferences.rs index 6cff8f6..18ff261 100644 --- a/src/app/preferences.rs +++ b/src/app/preferences.rs @@ -11,7 +11,6 @@ pub(super) struct PreferenceDraft { generation_profiles: BTreeMap>, model_profiles: BTreeMap, 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 { 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, String> { } } -fn parse_f32(name: &str, value: &str) -> Result { - value - .trim() - .parse() - .map_err(|_| format!("{name} must be a number.")) -} - fn parse_optional_u64(name: &str, value: &str) -> Result, 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; } diff --git a/src/app/view/preferences.rs b/src/app/view/preferences.rs index 5ba655e..ab56ea8 100644 --- a/src/app/view/preferences.rs +++ b/src/app/view/preferences.rs @@ -3,17 +3,6 @@ use iced::widget::column; impl App { pub(super) fn preferences_panel(&self) -> Element<'_, Message> { - let legacy_mtp_toggle: Option 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 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." diff --git a/src/app/view/stats.rs b/src/app/view/stats.rs index 89feaff..40d3809 100644 --- a/src/app/view/stats.rs +++ b/src/app/view/stats.rs @@ -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!( diff --git a/src/config.rs b/src/config.rs index aa723b1..3d85987 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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(¤t) { + 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 { @@ -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(); + } } diff --git a/src/engine.rs b/src/engine.rs index 62177a7..87dccb8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -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 { 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); diff --git a/src/engine/metal.rs b/src/engine/metal.rs index 63a763e..cd8b0e3 100644 --- a/src/engine/metal.rs +++ b/src/engine/metal.rs @@ -270,47 +270,6 @@ struct Weights { layers: Vec, } -struct LegacyMtpWeights { - e_proj: Weight, - h_proj: Weight, - enorm: Weight, - hnorm: Weight, - norm: Weight, - hc_head_base: Weight, - hc_head_fn: Weight, - hc_head_scale: Weight, - block: Layer, -} - -impl LegacyMtpWeights { - fn bind(model: &Gguf, shape: super::Shape) -> Result { - Ok(Self { - e_proj: Weight::bind(model, "mtp.0.e_proj.weight")?, - h_proj: Weight::bind(model, "mtp.0.h_proj.weight")?, - enorm: Weight::bind(model, "mtp.0.enorm.weight")?, - hnorm: Weight::bind(model, "mtp.0.hnorm.weight")?, - norm: Weight::bind(model, "mtp.0.norm.weight")?, - hc_head_base: Weight::bind(model, "mtp.0.hc_head_base.weight")?, - hc_head_fn: Weight::bind(model, "mtp.0.hc_head_fn.weight")?, - hc_head_scale: Weight::bind(model, "mtp.0.hc_head_scale.weight")?, - block: Layer::bind_prefix(model, shape, 1, "mtp.0")?, - }) - } -} - -struct LegacyMtp { - weights: LegacyMtpWeights, - layer: LayerState, - state_hc: Buffer, - next_hc: Buffer, - draft_token: Option, - raw_rows: u32, - draft_limit: u32, - margin: f32, - drafted: u64, - accepted: u64, -} - struct DsparkStageWeights { block: Layer, main_proj: Option, @@ -2207,7 +2166,7 @@ impl SsdPlan { } let mut loaded = 0_u32; let hotlist = match model.shape.model { - ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731 => hotlist::FLASH, + ModelChoice::DeepSeekV4Flash0731 => hotlist::FLASH, ModelChoice::DeepSeekV4Pro => hotlist::PRO, ModelChoice::Glm52 => unreachable!("GLM uses its dedicated executor"), }; @@ -3004,7 +2963,6 @@ pub(super) struct ExecutionStats { pub(super) struct DeepSeekExecutor { weights: Weights, session: Session, - legacy_mtp: Option, dspark: Option, steering: Option, ssd: Option, @@ -3029,7 +2987,6 @@ pub(super) struct DeepSeekExecutor { pub(super) struct DeepSeekResidentState { session: Session, - legacy_mtp: Option, dspark: Option, logits: Vec, tokens: Vec, @@ -3104,24 +3061,6 @@ impl DeepSeekExecutor { prefill_chunk }, )?; - let legacy_mtp = match (model.support_kind, model.support.as_ref()) { - (Some(SupportKind::LegacyMtp), Some(support)) => { - let hc_dim = model.shape.hc * model.shape.embd; - Some(LegacyMtp { - weights: LegacyMtpWeights::bind(support, model.shape)?, - layer: LayerState::allocate(&model, 1, context, session.raw_cap)?, - state_hc: Buffer::floats(hc_dim)?, - next_hc: Buffer::floats(hc_dim)?, - draft_token: None, - raw_rows: 0, - draft_limit: speculative.mtp_draft_tokens.max(1) as u32, - margin: speculative.mtp_margin, - drafted: 0, - accepted: 0, - }) - } - _ => None, - }; let dspark = match ( model.support_kind, model.support.as_ref(), @@ -3163,7 +3102,6 @@ impl DeepSeekExecutor { Ok(Self { weights, session, - legacy_mtp, dspark, steering, ssd: ssd_plan, @@ -3651,7 +3589,7 @@ impl DeepSeekExecutor { reasoning: ReasoningMode, cancelled: &std::sync::atomic::AtomicBool, ) -> Result, String> { - if self.dspark.is_some() || self.legacy_mtp.is_some() { + if self.dspark.is_some() { self.speculative_cycles += 1; } self.eval_target(first_token)?; @@ -3707,63 +3645,6 @@ impl DeepSeekExecutor { dspark.scheduler_note(verified.len() as u32, no_draft); return Ok(accepted); } - let Some(draft_cap) = self.legacy_mtp.as_ref().map(|mtp| { - mtp.draft_limit - .min(max_tokens.saturating_sub(1)) - .min(self.session.context.saturating_sub(self.session.position)) - }) else { - return Ok(accepted); - }; - if self - .legacy_mtp - .as_ref() - .is_some_and(|mtp| mtp.draft_limit <= 1) - || draft_cap == 0 - || cancelled.load(std::sync::atomic::Ordering::Relaxed) - { - return Ok(accepted); - } - - let (first_proposal, _) = self.legacy_mtp_draft(first_token, true)?; - let mtp_base_raw = self - .legacy_mtp - .as_ref() - .expect("legacy MTP disappeared") - .raw_rows; - if argmax(&self.logits) != first_proposal { - return Ok(accepted); - } - let mut proposals = vec![first_proposal]; - let mut last_margin = f32::INFINITY; - while proposals.len() < draft_cap as usize - && !self.model.is_stop_token_for_reasoning( - *proposals.last().expect("MTP proposal disappeared"), - reasoning, - ) - && !cancelled.load(std::sync::atomic::Ordering::Relaxed) - { - let (proposal, margin) = - self.legacy_mtp_draft(*proposals.last().expect("MTP proposal disappeared"), false)?; - proposals.push(proposal); - last_margin = margin; - } - if proposals.len() == 2 - && self - .legacy_mtp - .as_ref() - .is_some_and(|mtp| mtp.margin > 0.0 && last_margin < mtp.margin) - { - proposals.truncate(1); - } - let verified = self.verify_target_suffix(&proposals, cancelled)?; - accepted.extend_from_slice(&verified); - let mtp = self.legacy_mtp.as_mut().expect("legacy MTP disappeared"); - mtp.raw_rows = mtp_base_raw - .saturating_add(verified.len() as u32) - .min(self.model.shape.sliding_window as u32) - .min(self.session.raw_cap); - mtp.accepted += verified.len() as u64; - mtp.draft_token = None; Ok(accepted) } @@ -3852,168 +3733,6 @@ impl DeepSeekExecutor { Ok(emitted) } - fn legacy_mtp_draft(&mut self, token: i32, target_hc: bool) -> Result<(i32, f32), String> { - let support = self - .model - .support - .as_ref() - .ok_or("legacy MTP support model is missing")?; - let mtp = self - .legacy_mtp - .as_mut() - .ok_or("legacy MTP is not configured")?; - let shape = self.model.shape; - let base_map = self.model.main.map_ptr().cast(); - let base_size = self.model.main.len(); - let support_map = support.map_ptr().cast(); - let support_size = support.len(); - let scratch = &self.session.scratch; - let draft_pos = if target_hc { - self.session.position.saturating_sub(1) - } else { - self.session.position - }; - let prev = if target_hc { - scratch.current_hc.raw().cast_const() - } else { - mtp.state_hc.raw().cast_const() - }; - if self.ssd.is_some() { - install_speculative_model_maps(&self.model, "MTP model mapping")?; - } - let commands = Commands::begin()?; - call( - unsafe { - ds4_gpu_embed_token_hc_tensor( - scratch.current.raw(), - base_map, - base_size, - self.weights.token_embedding.offset, - shape.vocab as u32, - token as u32, - shape.embd as u32, - 1, - ) - }, - "MTP token embedding", - )?; - call( - unsafe { - ds4_gpu_rms_norm_weight_tensor( - scratch.norm.raw(), - scratch.current.raw(), - support_map, - support_size, - mtp.weights.enorm.offset, - shape.embd as u32, - shape.rms_epsilon, - ) - }, - "MTP embedding norm", - )?; - q8( - &scratch.attention_out, - mtp.weights.e_proj, - shape.embd, - shape.embd, - &scratch.norm, - support_map, - support_size, - )?; - call( - unsafe { - ds4_gpu_rms_norm_weight_rows_tensor( - scratch.flat_hc.raw(), - prev, - support_map, - support_size, - mtp.weights.hnorm.offset, - shape.embd as u32, - shape.hc as u32, - shape.rms_epsilon, - ) - }, - "MTP hidden norm", - )?; - q8_rows( - &scratch.next_hc, - mtp.weights.h_proj, - shape.embd, - shape.embd, - &scratch.flat_hc, - shape.hc as u32, - support_map, - support_size, - )?; - call( - unsafe { - ds4_gpu_repeat_hc_tensor( - scratch.current_hc.raw(), - scratch.attention_out.raw(), - shape.embd as u32, - shape.hc as u32, - ) - }, - "MTP embedding repeat", - )?; - call( - unsafe { - ds4_gpu_add_tensor( - scratch.current_hc.raw(), - scratch.current_hc.raw(), - scratch.next_hc.raw(), - (shape.hc * shape.embd) as u32, - ) - }, - "MTP input projection", - )?; - encode_layer_with_cache_rows( - scratch, - &mut mtp.layer, - &mtp.weights.block, - shape, - support_map, - support_size, - 1, - draft_pos, - token as u32, - self.session.raw_cap, - None, - None, - Some(mtp.raw_rows), - true, - false, - )?; - mtp.next_hc.copy_from( - 0, - &scratch.current_hc, - 0, - shape.hc * shape.embd * 4, - "MTP hidden-state capture", - )?; - encode_mtp_output( - scratch, - &mtp.weights, - self.weights.output, - shape, - support_map, - support_size, - base_map, - base_size, - )?; - commands.finish()?; - let mut logits = vec![0.0_f32; shape.vocab as usize]; - scratch.logits.read_f32(&mut logits)?; - let (top, margin) = top2(&logits); - std::mem::swap(&mut mtp.state_hc, &mut mtp.next_hc); - mtp.raw_rows = (mtp.raw_rows + 1) - .min(shape.sliding_window as u32) - .min(self.session.raw_cap); - mtp.draft_token = Some(top); - mtp.drafted += 1; - Ok((top, margin)) - } - pub(super) fn prefill( &mut self, tokens: &[i32], @@ -4022,18 +3741,18 @@ impl DeepSeekExecutor { if self.session.position == 0 && self.ssd.is_some() { unsafe { ds4_gpu_stream_expert_cache_reset_route_hotness() }; } - let streaming_decode_cap = if matches!( - self.model.shape.model, - ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731 - ) && self.weights.layers.first().is_some_and(|layer| { - matches!(layer.expert_gate.kind, Q4_K | MXFP4) - && layer.expert_up.kind == layer.expert_gate.kind - && layer.expert_down.kind == layer.expert_gate.kind - }) { - 64 - } else { - 18 - }; + let streaming_decode_cap = + if matches!(self.model.shape.model, ModelChoice::DeepSeekV4Flash0731) + && self.weights.layers.first().is_some_and(|layer| { + matches!(layer.expert_gate.kind, Q4_K | MXFP4) + && layer.expert_up.kind == layer.expert_gate.kind + && layer.expert_down.kind == layer.expert_gate.kind + }) + { + 64 + } else { + 18 + }; if self.ssd.is_some() && !self.quality && !tokens.is_empty() @@ -4368,14 +4087,10 @@ impl DeepSeekExecutor { } fn execution_stats(&self) -> ExecutionStats { - let (speculative_mode, drafted_tokens, accepted_draft_tokens) = - if let Some(dspark) = &self.dspark { - (2, dspark.drafted, dspark.accepted) - } else if let Some(mtp) = &self.legacy_mtp { - (1, mtp.drafted, mtp.accepted) - } else { - (0, 0, 0) - }; + let (speculative_mode, drafted_tokens, accepted_draft_tokens) = self + .dspark + .as_ref() + .map_or((0, 0, 0), |dspark| (2, dspark.drafted, dspark.accepted)); let mut stats = ExecutionStats { speculative_mode, speculative_cycles: self.speculative_cycles, @@ -4428,12 +4143,6 @@ impl DeepSeekExecutor { pub(super) fn reset(&mut self) -> Result<(), String> { self.session = Session::new(&self.model, self.session.context, self.session.prefill_cap)?; - if let Some(mtp) = &mut self.legacy_mtp { - mtp.layer = - LayerState::allocate(&self.model, 1, self.session.context, self.session.raw_cap)?; - mtp.draft_token = None; - mtp.raw_rows = 0; - } if let Some(dspark) = &mut self.dspark { dspark.capture_mask = 0; dspark.cache_start = 0; @@ -4446,24 +4155,6 @@ impl DeepSeekExecutor { fn blank_resident_state(&self) -> Result { let session = Session::new(&self.model, self.session.context, self.session.prefill_cap)?; - let legacy_mtp = match (self.model.support_kind, self.model.support.as_ref()) { - (Some(SupportKind::LegacyMtp), Some(support)) => { - let hc_dim = self.model.shape.hc * self.model.shape.embd; - Some(LegacyMtp { - weights: LegacyMtpWeights::bind(support, self.model.shape)?, - layer: LayerState::allocate(&self.model, 1, session.context, session.raw_cap)?, - state_hc: Buffer::floats(hc_dim)?, - next_hc: Buffer::floats(hc_dim)?, - draft_token: None, - raw_rows: 0, - draft_limit: self.speculative.mtp_draft_tokens.max(1) as u32, - margin: self.speculative.mtp_margin, - drafted: 0, - accepted: 0, - }) - } - _ => None, - }; let dspark = match ( self.model.support_kind, self.model.support.as_ref(), @@ -4480,7 +4171,6 @@ impl DeepSeekExecutor { }; Ok(DeepSeekResidentState { session, - legacy_mtp, dspark, logits: vec![0.0; self.model.shape.vocab as usize], tokens: Vec::new(), @@ -4496,7 +4186,6 @@ impl DeepSeekExecutor { .take() .map_or_else(|| self.blank_resident_state(), Ok)?; std::mem::swap(&mut self.session, &mut incoming.session); - std::mem::swap(&mut self.legacy_mtp, &mut incoming.legacy_mtp); std::mem::swap(&mut self.dspark, &mut incoming.dspark); std::mem::swap(&mut self.logits, &mut incoming.logits); std::mem::swap(&mut self.tokens, &mut incoming.tokens); @@ -4688,8 +4377,6 @@ impl Executor { prefill_chunk, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, @@ -7951,22 +7638,15 @@ fn update_compressor_stage( fn compression_ratio(shape: super::Shape, layer: u32) -> u32 { match shape.model { - crate::model::ModelChoice::DeepSeekV4Flash - | crate::model::ModelChoice::DeepSeekV4Flash0731 - if layer < 2 => - { - 0 - } + crate::model::ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0, crate::model::ModelChoice::DeepSeekV4Pro if layer < 2 => 128, - crate::model::ModelChoice::DeepSeekV4Flash - | crate::model::ModelChoice::DeepSeekV4Flash0731 + crate::model::ModelChoice::DeepSeekV4Flash0731 | crate::model::ModelChoice::DeepSeekV4Pro if layer.is_multiple_of(2) => { 4 } - crate::model::ModelChoice::DeepSeekV4Flash - | crate::model::ModelChoice::DeepSeekV4Flash0731 + crate::model::ModelChoice::DeepSeekV4Flash0731 | crate::model::ModelChoice::DeepSeekV4Pro => 128, crate::model::ModelChoice::Glm52 => 0, } @@ -8177,90 +7857,6 @@ fn encode_batch_output( ) } -#[allow(clippy::too_many_arguments)] -fn encode_mtp_output( - s: &Scratch, - mtp: &LegacyMtpWeights, - base_output: Weight, - shape: super::Shape, - support_map: *const c_void, - support_size: u64, - base_map: *const c_void, - base_size: u64, -) -> Result<(), String> { - let hc_dim = shape.hc * shape.embd; - call( - unsafe { - ds4_gpu_rms_norm_plain_tensor( - s.flat_hc.raw(), - s.current_hc.raw(), - hc_dim as u32, - shape.rms_epsilon, - ) - }, - "MTP output HC norm", - )?; - matmul( - &s.output_pre, - mtp.hc_head_fn, - hc_dim, - shape.hc, - &s.flat_hc, - support_map, - support_size, - )?; - call( - unsafe { - ds4_gpu_output_hc_weights_tensor( - s.output_weights.raw(), - s.output_pre.raw(), - support_map, - support_size, - mtp.hc_head_scale.offset, - mtp.hc_head_base.offset, - shape.hc as u32, - shape.hc_epsilon, - ) - }, - "MTP output HC weights", - )?; - call( - unsafe { - ds4_gpu_hc_weighted_sum_tensor( - s.output_embedding.raw(), - s.current_hc.raw(), - s.output_weights.raw(), - shape.embd as u32, - shape.hc as u32, - ) - }, - "MTP output HC collapse", - )?; - call( - unsafe { - ds4_gpu_rms_norm_weight_tensor( - s.output_norm.raw(), - s.output_embedding.raw(), - support_map, - support_size, - mtp.norm.offset, - shape.embd as u32, - shape.rms_epsilon, - ) - }, - "MTP output norm", - )?; - q8( - &s.logits, - base_output, - shape.embd, - shape.vocab, - &s.output_norm, - base_map, - base_size, - ) -} - fn argmax(values: &[f32]) -> i32 { values .iter() @@ -8269,20 +7865,6 @@ fn argmax(values: &[f32]) -> i32 { .map_or(-1, |(index, _)| index as i32) } -fn top2(values: &[f32]) -> (i32, f32) { - let mut first = (usize::MAX, f32::NEG_INFINITY); - let mut second = f32::NEG_INFINITY; - for (index, &value) in values.iter().enumerate() { - if value > first.1 { - second = first.1; - first = (index, value); - } else if value > second { - second = value; - } - } - (first.0 as i32, first.1 - second) -} - fn dense_row(model: &Gguf, weight: Weight, row: u32) -> Result, String> { if u64::from(row) >= weight.dims[1] { return Err("dense row is outside the tensor".into()); @@ -8714,14 +8296,13 @@ mod tests { estimated_deepseek_runtime_bytes, finish_deepseek_model_spans, gpu::ds4_gpu_print_memory_report, quantize_q8_activation, raw_batch_span, raw_decode_span, }; - use crate::engine::{FLASH, MXFP4, PRO}; + use crate::engine::{FLASH_0731 as FLASH, MXFP4, PRO}; fn installed_artifacts( model: crate::model::ModelChoice, - legacy_mtp: bool, dspark: bool, ) -> crate::model::EngineArtifacts { - crate::model::engine_artifacts(model, legacy_mtp, dspark, &crate::app::models_path()) + crate::model::engine_artifacts(model, dspark, &crate::app::models_path()) } #[test] @@ -8852,9 +8433,7 @@ mod tests { configure_sources().unwrap(); let path = std::env::var_os("DS4SERVER_BENCH_MODEL") .map(std::path::PathBuf::from) - .unwrap_or_else(|| { - installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, false).model - }); + .unwrap_or_else(|| installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false).model); let prompt_content = std::env::var_os("DS4SERVER_BENCH_CHAT_PROMPT_FILE") .map(|path| std::fs::read_to_string(path).unwrap()) .unwrap_or_else(|| "Count from one to two hundred, spelling out every number.".into()); @@ -8874,10 +8453,9 @@ mod tests { let run = || { let mut model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap(); if dspark { - let support_path = - installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, true) - .mtp - .unwrap(); + let support_path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, true) + .support + .unwrap(); let support = Gguf::open(&support_path).unwrap(); model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); model.support = Some(support); @@ -8919,8 +8497,6 @@ mod tests { context, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark, @@ -9085,9 +8661,7 @@ mod tests { configure_sources().unwrap(); let path = std::env::var_os("DS4SERVER_BENCH_MODEL") .map(std::path::PathBuf::from) - .unwrap_or_else(|| { - installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, false).model - }); + .unwrap_or_else(|| installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false).model); let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap(); let prompt = model.render_conversation( "", @@ -9111,8 +8685,6 @@ mod tests { 4_096, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, @@ -9146,146 +8718,18 @@ mod tests { } #[test] - #[ignore = "requires the installed 81 GiB Flash and legacy MTP GGUF fixtures"] - fn legacy_mtp_runs_a_target_owned_greedy_cycle() { - use super::{DeepSeekExecutor, argmax, configure_sources}; - use crate::engine::Model; - use crate::engine::gguf::Gguf; - use crate::engine::validation::validate_support; - use crate::model::ModelChoice; - use crate::settings::{ - EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, - }; - use std::sync::atomic::AtomicBool; - - let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash, true, false); - let main_path = artifacts.model; - configure_sources().unwrap(); - let support_path = artifacts.mtp.unwrap(); - let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash).unwrap(); - let support = Gguf::open(&support_path).unwrap(); - model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); - model.support = Some(support); - let prompt = model.render_conversation( - "", - &[crate::engine::ChatTurn { - user: true, - tool: false, - system: false, - skip_previous_eos: false, - reasoning: None, - reasoning_complete: true, - content: "hi".into(), - }], - crate::settings::ReasoningMode::Direct, - ); - let mut executor = DeepSeekExecutor::open( - model, - 512, - false, - 512, - 100, - EngineSpeculativeSettings { - mtp_draft_tokens: 4, - mtp_margin: 3.0, - glm_mtp: false, - glm_mtp_timing: false, - dspark: false, - dspark_confidence_threshold: 0.9, - dspark_confidence_threshold_set: false, - dspark_strict: false, - dspark_exact_sampling: false, - }, - EngineSsdSettings { - enabled: false, - cold: false, - cache_experts: 0, - cache_bytes: 0, - full_layers: 0, - full_layers_set: false, - preload_experts: 0, - }, - EngineSteeringSettings { - file: None, - ffn_scale: 0.0, - attention_scale: 0.0, - }, - ) - .unwrap(); - executor.prefill(&prompt, |_| true).unwrap(); - let mut generated = Vec::new(); - while generated.len() < 8 { - let first = argmax(executor.logits()); - let cycle = executor - .eval_speculative_greedy( - first, - (8 - generated.len()) as u32, - crate::settings::ReasoningMode::Direct, - &AtomicBool::new(false), - ) - .unwrap(); - generated.extend(cycle); - } - assert_eq!(generated, [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316]); - let mtp = executor.legacy_mtp.as_ref().unwrap(); - assert!(mtp.drafted > 0); - - executor.reset().unwrap(); - let copy_prompt = executor.model.render_conversation( - "", - &[crate::engine::ChatTurn { - user: true, - tool: false, - system: false, - skip_previous_eos: false, - reasoning: None, - reasoning_complete: true, - content: "Reproduce the following C code EXACTLY, character for character, inside a single code block and output nothing else:\n\n```c\nstatic uint32_t clamp_u32(uint32_t v, uint32_t lo, uint32_t hi) {\n if (v < lo) return lo;\n if (v > hi) return hi;\n return v;\n}\n\nstatic uint32_t ring_advance(uint32_t pos, uint32_t cap) {\n uint32_t next = pos + 1u;\n return next >= cap ? 0u : next;\n}\n\nstatic int scratch_init(scratch *s, uint32_t ctx_size) {\n if (ctx_size == 0u) ctx_size = 1u;\n s->ctx_size = ctx_size;\n s->comp_cap = ctx_size / 4u + 2u;\n s->rows = clamp_u32(s->comp_cap, 1u, 4096u);\n s->head = 0u;\n return s->rows > 0u ? 0 : -1;\n}\n```" - .into(), - }], - crate::settings::ReasoningMode::Direct, - ); - executor.prefill(©_prompt, |_| true).unwrap(); - let mut max_cycle = 0; - let mut copied = 0; - while copied < 128 { - let first = argmax(executor.logits()); - let cycle = executor - .eval_speculative_greedy( - first, - 128 - copied, - crate::settings::ReasoningMode::Direct, - &AtomicBool::new(false), - ) - .unwrap(); - max_cycle = max_cycle.max(cycle.len()); - copied += cycle.len() as u32; - } - assert!(max_cycle > 1, "legacy MTP never committed a draft token"); - - executor.reset().unwrap(); - executor.legacy_mtp.as_mut().unwrap().draft_limit = 1; - executor.prefill(&prompt, |_| true).unwrap(); - let mut target_only = Vec::new(); - while target_only.len() < 8 { - let first = argmax(executor.logits()); - let cycle = executor - .eval_speculative_greedy( - first, - (8 - target_only.len()) as u32, - crate::settings::ReasoningMode::Direct, - &AtomicBool::new(false), - ) - .unwrap(); - assert_eq!(cycle.len(), 1); - target_only.extend(cycle); - } - assert_eq!(target_only, [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316]); + #[ignore = "requires the installed 0731 Flash and checkpoint-specific DSpark GGUF fixtures"] + fn dspark_runs_a_target_owned_greedy_cycle() { + run_dspark_target_owned_greedy_cycle(false); } #[test] #[ignore = "requires the installed 0731 Flash and checkpoint-specific DSpark GGUF fixtures"] - fn dspark_runs_a_target_owned_greedy_cycle() { + fn ssd_streaming_supports_dspark() { + run_dspark_target_owned_greedy_cycle(true); + } + + fn run_dspark_target_owned_greedy_cycle(streaming: bool) { use super::{DeepSeekExecutor, argmax, configure_sources}; use crate::engine::Model; use crate::engine::gguf::Gguf; @@ -9297,11 +8741,11 @@ mod tests { use std::sync::atomic::AtomicBool; configure_sources().unwrap(); - let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, true); + let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, true); let main_path = std::env::var_os("DS4SERVER_BENCH_MODEL") .map(std::path::PathBuf::from) .unwrap_or(artifacts.model); - let support_path = artifacts.mtp.unwrap(); + let support_path = artifacts.support.unwrap(); let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash0731).unwrap(); let support = Gguf::open(&support_path).unwrap(); model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); @@ -9319,7 +8763,6 @@ mod tests { }], crate::settings::ReasoningMode::Direct, ); - let streaming = std::env::var_os("DS4SERVER_BENCH_SSD").is_some(); let mut executor = DeepSeekExecutor::open( model, 64, @@ -9327,8 +8770,6 @@ mod tests { 64, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: true, @@ -9442,21 +8883,16 @@ mod tests { use std::sync::atomic::AtomicBool; configure_sources().unwrap(); - let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, true); + let artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, true); let main_path = std::env::var_os("DS4SERVER_BENCH_MODEL") .map(std::path::PathBuf::from) .unwrap_or_else(|| { - validate_engine_artifacts( - ModelChoice::DeepSeekV4Flash0731, - false, - true, - &artifacts, - ) - .unwrap(); + validate_engine_artifacts(ModelChoice::DeepSeekV4Flash0731, true, &artifacts) + .unwrap(); artifacts.model.clone() }); let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash0731).unwrap(); - let support = Gguf::open(artifacts.mtp.as_ref().unwrap()).unwrap(); + let support = Gguf::open(artifacts.support.as_ref().unwrap()).unwrap(); model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); model.support = Some(support); let prompt = model.render_conversation( @@ -9480,8 +8916,6 @@ mod tests { 64, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: true, @@ -9527,101 +8961,6 @@ mod tests { assert!(executor.session.position >= prompt.len() as u32 + cycle.len() as u32); } - #[test] - #[ignore = "requires the installed Flash, legacy MTP, and DSpark GGUF fixtures"] - fn ssd_streaming_supports_legacy_mtp_and_dspark() { - use super::{DeepSeekExecutor, argmax, configure_sources}; - use crate::engine::Model; - use crate::engine::gguf::Gguf; - use crate::engine::validation::validate_support; - use crate::model::ModelChoice; - use crate::settings::{ - EngineSpeculativeSettings, EngineSsdSettings, EngineSteeringSettings, - }; - use std::sync::atomic::AtomicBool; - - configure_sources().unwrap(); - let legacy = installed_artifacts(ModelChoice::DeepSeekV4Flash, true, false); - let dspark_artifacts = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, true); - let main_path = legacy.model; - let cases = [ - (legacy.mtp.unwrap(), false, 4), - (dspark_artifacts.mtp.unwrap(), true, 1), - ]; - for (support_path, dspark, draft_tokens) in cases { - let mut model = Model::open_main(&main_path, ModelChoice::DeepSeekV4Flash).unwrap(); - let support = Gguf::open(&support_path).unwrap(); - model.support_kind = Some(validate_support(&support, &model.shape).unwrap()); - model.support = Some(support); - let prompt = model.render_conversation( - "", - &[crate::engine::ChatTurn { - user: true, - tool: false, - system: false, - skip_previous_eos: false, - reasoning: None, - reasoning_complete: true, - content: "hi".into(), - }], - crate::settings::ReasoningMode::Direct, - ); - let mut executor = DeepSeekExecutor::open( - model, - 64, - false, - 64, - 100, - EngineSpeculativeSettings { - mtp_draft_tokens: draft_tokens, - mtp_margin: 3.0, - glm_mtp: false, - glm_mtp_timing: false, - dspark, - dspark_confidence_threshold: 0.9, - dspark_confidence_threshold_set: false, - dspark_strict: false, - dspark_exact_sampling: false, - }, - EngineSsdSettings { - enabled: true, - cold: true, - cache_experts: 256, - cache_bytes: 0, - full_layers: 0, - full_layers_set: false, - preload_experts: 0, - }, - EngineSteeringSettings { - file: None, - ffn_scale: 0.0, - attention_scale: 0.0, - }, - ) - .unwrap(); - executor.prefill(&prompt, |_| true).unwrap(); - let mut generated = Vec::new(); - while generated.len() < 8 { - let first = argmax(executor.logits()); - let cycle = executor - .eval_speculative_greedy( - first, - (8 - generated.len()) as u32, - crate::settings::ReasoningMode::Direct, - &AtomicBool::new(false), - ) - .unwrap(); - generated.extend(cycle); - } - assert_eq!( - generated, - [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316], - "SSD speculative output differed for {}", - support_path.display() - ); - } - } - #[test] #[ignore = "requires the installed 81 GiB Flash GGUF fixture and a Metal device"] fn flash_0731_resident_and_ssd_streaming_choose_the_same_tokens() { @@ -9689,8 +9028,6 @@ mod tests { ); assert_eq!(prompt.len(), 5); let speculative = EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, @@ -9733,7 +9070,7 @@ mod tests { } configure_sources().unwrap(); - let path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, false).model; + let path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false).model; let resident = run(&path, false); let resident_rolled_back = { let _rollbacks = FusionRollbacks::activate(); @@ -9760,7 +9097,7 @@ mod tests { }; configure_sources().unwrap(); - let path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false, false).model; + let path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false).model; let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap(); let prompt = model.render_conversation( "", @@ -9783,8 +9120,6 @@ mod tests { 256, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, @@ -9854,8 +9189,8 @@ mod tests { }; configure_sources().unwrap(); - let path = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, false).model; - let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap(); + let path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false).model; + let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap(); let prompts = ["Reply with A.", "Reply with B."].map(|content| { model.render_conversation( "", @@ -9878,8 +9213,6 @@ mod tests { 64, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, @@ -9934,7 +9267,7 @@ mod tests { }; configure_sources().unwrap(); - let model_path = installed_artifacts(ModelChoice::DeepSeekV4Flash, false, false).model; + let model_path = installed_artifacts(ModelChoice::DeepSeekV4Flash0731, false).model; let steering_path = std::env::var("DS4_STEERING_FILE") .expect("set DS4_STEERING_FILE to the DS4 verbosity direction fixture"); let cases = [ @@ -9942,7 +9275,7 @@ mod tests { (0.0, 1.0, [19_923, 3, 1_730, 588, 342, 1_694, 440, 4_316]), ]; for (ffn_scale, attention_scale, expected) in cases { - let model = Model::open_main(&model_path, ModelChoice::DeepSeekV4Flash).unwrap(); + let model = Model::open_main(&model_path, ModelChoice::DeepSeekV4Flash0731).unwrap(); let prompt = model.render_conversation( "", &[crate::engine::ChatTurn { @@ -9963,8 +9296,6 @@ mod tests { 32, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, @@ -10011,7 +9342,7 @@ mod tests { }; configure_sources().unwrap(); - let path = installed_artifacts(ModelChoice::DeepSeekV4Pro, false, false).model; + let path = installed_artifacts(ModelChoice::DeepSeekV4Pro, false).model; if !path.is_file() { eprintln!("skipping unavailable Pro fixture: {}", path.display()); return; @@ -10039,8 +9370,6 @@ mod tests { 256, 100, EngineSpeculativeSettings { - mtp_draft_tokens: 1, - mtp_margin: 3.0, glm_mtp: false, glm_mtp_timing: false, dspark: false, diff --git a/src/engine/metal/checkpoint.rs b/src/engine/metal/checkpoint.rs index d1ae841..647a5fe 100644 --- a/src/engine/metal/checkpoint.rs +++ b/src/engine/metal/checkpoint.rs @@ -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; diff --git a/src/engine/metal/glm.rs b/src/engine/metal/glm.rs index 88b62ff..fa3f8c5 100644 --- a/src/engine/metal/glm.rs +++ b/src/engine/metal/glm.rs @@ -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, diff --git a/src/engine/metal/profile.rs b/src/engine/metal/profile.rs index 619dd27..c62543c 100644 --- a/src/engine/metal/profile.rs +++ b/src/engine/metal/profile.rs @@ -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(); diff --git a/src/engine/validation.rs b/src/engine/validation.rs index ae79e4d..123522b 100644 --- a/src/engine/validation.rs +++ b/src/engine/validation.rs @@ -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 { } pub(super) fn validate_support(model: &Gguf, shape: &Shape) -> Result { - 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 { 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 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(); } } } diff --git a/src/metrics.rs b/src/metrics.rs index 32fa7b8..d4c0d04 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -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")); diff --git a/src/model.rs b/src/model.rs index d0118cd..bc57eb2 100644 --- a/src/model.rs +++ b/src/model.rs @@ -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 { + fn artifacts(self, dspark_enabled: bool) -> impl Iterator { [ 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, + pub(crate) support: Option, } 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", diff --git a/src/model/transfer.rs b/src/model/transfer.rs index 5753815..86ece2d 100644 --- a/src/model/transfer.rs +++ b/src/model/transfer.rs @@ -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, diff --git a/src/server.rs b/src/server.rs index 1076e57..5ab98e3 100644 --- a/src/server.rs +++ b/src/server.rs @@ -546,7 +546,7 @@ fn model_json(id: &str, model: ModelChoice, context: i32, default_tokens: i32) - fn model_alias(id: &str) -> Option { 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, ); diff --git a/src/settings.rs b/src/settings.rs index e3027bc..10b5bc1 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -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",