Context
#638 part 2 asked to replace the run-to-completion speculative burst (src/server/batch/speculative_burst.rs, dispatched from execute_prefill) with tick-cooperative slices so a speculative request no longer stalls every concurrent classic decode row for its whole duration (HOL block documented at src/server/model_worker.rs:400-438). PR #733 landed the measurement side (pairing matrix, adaptive-policy de-rate so GB10 declines regressing pairings, and a BurstFinalized.burst_wall_ms observability counter that measures the HOL stall) but deferred the slice itself.
Blocker found (by #733)
MtpGenerator<T> borrows the model through its adapter, so the generator cannot be stashed across scheduler ticks (self-referential borrow); this is exactly why the burst runs to completion today. A resumable design needs a generator step API with stored per-round state (feasible: MtpVerifyOutput owns MLX handles, not model borrows) plus drafter re-seed on resume.
Design sketch (from #638's plan and #733's analysis)
One speculative round (draft K + verify) per scheduler tick, re-entering the tick loop between rounds (round_loop.rs / round_loop_batched.rs are already per-round structured); per-sequence KV slots preserve state across interleaved classic decodes; the cross-tick lookahead pipeline (#632) is already excluded under speculative dispatch (lookahead_params returns None), and any new action type must call discard_lookahead like the Prefill action does; preferred end state is speculative rounds as first-class decide_action outcomes.
Acceptance criteria
Refs: deferred from #638 / PR #733.
Context
#638 part 2 asked to replace the run-to-completion speculative burst (
src/server/batch/speculative_burst.rs, dispatched fromexecute_prefill) with tick-cooperative slices so a speculative request no longer stalls every concurrent classic decode row for its whole duration (HOL block documented atsrc/server/model_worker.rs:400-438). PR #733 landed the measurement side (pairing matrix, adaptive-policy de-rate so GB10 declines regressing pairings, and aBurstFinalized.burst_wall_msobservability counter that measures the HOL stall) but deferred the slice itself.Blocker found (by #733)
MtpGenerator<T>borrows the model through its adapter, so the generator cannot be stashed across scheduler ticks (self-referential borrow); this is exactly why the burst runs to completion today. A resumable design needs a generator step API with stored per-round state (feasible:MtpVerifyOutputowns MLX handles, not model borrows) plus drafter re-seed on resume.Design sketch (from #638's plan and #733's analysis)
One speculative round (draft K + verify) per scheduler tick, re-entering the tick loop between rounds (
round_loop.rs/round_loop_batched.rsare already per-round structured); per-sequence KV slots preserve state across interleaved classic decodes; the cross-tick lookahead pipeline (#632) is already excluded under speculative dispatch (lookahead_paramsreturnsNone), and any new action type must calldiscard_lookaheadlike thePrefillaction does; preferred end state is speculative rounds as first-classdecide_actionoutcomes.Acceptance criteria
scripts/bench_serving_concurrency.py --concurrency 2).burst_wall_msdrops to about one round length.cargo test --features cudapasses.Refs: deferred from #638 / PR #733.