Skip to content

feat(qwen3): rejection sampling for speculative decoding (non-greedy requests) #512

Description

@xiaguan

Current state

Qwen3 speculative decoding (DFlash) is greedy-only. Acceptance is argmax equality, not distribution-correct rejection sampling:

  • accept_greedy in openinfer-qwen3/src/speculative.rs compares draft tokens against the target model's argmax at each position. The module doc explicitly notes: "Sampling-correct acceptance would need the target model's full distribution … it is left until a sampling method actually lands."
  • The executor hard-asserts greedy in openinfer-qwen3/src/executor/spec.rs (both draft and verify paths): speculative verification currently supports greedy sampling only.
  • The scheduler gates the speculative path via should_speculative_decode in openinfer-qwen3/src/scheduler/plan.rs, requiring params.is_greedy() for every active request. A single sampled request silently drops the whole batch back to plain decode.

Net effect: any request with temperature > 0 gets zero speedup from speculative decoding.

What's needed

Standard rejection sampling (Leviathan et al. 2023 / vLLM-style):

  1. Verify forward must expose the target distribution (post-temperature/top-p) at each of the K + 1 positions, not just argmax.
  2. Draft side must record its proposal probabilities q(x) per proposed token.
  3. Acceptance: accept draft token x with probability min(1, p(x) / q(x)); on rejection, resample from the normalized residual max(0, p − q) and stop.
  4. Bonus token at full acceptance is sampled from the target distribution at position K + 1.
  5. Relax the is_greedy() gates in the scheduler and executor; keep per-request SamplingParams respected within a batch.

Correctness bar: the output distribution must be provably identical to non-speculative sampling (the rejection-sampling guarantee), gated by a statistical test against plain decode at matching seeds/params.

Notes

  • Batch-level all-or-nothing gating in should_speculative_decode could also be revisited (mixed greedy/sampled batches), but that's a follow-up — per-request rejection sampling is the prerequisite.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions