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

@@ -126,13 +126,10 @@ typedef struct {
typedef struct { typedef struct {
const char *model_path; const char *model_path;
const char *mtp_path;
ds4_backend backend; ds4_backend backend;
int n_threads; int n_threads;
int context_size; int context_size;
uint32_t prefill_chunk; uint32_t prefill_chunk;
int mtp_draft_tokens;
float mtp_margin;
float dspark_confidence_threshold; float dspark_confidence_threshold;
const char *directional_steering_file; const char *directional_steering_file;
const char *expert_profile_path; 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_session_prefill_cap(ds4_session *s);
int ds4_engine_routed_quant_bits(ds4_engine *e); int ds4_engine_routed_quant_bits(ds4_engine *e);
bool ds4_engine_has_output_head(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); const ds4_tokens *ds4_session_tokens(ds4_session *s);
/* Low-level graph slice entry points used by distributed inference. The /* Low-level graph slice entry points used by distributed inference. The

View File

@@ -45,7 +45,7 @@ def post(path, payload):
first = post( first = post(
"/v1/chat/completions", "/v1/chat/completions",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [{"role": "user", "content": prompt}], "messages": [{"role": "user", "content": prompt}],
"tools": [chat_tool], "tools": [chat_tool],
"reasoning_effort": "none", "reasoning_effort": "none",
@@ -58,7 +58,7 @@ print("chat-first", json.dumps(first["usage"], separators=(",", ":")))
second = post( second = post(
"/v1/chat/completions", "/v1/chat/completions",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [ "messages": [
{"role": "user", "content": prompt}, {"role": "user", "content": prompt},
{"role": "assistant", "content": "", "tool_calls": [call]}, {"role": "assistant", "content": "", "tool_calls": [call]},
@@ -79,7 +79,7 @@ print("chat", json.dumps(second, separators=(",", ":")))
first = post( first = post(
"/v1/messages", "/v1/messages",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [{"role": "user", "content": prompt}], "messages": [{"role": "user", "content": prompt}],
"tools": [anthropic_tool], "tools": [anthropic_tool],
"thinking": {"type": "disabled"}, "thinking": {"type": "disabled"},
@@ -92,7 +92,7 @@ print("anthropic-first", json.dumps(first["usage"], separators=(",", ":")))
second = post( second = post(
"/v1/messages", "/v1/messages",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [ "messages": [
{"role": "user", "content": prompt}, {"role": "user", "content": prompt},
{"role": "assistant", "content": [call]}, {"role": "assistant", "content": [call]},
@@ -118,7 +118,7 @@ print("anthropic", json.dumps(second, separators=(",", ":")))
first = post( first = post(
"/v1/responses", "/v1/responses",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"input": prompt, "input": prompt,
"tools": [responses_tool], "tools": [responses_tool],
"reasoning": {"effort": "none"}, "reasoning": {"effort": "none"},
@@ -131,7 +131,7 @@ print("responses-first", json.dumps(first["usage"], separators=(",", ":")))
second = post( second = post(
"/v1/responses", "/v1/responses",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"input": [ "input": [
{ {
"type": "message", "type": "message",

View File

@@ -16,7 +16,7 @@ cases = [
"chat", "chat",
"/v1/chat/completions", "/v1/chat/completions",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -42,7 +42,7 @@ cases = [
"anthropic", "anthropic",
"/v1/messages", "/v1/messages",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
@@ -65,7 +65,7 @@ cases = [
"responses", "responses",
"/v1/responses", "/v1/responses",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"input": "Call echo with text hi. Do not answer normally.", "input": "Call echo with text hi. Do not answer normally.",
"tools": [ "tools": [
{ {

View File

@@ -11,7 +11,7 @@ cases = [
"chat", "chat",
"/v1/chat/completions", "/v1/chat/completions",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [{"role": "user", "content": prompt}], "messages": [{"role": "user", "content": prompt}],
"reasoning_effort": "low", "reasoning_effort": "low",
"temperature": 0, "temperature": 0,
@@ -22,7 +22,7 @@ cases = [
"anthropic", "anthropic",
"/v1/messages", "/v1/messages",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [{"role": "user", "content": prompt}], "messages": [{"role": "user", "content": prompt}],
"thinking": {"type": "enabled", "budget_tokens": 64}, "thinking": {"type": "enabled", "budget_tokens": 64},
"output_config": {"effort": "low"}, "output_config": {"effort": "low"},
@@ -34,7 +34,7 @@ cases = [
"responses", "responses",
"/v1/responses", "/v1/responses",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"input": prompt, "input": prompt,
"reasoning": {"effort": "low", "summary": "auto"}, "reasoning": {"effort": "low", "summary": "auto"},
"temperature": 0, "temperature": 0,

View File

@@ -12,9 +12,8 @@ HARDWARE_TESTS = (
"flash_resident_and_ssd_streaming_choose_the_same_tokens", "flash_resident_and_ssd_streaming_choose_the_same_tokens",
"flash_ssd_streaming_maps_batched_prefill_layers", "flash_ssd_streaming_maps_batched_prefill_layers",
"resident_multi_session_switching_preserves_each_kv_frontier", "resident_multi_session_switching_preserves_each_kv_frontier",
"legacy_mtp_runs_a_target_owned_greedy_cycle",
"dspark_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", "directional_steering_matches_the_ds4_token_oracle",
"resident_and_streamed_glm_match_ds4_decode_oracles", "resident_and_streamed_glm_match_ds4_decode_oracles",
"streamed_glm_uses_ds4_indexed_prefill_for_long_prompts", "streamed_glm_uses_ds4_indexed_prefill_for_long_prompts",

View File

@@ -28,7 +28,7 @@ def chat(base_url, prompt):
base_url, base_url,
"/v1/chat/completions", "/v1/chat/completions",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [{"role": "user", "content": prompt}], "messages": [{"role": "user", "content": prompt}],
"reasoning_effort": "none", "reasoning_effort": "none",
"temperature": 0, "temperature": 0,
@@ -50,7 +50,7 @@ def anthropic(base_url, prompt):
base_url, base_url,
"/v1/messages", "/v1/messages",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"messages": [{"role": "user", "content": prompt}], "messages": [{"role": "user", "content": prompt}],
"thinking": {"type": "disabled"}, "thinking": {"type": "disabled"},
"temperature": 0, "temperature": 0,
@@ -69,7 +69,7 @@ def responses(base_url, prompt):
base_url, base_url,
"/v1/responses", "/v1/responses",
{ {
"model": "deepseek-v4-flash", "model": "deepseek-v4-flash-0731",
"input": prompt, "input": prompt,
"reasoning": {"effort": "none"}, "reasoning": {"effort": "none"},
"temperature": 0, "temperature": 0,

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,6 @@ pub(super) struct PreferenceDraft {
generation_profiles: BTreeMap<ModelChoice, BTreeMap<ReasoningMode, GenerationPreferences>>, generation_profiles: BTreeMap<ModelChoice, BTreeMap<ReasoningMode, GenerationPreferences>>,
model_profiles: BTreeMap<ModelChoice, ModelPreferences>, model_profiles: BTreeMap<ModelChoice, ModelPreferences>,
pub(super) default_permission_mode: PermissionMode, pub(super) default_permission_mode: PermissionMode,
pub(super) legacy_mtp_enabled: bool,
pub(super) dspark_enabled: bool, pub(super) dspark_enabled: bool,
pub(super) idle_timeout_minutes: String, pub(super) idle_timeout_minutes: String,
pub(super) a2ui_enabled: bool, pub(super) a2ui_enabled: bool,
@@ -39,8 +38,6 @@ pub(super) struct PreferenceDraft {
pub(super) prefill_chunk: String, pub(super) prefill_chunk: String,
pub(super) quality: bool, pub(super) quality: bool,
pub(super) warm_weights: 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: bool,
pub(super) glm_mtp_timing: bool, pub(super) glm_mtp_timing: bool,
pub(super) dspark_confidence_threshold: String, pub(super) dspark_confidence_threshold: String,
@@ -78,7 +75,6 @@ impl PreferenceDraft {
generation_profiles: config.generation_profiles.clone(), generation_profiles: config.generation_profiles.clone(),
model_profiles: config.model_profiles.clone(), model_profiles: config.model_profiles.clone(),
default_permission_mode: config.default_permission_mode, default_permission_mode: config.default_permission_mode,
legacy_mtp_enabled: speculative.legacy_mtp_enabled,
dspark_enabled: speculative.dspark_enabled, dspark_enabled: speculative.dspark_enabled,
idle_timeout_minutes: config.idle_timeout_minutes.to_string(), idle_timeout_minutes: config.idle_timeout_minutes.to_string(),
a2ui_enabled: config.a2ui_enabled, a2ui_enabled: config.a2ui_enabled,
@@ -106,8 +102,6 @@ impl PreferenceDraft {
prefill_chunk: optional_string(execution.prefill_chunk), prefill_chunk: optional_string(execution.prefill_chunk),
quality: execution.quality, quality: execution.quality,
warm_weights: execution.warm_weights, 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: speculative.glm_mtp,
glm_mtp_timing: speculative.glm_mtp_timing, glm_mtp_timing: speculative.glm_mtp_timing,
dspark_confidence_threshold: optional_string(speculative.dspark_confidence_threshold), dspark_confidence_threshold: optional_string(speculative.dspark_confidence_threshold),
@@ -217,9 +211,6 @@ impl PreferenceDraft {
pub(super) fn speculative(&self) -> Result<SpeculativePreferences, String> { pub(super) fn speculative(&self) -> Result<SpeculativePreferences, String> {
Ok(SpeculativePreferences { 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: self.glm_mtp,
glm_mtp_timing: self.glm_mtp_timing, glm_mtp_timing: self.glm_mtp_timing,
dspark_enabled: self.dspark_enabled, dspark_enabled: self.dspark_enabled,
@@ -262,10 +253,7 @@ impl PreferenceDraft {
let speculative = profile.speculative; let speculative = profile.speculative;
let ssd = profile.ssd; let ssd = profile.ssd;
self.acceleration_model = model; self.acceleration_model = model;
self.legacy_mtp_enabled = speculative.legacy_mtp_enabled;
self.dspark_enabled = speculative.dspark_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 = speculative.glm_mtp;
self.glm_mtp_timing = speculative.glm_mtp_timing; self.glm_mtp_timing = speculative.glm_mtp_timing;
self.dspark_confidence_threshold = optional_string(speculative.dspark_confidence_threshold); 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> { fn parse_optional_u64(name: &str, value: &str) -> Result<Option<u64>, String> {
let value = value.trim(); let value = value.trim();
if value.is_empty() { if value.is_empty() {
@@ -715,20 +696,6 @@ impl App {
self.preference_draft.default_permission_mode = mode; self.preference_draft.default_permission_mode = mode;
self.preference_error = None; 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) => { Message::PreferenceDsparkChanged(enabled) => {
self.preference_draft.dspark_enabled = self.preference_draft.dspark_enabled =
self.preference_draft.acceleration_model.supports_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_confidence_threshold.clear();
self.preference_draft.dspark_strict = false; self.preference_draft.dspark_strict = false;
self.preference_draft.dspark_exact_sampling = false; self.preference_draft.dspark_exact_sampling = false;
} else {
self.preference_draft.legacy_mtp_enabled = false;
} }
self.preference_error = None; self.preference_error = None;
} }
@@ -884,14 +849,6 @@ impl App {
self.preference_draft.warm_weights = value; self.preference_draft.warm_weights = value;
self.preference_error = None; 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) => { Message::PreferenceGlmMtpChanged(value) => {
self.preference_draft.glm_mtp = self.preference_draft.glm_mtp =
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value; self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
@@ -918,7 +875,6 @@ impl App {
.is_empty() .is_empty()
{ {
self.preference_draft.dspark_enabled = true; self.preference_draft.dspark_enabled = true;
self.preference_draft.legacy_mtp_enabled = false;
} }
self.preference_error = None; self.preference_error = None;
} }
@@ -927,7 +883,6 @@ impl App {
self.preference_draft.acceleration_model.supports_dspark() && value; self.preference_draft.acceleration_model.supports_dspark() && value;
if self.preference_draft.dspark_strict { if self.preference_draft.dspark_strict {
self.preference_draft.dspark_enabled = true; self.preference_draft.dspark_enabled = true;
self.preference_draft.legacy_mtp_enabled = false;
} }
self.preference_error = None; self.preference_error = None;
} }
@@ -936,7 +891,6 @@ impl App {
self.preference_draft.acceleration_model.supports_dspark() && value; self.preference_draft.acceleration_model.supports_dspark() && value;
if self.preference_draft.dspark_exact_sampling { if self.preference_draft.dspark_exact_sampling {
self.preference_draft.dspark_enabled = true; self.preference_draft.dspark_enabled = true;
self.preference_draft.legacy_mtp_enabled = false;
} }
self.preference_error = None; self.preference_error = None;
} }

View File

@@ -3,17 +3,6 @@ use iced::widget::column;
impl App { impl App {
pub(super) fn preferences_panel(&self) -> Element<'_, Message> { 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 let dspark_toggle: Option<fn(bool) -> Message> = self
.preference_draft .preference_draft
.acceleration_model .acceleration_model
@@ -131,7 +120,7 @@ impl App {
|engine| engine.artifacts.model.display().to_string(), |engine| engine.artifacts.model.display().to_string(),
), ),
engine engine
.and_then(|engine| engine.artifacts.mtp.as_ref()) .and_then(|engine| engine.artifacts.support.as_ref())
.map_or_else(String::new, |path| format!( .map_or_else(String::new, |path| format!(
" • support: {}", " • support: {}",
path.display() path.display()
@@ -602,18 +591,6 @@ impl App {
.spacing(12) .spacing(12)
.align_y(Alignment::Center), .align_y(Alignment::Center),
text("SPECULATIVE DECODING").size(11).color(muted_text()), 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( hint(
toggle(self.preference_draft.glm_mtp) toggle(self.preference_draft.glm_mtp)
.label("Enable integrated GLM MTP") .label("Enable integrated GLM MTP")
@@ -626,7 +603,6 @@ impl App {
.on_toggle_maybe(glm_mtp_timing_toggle), .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.", "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, dspark,
preference_input_row( preference_input_row(
"DSpark confidence threshold", "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.", "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() { 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 { } else if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
"GLM MTP is integrated; DSpark is unavailable for this model." "GLM MTP is integrated; DSpark is unavailable for this model."
} else { } else {
"No managed MTP support artifact is available for this model." "No speculative-decoding support is available for this model."
}) })
.size(12), .size(12),
text(acceleration_engine.map_or_else( text(acceleration_engine.map_or_else(
@@ -659,10 +635,7 @@ impl App {
|engine| { |engine| {
let settings = engine.speculative; let settings = engine.speculative;
format!( format!(
"Engine: MTP draft {} • margin {} • legacy MTP {} GLM MTP {} • timing {} • DSpark {} • confidence {}{} • target-only {} • exact sampling {}", "Engine: 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" },
if settings.glm_mtp { "on" } else { "off" }, if settings.glm_mtp { "on" } else { "off" },
if settings.glm_mtp_timing { "on" } else { "off" }, if settings.glm_mtp_timing { "on" } else { "off" },
if settings.dspark { "on" } else { "off" }, if settings.dspark { "on" } else { "off" },
@@ -705,7 +678,7 @@ impl App {
text_input("Automatic", &self.preference_draft.ssd_preload_experts) text_input("Automatic", &self.preference_draft.ssd_preload_experts)
.on_input(Message::PreferenceSsdPreloadChanged), .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), .size(12),
text(acceleration_engine.map_or_else( text(acceleration_engine.map_or_else(
|| "Effective SSD settings will appear after valid values are entered." || "Effective SSD settings will appear after valid values are entered."

View File

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

View File

@@ -300,6 +300,7 @@ impl Config {
let mut value: Value = serde_norway::from_str(&text) let mut value: Value = serde_norway::from_str(&text)
.map_err(|error| format!("Could not read {}: {error}", path.display()))?; .map_err(|error| format!("Could not read {}: {error}", path.display()))?;
drop_legacy_model_settings(&mut value); drop_legacy_model_settings(&mut value);
let migrated = migrate_deprecated_flash(&mut value);
let dspark_explicit = deepseek_0731_dspark_is_explicit(&value); let dspark_explicit = deepseek_0731_dspark_is_explicit(&value);
let mut config: Self = serde_norway::from_value(value) let mut config: Self = serde_norway::from_value(value)
.map_err(|error| format!("Could not read {}: {error}", path.display()))?; .map_err(|error| format!("Could not read {}: {error}", path.display()))?;
@@ -313,6 +314,9 @@ impl Config {
.dspark_enabled = true; .dspark_enabled = true;
} }
config.validate()?; config.validate()?;
if migrated {
config.save(path)?;
}
Ok(config) 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 /// 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. /// the user actually set. Mappings that end up empty disappear with their key.
fn without_defaults(value: Value, defaults: &Value) -> Option<Value> { fn without_defaults(value: Value, defaults: &Value) -> Option<Value> {
@@ -601,4 +644,37 @@ mod tests {
// A missing file is simply the defaults. // A missing file is simply the defaults.
assert_eq!(Config::load(&path).unwrap(), Config::default()); 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, original_context: u64,
} }
const FLASH: Shape = Shape { const DEEPSEEK_BASE: Shape = Shape {
model: ModelChoice::DeepSeekV4Flash, model: ModelChoice::DeepSeekV4Flash0731,
family: ModelFamily::DeepSeek, family: ModelFamily::DeepSeek,
layers: 43, layers: 43,
embd: 4096, embd: 4096,
@@ -152,10 +152,7 @@ const FLASH: Shape = Shape {
original_context: 65_536, original_context: 65_536,
}; };
const FLASH_0731: Shape = Shape { const FLASH_0731: Shape = Shape { ..DEEPSEEK_BASE };
model: ModelChoice::DeepSeekV4Flash0731,
..FLASH
};
const PRO: Shape = Shape { const PRO: Shape = Shape {
model: ModelChoice::DeepSeekV4Pro, model: ModelChoice::DeepSeekV4Pro,
@@ -168,7 +165,7 @@ const PRO: Shape = Shape {
ff_expert: 3072, ff_expert: 3072,
indexer_top_k: 1024, indexer_top_k: 1024,
expert_weight_scale: 2.5, expert_weight_scale: 2.5,
..FLASH ..DEEPSEEK_BASE
}; };
const GLM: Shape = Shape { const GLM: Shape = Shape {
@@ -236,7 +233,6 @@ impl Model {
pub(crate) fn open(settings: &EngineSettings) -> Result<Self, String> { pub(crate) fn open(settings: &EngineSettings) -> Result<Self, String> {
validate_engine_artifacts( validate_engine_artifacts(
settings.model, settings.model,
settings.artifacts.mtp.is_some() && !settings.speculative.dspark,
settings.speculative.dspark, settings.speculative.dspark,
&settings.artifacts, &settings.artifacts,
)?; )?;
@@ -244,7 +240,7 @@ impl Model {
if settings.execution.warm_weights { if settings.execution.warm_weights {
model.main.warm()?; model.main.warm()?;
} }
if let Some(path) = &settings.artifacts.mtp { if let Some(path) = &settings.artifacts.support {
let support = Gguf::open(path)?; let support = Gguf::open(path)?;
let kind = validate_support(&support, &model.shape)?; let kind = validate_support(&support, &model.shape)?;
if settings.execution.warm_weights { if settings.execution.warm_weights {
@@ -1958,13 +1954,12 @@ mod sampling_tests {
fn metal_executes_real_flash_token() { fn metal_executes_real_flash_token() {
configure_metal_sources().unwrap(); configure_metal_sources().unwrap();
let path = crate::model::engine_artifacts( let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
false,
false, false,
&crate::app::models_path(), &crate::app::models_path(),
) )
.model; .model;
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap(); let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
let tokens = model.render_prompt( let tokens = model.render_prompt(
"You are a helpful assistant", "You are a helpful assistant",
"Hello", "Hello",
@@ -2008,7 +2003,7 @@ mod sampling_tests {
.0; .0;
drop(executor); 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(); let mut restored = metal::Executor::open(model, 32_768, false, 0).unwrap();
assert!(restored.load_checkpoint(&checkpoint, &mut |_| {}).unwrap()); assert!(restored.load_checkpoint(&checkpoint, &mut |_| {}).unwrap());
assert_eq!(restored.position(), tokens.len() as u32); 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.tokens = tokens;
self.logits = logits; self.logits = logits;
self.checkpoint_tag = checkpoint_tag; 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 { if let Some(dspark) = &mut self.dspark {
dspark.capture_mask = 0; dspark.capture_mask = 0;
dspark.cache_start = 0; dspark.cache_start = 0;

View File

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

View File

@@ -288,7 +288,8 @@ mod tests {
std::process::id(), std::process::id(),
std::thread::current().name().unwrap_or("test") std::thread::current().name().unwrap_or("test")
)); ));
let mut profile = ExpertProfile::new(path.to_str(), ModelChoice::DeepSeekV4Flash, 1, 8, 2) let mut profile =
ExpertProfile::new(path.to_str(), ModelChoice::DeepSeekV4Flash0731, 1, 8, 2)
.unwrap() .unwrap()
.unwrap(); .unwrap();
profile profile

View File

@@ -8,7 +8,6 @@ pub(crate) fn validate_model_artifact(
if support { if support {
let model = Gguf::open(path)?; let model = Gguf::open(path)?;
let shape = match expected { let shape = match expected {
ModelChoice::DeepSeekV4Flash => FLASH,
ModelChoice::DeepSeekV4Flash0731 => FLASH_0731, ModelChoice::DeepSeekV4Flash0731 => FLASH_0731,
ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 => { ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 => {
return Err(format!("{expected} does not use an external support GGUF")); 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)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum SupportKind { pub(super) enum SupportKind {
LegacyMtp,
DSpark, 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> { pub(super) fn validate_support(model: &Gguf, shape: &Shape) -> Result<SupportKind, String> {
if model.tensors.contains_key("mtp.0.e_proj.weight") if model.metadata.contains_key("deepseek4.dspark.block_size")
&& 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")
|| model.metadata.contains_key("deepseek4.dspark_block_size") || model.metadata.contains_key("deepseek4.dspark_block_size")
|| model.metadata.contains_key("dspark.block_size") || model.metadata.contains_key("dspark.block_size")
{ {
validate_dspark(model, shape)?; validate_dspark(model, shape)?;
Ok(SupportKind::DSpark) Ok(SupportKind::DSpark)
} else { } 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> { pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape, String> {
let family = if model.bytes("general.architecture").ok() == Some(b"glm-dsa") { let family = if model.bytes("general.architecture").ok() == Some(b"glm-dsa") {
ModelFamily::Glm ModelFamily::Glm
@@ -232,7 +125,7 @@ pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape
ModelFamily::Glm => GLM, ModelFamily::Glm => GLM,
ModelFamily::DeepSeek => match (model.u32("deepseek4.block_count")?, expected) { ModelFamily::DeepSeek => match (model.u32("deepseek4.block_count")?, expected) {
(43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731, (43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731,
(43, _) => FLASH, (43, _) => FLASH_0731,
(61, _) => PRO, (61, _) => PRO,
(layers, _) => return Err(format!("unsupported DeepSeek layer count: {layers}")), (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> { pub(super) fn validate_dspark(model: &Gguf, shape: &Shape) -> Result<(), String> {
if !matches!( if !matches!(shape.model, ModelChoice::DeepSeekV4Flash0731) {
shape.model,
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731
) {
return Err("DSpark support is available only for DeepSeek V4 Flash".into()); return Err("DSpark support is available only for DeepSeek V4 Flash".into());
} }
let DsparkConfig { let DsparkConfig {
@@ -1004,18 +894,14 @@ fn float_eq(actual: f32, expected: f32) -> bool {
fn compression_ratio(shape: &Shape, layer: u32) -> u32 { fn compression_ratio(shape: &Shape, layer: u32) -> u32 {
match shape.model { match shape.model {
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0, ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0,
ModelChoice::DeepSeekV4Pro if layer < 2 => 128, ModelChoice::DeepSeekV4Pro if layer < 2 => 128,
ModelChoice::DeepSeekV4Flash ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro
| ModelChoice::DeepSeekV4Flash0731
| ModelChoice::DeepSeekV4Pro
if layer.is_multiple_of(2) => if layer.is_multiple_of(2) =>
{ {
4 4
} }
ModelChoice::DeepSeekV4Flash ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro => 128,
| ModelChoice::DeepSeekV4Flash0731
| ModelChoice::DeepSeekV4Pro => 128,
ModelChoice::Glm52 => 0, ModelChoice::Glm52 => 0,
} }
} }
@@ -1054,8 +940,7 @@ mod tests {
#[test] #[test]
fn installed_ds4_fixture_opens_and_renders_a_prompt() { fn installed_ds4_fixture_opens_and_renders_a_prompt() {
let path = crate::model::engine_artifacts( let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
false,
false, false,
&crate::app::models_path(), &crate::app::models_path(),
) )
@@ -1063,9 +948,9 @@ mod tests {
if !path.exists() { if !path.exists() {
return; return;
} }
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap(); let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
let summary = model.summary(); 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!(summary.vocabulary_size, 129_280);
assert_eq!( assert_eq!(
model.tokenize("Hello, world! 1234\nint café = 7;\n中文テスト"), model.tokenize("Hello, world! 1234\nint café = 7;\n中文テスト"),
@@ -1262,33 +1147,14 @@ mod tests {
#[test] #[test]
fn installed_dspark_fixture_passes_the_target_layout() { fn installed_dspark_fixture_passes_the_target_layout() {
let path = crate::model::engine_artifacts( let path = crate::model::engine_artifacts(
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
false,
true, true,
&crate::app::models_path(), &crate::app::models_path(),
) )
.mtp .support
.unwrap(); .unwrap();
if path.exists() { if path.exists() {
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash, true).unwrap(); validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash0731, 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
);
} }
} }
} }

View File

@@ -844,7 +844,6 @@ fn source(value: u8) -> WorkSource {
fn model_code(model: ModelChoice) -> u8 { fn model_code(model: ModelChoice) -> u8 {
match model { match model {
ModelChoice::DeepSeekV4Flash => 1,
ModelChoice::DeepSeekV4Pro => 2, ModelChoice::DeepSeekV4Pro => 2,
ModelChoice::Glm52 => 3, ModelChoice::Glm52 => 3,
ModelChoice::DeepSeekV4Flash0731 => 4, ModelChoice::DeepSeekV4Flash0731 => 4,
@@ -1027,6 +1026,12 @@ fn cache_usage(root: &Path) -> CacheUsage {
mod tests { mod tests {
use super::*; 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] #[test]
fn snapshots_track_runtime_and_server_counters() { fn snapshots_track_runtime_and_server_counters() {
let metrics = Metrics::new(Path::new("/path/that/does/not/exist")); 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::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub(crate) const MODEL_CHOICES: [ModelChoice; 4] = [ pub(crate) const MODEL_CHOICES: [ModelChoice; 3] = [
ModelChoice::DeepSeekV4Flash0731, ModelChoice::DeepSeekV4Flash0731,
ModelChoice::DeepSeekV4Flash,
ModelChoice::DeepSeekV4Pro, ModelChoice::DeepSeekV4Pro,
ModelChoice::Glm52, ModelChoice::Glm52,
]; ];
pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 7] = [ pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 4] = [
ManagedArtifactId::DeepSeekV4Flash0731, ManagedArtifactId::DeepSeekV4Flash0731,
ManagedArtifactId::DeepSeekV4Flash0731Dspark, ManagedArtifactId::DeepSeekV4Flash0731Dspark,
ManagedArtifactId::DeepSeekV4Flash,
ManagedArtifactId::DeepSeekV4FlashMtp,
ManagedArtifactId::DeepSeekV4FlashDspark,
ManagedArtifactId::DeepSeekV4Pro, ManagedArtifactId::DeepSeekV4Pro,
ManagedArtifactId::Glm52, ManagedArtifactId::Glm52,
]; ];
@@ -28,30 +24,6 @@ pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 7] = [
const DEEPSEEK_REPOSITORY: &str = "antirez/deepseek-v4-gguf"; const DEEPSEEK_REPOSITORY: &str = "antirez/deepseek-v4-gguf";
const GLM_REPOSITORY: &str = "antirez/glm-5.2-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 { const FLASH_0731: Artifact = Artifact {
label: "DeepSeek V4 Flash 0731 model", label: "DeepSeek V4 Flash 0731 model",
file_name: "DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix-0731.gguf", 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)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) enum ModelChoice { pub(crate) enum ModelChoice {
#[serde(rename = "deepseek-v4-flash")]
DeepSeekV4Flash,
#[default] #[default]
#[serde(rename = "deepseek-v4-flash-0731")] #[serde(rename = "deepseek-v4-flash-0731")]
DeepSeekV4Flash0731, DeepSeekV4Flash0731,
@@ -101,7 +71,6 @@ pub(crate) enum ModelChoice {
impl ModelChoice { impl ModelChoice {
pub(crate) fn id(self) -> &'static str { pub(crate) fn id(self) -> &'static str {
match self { match self {
Self::DeepSeekV4Flash => "deepseek-v4-flash",
Self::DeepSeekV4Flash0731 => "deepseek-v4-flash-0731", Self::DeepSeekV4Flash0731 => "deepseek-v4-flash-0731",
Self::DeepSeekV4Pro => "deepseek-v4-pro", Self::DeepSeekV4Pro => "deepseek-v4-pro",
Self::Glm52 => "glm-5.2", Self::Glm52 => "glm-5.2",
@@ -112,17 +81,12 @@ impl ModelChoice {
MODEL_CHOICES.into_iter().find(|model| model.id() == id) 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 { pub(crate) fn supports_dspark(self) -> bool {
matches!(self, Self::DeepSeekV4Flash | Self::DeepSeekV4Flash0731) self == Self::DeepSeekV4Flash0731
} }
fn main_artifact(self) -> &'static Artifact { fn main_artifact(self) -> &'static Artifact {
match self { match self {
Self::DeepSeekV4Flash => &FLASH,
Self::DeepSeekV4Flash0731 => &FLASH_0731, Self::DeepSeekV4Flash0731 => &FLASH_0731,
Self::DeepSeekV4Pro => &PRO, Self::DeepSeekV4Pro => &PRO,
Self::Glm52 => &GLM, Self::Glm52 => &GLM,
@@ -131,21 +95,15 @@ impl ModelChoice {
fn dspark_artifact(self) -> Option<&'static Artifact> { fn dspark_artifact(self) -> Option<&'static Artifact> {
match self { match self {
Self::DeepSeekV4Flash => Some(&FLASH_DSPARK),
Self::DeepSeekV4Flash0731 => Some(&FLASH_0731_DSPARK), Self::DeepSeekV4Flash0731 => Some(&FLASH_0731_DSPARK),
Self::DeepSeekV4Pro | Self::Glm52 => None, Self::DeepSeekV4Pro | Self::Glm52 => None,
} }
} }
#[cfg(test)] #[cfg(test)]
fn artifacts( fn artifacts(self, dspark_enabled: bool) -> impl Iterator<Item = &'static Artifact> {
self,
legacy_mtp_enabled: bool,
dspark_enabled: bool,
) -> impl Iterator<Item = &'static Artifact> {
[ [
Some(self.main_artifact()), Some(self.main_artifact()),
(self.supports_legacy_mtp() && legacy_mtp_enabled).then_some(&FLASH_MTP),
dspark_enabled.then(|| self.dspark_artifact()).flatten(), dspark_enabled.then(|| self.dspark_artifact()).flatten(),
] ]
.into_iter() .into_iter()
@@ -156,20 +114,17 @@ impl ModelChoice {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct EngineArtifacts { pub(crate) struct EngineArtifacts {
pub(crate) model: PathBuf, pub(crate) model: PathBuf,
pub(crate) mtp: Option<PathBuf>, pub(crate) support: Option<PathBuf>,
} }
pub(crate) fn engine_artifacts( pub(crate) fn engine_artifacts(
model: ModelChoice, model: ModelChoice,
legacy_mtp_enabled: bool,
dspark_enabled: bool, dspark_enabled: bool,
models_path: &Path, models_path: &Path,
) -> EngineArtifacts { ) -> EngineArtifacts {
EngineArtifacts { EngineArtifacts {
model: model.main_artifact().path(model, models_path), model: model.main_artifact().path(model, models_path),
mtp: if model.supports_legacy_mtp() && legacy_mtp_enabled { support: if dspark_enabled {
Some(FLASH_MTP.path(model, models_path))
} else if dspark_enabled {
model model
.dspark_artifact() .dspark_artifact()
.map(|artifact| artifact.path(model, models_path)) .map(|artifact| artifact.path(model, models_path))
@@ -181,30 +136,21 @@ pub(crate) fn engine_artifacts(
pub(crate) fn validate_engine_artifacts( pub(crate) fn validate_engine_artifacts(
model: ModelChoice, model: ModelChoice,
legacy_mtp_enabled: bool,
dspark_enabled: bool, dspark_enabled: bool,
artifacts: &EngineArtifacts, artifacts: &EngineArtifacts,
) -> Result<(), String> { ) -> 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() { if dspark_enabled && !model.supports_dspark() {
return Err(format!("DSpark is not compatible with {model}")); return Err(format!("DSpark is not compatible with {model}"));
} }
model model
.main_artifact() .main_artifact()
.validate_installed_path(&artifacts.model)?; .validate_installed_path(&artifacts.model)?;
let expected_support = if legacy_mtp_enabled { let expected_support = if dspark_enabled {
model.supports_legacy_mtp().then_some(&FLASH_MTP)
} else if dspark_enabled {
model.dspark_artifact() model.dspark_artifact()
} else { } else {
None None
}; };
match (expected_support, artifacts.mtp.as_deref()) { match (expected_support, artifacts.support.as_deref()) {
(Some(expected), Some(path)) => expected.validate_installed_path(path), (Some(expected), Some(path)) => expected.validate_installed_path(path),
(None, None) => Ok(()), (None, None) => Ok(()),
(Some(_), None) => Err(format!("{model} is missing its required support GGUF")), (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)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ManagedArtifactId { pub(crate) enum ManagedArtifactId {
DeepSeekV4Flash,
DeepSeekV4FlashMtp,
DeepSeekV4FlashDspark,
DeepSeekV4Flash0731, DeepSeekV4Flash0731,
DeepSeekV4Flash0731Dspark, DeepSeekV4Flash0731Dspark,
DeepSeekV4Pro, DeepSeekV4Pro,
@@ -229,9 +172,6 @@ pub(crate) enum ManagedArtifactId {
impl ManagedArtifactId { impl ManagedArtifactId {
pub(crate) fn model(self) -> ModelChoice { pub(crate) fn model(self) -> ModelChoice {
match self { match self {
Self::DeepSeekV4Flash | Self::DeepSeekV4FlashMtp | Self::DeepSeekV4FlashDspark => {
ModelChoice::DeepSeekV4Flash
}
Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Flash0731Dspark => { Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Flash0731Dspark => {
ModelChoice::DeepSeekV4Flash0731 ModelChoice::DeepSeekV4Flash0731
} }
@@ -242,9 +182,6 @@ impl ManagedArtifactId {
fn artifact(self) -> &'static Artifact { fn artifact(self) -> &'static Artifact {
match self { match self {
Self::DeepSeekV4Flash => &FLASH,
Self::DeepSeekV4FlashMtp => &FLASH_MTP,
Self::DeepSeekV4FlashDspark => &FLASH_DSPARK,
Self::DeepSeekV4Flash0731 => &FLASH_0731, Self::DeepSeekV4Flash0731 => &FLASH_0731,
Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK, Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK,
Self::DeepSeekV4Pro => &PRO, Self::DeepSeekV4Pro => &PRO,
@@ -340,7 +277,6 @@ pub(crate) enum DownloadOutcome {
impl fmt::Display for ModelChoice { impl fmt::Display for ModelChoice {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self { formatter.write_str(match self {
Self::DeepSeekV4Flash => "DeepSeek V4 Flash (deprecated preview)",
Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731", Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731",
Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813", Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813",
Self::Glm52 => "GLM 5.2", Self::Glm52 => "GLM 5.2",

View File

@@ -330,50 +330,33 @@ mod tests {
Some(ModelChoice::DeepSeekV4Flash0731) Some(ModelChoice::DeepSeekV4Flash0731)
); );
assert!(ModelChoice::from_id("unknown").is_none()); assert!(ModelChoice::from_id("unknown").is_none());
assert_eq!( assert_eq!(MODEL_CHOICES.len(), 3);
ModelChoice::DeepSeekV4Flash.main_artifact().size, assert_eq!(MANAGED_ARTIFACTS.len(), 4);
86_720_111_488
);
assert_eq!(ModelChoice::Glm52.main_artifact().size, 211_075_856_448); assert_eq!(ModelChoice::Glm52.main_artifact().size, 211_075_856_448);
assert_eq!( assert_eq!(
ModelChoice::DeepSeekV4Flash0731.main_artifact().size, ModelChoice::DeepSeekV4Flash0731.main_artifact().size,
86_720_111_488 86_720_111_488
); );
assert_eq!( assert_eq!(ModelChoice::DeepSeekV4Flash0731.artifacts(true).count(), 2);
ModelChoice::DeepSeekV4Flash.artifacts(true, true).count(), assert_eq!(ModelChoice::Glm52.artifacts(true).count(), 1);
3
);
assert_eq!(ModelChoice::Glm52.artifacts(true, true).count(), 1);
assert_eq!(
ModelChoice::DeepSeekV4Flash0731
.artifacts(true, true)
.count(),
2
);
let id = SystemTime::now() let id = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_nanos(); .as_nanos();
let models_path = std::env::temp_dir().join(format!("ds4-server-models-{id}")); 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!( assert_eq!(
engine.model.file_name(), engine.model.file_name(),
Some(std::ffi::OsStr::new(FLASH.file_name)) Some(std::ffi::OsStr::new(FLASH_0731.file_name))
); );
assert_eq!( assert_eq!(
engine.mtp.as_deref().and_then(Path::file_name), engine.support.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),
Some(std::ffi::OsStr::new(FLASH_0731_DSPARK.file_name)) Some(std::ffi::OsStr::new(FLASH_0731_DSPARK.file_name))
); );
assert!( assert!(
engine_artifacts(ModelChoice::DeepSeekV4Flash0731, true, false, &models_path) engine_artifacts(ModelChoice::DeepSeekV4Flash0731, false, &models_path)
.mtp .support
.is_none() .is_none()
); );
let empty = Artifact { let empty = Artifact {
@@ -384,18 +367,20 @@ mod tests {
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
support: None, 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::create_dir_all(partial.parent().unwrap()).unwrap();
fs::write(&partial, []).unwrap(); fs::write(&partial, []).unwrap();
download_artifact(ModelChoice::DeepSeekV4Flash, &empty, &models_path).unwrap(); download_artifact(ModelChoice::DeepSeekV4Flash0731, &empty, &models_path).unwrap();
assert!(empty.is_installed(ModelChoice::DeepSeekV4Flash, &models_path)); assert!(empty.is_installed(ModelChoice::DeepSeekV4Flash0731, &models_path));
assert!(!partial.exists()); assert!(!partial.exists());
assert_eq!( assert_eq!(
fs::read_to_string(empty.verification_path(ModelChoice::DeepSeekV4Flash, &models_path)) fs::read_to_string(
empty.verification_path(ModelChoice::DeepSeekV4Flash0731, &models_path)
)
.unwrap(), .unwrap(),
empty.sha256 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()); assert!(empty.validate_installed_path(&installed).is_ok());
let wrong_name = installed.with_file_name("wrong-checkpoint.gguf"); let wrong_name = installed.with_file_name("wrong-checkpoint.gguf");
fs::write(&wrong_name, []).unwrap(); fs::write(&wrong_name, []).unwrap();
@@ -405,10 +390,9 @@ mod tests {
validate_engine_artifacts( validate_engine_artifacts(
ModelChoice::DeepSeekV4Flash0731, ModelChoice::DeepSeekV4Flash0731,
true, true,
false,
&EngineArtifacts { &EngineArtifacts {
model: installed.clone(), model: installed.clone(),
mtp: None, support: None,
}, },
) )
.is_err() .is_err()
@@ -416,11 +400,10 @@ mod tests {
assert!( assert!(
validate_engine_artifacts( validate_engine_artifacts(
ModelChoice::DeepSeekV4Pro, ModelChoice::DeepSeekV4Pro,
false,
true, true,
&EngineArtifacts { &EngineArtifacts {
model: installed, model: installed,
mtp: None, support: None,
}, },
) )
.is_err() .is_err()
@@ -452,7 +435,7 @@ mod tests {
verify( verify(
&path, &path,
&artifact, &artifact,
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
&AtomicBool::new(false), &AtomicBool::new(false),
&verified_bytes, &verified_bytes,
) )
@@ -472,7 +455,7 @@ mod tests {
.unwrap() .unwrap()
.as_nanos() .as_nanos()
)); ));
let id = ManagedArtifactId::DeepSeekV4Flash; let id = ManagedArtifactId::DeepSeekV4Flash0731;
let partial = id.artifact().partial_path(id.model(), &models_path); let partial = id.artifact().partial_path(id.model(), &models_path);
fs::create_dir_all(partial.parent().unwrap()).unwrap(); fs::create_dir_all(partial.parent().unwrap()).unwrap();
fs::write(&partial, b"part").unwrap(); fs::write(&partial, b"part").unwrap();
@@ -611,14 +594,14 @@ mod tests {
sha256: "unused", sha256: "unused",
support: None, 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::create_dir_all(partial.parent().unwrap()).unwrap();
fs::write(&partial, b"part").unwrap(); fs::write(&partial, b"part").unwrap();
let cancel = AtomicBool::new(true); let cancel = AtomicBool::new(true);
assert_eq!( assert_eq!(
download_artifact_with_cancel( download_artifact_with_cancel(
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
&artifact, &artifact,
&directory, &directory,
&cancel, &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> { fn model_alias(id: &str) -> Option<ModelChoice> {
match id { match id {
"deepseek-chat" | "deepseek-reasoner" => Some(ModelChoice::DeepSeekV4Flash), "deepseek-chat" | "deepseek-reasoner" => Some(ModelChoice::DeepSeekV4Flash0731),
"glm-5.2-chat" "glm-5.2-chat"
| "glm-5.2-no-think" | "glm-5.2-no-think"
| "glm-5.2-nothink" | "glm-5.2-nothink"
@@ -929,7 +929,7 @@ mod tests {
let request = ResponseOptions { let request = ResponseOptions {
protocol: Protocol::Chat, protocol: Protocol::Chat,
reasoning_summary: false, reasoning_summary: false,
model_id: "deepseek-v4-flash".into(), model_id: "deepseek-v4-flash-0731".into(),
stream: true, stream: true,
include_usage: false, include_usage: false,
has_tools: true, has_tools: true,
@@ -1002,9 +1002,17 @@ mod tests {
#[test] #[test]
fn model_metadata_uses_the_requested_alias_and_default_token_limit() { 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( let model = model_json(
"deepseek-reasoner", "deepseek-reasoner",
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
32_768, 32_768,
50_000, 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 STANDARD_REASONING_MODES: [ReasoningMode; 2] = [ReasoningMode::High, ReasoningMode::Direct];
const THINK_MAX_MIN_CONTEXT: i32 = 393_216; const THINK_MAX_MIN_CONTEXT: i32 = 393_216;
const MAX_CPU_THREADS: u32 = 32; const MAX_CPU_THREADS: u32 = 32;
const MAX_MTP_DRAFT_TOKENS: i32 = 16;
pub(crate) const GIB: u64 = 1024 * 1024 * 1024; pub(crate) const GIB: u64 = 1024 * 1024 * 1024;
/// DS4 disk KV cache defaults, from `ds4_kvstore.h` and `--kv-disk-space-mb`. /// DS4 disk KV cache defaults, from `ds4_kvstore.h` and `--kv-disk-space-mb`.
pub(crate) const DEFAULT_KV_BUDGET_GIB: u64 = 4; 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_COLD_MAX_TOKENS: u32 = 30_000;
const DEFAULT_KV_CONTINUED_INTERVAL_TOKENS: u32 = 10_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)] #[serde(default, deny_unknown_fields)]
pub(crate) struct SpeculativePreferences { 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: bool,
pub(crate) glm_mtp_timing: bool, pub(crate) glm_mtp_timing: bool,
pub(crate) dspark_enabled: bool, pub(crate) dspark_enabled: bool,
@@ -33,28 +29,8 @@ pub(crate) struct SpeculativePreferences {
pub(crate) dspark_exact_sampling: bool, 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 { impl SpeculativePreferences {
pub(crate) fn validate(&self, model: ModelChoice) -> Result<(), String> { 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 { if self.glm_mtp_timing && !self.glm_mtp {
return Err("GLM MTP timing requires GLM MTP.".into()); return Err("GLM MTP timing requires GLM MTP.".into());
} }
@@ -64,12 +40,6 @@ impl SpeculativePreferences {
if self.dspark_enabled && !model.supports_dspark() { if self.dspark_enabled && !model.supports_dspark() {
return Err("DSpark is not available for the selected model.".into()); 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() if (self.dspark_confidence_threshold.is_some()
|| self.dspark_strict || self.dspark_strict
|| self.dspark_exact_sampling) || self.dspark_exact_sampling)
@@ -85,8 +55,6 @@ impl SpeculativePreferences {
pub(crate) fn engine_settings(&self) -> EngineSpeculativeSettings { pub(crate) fn engine_settings(&self) -> EngineSpeculativeSettings {
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: self.glm_mtp,
glm_mtp_timing: self.glm_mtp_timing, glm_mtp_timing: self.glm_mtp_timing,
dspark: self.dspark_enabled, dspark: self.dspark_enabled,
@@ -100,8 +68,6 @@ impl SpeculativePreferences {
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct EngineSpeculativeSettings { pub(crate) struct EngineSpeculativeSettings {
pub(crate) mtp_draft_tokens: i32,
pub(crate) mtp_margin: f32,
pub(crate) glm_mtp: bool, pub(crate) glm_mtp: bool,
pub(crate) glm_mtp_timing: bool, pub(crate) glm_mtp_timing: bool,
pub(crate) dspark: bool, pub(crate) dspark: bool,
@@ -415,12 +381,7 @@ impl RuntimePreferences {
self.validate(model)?; self.validate(model)?;
Ok(EngineSettings { Ok(EngineSettings {
model, model,
artifacts: model::engine_artifacts( artifacts: model::engine_artifacts(model, self.speculative.dspark_enabled, models_path),
model,
self.speculative.legacy_mtp_enabled,
self.speculative.dspark_enabled,
models_path,
),
context_tokens, context_tokens,
execution: self.execution.engine_settings(), execution: self.execution.engine_settings(),
speculative: self.speculative.engine_settings(), speculative: self.speculative.engine_settings(),
@@ -698,7 +659,7 @@ mod tests {
assert_eq!(defaults.system_prompt, DEFAULT_SYSTEM_PROMPT); assert_eq!(defaults.system_prompt, DEFAULT_SYSTEM_PROMPT);
assert!(defaults.system_prompt.contains("\n\nGuidelines:\n-")); assert!(defaults.system_prompt.contains("\n\nGuidelines:\n-"));
let cache = KvCachePreferences::default().settings(); let cache = KvCachePreferences::default().settings();
let deepseek = defaults.turn_settings(ModelChoice::DeepSeekV4Flash, cache); let deepseek = defaults.turn_settings(ModelChoice::DeepSeekV4Flash0731, cache);
assert_eq!( assert_eq!(
(deepseek.temperature, deepseek.top_p, deepseek.min_p), (deepseek.temperature, deepseek.top_p, deepseek.min_p),
(1.0, 1.0, 0.05) (1.0, 1.0, 0.05)
@@ -773,7 +734,7 @@ mod tests {
}; };
assert!( assert!(
unsupported_threads unsupported_threads
.validate(ModelChoice::DeepSeekV4Flash) .validate(ModelChoice::DeepSeekV4Flash0731)
.is_err() .is_err()
); );
@@ -782,7 +743,7 @@ mod tests {
prefill_chunk: Some(4096), prefill_chunk: Some(4096),
..ExecutionPreferences::default() ..ExecutionPreferences::default()
}; };
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash).is_ok()); assert!(tuned.validate(ModelChoice::DeepSeekV4Flash0731).is_ok());
assert!(tuned.validate(ModelChoice::Glm52).is_err()); assert!(tuned.validate(ModelChoice::Glm52).is_err());
} }
@@ -790,21 +751,18 @@ mod tests {
fn speculative_settings_match_acceleration_defaults_and_dependencies() { fn speculative_settings_match_acceleration_defaults_and_dependencies() {
let defaults = SpeculativePreferences::default(); let defaults = SpeculativePreferences::default();
let engine = defaults.engine_settings(); 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_eq!(engine.dspark_confidence_threshold, 0.8);
assert!(!engine.dspark_confidence_threshold_set); assert!(!engine.dspark_confidence_threshold_set);
let tuned = SpeculativePreferences { let tuned = SpeculativePreferences {
mtp_draft_tokens: 20,
dspark_enabled: true, dspark_enabled: true,
dspark_confidence_threshold: Some(0.7), dspark_confidence_threshold: Some(0.7),
dspark_strict: true, dspark_strict: true,
dspark_exact_sampling: true, dspark_exact_sampling: true,
..defaults ..defaults
}; };
assert!(tuned.validate(ModelChoice::DeepSeekV4Flash).is_ok()); assert!(tuned.validate(ModelChoice::DeepSeekV4Flash0731).is_ok());
assert!(tuned.validate(ModelChoice::Glm52).is_err()); assert!(tuned.validate(ModelChoice::Glm52).is_err());
assert_eq!(tuned.engine_settings().mtp_draft_tokens, 16);
let glm = SpeculativePreferences { let glm = SpeculativePreferences {
glm_mtp: true, glm_mtp: true,
@@ -814,21 +772,6 @@ mod tests {
assert!(glm.validate(ModelChoice::Glm52).is_ok()); assert!(glm.validate(ModelChoice::Glm52).is_ok());
assert!(glm.validate(ModelChoice::DeepSeekV4Pro).is_err()); 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!( assert!(
SpeculativePreferences { SpeculativePreferences {
dspark_exact_sampling: true, dspark_exact_sampling: true,
@@ -892,7 +835,7 @@ mod tests {
..RuntimePreferences::default() ..RuntimePreferences::default()
}; };
let effective = effective_settings( let effective = effective_settings(
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
&GenerationPreferences::default(), &GenerationPreferences::default(),
&runtime, &runtime,
Path::new("/models"), Path::new("/models"),
@@ -900,7 +843,7 @@ mod tests {
.unwrap(); .unwrap();
let engine = effective.engine; let engine = effective.engine;
assert_eq!(engine.context_tokens, 32_768); 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_eq!(engine.ssd.cache_bytes, 64 * GIB);
assert!(engine.ssd.full_layers_set); assert!(engine.ssd.full_layers_set);
assert_eq!(engine.ssd.full_layers, 0); assert_eq!(engine.ssd.full_layers, 0);
@@ -914,7 +857,7 @@ mod tests {
}, },
..runtime ..runtime
}; };
assert!(combined.validate(ModelChoice::DeepSeekV4Flash).is_ok()); assert!(combined.validate(ModelChoice::DeepSeekV4Flash0731).is_ok());
} }
#[test] #[test]
@@ -932,7 +875,7 @@ mod tests {
..RuntimePreferences::default() ..RuntimePreferences::default()
}; };
let effective = effective_settings( let effective = effective_settings(
ModelChoice::DeepSeekV4Flash, ModelChoice::DeepSeekV4Flash0731,
&generation, &generation,
&runtime, &runtime,
Path::new("/models"), Path::new("/models"),
@@ -942,9 +885,9 @@ mod tests {
assert_eq!(effective.engine.context_tokens, 65_536); assert_eq!(effective.engine.context_tokens, 65_536);
assert_eq!( assert_eq!(
effective.engine.artifacts.model.parent(), 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); assert_eq!(effective.turn.temperature, 0.25);
} }
@@ -967,9 +910,6 @@ mod tests {
"--kv-disk-space-mb", "--kv-disk-space-mb",
"--min-p", "--min-p",
"--model", "--model",
"--mtp",
"--mtp-draft",
"--mtp-margin",
"--nothink", "--nothink",
"--power", "--power",
"--prefill-chunk", "--prefill-chunk",