Remove deprecated Flash preview and legacy MTP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
20
src/agent.rs
20
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</|DSML|parameter></|DSML|invoke></|DSML|tool_calls>";
|
||||
let (visible, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, dsml).unwrap();
|
||||
let (visible, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, dsml).unwrap();
|
||||
assert_eq!(visible, "done");
|
||||
assert_eq!(calls[0].arguments["path"], "src/main.rs");
|
||||
assert!(
|
||||
parse_tool_calls(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
"<|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><|DSML|parameter name=\"whole\" string=\"false\">true</|DSML|parameter></|DSML|invoke></|DSML|tool_calls>";
|
||||
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, dsml).unwrap();
|
||||
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, dsml).unwrap();
|
||||
assert_eq!(calls[0].arguments["whole"], true);
|
||||
assert!(validate_tool_call(&calls[0]).is_ok());
|
||||
|
||||
let quoted = "<|DSML|tool_calls><|DSML|invoke name=\"read\"><|DSML|parameter name=\"path\" string=\"true\">README.md</|DSML|parameter><|DSML|parameter name=\"max_lines\" string=\"true\">20</|DSML|parameter></|DSML|invoke></|DSML|tool_calls>";
|
||||
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, quoted).unwrap();
|
||||
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, quoted).unwrap();
|
||||
assert!(
|
||||
validation_error(&calls[0])
|
||||
.render()
|
||||
@@ -4101,7 +4101,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn ralph_report_schemas_are_child_only_and_parse_for_both_models() {
|
||||
for model in [ModelChoice::DeepSeekV4Flash, ModelChoice::Glm52] {
|
||||
for model in [ModelChoice::DeepSeekV4Flash0731, ModelChoice::Glm52] {
|
||||
let parent = system_prompt(model, "", false);
|
||||
let child = ralph_system_prompt(model, "", false);
|
||||
assert!(parent.contains("\"name\":\"ralph\""));
|
||||
@@ -4118,7 +4118,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let dsml = "<|DSML|tool_calls><|DSML|invoke name=\"ralph_report\"><|DSML|parameter name=\"status\" string=\"true\">continue</|DSML|parameter><|DSML|parameter name=\"summary\" string=\"true\">Inspected the failing test.</|DSML|parameter><|DSML|parameter name=\"evidence\" string=\"true\">failure reproduced</|DSML|parameter><|DSML|parameter name=\"next_steps\" string=\"true\">Fix the shared parser.</|DSML|parameter><|DSML|parameter name=\"blocker\" string=\"true\"></|DSML|parameter></|DSML|invoke></|DSML|tool_calls>";
|
||||
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash, dsml).unwrap();
|
||||
let (_, calls) = parse_tool_calls(ModelChoice::DeepSeekV4Flash0731, dsml).unwrap();
|
||||
assert_eq!(
|
||||
ralph_report(&calls[0]).unwrap().status,
|
||||
RalphStatus::Continue
|
||||
@@ -4700,7 +4700,7 @@ mod tests {
|
||||
<|DSML|invoke name="read"><|DSML|parameter name="path" string="true">README.md</|DSML|parameter></|DSML|invoke>
|
||||
</|DSML|tool_calls>"#;
|
||||
let cards = stored_tool_cards(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
assistant,
|
||||
None,
|
||||
&[Some("Only reads the working directory.".into()), None],
|
||||
|
||||
@@ -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</|DSML|parameter></|DSML|invoke>
|
||||
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"),
|
||||
|
||||
@@ -2612,11 +2612,11 @@ mod tests {
|
||||
</|DSML|tool_calls>"#;
|
||||
let malformed = assistant(Some(call), "");
|
||||
assert!(has_misplaced_tool_call(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
&malformed
|
||||
));
|
||||
assert!(!has_misplaced_tool_call(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
&assistant(Some(call), "I was only discussing this call.")
|
||||
));
|
||||
assert!(is_empty_response(&assistant(Some(""), "")));
|
||||
|
||||
@@ -11,7 +11,6 @@ pub(super) struct PreferenceDraft {
|
||||
generation_profiles: BTreeMap<ModelChoice, BTreeMap<ReasoningMode, GenerationPreferences>>,
|
||||
model_profiles: BTreeMap<ModelChoice, ModelPreferences>,
|
||||
pub(super) default_permission_mode: PermissionMode,
|
||||
pub(super) legacy_mtp_enabled: bool,
|
||||
pub(super) dspark_enabled: bool,
|
||||
pub(super) idle_timeout_minutes: String,
|
||||
pub(super) a2ui_enabled: bool,
|
||||
@@ -39,8 +38,6 @@ pub(super) struct PreferenceDraft {
|
||||
pub(super) prefill_chunk: String,
|
||||
pub(super) quality: bool,
|
||||
pub(super) warm_weights: bool,
|
||||
pub(super) mtp_draft_tokens: String,
|
||||
pub(super) mtp_margin: String,
|
||||
pub(super) glm_mtp: bool,
|
||||
pub(super) glm_mtp_timing: bool,
|
||||
pub(super) dspark_confidence_threshold: String,
|
||||
@@ -78,7 +75,6 @@ impl PreferenceDraft {
|
||||
generation_profiles: config.generation_profiles.clone(),
|
||||
model_profiles: config.model_profiles.clone(),
|
||||
default_permission_mode: config.default_permission_mode,
|
||||
legacy_mtp_enabled: speculative.legacy_mtp_enabled,
|
||||
dspark_enabled: speculative.dspark_enabled,
|
||||
idle_timeout_minutes: config.idle_timeout_minutes.to_string(),
|
||||
a2ui_enabled: config.a2ui_enabled,
|
||||
@@ -106,8 +102,6 @@ impl PreferenceDraft {
|
||||
prefill_chunk: optional_string(execution.prefill_chunk),
|
||||
quality: execution.quality,
|
||||
warm_weights: execution.warm_weights,
|
||||
mtp_draft_tokens: speculative.mtp_draft_tokens.to_string(),
|
||||
mtp_margin: speculative.mtp_margin.to_string(),
|
||||
glm_mtp: speculative.glm_mtp,
|
||||
glm_mtp_timing: speculative.glm_mtp_timing,
|
||||
dspark_confidence_threshold: optional_string(speculative.dspark_confidence_threshold),
|
||||
@@ -217,9 +211,6 @@ impl PreferenceDraft {
|
||||
|
||||
pub(super) fn speculative(&self) -> Result<SpeculativePreferences, String> {
|
||||
Ok(SpeculativePreferences {
|
||||
mtp_draft_tokens: parse_positive_i32("MTP draft tokens", &self.mtp_draft_tokens)?,
|
||||
mtp_margin: parse_f32("MTP margin", &self.mtp_margin)?,
|
||||
legacy_mtp_enabled: self.legacy_mtp_enabled,
|
||||
glm_mtp: self.glm_mtp,
|
||||
glm_mtp_timing: self.glm_mtp_timing,
|
||||
dspark_enabled: self.dspark_enabled,
|
||||
@@ -262,10 +253,7 @@ impl PreferenceDraft {
|
||||
let speculative = profile.speculative;
|
||||
let ssd = profile.ssd;
|
||||
self.acceleration_model = model;
|
||||
self.legacy_mtp_enabled = speculative.legacy_mtp_enabled;
|
||||
self.dspark_enabled = speculative.dspark_enabled;
|
||||
self.mtp_draft_tokens = speculative.mtp_draft_tokens.to_string();
|
||||
self.mtp_margin = speculative.mtp_margin.to_string();
|
||||
self.glm_mtp = speculative.glm_mtp;
|
||||
self.glm_mtp_timing = speculative.glm_mtp_timing;
|
||||
self.dspark_confidence_threshold = optional_string(speculative.dspark_confidence_threshold);
|
||||
@@ -374,13 +362,6 @@ fn parse_optional_f32(name: &str, value: &str) -> Result<Option<f32>, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_f32(name: &str, value: &str) -> Result<f32, String> {
|
||||
value
|
||||
.trim()
|
||||
.parse()
|
||||
.map_err(|_| format!("{name} must be a number."))
|
||||
}
|
||||
|
||||
fn parse_optional_u64(name: &str, value: &str) -> Result<Option<u64>, String> {
|
||||
let value = value.trim();
|
||||
if value.is_empty() {
|
||||
@@ -715,20 +696,6 @@ impl App {
|
||||
self.preference_draft.default_permission_mode = mode;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceLegacyMtpChanged(enabled) => {
|
||||
self.preference_draft.legacy_mtp_enabled = self
|
||||
.preference_draft
|
||||
.acceleration_model
|
||||
.supports_legacy_mtp()
|
||||
&& enabled;
|
||||
if self.preference_draft.legacy_mtp_enabled {
|
||||
self.preference_draft.dspark_enabled = false;
|
||||
self.preference_draft.dspark_confidence_threshold.clear();
|
||||
self.preference_draft.dspark_strict = false;
|
||||
self.preference_draft.dspark_exact_sampling = false;
|
||||
}
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceDsparkChanged(enabled) => {
|
||||
self.preference_draft.dspark_enabled =
|
||||
self.preference_draft.acceleration_model.supports_dspark() && enabled;
|
||||
@@ -736,8 +703,6 @@ impl App {
|
||||
self.preference_draft.dspark_confidence_threshold.clear();
|
||||
self.preference_draft.dspark_strict = false;
|
||||
self.preference_draft.dspark_exact_sampling = false;
|
||||
} else {
|
||||
self.preference_draft.legacy_mtp_enabled = false;
|
||||
}
|
||||
self.preference_error = None;
|
||||
}
|
||||
@@ -884,14 +849,6 @@ impl App {
|
||||
self.preference_draft.warm_weights = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceMtpDraftChanged(value) => {
|
||||
self.preference_draft.mtp_draft_tokens = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceMtpMarginChanged(value) => {
|
||||
self.preference_draft.mtp_margin = value;
|
||||
self.preference_error = None;
|
||||
}
|
||||
Message::PreferenceGlmMtpChanged(value) => {
|
||||
self.preference_draft.glm_mtp =
|
||||
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
|
||||
@@ -918,7 +875,6 @@ impl App {
|
||||
.is_empty()
|
||||
{
|
||||
self.preference_draft.dspark_enabled = true;
|
||||
self.preference_draft.legacy_mtp_enabled = false;
|
||||
}
|
||||
self.preference_error = None;
|
||||
}
|
||||
@@ -927,7 +883,6 @@ impl App {
|
||||
self.preference_draft.acceleration_model.supports_dspark() && value;
|
||||
if self.preference_draft.dspark_strict {
|
||||
self.preference_draft.dspark_enabled = true;
|
||||
self.preference_draft.legacy_mtp_enabled = false;
|
||||
}
|
||||
self.preference_error = None;
|
||||
}
|
||||
@@ -936,7 +891,6 @@ impl App {
|
||||
self.preference_draft.acceleration_model.supports_dspark() && value;
|
||||
if self.preference_draft.dspark_exact_sampling {
|
||||
self.preference_draft.dspark_enabled = true;
|
||||
self.preference_draft.legacy_mtp_enabled = false;
|
||||
}
|
||||
self.preference_error = None;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,6 @@ use iced::widget::column;
|
||||
|
||||
impl App {
|
||||
pub(super) fn preferences_panel(&self) -> Element<'_, Message> {
|
||||
let legacy_mtp_toggle: Option<fn(bool) -> Message> = self
|
||||
.preference_draft
|
||||
.acceleration_model
|
||||
.supports_legacy_mtp()
|
||||
.then_some(Message::PreferenceLegacyMtpChanged);
|
||||
let legacy_mtp = hint(
|
||||
toggle(self.preference_draft.legacy_mtp_enabled)
|
||||
.label("Enable legacy MTP for this model")
|
||||
.on_toggle_maybe(legacy_mtp_toggle),
|
||||
"Uses the managed one-stage MTP support GGUF. The target model verifies every drafted token; it is mutually exclusive with DSpark.",
|
||||
);
|
||||
let dspark_toggle: Option<fn(bool) -> Message> = self
|
||||
.preference_draft
|
||||
.acceleration_model
|
||||
@@ -131,7 +120,7 @@ impl App {
|
||||
|engine| engine.artifacts.model.display().to_string(),
|
||||
),
|
||||
engine
|
||||
.and_then(|engine| engine.artifacts.mtp.as_ref())
|
||||
.and_then(|engine| engine.artifacts.support.as_ref())
|
||||
.map_or_else(String::new, |path| format!(
|
||||
" • support: {}",
|
||||
path.display()
|
||||
@@ -602,18 +591,6 @@ impl App {
|
||||
.spacing(12)
|
||||
.align_y(Alignment::Center),
|
||||
text("SPECULATIVE DECODING").size(11).color(muted_text()),
|
||||
preference_input_row(
|
||||
"MTP draft tokens",
|
||||
"How many tokens the multi-token-prediction head guesses ahead for the main model to check in a single pass. More drafting pays off on predictable text and is wasted work on surprising text; the engine caps it at 16.",
|
||||
text_input("1", &self.preference_draft.mtp_draft_tokens)
|
||||
.on_input(Message::PreferenceMtpDraftChanged),
|
||||
),
|
||||
preference_input_row(
|
||||
"MTP verifier margin",
|
||||
"How much more likely the main model must find a drafted token before accepting it. A high margin accepts few drafts and stays close to plain decoding; a low one accepts more and rolls back more often.",
|
||||
text_input("3", &self.preference_draft.mtp_margin)
|
||||
.on_input(Message::PreferenceMtpMarginChanged),
|
||||
),
|
||||
hint(
|
||||
toggle(self.preference_draft.glm_mtp)
|
||||
.label("Enable integrated GLM MTP")
|
||||
@@ -626,7 +603,6 @@ impl App {
|
||||
.on_toggle_maybe(glm_mtp_timing_toggle),
|
||||
"Records per-stage timings of the speculative path to the log, to show where the acceleration actually goes. A diagnostic aid that costs a little throughput.",
|
||||
),
|
||||
legacy_mtp,
|
||||
dspark,
|
||||
preference_input_row(
|
||||
"DSpark confidence threshold",
|
||||
@@ -646,11 +622,11 @@ impl App {
|
||||
"For non-zero temperatures, applies DS4's exact acceptance and corrected rejection sampling. Off uses the faster opportunistic mode: sample a boundary token, then accept DSpark tokens only while they match the target's greedy path.",
|
||||
),
|
||||
text(if self.preference_draft.acceleration_model.supports_dspark() {
|
||||
"Legacy MTP and DSpark use separate managed support artifacts; entering a DSpark threshold or enabling strict mode selects DSpark."
|
||||
"DeepSeek V4 Flash 0731 uses its managed DSpark support artifact."
|
||||
} else if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
|
||||
"GLM MTP is integrated; DSpark is unavailable for this model."
|
||||
} else {
|
||||
"No managed MTP support artifact is available for this model."
|
||||
"No speculative-decoding support is available for this model."
|
||||
})
|
||||
.size(12),
|
||||
text(acceleration_engine.map_or_else(
|
||||
@@ -659,10 +635,7 @@ impl App {
|
||||
|engine| {
|
||||
let settings = engine.speculative;
|
||||
format!(
|
||||
"Engine: MTP draft {} • margin {} • legacy MTP {} • GLM MTP {} • timing {} • DSpark {} • confidence {}{} • target-only {} • exact sampling {}",
|
||||
settings.mtp_draft_tokens,
|
||||
settings.mtp_margin,
|
||||
if self.preference_draft.legacy_mtp_enabled { "on" } else { "off" },
|
||||
"Engine: GLM MTP {} • timing {} • DSpark {} • confidence {}{} • target-only {} • exact sampling {}",
|
||||
if settings.glm_mtp { "on" } else { "off" },
|
||||
if settings.glm_mtp_timing { "on" } else { "off" },
|
||||
if settings.dspark { "on" } else { "off" },
|
||||
@@ -705,7 +678,7 @@ impl App {
|
||||
text_input("Automatic", &self.preference_draft.ssd_preload_experts)
|
||||
.on_input(Message::PreferenceSsdPreloadChanged),
|
||||
),
|
||||
text("A blank full-layer value is automatic; an explicit 0 disables fully resident GLM layers. Flash legacy MTP and DSpark support weights remain resident when target experts stream.")
|
||||
text("A blank full-layer value is automatic; an explicit 0 disables fully resident GLM layers. DSpark support weights remain resident when target experts stream.")
|
||||
.size(12),
|
||||
text(acceleration_engine.map_or_else(
|
||||
|| "Effective SSD settings will appear after valid values are entered."
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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<Value> {
|
||||
@@ -601,4 +644,37 @@ mod tests {
|
||||
// A missing file is simply the defaults.
|
||||
assert_eq!(Config::load(&path).unwrap(), Config::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecated_flash_config_migrates_and_persists_only_0731() {
|
||||
let directory =
|
||||
std::env::temp_dir().join(format!("ds4-config-preview-{}", std::process::id()));
|
||||
let path = directory.join("config.yaml");
|
||||
fs::create_dir_all(&directory).unwrap();
|
||||
fs::write(
|
||||
&path,
|
||||
"model: deepseek-v4-flash\ngeneration_profiles:\n deepseek-v4-flash:\n none:\n max_generated_tokens: 123\nmodel_profiles:\n deepseek-v4-flash:\n speculative:\n legacy_mtp_enabled: true\n mtp_draft_tokens: 4\n mtp_margin: 2\n dspark_enabled: false\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = Config::load(&path).unwrap();
|
||||
assert_eq!(config.model, ModelChoice::DeepSeekV4Flash0731);
|
||||
assert_eq!(
|
||||
config
|
||||
.generation_profiles
|
||||
.get(&ModelChoice::DeepSeekV4Flash0731)
|
||||
.unwrap()
|
||||
.get(&ReasoningMode::Direct)
|
||||
.unwrap()
|
||||
.max_generated_tokens,
|
||||
123
|
||||
);
|
||||
assert!(!config.runtime_for(config.model).speculative.dspark_enabled);
|
||||
let persisted = fs::read_to_string(&path).unwrap();
|
||||
assert!(!persisted.contains("deepseek-v4-flash:"));
|
||||
assert!(!persisted.contains("legacy_mtp"));
|
||||
assert!(!persisted.contains("mtp_draft_tokens"));
|
||||
assert!(persisted.contains("deepseek-v4-flash-0731"));
|
||||
fs::remove_dir_all(&directory).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ struct Shape {
|
||||
original_context: u64,
|
||||
}
|
||||
|
||||
const FLASH: Shape = Shape {
|
||||
model: ModelChoice::DeepSeekV4Flash,
|
||||
const DEEPSEEK_BASE: Shape = Shape {
|
||||
model: ModelChoice::DeepSeekV4Flash0731,
|
||||
family: ModelFamily::DeepSeek,
|
||||
layers: 43,
|
||||
embd: 4096,
|
||||
@@ -152,10 +152,7 @@ const FLASH: Shape = Shape {
|
||||
original_context: 65_536,
|
||||
};
|
||||
|
||||
const FLASH_0731: Shape = Shape {
|
||||
model: ModelChoice::DeepSeekV4Flash0731,
|
||||
..FLASH
|
||||
};
|
||||
const FLASH_0731: Shape = Shape { ..DEEPSEEK_BASE };
|
||||
|
||||
const PRO: Shape = Shape {
|
||||
model: ModelChoice::DeepSeekV4Pro,
|
||||
@@ -168,7 +165,7 @@ const PRO: Shape = Shape {
|
||||
ff_expert: 3072,
|
||||
indexer_top_k: 1024,
|
||||
expert_weight_scale: 2.5,
|
||||
..FLASH
|
||||
..DEEPSEEK_BASE
|
||||
};
|
||||
|
||||
const GLM: Shape = Shape {
|
||||
@@ -236,7 +233,6 @@ impl Model {
|
||||
pub(crate) fn open(settings: &EngineSettings) -> Result<Self, String> {
|
||||
validate_engine_artifacts(
|
||||
settings.model,
|
||||
settings.artifacts.mtp.is_some() && !settings.speculative.dspark,
|
||||
settings.speculative.dspark,
|
||||
&settings.artifacts,
|
||||
)?;
|
||||
@@ -244,7 +240,7 @@ impl Model {
|
||||
if settings.execution.warm_weights {
|
||||
model.main.warm()?;
|
||||
}
|
||||
if let Some(path) = &settings.artifacts.mtp {
|
||||
if let Some(path) = &settings.artifacts.support {
|
||||
let support = Gguf::open(path)?;
|
||||
let kind = validate_support(&support, &model.shape)?;
|
||||
if settings.execution.warm_weights {
|
||||
@@ -1958,13 +1954,12 @@ mod sampling_tests {
|
||||
fn metal_executes_real_flash_token() {
|
||||
configure_metal_sources().unwrap();
|
||||
let path = crate::model::engine_artifacts(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
false,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
false,
|
||||
&crate::app::models_path(),
|
||||
)
|
||||
.model;
|
||||
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
|
||||
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
|
||||
let tokens = model.render_prompt(
|
||||
"You are a helpful assistant",
|
||||
"Hello",
|
||||
@@ -2008,7 +2003,7 @@ mod sampling_tests {
|
||||
.0;
|
||||
drop(executor);
|
||||
|
||||
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
|
||||
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
|
||||
let mut restored = metal::Executor::open(model, 32_768, false, 0).unwrap();
|
||||
assert!(restored.load_checkpoint(&checkpoint, &mut |_| {}).unwrap());
|
||||
assert_eq!(restored.position(), tokens.len() as u32);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -8,7 +8,6 @@ pub(crate) fn validate_model_artifact(
|
||||
if support {
|
||||
let model = Gguf::open(path)?;
|
||||
let shape = match expected {
|
||||
ModelChoice::DeepSeekV4Flash => FLASH,
|
||||
ModelChoice::DeepSeekV4Flash0731 => FLASH_0731,
|
||||
ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 => {
|
||||
return Err(format!("{expected} does not use an external support GGUF"));
|
||||
@@ -38,7 +37,6 @@ pub(crate) fn validate_model_artifact(
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(super) enum SupportKind {
|
||||
LegacyMtp,
|
||||
DSpark,
|
||||
}
|
||||
|
||||
@@ -106,122 +104,17 @@ pub(super) fn dspark_config(model: &Gguf) -> Result<DsparkConfig, String> {
|
||||
}
|
||||
|
||||
pub(super) fn validate_support(model: &Gguf, shape: &Shape) -> Result<SupportKind, String> {
|
||||
if model.tensors.contains_key("mtp.0.e_proj.weight")
|
||||
&& model.tensors.contains_key("mtp.0.h_proj.weight")
|
||||
&& model.tensors.contains_key("mtp.0.hc_head_base.weight")
|
||||
{
|
||||
validate_legacy_mtp(model, shape)?;
|
||||
Ok(SupportKind::LegacyMtp)
|
||||
} else if model.metadata.contains_key("deepseek4.dspark.block_size")
|
||||
if model.metadata.contains_key("deepseek4.dspark.block_size")
|
||||
|| model.metadata.contains_key("deepseek4.dspark_block_size")
|
||||
|| model.metadata.contains_key("dspark.block_size")
|
||||
{
|
||||
validate_dspark(model, shape)?;
|
||||
Ok(SupportKind::DSpark)
|
||||
} else {
|
||||
Err("support GGUF is neither legacy MTP nor DSpark".into())
|
||||
Err("support GGUF is not a DSpark artifact".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_legacy_mtp(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
if shape.model != ModelChoice::DeepSeekV4Flash {
|
||||
return Err("legacy MTP support is available only for DeepSeek V4 Flash".into());
|
||||
}
|
||||
let prefix = "mtp.0";
|
||||
let hc_dim = shape.embd * shape.hc;
|
||||
let hc_mix = 2 * shape.hc + shape.hc * shape.hc;
|
||||
let q_dim = shape.heads * shape.head_dim;
|
||||
let output_low = shape.out_groups * shape.lora_o;
|
||||
for (suffix, types, dims) in [
|
||||
("hc_head_base.weight", &[F32][..], vec![shape.hc]),
|
||||
("hc_head_fn.weight", PLAIN, vec![hc_dim, shape.hc]),
|
||||
("hc_head_scale.weight", &[F32][..], vec![1]),
|
||||
("e_proj.weight", &[Q8_0][..], vec![shape.embd, shape.embd]),
|
||||
("h_proj.weight", &[Q8_0][..], vec![shape.embd, shape.embd]),
|
||||
("enorm.weight", &[F32][..], vec![shape.embd]),
|
||||
("hnorm.weight", &[F32][..], vec![shape.embd]),
|
||||
("norm.weight", &[F32][..], vec![shape.embd]),
|
||||
("hc_attn_fn.weight", PLAIN, vec![hc_dim, hc_mix]),
|
||||
("hc_attn_scale.weight", &[F32][..], vec![3]),
|
||||
("hc_attn_base.weight", &[F32][..], vec![hc_mix]),
|
||||
("attn_norm.weight", &[F32][..], vec![shape.embd]),
|
||||
(
|
||||
"attn_q_a.weight",
|
||||
&[Q8_0][..],
|
||||
vec![shape.embd, shape.lora_q],
|
||||
),
|
||||
("attn_q_a_norm.weight", &[F32][..], vec![shape.lora_q]),
|
||||
("attn_q_b.weight", &[Q8_0][..], vec![shape.lora_q, q_dim]),
|
||||
(
|
||||
"attn_kv.weight",
|
||||
&[Q8_0][..],
|
||||
vec![shape.embd, shape.head_dim],
|
||||
),
|
||||
("attn_kv_a_norm.weight", &[F32][..], vec![shape.head_dim]),
|
||||
("attn_sinks.weight", &[F32][..], vec![shape.heads]),
|
||||
(
|
||||
"attn_output_a.weight",
|
||||
&[Q8_0][..],
|
||||
vec![
|
||||
shape.head_dim * (shape.heads / shape.out_groups),
|
||||
output_low,
|
||||
],
|
||||
),
|
||||
(
|
||||
"attn_output_b.weight",
|
||||
&[Q8_0][..],
|
||||
vec![output_low, shape.embd],
|
||||
),
|
||||
("hc_ffn_fn.weight", PLAIN, vec![hc_dim, hc_mix]),
|
||||
("hc_ffn_scale.weight", &[F32][..], vec![3]),
|
||||
("hc_ffn_base.weight", &[F32][..], vec![hc_mix]),
|
||||
("ffn_norm.weight", &[F32][..], vec![shape.embd]),
|
||||
(
|
||||
"ffn_gate_inp.weight",
|
||||
PLAIN,
|
||||
vec![shape.embd, shape.experts],
|
||||
),
|
||||
("exp_probs_b.bias", &[F32][..], vec![shape.experts]),
|
||||
(
|
||||
"ffn_gate_exps.weight",
|
||||
ROUTED,
|
||||
vec![shape.embd, shape.ff_expert, shape.experts],
|
||||
),
|
||||
(
|
||||
"ffn_up_exps.weight",
|
||||
ROUTED,
|
||||
vec![shape.embd, shape.ff_expert, shape.experts],
|
||||
),
|
||||
(
|
||||
"ffn_down_exps.weight",
|
||||
ROUTED,
|
||||
vec![shape.ff_expert, shape.embd, shape.experts],
|
||||
),
|
||||
(
|
||||
"ffn_gate_shexp.weight",
|
||||
&[Q8_0][..],
|
||||
vec![shape.embd, shape.ff_expert],
|
||||
),
|
||||
(
|
||||
"ffn_up_shexp.weight",
|
||||
&[Q8_0][..],
|
||||
vec![shape.embd, shape.ff_expert],
|
||||
),
|
||||
(
|
||||
"ffn_down_shexp.weight",
|
||||
&[Q8_0][..],
|
||||
vec![shape.ff_expert, shape.embd],
|
||||
),
|
||||
] {
|
||||
expect(model, &format!("{prefix}.{suffix}"), types, &dims)?;
|
||||
}
|
||||
same_type(
|
||||
model,
|
||||
"mtp.0.ffn_gate_exps.weight",
|
||||
"mtp.0.ffn_up_exps.weight",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape, String> {
|
||||
let family = if model.bytes("general.architecture").ok() == Some(b"glm-dsa") {
|
||||
ModelFamily::Glm
|
||||
@@ -232,7 +125,7 @@ pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape
|
||||
ModelFamily::Glm => GLM,
|
||||
ModelFamily::DeepSeek => match (model.u32("deepseek4.block_count")?, expected) {
|
||||
(43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731,
|
||||
(43, _) => FLASH,
|
||||
(43, _) => FLASH_0731,
|
||||
(61, _) => PRO,
|
||||
(layers, _) => return Err(format!("unsupported DeepSeek layer count: {layers}")),
|
||||
},
|
||||
@@ -766,10 +659,7 @@ fn validate_glm_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
}
|
||||
|
||||
pub(super) fn validate_dspark(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
if !matches!(
|
||||
shape.model,
|
||||
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731
|
||||
) {
|
||||
if !matches!(shape.model, ModelChoice::DeepSeekV4Flash0731) {
|
||||
return Err("DSpark support is available only for DeepSeek V4 Flash".into());
|
||||
}
|
||||
let DsparkConfig {
|
||||
@@ -1004,18 +894,14 @@ fn float_eq(actual: f32, expected: f32) -> bool {
|
||||
|
||||
fn compression_ratio(shape: &Shape, layer: u32) -> u32 {
|
||||
match shape.model {
|
||||
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0,
|
||||
ModelChoice::DeepSeekV4Flash0731 if layer < 2 => 0,
|
||||
ModelChoice::DeepSeekV4Pro if layer < 2 => 128,
|
||||
ModelChoice::DeepSeekV4Flash
|
||||
| ModelChoice::DeepSeekV4Flash0731
|
||||
| ModelChoice::DeepSeekV4Pro
|
||||
ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro
|
||||
if layer.is_multiple_of(2) =>
|
||||
{
|
||||
4
|
||||
}
|
||||
ModelChoice::DeepSeekV4Flash
|
||||
| ModelChoice::DeepSeekV4Flash0731
|
||||
| ModelChoice::DeepSeekV4Pro => 128,
|
||||
ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro => 128,
|
||||
ModelChoice::Glm52 => 0,
|
||||
}
|
||||
}
|
||||
@@ -1054,8 +940,7 @@ mod tests {
|
||||
#[test]
|
||||
fn installed_ds4_fixture_opens_and_renders_a_prompt() {
|
||||
let path = crate::model::engine_artifacts(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
false,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
false,
|
||||
&crate::app::models_path(),
|
||||
)
|
||||
@@ -1063,9 +948,9 @@ mod tests {
|
||||
if !path.exists() {
|
||||
return;
|
||||
}
|
||||
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash).unwrap();
|
||||
let model = Model::open_main(&path, ModelChoice::DeepSeekV4Flash0731).unwrap();
|
||||
let summary = model.summary();
|
||||
assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash);
|
||||
assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash0731);
|
||||
assert_eq!(summary.vocabulary_size, 129_280);
|
||||
assert_eq!(
|
||||
model.tokenize("Hello, world! 1234\nint café = 7;\n中文テスト"),
|
||||
@@ -1262,33 +1147,14 @@ mod tests {
|
||||
#[test]
|
||||
fn installed_dspark_fixture_passes_the_target_layout() {
|
||||
let path = crate::model::engine_artifacts(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
false,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
true,
|
||||
&crate::app::models_path(),
|
||||
)
|
||||
.mtp
|
||||
.support
|
||||
.unwrap();
|
||||
if path.exists() {
|
||||
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash, true).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installed_legacy_mtp_fixture_passes_the_target_layout() {
|
||||
let path = crate::model::engine_artifacts(
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
true,
|
||||
false,
|
||||
&crate::app::models_path(),
|
||||
)
|
||||
.mtp
|
||||
.unwrap();
|
||||
if path.exists() {
|
||||
assert_eq!(
|
||||
validate_support(&Gguf::open(&path).unwrap(), &FLASH).unwrap(),
|
||||
SupportKind::LegacyMtp
|
||||
);
|
||||
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash0731, true).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
80
src/model.rs
80
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<Item = &'static Artifact> {
|
||||
fn artifacts(self, dspark_enabled: bool) -> impl Iterator<Item = &'static Artifact> {
|
||||
[
|
||||
Some(self.main_artifact()),
|
||||
(self.supports_legacy_mtp() && legacy_mtp_enabled).then_some(&FLASH_MTP),
|
||||
dspark_enabled.then(|| self.dspark_artifact()).flatten(),
|
||||
]
|
||||
.into_iter()
|
||||
@@ -156,20 +114,17 @@ impl ModelChoice {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct EngineArtifacts {
|
||||
pub(crate) model: PathBuf,
|
||||
pub(crate) mtp: Option<PathBuf>,
|
||||
pub(crate) support: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub(crate) fn engine_artifacts(
|
||||
model: ModelChoice,
|
||||
legacy_mtp_enabled: bool,
|
||||
dspark_enabled: bool,
|
||||
models_path: &Path,
|
||||
) -> EngineArtifacts {
|
||||
EngineArtifacts {
|
||||
model: model.main_artifact().path(model, models_path),
|
||||
mtp: if model.supports_legacy_mtp() && legacy_mtp_enabled {
|
||||
Some(FLASH_MTP.path(model, models_path))
|
||||
} else if dspark_enabled {
|
||||
support: if dspark_enabled {
|
||||
model
|
||||
.dspark_artifact()
|
||||
.map(|artifact| artifact.path(model, models_path))
|
||||
@@ -181,30 +136,21 @@ pub(crate) fn engine_artifacts(
|
||||
|
||||
pub(crate) fn validate_engine_artifacts(
|
||||
model: ModelChoice,
|
||||
legacy_mtp_enabled: bool,
|
||||
dspark_enabled: bool,
|
||||
artifacts: &EngineArtifacts,
|
||||
) -> Result<(), String> {
|
||||
if legacy_mtp_enabled && dspark_enabled {
|
||||
return Err("Legacy MTP and DSpark cannot be enabled together".into());
|
||||
}
|
||||
if legacy_mtp_enabled && !model.supports_legacy_mtp() {
|
||||
return Err(format!("Legacy MTP is not compatible with {model}"));
|
||||
}
|
||||
if dspark_enabled && !model.supports_dspark() {
|
||||
return Err(format!("DSpark is not compatible with {model}"));
|
||||
}
|
||||
model
|
||||
.main_artifact()
|
||||
.validate_installed_path(&artifacts.model)?;
|
||||
let expected_support = if legacy_mtp_enabled {
|
||||
model.supports_legacy_mtp().then_some(&FLASH_MTP)
|
||||
} else if dspark_enabled {
|
||||
let expected_support = if dspark_enabled {
|
||||
model.dspark_artifact()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match (expected_support, artifacts.mtp.as_deref()) {
|
||||
match (expected_support, artifacts.support.as_deref()) {
|
||||
(Some(expected), Some(path)) => expected.validate_installed_path(path),
|
||||
(None, None) => Ok(()),
|
||||
(Some(_), None) => Err(format!("{model} is missing its required support GGUF")),
|
||||
@@ -217,9 +163,6 @@ pub(crate) fn validate_engine_artifacts(
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum ManagedArtifactId {
|
||||
DeepSeekV4Flash,
|
||||
DeepSeekV4FlashMtp,
|
||||
DeepSeekV4FlashDspark,
|
||||
DeepSeekV4Flash0731,
|
||||
DeepSeekV4Flash0731Dspark,
|
||||
DeepSeekV4Pro,
|
||||
@@ -229,9 +172,6 @@ pub(crate) enum ManagedArtifactId {
|
||||
impl ManagedArtifactId {
|
||||
pub(crate) fn model(self) -> ModelChoice {
|
||||
match self {
|
||||
Self::DeepSeekV4Flash | Self::DeepSeekV4FlashMtp | Self::DeepSeekV4FlashDspark => {
|
||||
ModelChoice::DeepSeekV4Flash
|
||||
}
|
||||
Self::DeepSeekV4Flash0731 | Self::DeepSeekV4Flash0731Dspark => {
|
||||
ModelChoice::DeepSeekV4Flash0731
|
||||
}
|
||||
@@ -242,9 +182,6 @@ impl ManagedArtifactId {
|
||||
|
||||
fn artifact(self) -> &'static Artifact {
|
||||
match self {
|
||||
Self::DeepSeekV4Flash => &FLASH,
|
||||
Self::DeepSeekV4FlashMtp => &FLASH_MTP,
|
||||
Self::DeepSeekV4FlashDspark => &FLASH_DSPARK,
|
||||
Self::DeepSeekV4Flash0731 => &FLASH_0731,
|
||||
Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK,
|
||||
Self::DeepSeekV4Pro => &PRO,
|
||||
@@ -340,7 +277,6 @@ pub(crate) enum DownloadOutcome {
|
||||
impl fmt::Display for ModelChoice {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(match self {
|
||||
Self::DeepSeekV4Flash => "DeepSeek V4 Flash (deprecated preview)",
|
||||
Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731",
|
||||
Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813",
|
||||
Self::Glm52 => "GLM 5.2",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -546,7 +546,7 @@ fn model_json(id: &str, model: ModelChoice, context: i32, default_tokens: i32) -
|
||||
|
||||
fn model_alias(id: &str) -> Option<ModelChoice> {
|
||||
match id {
|
||||
"deepseek-chat" | "deepseek-reasoner" => Some(ModelChoice::DeepSeekV4Flash),
|
||||
"deepseek-chat" | "deepseek-reasoner" => Some(ModelChoice::DeepSeekV4Flash0731),
|
||||
"glm-5.2-chat"
|
||||
| "glm-5.2-no-think"
|
||||
| "glm-5.2-nothink"
|
||||
@@ -929,7 +929,7 @@ mod tests {
|
||||
let request = ResponseOptions {
|
||||
protocol: Protocol::Chat,
|
||||
reasoning_summary: false,
|
||||
model_id: "deepseek-v4-flash".into(),
|
||||
model_id: "deepseek-v4-flash-0731".into(),
|
||||
stream: true,
|
||||
include_usage: false,
|
||||
has_tools: true,
|
||||
@@ -1002,9 +1002,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn model_metadata_uses_the_requested_alias_and_default_token_limit() {
|
||||
assert_eq!(
|
||||
model_alias("deepseek-chat"),
|
||||
Some(ModelChoice::DeepSeekV4Flash0731)
|
||||
);
|
||||
assert_eq!(
|
||||
model_alias("deepseek-reasoner"),
|
||||
Some(ModelChoice::DeepSeekV4Flash0731)
|
||||
);
|
||||
let model = model_json(
|
||||
"deepseek-reasoner",
|
||||
ModelChoice::DeepSeekV4Flash,
|
||||
ModelChoice::DeepSeekV4Flash0731,
|
||||
32_768,
|
||||
50_000,
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user