Skip to content

vllm.model_executor.models.qwen3_dspark

Qwen3 DSpark draft model for semi-autoregressive drafting.

DSpark drafts a whole block in one parallel pass (DFlash-style: context-KV precompute + a non-causal query-block forward) and then injects intra-block dependency with a lightweight sequential Markov head.

The parallel backbone is a standard Qwen3 decoder stack reused from the DFlash Qwen3 draft (see qwen3_dflash.py). DSpark adds: * markov_head: low-rank V x r / r x V transition bias added to the base logits, sampled left-to-right by the speculator (the sequential stage).

DSparkMarkovHead is shared with the DSV4-style DSpark model.

Classes:

DSparkMarkovHead

Bases: Module

Sequential transition-bias head (low-rank V x r, r x V).

markov_w1[token] embeds the previously sampled token (target vocab, vocab_size); markov_w2 projects it to a draft-vocab bias (draft_vocab_size) added to the base draft logits. The two sizes coincide for full-vocab drafts.

Both weights are replicated because the head runs sequentially for every draft position. Sharding them would add an all-reduce and a full-vocab gather to each position.

Methods:

  • apply_bias_gathered

    Apply the Markov bias only to selected rows of logits.

  • bias

    Vocab-size transition bias from a Markov embedding ([B, r] -> [B, V]).

  • embed

    r-dim Markov embedding of token_ids ([B] -> [B, r]).

Source code in vllm/model_executor/models/qwen3_dspark.py
class DSparkMarkovHead(nn.Module):
    """Sequential transition-bias head (low-rank V x r, r x V).

    ``markov_w1[token]`` embeds the previously sampled token (target vocab,
    ``vocab_size``); ``markov_w2`` projects it to a draft-vocab bias
    (``draft_vocab_size``) added to the base draft logits. The two sizes
    coincide for full-vocab drafts.

    Both weights are replicated because the head runs sequentially for every
    draft position. Sharding them would add an all-reduce and a full-vocab
    gather to each position.
    """

    def __init__(
        self,
        vocab_size: int,
        draft_vocab_size: int,
        markov_rank: int,
        prefix: str,
        quant_config: QuantizationConfig | None = None,
    ) -> None:
        super().__init__()
        self.markov_w1 = nn.Embedding(vocab_size, markov_rank)
        self.markov_w2 = ParallelLMHead(
            draft_vocab_size,
            markov_rank,
            bias=False,
            quant_config=quant_config,
            prefix=maybe_prefix(prefix, "markov_w2"),
            disable_tp=True,
        )

    def embed(self, token_ids: torch.Tensor) -> torch.Tensor:
        """r-dim Markov embedding of ``token_ids`` ([B] -> [B, r])."""
        return self.markov_w1(token_ids)

    def bias(
        self,
        markov_embed: torch.Tensor,
        logits_processor: LogitsProcessor,
    ) -> torch.Tensor:
        """Vocab-size transition bias from a Markov embedding ([B, r] -> [B, V])."""
        return logits_processor(self.markov_w2, markov_embed)

    def apply_bias_gathered(
        self,
        markov_embed: torch.Tensor,
        logits: torch.Tensor,
        values: torch.Tensor,
        index: torch.Tensor,
        scale: float = 1.0,
    ) -> torch.Tensor:
        """Apply the Markov bias only to selected rows of ``logits``.

        The caller initializes ``logits`` to ``-inf`` once for all draft
        positions. This method scatters the corrected candidate values into
        that dense buffer so the normal sampler sees the truncated proposal.
        """
        weight = self.markov_w2.weight[index]
        corrected = values.unsqueeze(-1)
        corrected.baddbmm_(
            weight,
            markov_embed.unsqueeze(-1),
            beta=1.0,
            alpha=scale,
        )
        return logits.scatter_(1, index, corrected.squeeze(-1))

apply_bias_gathered(markov_embed, logits, values, index, scale=1.0)

Apply the Markov bias only to selected rows of logits.

The caller initializes logits to -inf once for all draft positions. This method scatters the corrected candidate values into that dense buffer so the normal sampler sees the truncated proposal.

Source code in vllm/model_executor/models/qwen3_dspark.py
def apply_bias_gathered(
    self,
    markov_embed: torch.Tensor,
    logits: torch.Tensor,
    values: torch.Tensor,
    index: torch.Tensor,
    scale: float = 1.0,
) -> torch.Tensor:
    """Apply the Markov bias only to selected rows of ``logits``.

    The caller initializes ``logits`` to ``-inf`` once for all draft
    positions. This method scatters the corrected candidate values into
    that dense buffer so the normal sampler sees the truncated proposal.
    """
    weight = self.markov_w2.weight[index]
    corrected = values.unsqueeze(-1)
    corrected.baddbmm_(
        weight,
        markov_embed.unsqueeze(-1),
        beta=1.0,
        alpha=scale,
    )
    return logits.scatter_(1, index, corrected.squeeze(-1))

bias(markov_embed, logits_processor)

Vocab-size transition bias from a Markov embedding ([B, r] -> [B, V]).

Source code in vllm/model_executor/models/qwen3_dspark.py
def bias(
    self,
    markov_embed: torch.Tensor,
    logits_processor: LogitsProcessor,
) -> torch.Tensor:
    """Vocab-size transition bias from a Markov embedding ([B, r] -> [B, V])."""
    return logits_processor(self.markov_w2, markov_embed)

embed(token_ids)

r-dim Markov embedding of token_ids ([B] -> [B, r]).

Source code in vllm/model_executor/models/qwen3_dspark.py
def embed(self, token_ids: torch.Tensor) -> torch.Tensor:
    """r-dim Markov embedding of ``token_ids`` ([B] -> [B, r])."""
    return self.markov_w1(token_ids)

Qwen3DSparkModel

Bases: DFlashQwen3Model

DFlash Qwen3 backbone + DSpark Markov head.

Source code in vllm/model_executor/models/qwen3_dspark.py
class Qwen3DSparkModel(DFlashQwen3Model):
    """DFlash Qwen3 backbone + DSpark Markov head."""

    def __init__(
        self,
        *,
        vllm_config: VllmConfig,
        start_layer_id: int = 0,
        prefix: str = "",
    ) -> None:
        super().__init__(
            vllm_config=vllm_config, start_layer_id=start_layer_id, prefix=prefix
        )
        config = self.config
        draft_vocab_size = (
            getattr(config, "draft_vocab_size", None) or config.vocab_size
        )
        self.markov_head = DSparkMarkovHead(
            config.vocab_size,
            draft_vocab_size,
            config.markov_rank,
            prefix=maybe_prefix(prefix, "markov_head"),
            quant_config=self.quant_config,
        )