docs: DSpark speculative-decoding feasibility & adaptive-placement spec - #413
Conversation
Surveys DeepSeek's DSpark/DeepSpec framework, maps it onto our existing speculative-decoding infra (MtpDecoder, SpeculativeDecoder, TierPlanner, HardwareProfile), and specs an auto-placement planner (GPU-colocated vs CPU vs off) for the draft head with user-overridable CLI/env flags, mirroring the pinGpuLayers / SpecType conventions already in the codebase.
- Correct the Vulkan+--draft-model claim: RunCommand.cs rejects that combo outright rather than falling back to a CPU draft. - Fill in the dspark_qwen3_14b_block7 param count (3.42B). - Point the safetensors-loader work at the existing SafetensorsLoader in SharpInference.Diffusion instead of proposing a second one, and call out the NativeAOT/trim-safe JSON parsing constraint from CLAUDE.md. - Fix the RAM-headroom formula: LayerPlacement has no per-CPU-layer byte field today, so note the plumbing (or new field) needed. - Have DSparkPlacementPlanner reuse TierPlanner's reserved-VRAM-floor helper instead of re-deriving it, and spell out that the runtime free-VRAM recheck is a concrete Phase 3 step, not implicit. - Note the coupling between §4 placement margins and §7's variable verify length so Phase 5 revisits rather than assumes them.
There was a problem hiding this comment.
Code Review
This pull request introduces a feasibility and adaptive-placement specification for DSpark Speculative Decoding, detailing placement options, auto-placement algorithms, user overrides, and a phased rollout plan. Feedback on the specification highlights a logical conflict in subtracting the expert cache budget from free VRAM, the risk of using total system RAM instead of actual available RAM for headroom calculations, and a technical inaccuracy regarding PCIe round-trip overhead for independent draft models.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| 2. **Compute GPU headroom.** `vramFree = hardware.VramBytes - targetPlacement.GpuWeightBytes | ||
| - targetPlacement.GpuKvBytes - targetPlacement.ExpertCacheBudgetBytes (if MoE) - reserved`, |
There was a problem hiding this comment.
There is a logical conflict in subtracting targetPlacement.ExpertCacheBudgetBytes from hardware.VramBytes when computing vramFree for DSpark:
- Since
ExpertCacheBudgetBytesis defined as the remaining VRAM after trunk weights, KV, and reserved space are subtracted, subtracting it here would reducevramFreeto nearly zero (or just the scratch buffer size), preventing DSpark from ever being placed on the GPU for MoE models. - If DSpark were to be supported on MoE models in the future, the DSpark draft head should carve its allocation out of the remaining VRAM before the SLRU expert cache budget is determined, rather than treating the entire expert cache budget as an untouchable prior allocation.
- Since DSpark is currently inapplicable to MoE architectures (as noted in §9), this term is always
0for all currently supported targets anyway, making its inclusion in the formula unnecessary and potentially confusing.
| With that available: `ramFree = hardware.RamBytes - cpuResidentTrunkBytes - reserved`. If | ||
| `ramFree >= draftHeadBytesCpuQuant * 1.15` **and** `hardware.CpuCores >= 4` (a floor below | ||
| which a sequential-ish per-token draft chain would itself become the bottleneck) → **`Cpu`**. |
There was a problem hiding this comment.
Using hardware.RamBytes (which represents the total physical system memory or container limit) as the baseline for ramFree is highly optimistic and risky.
In real-world environments, a significant portion of system RAM is already consumed by the OS, other processes, and the current process's own non-trunk allocations. Assuming all non-trunk RAM is free can easily lead to over-allocation, triggering heavy disk paging/swapping or Out-Of-Memory (OOM) crashes when loading a multi-gigabyte draft model (1.4B–3.4B parameters).
To make this safer and more robust, consider:
- Querying the actual available/free physical memory instead of total memory. In .NET,
GC.GetGCMemoryInfo()providesMemoryLoadBytes(used physical memory), soTotalAvailableMemoryBytes - MemoryLoadBytescan be used to estimate the actual free RAM. - Applying a much larger safety buffer or a conservative scaling factor if total RAM must be used as the baseline.
| - **PCIe cost of `Gpu` vs `Cpu` isn't symmetric with `--draft-model`'s existing CPU case.** | ||
| When target=GPU and draft=CPU, every draft step round-trips the current hidden | ||
| state/token across PCIe and back for verification — unlike the existing all-CPU draft | ||
| path (target and draft both CPU, no PCIe at all) or the existing all-GPU draft path (no |
There was a problem hiding this comment.
The assertion that "every draft step round-trips the current hidden state/token across PCIe and back" is inaccurate for independent draft models like DSpark:
- Unlike draft frameworks that depend on the target model's hidden states (such as EAGLE), DSpark is an independent draft model (using a DFlash backbone + Markov head) with its own weights.
- Therefore, the CPU draft model only needs the last accepted token ID(s) to seed its generation, and it produces
$k$ candidate token IDs. The only data transferred across PCIe are these small token ID arrays (integers), which has negligible latency (microseconds). - The actual bottleneck for a CPU-draft / GPU-target setup is the CPU execution latency of the 1.4B–3.4B parameter draft model itself, not the PCIe transfer overhead.
Correcting this in the spec will help align the performance heuristics with the actual hardware bottlenecks.
feat: DSpark block-speculative decoding — spec Phases 0-4 + 6 (implements the PR #413 plan)
Summary
MtpDecoder,SpeculativeDecoder,TierPlanner,HardwareProfile).DSparkPlacementPlannerthat auto-decides whether the draft head runs on GPU, CPU, or is disabled, based on measured VRAM/RAM headroom — with user-overridable CLI flags/env vars (--dspark-place,--dspark-verify-len,--dspark-min-confidence,SHARPI_DSPARK_*) following the existingpinGpuLayers/SpecTypeprecedent.Went through an internal review pass (fact-checking claims against the actual source, plus a design-quality pass) before opening this PR; fixes already folded in:
--draft-model+ Vulkan-target claim (rejected outright today, not routed to a CPU draft).dspark_qwen3_14b_block7param count.SafetensorsLoaderinSharpInference.Diffusioninstead of proposing a redundant one, and called out the NativeAOT/trim-safe JSON parsing constraint from CLAUDE.md.LayerPlacementhas no per-CPU-layer byte field today) and had the planner reuseTierPlanner's reserved-VRAM-floor logic instead of re-deriving it.Test plan
Generated by Claude Code