diff --git a/cli/index.ts b/cli/index.ts index 50a7556560..f329b4a25c 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -1255,6 +1255,13 @@ interface ModelEntry { /// `crates/hipfire-arch-deepseek4/src/arch.rs`), so no explicit env var /// is required once the file is in MODELS_DIR. mtp?: { file: string }; + /// Optional published DSpark 3-stage drafter sidecar — currently DeepSeek V4 + /// only. When set, `hipfire pull` also fetches the file next to the weights. + /// The deepseek4 loader auto-discovers it via the `-dspark.` + /// sibling convention (see `crates/hipfire-arch-deepseek4/src/arch.rs`) at + /// load time, so no explicit env var is required once the file is in + /// MODELS_DIR; under `--spec dspark`/auto it wins over the in-trunk MTP head. + dspark?: { file: string }; /// Optional per-model KV-cache default (the registry is the per-model card). /// When present it takes precedence over the q8 default in resolveKvMode (but /// still loses to HIPFIRE_KV_MODE env and per-model config). This is the @@ -2428,6 +2435,36 @@ async function pull(tag: string): Promise { } } + // DSpark 3-stage drafter sidecar — same `-dspark.` sibling + // convention (see arch-deepseek4/src/arch.rs); missing sidecar = MTP/plain + // decode only, no DSpark spec-decode. + if (entry.dspark?.file) { + const sidecarDest = join(MODELS_DIR, entry.dspark.file); + if (existsSync(sidecarDest)) { + console.error(` DSpark sidecar already present: ${entry.dspark.file}`); + } else { + const sidecarUrl = `${HF_BASE}/${entry.repo}/resolve/main/${entry.dspark.file}`; + console.error(` Fetching DSpark sidecar: ${entry.dspark.file}`); + try { + const sres = await fetch(sidecarUrl, { headers: hfHeaders() }); + if (!sres.ok) { + console.error(` WARN: DSpark sidecar fetch failed (${sres.status} ${sres.statusText}) — base is usable; DSpark spec-decode unavailable until sidecar present.`); + } else { + const sTmp = sidecarDest + ".tmp"; + const sWriter = Bun.file(sTmp).writer(); + for await (const chunk of sres.body as AsyncIterable) sWriter.write(chunk); + await sWriter.end(); + const { renameSync } = await import("fs"); + renameSync(sTmp, sidecarDest); + const ssz = (statSync(sidecarDest).size / 1e9).toFixed(2); + console.error(` Saved: ${sidecarDest} (${ssz}GB)`); + } + } catch (e) { + console.error(` WARN: DSpark sidecar fetch error: ${e} — non-fatal.`); + } + } + } + return dest; } diff --git a/cli/registry.json b/cli/registry.json index f035a0440a..e288d5ebc1 100644 --- a/cli/registry.json +++ b/cli/registry.json @@ -53,11 +53,14 @@ "mtp": { "file": "deepseek-v4-flash-mtp.mq2lloyd" }, + "dspark": { + "file": "deepseek-v4-flash-dspark.mq2lloyd" + }, "recommended_settings": { "temperature": 1.0, "top_p": 1.0 }, - "desc": "DeepSeek V4 Flash, MQ2-Lloyd routed-expert MoE (arch_id=9). Includes MTP sidecar for K=2 spec-decode (+29% TG on code). temp=1.0 is safety-critical: greedy/low-temp falls into token loops on the quant." + "desc": "DeepSeek V4 Flash, MQ2-Lloyd routed-expert MoE (arch_id=9). Includes MTP sidecar for K=2 spec-decode (+29% TG on code) and a DSpark 3-stage drafter sidecar (auto-wins over MTP under --spec dspark/auto). temp=1.0 is safety-critical: greedy/low-temp falls into token loops on the quant." }, "minimax-m2.7": { "repo": "hipfire-models/hipfire-MiniMax-M2.7", diff --git a/cli/registry_loader.ts b/cli/registry_loader.ts index 651117cc1f..2eb34d706b 100644 --- a/cli/registry_loader.ts +++ b/cli/registry_loader.ts @@ -41,6 +41,7 @@ export interface RegistryModelEntryV1 { desc: string; triattn?: RegistrySidecarV1; mtp?: RegistrySidecarV1; + dspark?: RegistrySidecarV1; sha256?: string | null; size_bytes?: number | null; arch_id?: number | null; @@ -130,6 +131,7 @@ function validEntry(v: unknown): v is RegistryModelEntryV1 { if (typeof v.desc !== "string") return false; if (v.triattn !== undefined && !validSidecar(v.triattn)) return false; if (v.mtp !== undefined && !validSidecar(v.mtp)) return false; + if (v.dspark !== undefined && !validSidecar(v.dspark)) return false; // Fail-closed on default_kv_mode: a present value must be a known KV mode. // null/undefined means "no per-model recommendation" → arch fallback. if (v.default_kv_mode !== undefined && v.default_kv_mode !== null) { diff --git a/crates/hipfire-runtime/src/ddtree.rs b/crates/hipfire-runtime/src/ddtree.rs index b7a70e3cc5..6cb47db951 100644 --- a/crates/hipfire-runtime/src/ddtree.rs +++ b/crates/hipfire-runtime/src/ddtree.rs @@ -1220,9 +1220,10 @@ mod tests { let vocab = 6usize; let drafts = [2u32, 1]; let mut logits = vec![0.0f32; (drafts.len() + 1) * vocab]; - logits[0 * vocab + 2] = 10.0; // pos 0 argmax = 2 (== draft 0 ⇒ accept) - logits[1 * vocab + 4] = 10.0; // pos 1 argmax = 4 (!= draft 1 ⇒ stop) - logits[2 * vocab + 5] = 10.0; // pos 2 argmax = 5 (unused) + let at = |pos: usize, tok: usize| pos * vocab + tok; + logits[at(0, 2)] = 10.0; // pos 0 argmax = 2 (== draft 0 ⇒ accept) + logits[at(1, 4)] = 10.0; // pos 1 argmax = 4 (!= draft 1 ⇒ stop) + logits[at(2, 5)] = 10.0; // pos 2 argmax = 5 (unused) let mut rng = 0x1u64; let (accepted, bonus) = naive_sample_chain(&logits, &drafts, vocab, 0.0, &mut rng); assert_eq!(accepted, 1); diff --git a/registry/v1.json b/registry/v1.json index fbf99f70ce..32e4572421 100644 --- a/registry/v1.json +++ b/registry/v1.json @@ -80,11 +80,16 @@ "sha256": "596ab559d40319fd070baa34b24f8addcb64245cd0dd61fb468264fded89bfc7", "size_bytes": 1998047355 }, + "dspark": { + "file": "deepseek-v4-flash-dspark.mq2lloyd", + "sha256": "0ec4cdbadfd8f5b1e8eead5b864ec3ec2233839f266221e6111839efd5813375", + "size_bytes": 5996334814 + }, "recommended_settings": { "temperature": 1.0, "top_p": 1.0 }, - "desc": "DeepSeek V4 Flash, MQ2-Lloyd routed-expert MoE (arch_id=9). Includes MTP sidecar for K=2 spec-decode (+29% TG on code). temp=1.0 is safety-critical: greedy/low-temp falls into token loops on the quant.", + "desc": "DeepSeek V4 Flash, MQ2-Lloyd routed-expert MoE (arch_id=9). Includes MTP sidecar for K=2 spec-decode (+29% TG on code) and a DSpark 3-stage drafter sidecar (auto-wins over MTP under --spec dspark/auto). temp=1.0 is safety-critical: greedy/low-temp falls into token loops on the quant.", "sha256": "ab8a8900f6792199975e7e2b15854bb869a023ca3fddf62981c1b9cd600e9b96", "size_bytes": 86184307283, "arch_id": 9,