Skip to content

Per-request MTP/DFlash selection + DFlash streaming - #23

Open
dusterbloom wants to merge 2 commits into
feat/dflash-modalfrom
feat/dflash-stream-select
Open

Per-request MTP/DFlash selection + DFlash streaming#23
dusterbloom wants to merge 2 commits into
feat/dflash-modalfrom
feat/dflash-stream-select

Conversation

@dusterbloom

Copy link
Copy Markdown
Owner

What

Builds on the DFlash engine in panbanda#204 (base branch feat/dflash-modal). Two fixes:

  1. DFlash now works while streaming. The streaming path had no DFlash branch, so a streamed request — what interactive clients send — silently fell back to MTP even with a drafter loaded. The ~350-line draft-verify loop is refactored into one shared dflash_decode<S: DflashSink> driver feeding two sinks: DflashBufferedSink (non-streaming GenerationOutput, behavior unchanged) and DflashStreamSink (per-round StreamingOutput chunks). The gate/EMA/thermal logic is written once; streaming dispatches before prefill so DFlash runs its own tap-prefill with no double prefill.

  2. Per-request method selection. New Speculation { Auto, DFlash, Mtp, None } on SamplingParams, exposed as a "speculation" request field on the OpenAI chat + Anthropic routes (400 on invalid value). Default auto = DFlash when a drafter is loaded (incl. streaming), else MTP; mtp forces MTP even with a drafter; none = plain AR. Both heads already coexist in a drafter-loaded engine, so this is dispatch-only — no extra weights.

Verification (Apple Silicon + MLX)

HIGGS_DFLASH_TARGET_DIR=<9B-target> HIGGS_DFLASH_DRAFTER_DIR=<9B-drafter> \
cargo test --release -p higgs-engine dflash_streaming_matches_nonstreaming -- --ignored --nocapture

Asserts streaming DFlash == non-streaming DFlash byte-for-byte (deterministic gate-off run), and that speculation=mtp routes to MTP even with a drafter loaded (last_dflash_accepts stays empty). Plus a Speculation::parse unit test. clippy (nursery) + fmt clean.

Base / stacking

Targets feat/dflash-modal so the diff here is just this change (~+570), not the whole engine. Retarget to main once panbanda#204 merges.

🤖 Generated with Claude Code

dusterbloom and others added 2 commits June 23, 2026 15:01
DFlash speculative decoding was bound at model-load and was absent from the
streaming code path, so a streamed request silently fell back to MTP even when
a DFlash drafter was loaded. There was also no way to choose the method per
request.

- Refactor the ~350-line DFlash draft-verify loop into a shared
  `dflash_decode<S: DflashSink>` driver fed by two sinks: `DflashBufferedSink`
  (non-streaming GenerationOutput, unchanged behavior) and `DflashStreamSink`
  (per-round StreamingOutput chunks). The gate/EMA/thermal logic is written once.
- Add `Speculation { Auto, DFlash, Mtp, None }` to SamplingParams and honor it at
  all four dispatch sites; streaming branches before prefill (mirrors the
  non-streaming site) to avoid a double prefill. Both heads already coexist in a
  drafter-loaded engine, so this is dispatch-only — no extra weights.
- Parse a per-request `speculation` field on the OpenAI chat + Anthropic routes
  (400 on invalid). Default `auto` = DFlash when a drafter is loaded (incl.
  streaming), else MTP.
- Verify: dflash_streaming_matches_nonstreaming loads the real 9B + drafter and
  asserts streaming == non-streaming DFlash byte-for-byte (deterministic gate-off
  run), plus MTP routing via last_dflash_accepts. Speculation::parse unit test.
- Docs: README + docs/configuration.md document the field and draft_model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`trim_drafter_cache` and `rollback_kv_only` (both pub fn in dflash.rs) had zero
callers anywhere in the repo — superseded by `crop_drafter_cache` (the one the
draft-verify loop uses) and the tape-replay rollback path. Remove them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant