Repository navigation
feat(speculative): Muse Glimmer DFlash assistant drafter on the round loop - #1762
Merged
inureyes merged 3 commits intoSep 10, 2026
Merged
Conversation
… loop Muse Glimmer ships a DFlash-style block drafter (`meta-models/Muse-Glimmer-30B-assistant`, `model_type: muse_glimmer_assistant`) that mlxcel could not run: the server refused every `--draft-*` flag on a Muse target and the existing DFlash drafter is the Qwen-shaped one. This adds the assistant model, the Muse target adapter and the dispatch wiring so `--model-draft <assistant>` runs the DFlash round loop on text-only Muse Glimmer requests at B = 1. - `drafter::dflash::muse`: flat config (no `num_target_layers` / `vocab_size`, both checked against the bound target), five sliding-attention layers with per-head q/k norms under a bidirectional sliding mask over absolute positions, a temporal context window instead of a `RotatingKVCache` (a wrapped ring returns physical slot order), and a bind to the target's raw embedding table and untied `lm_head`. - Muse target: residual-stream capture before the final norm, trim rollback over the mixed caches with the sliding ones armed with a speculative buffer, and the block-versus-chain exactness probe as the gate. - Round loop: a drafter declaring a configured depth below the requested width runs the MTP throughput comparator between the two. Muse starts at 4 rows under the 16-row ceiling: on an M5 Max a fixed 16 measured 13 to 18 tok/s against 18 classic on natural text, 4 rows 34 to 41 tok/s, and 16 rows 94 tok/s on repetitive text. - Server: `DFlashTargetModel::supports_batched()` and `required_drafter_family()` replace the hardcoded LFM2 arms; the Muse startup guard admits exactly the assistant drafter. Closes #1343
inureyes
force-pushed
the
feature/issue-1343-muse-glimmer-assistant-drafter
branch
from
September 10, 2026 14:18
a2388bc to
1d78342
Compare
Pre-merge review follow-up on #1343, in three parts. The exactness gate probed only the requested ceiling. Since this change the DFlash round loop no longer holds one width for a Muse run: it warms up at the drafter's declared depth of four rows and widens to the ceiling only when a measurement window pays for it, so the width the burst actually starts at was never measured. The forward width selects which quantized-matmul kernel MLX dispatches, so a pass at the ceiling is not evidence about the depth; `docs/benchmarks.md` records the same comparison reading 20.6 percent disagreement at width 8 and 0.0 percent at width 32. `probed_verify_widths` names the widths a run can settle on and `dflash_exactness_allows_every_width` requires all of them, which is what the two Muse `DFlashTargetModel::exactness_allows` arms now call. Measured on the M5 Max pairing: the probe declines at width 4 as well (162404 of 404096 logit bytes differ, against 187123 at width 16), so the gate was previously reporting a verdict for a width the loop would have left immediately. The speculative buffer could be narrower than the block it buffers. `speculative_buffer_size` was the Gemma 4 rule verbatim, `clamp(block * 8, 32, 128)`, and the buffer is the slack past `max_size` that lets a verify block be appended across the ring boundary and then rewound. `--draft-block-size` is not bounded above anywhere on the way in, so at 129 rows and above the cap handed the block less slack than it needed: the append overwrites window rows that are still visible, and `RotatingKVCache::trim` rewinds the offsets without restoring them, leaving the sequence continuing from a window with holes in it and no error anywhere. The rule now floors at the block size, which is a no-op at every width a real run reaches. The rollback also names a short rewind through `tracing::error!` rather than only a `debug_assert`, and a verify that captured no residual stream for a drafter slot says so instead of handing over a zero slab. Two smaller ones. The drafter's `forward` drops the rows past the sliding window before the encoder runs rather than after: `encoder.fc` and `output_norm_enc` are both per-row, so the context is the same, but at `-c 16384` the incoming residual slab is 1.1 GB in f16 and seven eighths of it was being cast and projected only to be sliced away. And `MuseAssistantConfig::validate` now bounds every dimension it hands MLX as an `i32`, so a malformed `config.json` naming one past the wrap point fails as a load message instead of reaching MLX as a negative extent, which crosses the cxx bridge as an abort. Docs: the supported-models DFlash row and `speculative-acceptance.md` record the multi-width gate and the buffer floor, and the README speculative bullet now names the Muse Glimmer path beside the MTP and DSpark ones.
Bilingual report for the Muse Glimmer DFlash assistant drafter: what the published checkpoint carries and what it omits, why the drafter's bidirectional absolute-position mask rules out a rotating cache for its own context, the measurement that made the verify width adaptive, the server contract change that PR #1751 deferred to this family, and the pre-merge review round with the three findings fixed and the six left as reported.
inureyes
deleted the
feature/issue-1343-muse-glimmer-assistant-drafter
branch
September 10, 2026 14:59
inureyes
added a commit
that referenced
this pull request
Sep 10, 2026
Main's DFlash server path moved to the `DFlashTargetModel` trait in `src/server/batch/dflash_target.rs` (#1751, #1762) with a per-family exactness gate and drafter-family pairing check, and the drafter refusal message grew a family list (#1770). This merge drops the branch's own `DFlashBurstTarget` trait in favor of main's design and integrates Laguna into it: `LagunaWrapper` implements `DFlashTargetModel` (every prompt row as the first hidden, `block_size` rows of speculative slack on the rotating caches, no batched burst, a `Laguna` drafter family that the target requires and the Laguna drafter self-reports through `Drafter::is_laguna_dflash`), `LagunaVerifyOutput` implements `DFlashVerifyOutput`, and the burst's variant gate, `drive!` match and batched gate gain Laguna arms. `LagunaModel::dflash_exactness_allows` runs the same measured block-versus-chain probe LFM2 and Muse Glimmer run, memoized through `mtp_exactness_gate` with the `MLXCEL_MTP_ALLOW_INEXACT` override; both the server burst and the offline `mlxcel generate --draft-kind dflash` arm fail closed on it. The DFlash probe recognizes `DFlashLagunaForCausalLM` next to the DSpark marker, `load_drafter` checks the Muse assistant, then Laguna, then the default, and the standalone-model refusal names the Laguna drafter. Refs #1351
inureyes
added a commit
that referenced
this pull request
Sep 11, 2026
## Summary Adds the Poolside Laguna DFlash drafter (`poolside/Laguna-*-DFlash`) as `mlxcel_core::drafter::laguna_dflash`, implements `SpeculativeTarget` on the Laguna target, routes `model_type: laguna` drafters through `load_drafter`, integrates Laguna into the server's `DFlashTargetModel` design (#1751, #1762) as its own drafter family, and wires the pairing into both `mlxcel-server` and offline `mlxcel generate --draft-kind dflash` (the offline DFlash arm did not exist for any target before; it is Laguna-only here). Like the LFM2 and Muse Glimmer arms, the pairing runs behind the measured block-versus-chain exactness gate (`LagunaModel::dflash_exactness_allows`, `MLXCEL_MTP_ALLOW_INEXACT` override) on both the server and the offline arm. ## Where the landed code and the issue text disagree - The forward follows vLLM's `laguna_dflash.py` (vllm-project/vllm#46853), which is what the published checkpoint is served with: every drafter layer passes the projected context through its own `input_layernorm` before the K/V projection (the issue has `ctx_qkv = qkv_proj(ctx_i)`), and the sliding window is a per-query limit over `[context | block]` rather than a fixed 511-row context view. With the checkpoint's norm weights all exactly 1.0 the first difference is numerically a no-op today; the second only matters past 512 context tokens. - The drafter's initial context is every captured prompt row (vLLM's DFlash proposer; `FirstHiddenRows::EveryPromptRow`), not the last row the Qwen 3.5 port uses. - `src/server/speculative_dispatch.rs` needed no Laguna entry: dispatch is by drafter kind; the target arm lives in `dflash_target.rs` and `speculative_burst.rs`. - The sliding target caches are armed with `enable_speculative_buffer(block_size)` so rollback is a tail trim; the drafter's context cache is a temporal buffer capped at `window - 1`, not a ring. ## Measurements (GB10 sm_121, NVFP4 target, post-rebase on main `0c21aed2` with #1795, `MLXCEL_MTP_ALLOW_INEXACT=1`) **Exactness probe, re-run after #1795:** still declines this host at block 16 (107246 of 200704 logit bytes differ at the first verify position) and at block 8 (107466); `qmv_wide` off does not change it. Neither #1795 fix applies to this pairing: the Laguna loader already kept the drafter in bf16 (it now goes through the shared `apply_drafter_load_dtype_policy`), and Laguna has no gated-delta layers. Without the override the offline arm exits with the verdict and the server burst declines to classic decode, so the feature ships inert here by default. **Throughput A/B, same binary, feature off (no `--draft-model`) versus on per block size.** Raw code prompts, no chat template, 200 tokens, GPU lock held for the whole sweep (GPU exclusive, 0 percent at start); the host was not otherwise idle: another session's `cargo build` / `rustc` ran at full CPU throughout (load 2.6 to 3.9), which is the same for both arms. Decode tok/s as the CLI reports it; n runs, min to max; per-round device time is the synchronized verify (drafter plus target) and the drafter's host-side graph build is listed separately. | Configuration | n | tok/s mean (min to max) | Mean accepted | Device ms/round | Drafter build ms/round | vs off | |---|---|---|---|---|---|---| | code 0 (`retry_with_backoff`), off | 5 | 31.18 (30.41 to 31.98) | | | | | | code 0, block 2 | 3 | 16.19 (16.12 to 16.25) | 0.84 | 80 | 30 | 0.52x | | code 0, block 3 | 3 | 21.61 (21.52 to 21.77) | 1.52 | 83 | 30 | 0.69x | | code 0, block 4 | 3 | 27.05 (26.61 to 27.53) | 2.21 | 85 | 31 | 0.87x | | code 0, block 5 | 3 | 28.33 (26.21 to 29.51) | 2.55 | 89 | 34 | 0.91x | | code 0, block 6 | 3 | 30.46 (30.05 to 31.26) | 2.90 | 92 | 33 | 0.98x | | code 0, block 7 | 3 | 30.24 (30.12 to 30.47) | 3.06 | 97 | 34 | 0.97x | | code 0, block 8 | 3 | 31.26 (31.07 to 31.36) | 3.33 | 101 | 34 | 1.00x | | code 0, block 9 | 3 | 30.58 (30.37 to 30.75) | 3.33 | 106 | 33 | 0.98x | | code 0, block 10 | 3 | 30.02 (29.68 to 30.19) | 3.42 | 109 | 35 | 0.96x | | code 0, block 12 | 3 | 28.94 (28.66 to 29.41) | 3.52 | 116 | 37 | 0.93x | | code 0, block 16 (default) | 3 | 27.41 (26.90 to 27.90) | 3.55 | 127 | 35 | 0.88x | | code 1 (`lru_get`), off | 3 | 31.80 (31.50 to 32.38) | | | | | | code 1, block 4 | 3 | 26.10 (25.74 to 26.47) | 2.08 | 83 | 31 | 0.82x | | code 1, block 6 | 3 | 30.42 (30.11 to 30.64) | 2.92 | 91 | 34 | 0.96x | | code 1, block 8 | 3 | 29.95 (29.60 to 30.56) | 3.17 | 102 | 34 | 0.94x | | code 1, block 10 | 3 | 30.16 (29.88 to 30.58) | 3.44 | 109 | 35 | 0.95x | **No configuration is a net win on this host, before or after #1795.** The best width is block 8 at 1.00x on code 0 (31.26 versus 31.18, its whole range inside the off arm's 30.41 to 31.98) and 0.94x on code 1; the default block 16 is 0.88x. The pre-rebase sweep on an idle host gave the same picture (block 8 at 0.97x, block 16 at 0.86x). There is no sub-8 cliff on this NVFP4 path (5, 6, 7 sit on a smooth curve) and no crossover past 8. The default stays at 16 (`DEFAULT_DFLASH_BLOCK_SIZE`); if a served width is ever retuned for Laguna, block 8 is the least-bad on GB10 and that change belongs to #1797, not this PR. **Why the ceiling is where it is.** The device time of a verify round is a fixed 77 ms plus about 3.1 ms per row (80 ms at 2 rows, 127 ms at 16) against a 31 to 32 ms classic token, 2.5x a single-token step even at 2 rows, plus about 30 to 35 ms of host-side drafter graph construction per round, one classic token's worth. The classic step is graph-replayed while the multi-row verify runs eagerly and launch-bound; that is a runtime property of the CUDA backend on this host, unchanged by #1795 for this pairing, and out of scope for a drafter port. Even perfect acceptance at block 8 would reach about 17 ms per token; at the measured 3.3 to 3.6 accepted, 0.88x to 1.00x. Thinking-channel prompts (1.1 to 1.8 accepted) are about 0.4x. Every greedy-id divergence from classic decode was checked with the block-versus-chain probe in `laguna_real_checkpoint_probe`: the chain arm's top-2 margin is 0.0 or 0.125 (one bf16 ulp) at each, so it is the `M >= 2` versus `M = 1` quantized-kernel property the exactness gate measures, not cache or mask logic; the tiny f32 model is token-exact with forced accept lengths 0, 1, 2 and full across the window wrap. The drafter's per-position accuracy along the reference path is 0.88, 1.00, 0.75, 0.62 for d_0 to d_3 on code and 0.88, 0.50, 0.25 inside `<think>`. Server path: same counters as offline (`rounds=60 proposed=835 accepted=67` on the issue's chat prompt). ## Acceptance criteria - Drafter directory loads through `load_drafter` and is refused as a standalone model: verified (detection test plus real runs). - Greedy speculative output token-identical to non-speculative: **not met on this host**, unchanged after #1795: the probe declines by default (107246 of 200704 bytes differ at block 16, 107466 at block 8); with the override, identical on 1 of 6 prompts and differing at one or two bf16 tie positions per 128 tokens on the other 5. Documented in `docs/supported-models.md` and the report. - Rollback trims full and sliding target caches and the drafter context caches: verified by `greedy_invariant_with_forced_rejections` (oracle drafter forcing accept lengths 0, 1, 2, full over a window of 6) and `greedy_invariant_with_real_drafter`. - Mean accepted length above 3 on the code prompt: 1.12 on the issue's chat prompt (the model answers inside `<think>`); 3.55 at block 16 and 3.33 at block 8 on the raw code body (n=3, post-rebase). - Higher decode throughput: **not met on this host** at any block size 2 to 16, before or after #1795 (best 1.00x at block 8, n=3, range inside the off arm's; 0.88x at the default block 16), for the launch-bound reason above; the feature is gated off here by the exactness probe regardless. - `docs/supported-models.md` and the detection test: done. - `cargo test --workspace --profile test-fast --features metal,accelerate`: not runnable on this Linux/CUDA host; unrun. Ran instead on the merged tree: core `drafter::laguna_dflash`, `drafter::dflash`, `drafter::tests` (123 passed); binary `models::laguna_dflash_tests`, `models::detection_tests`, `models::laguna_tests`, `server::batch::dflash_target`, `server::speculative_dispatch_tests` (27 passed, 1 ignored real-checkpoint probe). - `cargo clippy --profile test-fast --features cuda --lib --bins --tests -- -D warnings` (both crates) and `cargo fmt --all -- --check`: clean. ## Changes during review - Merged `origin/main` (nine PRs, including the `DFlashTargetModel` server design from #1751 and #1762 and the drafter-refusal wording from #1770); the branch's own burst trait was dropped in favor of main's and Laguna became a fourth drafter family with the shared measured exactness gate. Merged again at `0c21aed2` to pick up #1795; the Laguna loader now uses the shared `apply_drafter_load_dtype_policy`. - `from_weights` validates the `qkv_proj`, `o_proj`, `g_proj` and `fc` row counts against the config so a mismatched checkpoint is a load error rather than an MLX reshape throw inside the first draft, and the per-round `copy` of the captured hidden input was removed. - Implementation review: the offline arm now refuses a non-Laguna DFlash drafter by name (the pairing gate the server already ran) and refuses sampling requests (the round loop is greedy-only); the drafter prefers its one trained block width so a wider `--draft-block-size` cannot engage the adaptive width controller at an unprobed width; context rows older than the window are dropped before `aux_hidden_norms` and `fc` instead of after; `concatenate_many` replaces the pairwise fold; the rotating caches take the Muse Glimmer slack rule and warn instead of panicking; a short rollback trim logs an error in release builds; `load_drafter` routes on the same Laguna predicate detection uses; `attention_bias: true` is rejected and `intermediate_size` is checked at load; `MLXCEL_PRINT_TOKEN_IDS` keeps its single stderr contract with a prompt-ids line added; two tests pin context-offset continuity and the fused q/k/v row order. - Security and performance review: `greedy_only` is true so the server burst declines sampling requests to classic decode instead of silently returning greedy output; `dflash_config.block_size` (2..=512) and the sliding windows (2..=2^20) are bounded at config load because the drafter's `config.json` is untrusted and sizes allocations and `i32` mask arithmetic; the sanitizer checks split q/k/v shapes before fusing and refuses prefixed-plus-unprefixed duplicates; projection input widths and norm lengths are checked at load; the context K/V comes from a row-sliced K/V-only projection on plain checkpoints, the context cache exposes its buffer instead of returning a copy, and `combine_hidden` builds one `concatenate_many` node; the offline arm refuses a token bias. The regression check after each fix commit reproduced the same 128 ids and `accepted=98` on the code prompt. Closes #1351
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the Muse Glimmer assistant drafter (
meta-models/Muse-Glimmer-30B-assistant,model_type: muse_glimmer_assistant) to the existing DFlash round loop, with the Muse target adapter and the server dispatch, somlxcel-server -m <Muse-Glimmer-30B> --model-draft <assistant>serves text-only requests speculatively at B = 1.What changed
mlxcel-coredrafter::dflash::muse: config (flat;num_target_layersandvocab_sizeareOptions checked against the bound target, because the publishedconfig.jsoncarries neither), five sliding-attention layers with per-head q/k norms under a bidirectional sliding mask over absolute positions (|q - k| <= 2048),encoder.fcplusencoder.output_norm_enc, and aDrafteradapter that binds the target's raw embedding table and untiedlm_head. The context cache is a temporal window rather than aRotatingKVCache: a wrapped ring returns physical slot order and the mask needs absolute key positions.muse_glimmer_assistantresolves toDrafterKind::Dflash; its markers join the standalone-model rejection.src/models/muse_glimmer_speculative.rs): residual-stream capture before the final norm, trim rollback over the mixed caches, sliding caches armed withclamp(block * 8, 32, 128)rows of speculative buffer floored at the block size, the block-versus-chain exactness probe as the gate.MuseGlimmerTextWrapperand the VLM wrapper hand outembed_tokens_module(raw table, not theembed_normlookup) andlm_head_module(no softcap).prefill_forward_with_capture_layerskeeps the trait default on purpose: a Muse verify output carries no rollback state (the trim reads cache offsets), so there is nothing prompt-sized to skip.DFlashGeneratorreadsDrafter::configured_block_size/prefer_requested_block_sizeand, for a drafter that declares a depth below the requested width without insisting on it, runs the MTP loop'sBlockThroughputControllerbetween the two. The Muse assistant starts at 4 rows under the checkpoint's 16-row ceiling (measurement below). Qwen 3.5 DFlash (no depth) and DSpark (prefers the requested width) keep a fixed width; pinned bydflash_round_loop_starts_at_the_configured_depth.DFlashTargetModel::supports_batched()replaces the hardcoded LFM2 arm in the batched variant gate,required_drafter_family()replaces the DSpark boolean and refuses a mismatched pairing by name for both LFM2 and Muse, and the module doc no longer describes per-family policy living outside the trait.run_dflash_burst's three arms andmodel_variant_labelcoverMuseGlimmerVLM. The Muse startup guard admits exactly the assistant drafter (--draft-kind dflashor unset), still rejecting adapters, KV modes, TP/PP and draft flags without a drafter.docs/supported-models.md(DFlash row for Muse, family paragraph, the "not a speculative target" sentence replaced),docs/speculative-acceptance.md, and the README speculative bullet.Pre-merge review round (commit
796604f1)Three findings were fixed in this PR; the rest are listed below as reported and not fixed.
exactness_allows(block_size)with the ceiling alone. The forward width selects which quantized-matmul kernel MLX dispatches, so a pass at the ceiling is not evidence about the depth:docs/benchmarks.mdrecords the same comparison reading 20.6 percent disagreement at width 8 and 0.0 percent at width 32.probed_verify_widthsnow names the widths a run can settle on anddflash_exactness_allows_every_widthrequires all of them. On this host the probe declines at width 4 as well (162404 of 404096 logit bytes differ, against 187123 at width 16), so the gate had been reporting a verdict for a width the loop would have left after the warm-up. Pinned byprobed_widths_cover_the_warm_up_depth_and_the_ceiling.speculative_buffer_sizewas the Gemma 4 rule verbatim,clamp(block * 8, 32, 128), and the buffer is the slack pastmax_sizethat lets a verify block be appended across the ring boundary and rewound.--draft-block-sizeis not bounded above anywhere on the way in, so at 129 rows and above the cap handed the block less slack than it needed: the append overwrites still-visible window rows,RotatingKVCache::trimrewinds the offsets without restoring them, and the sequence continues from a window with holes in it with no error anywhere. The rule now floors at the block size, a no-op at every width a real run reaches. Pinned byspeculative_buffer_never_falls_below_the_block_it_buffers. The rollback also names a short rewind throughtracing::error!rather than only adebug_assert, and a verify that captured no residual stream for a drafter slot now says so instead of handing over a zero slab that reads as a residual stream.MuseAssistantConfig::validatenow bounds every dimension it hands MLX as ani32, so a malformedconfig.jsonnaming one past the wrap point fails as a load message rather than reaching MLX as a negative extent, which crosses the cxx bridge as an abort (config_refuses_dimensions_that_would_wrap_an_i32). And the drafter'sforwarddrops the rows past the sliding window before the encoder runs rather than after:encoder.fcandoutput_norm_encare both per-row so the context is unchanged, but at-c 16384the incoming residual slab is 1.1 GB in f16 and seven eighths of it was being cast and projected only to be sliced away.Reported and not fixed:
--draft-block-sizehas no upper bound.resolve_draft_block_sizereturns an override verbatim ("no further validation here; concrete generators enforce their own minimums"), which is family-wide and predates this change. The buffer floor above removes the silent-corruption consequence for Muse, but an absurd width still allocates an absurd verify block. A shared clamp belongs inresolve_draft_block_sizeor the server startup validation and would touch every DFlash and MTP family.block_size=16) while the probe verdict immediately above it names the width that actually failed (block_size=4). The burst message is shared by all DFlash families and it points the reader at the verdict line, so it is left alone rather than made family-specific.MuseAssistantContextCache::appendrebuilds the whole window every round (concatenate, slice,contiguous, then twoffi::copy), so a steady-state round copies roughly 2 x 2048 x 8 x 128 x 2 bytes per drafter layer. A ring with a rotate-on-read, orRotatingKVCachetaught to report absolute key positions, would drop it. It is a fraction of one target verify forward and does not move the measurements below.MuseAssistantDrafter::binddoes not re-runvalidate_target_compatthe wayGlm4MoeLiteMtpDrafter::binddoes. Every current caller (run_dflash_on_target, the batched arm,speculative_bench, the scheduler hook) validates first, and putting it inbindwould repeat a full-vocabulary probe matmul on every burst.MuseAssistantModel::forwardguards its cache-count invariant withdebug_assert_eq!andzip, which truncates silently in release. The only constructor of that vector ismake_caches.mtp_exactness_gateprobe key is(block_size, hidden_size, num_hidden_layers), which cannot tell two Muse checkpoints of the same shape apart within one process. Pre-existing and shared with the MTP and DSpark arms.Real-checkpoint validation (M5 Max, GPU generation 17;
models/muse-glimmer-30b-4bitwithmodels/muse-glimmer-30b-assistant-bf16, port 19343)Re-run at
796604f1against amlxcel-serverrebuilt after the rebase ontoe3ca8afcand after the review fixes; no source file is newer than the binary. Driver:validate_muse.pystartstarget/release/mlxcel-server -m models/muse-glimmer-30b-4bit --port 19343 -c 16384 --parallel 1 [--model-draft models/muse-glimmer-30b-assistant-bf16], sendsPOST /v1/chat/completionswithtemperature: 0, max_tokens: 256, logprobs: true, and compareslogprobs.content[].tokenbetween arms. Three text prompts: a 66-token tides prompt, a 1968-token log prompt whose 256-token answer crosses the 2048-token sliding window during decode, and a 2660-token log prompt whose caches wrap during the prefill; plus oneimage_urlrequest.MTP declined: verify block position 0 differs from the single-token chain in 162404 of 404096 logit bytes. Disabling qmv_wide did not make it exact either. block_size=4), the burst logsDFlash speculative dispatch declined for seq ...: the block-versus-chain exactness probe did not pass at block_size=16once per request and serves classically. All three prompts: IDENTICAL, 256 of 256 tokens against the same server without--model-draft. This is the documented generation-15-and-newer condition, the same as LFM2 DSpark on this host.MLXCEL_MTP_ALLOW_INEXACT=1: cross-window-decode IDENTICAL (256 tokens, 47 rounds, 209 of 213 proposals accepted, mean accepted 4.45, 60.5 tok/s decode against 22.5 tok/s classic end to end); wrapped-in-prefill IDENTICAL (256 tokens, 47 rounds, 209 of 214 accepted, mean 4.45, 60.6 tok/s against 20.1); tides DIVERGES at token 59 (Earth's rotationagainstcentrifugal force, 109 rounds, 146 of 375 accepted, mean accepted 1.34, 38.4 tok/s decode against 31.6 classic end to end). The divergence is the byte-identity the probe declined, observed after overriding it, which is why the override is not the default.--model-draft: oneWARN ... DFlash speculative dispatch declined for seq seq-4: multimodal VLM request detected ...line, served classically, HTTP 200.timingson every speculative response carrydraft_kind: dflash,draft_rounds,draft_n,draft_n_accepted; mean accepted length is above 1 on every prompt (1.34 to 4.45) and every speculative arm decodes faster than the classic arm of the same prompt.hidden_states[i]indexing) changed nothing (1.83 / 3.47 / 1.50), and feeding the drafter theembed_norm-wrapped embedding halved acceptance (1.28 / 1.35 / 0.90).Not in this PR
supports_batched(); multimodal requests decline;draft_window_sizeis accepted and unused (the issue's out-of-scope list).initial_block_sizeonDFlashGenerator::newand nodflash_round_loop_initial_block_size_fourtest: the drafter-declared depth plus the throughput comparator covers the first-round narrowing the issue asked for, and is what the measurement supports.draftersentry for LFM2 was already absent before this change and is left as is.Checks
cargo fmt --all -- --check;cargo clippy --lib --testsandcargo clippy -p mlxcel-core --lib --testswith-D warnings, both under--features metal,accelerate. Tests under--profile test-fast --features metal,accelerate:-p mlxcel-core drafter::dflash(86 passed) and the root crate filtered onmuse_glimmer(109),models::muse_glimmer(21),server::batch::speculative_burst(67),server::startup(80),models::detection(63),cli::speculative_args(22),server::batch::dflash_target(4). The--workspacegate was not run locally because the machine is shared; the narrow scopes plus CI cover it.Closes #1343