nemo_automodel.components.speculative.dflash.draft_qwen3_dflash2
nemo_automodel.components.speculative.dflash.draft_qwen3_dflash2
DFlash 2 draft model (Qwen3-style).
DFlash 2 (https://inco.ai/blog/dflash2/) keeps DFlash’s one-pass parallel block draft — the block is still predicted in a single non-causal forward — and adds two cheap modules on top of it:
-
A two-tap dynamic depthwise convolution before and after each attention and MLP sublayer of every draft layer::
Conv(x)t = k{t,0} * x_t + k_{t,1} * x_{t-1}
Each coefficient is a learned base kernel plus a small correction predicted from the current hidden state and shared across
conv_group_sizechannels. The taps never cross a draft-block boundary, so block position 1 reads block position 0 — the last verified (anchor) token — and block position 0 reads zero padding. This moves the short-range within-block work off attention (which goes back to reading the target context) and removes most of DFlash’s suffix decay: prediction quality at the end of the block, for ~3% extra parameters. -
A pairwise path selector. DFlash picks every position’s top-1 candidate independently, so neighbours can disagree (a repeated word, a broken phrase) and the block is cut short at verification — even though the right token is usually already in the position’s candidate list. The selector keeps each position’s top
selector_top_kcandidates and scores every adjacent pair in one shot::S_t(a, b) = U_t(b) + <A(a) * H(h_t), B(b)>
U_t(b)is DFlash’s own logit for candidateb;AandBare rank-selector_ranktoken codebooks matched under a context gateH(h_t)— a low-rank bilinear score over adjacent candidates. Scoring is fully parallel; only the final walk over the precomputed scores is sequential, and it touches no backbone or LM head.
Both modules are initialised to the identity (zero conv correction, zero successor codebook), so a freshly constructed DFlash 2 draft starts out numerically equal to plain DFlash and training moves it away from there.
Module Contents
Classes
Functions
API
Bases: Module
Pairwise path selector over each draft position’s top-k candidates.
Scores S_t(a, b) = U_t(b) + <A(a) * H(h_t), B(b)> for predecessor token
a and candidate b: DFlash’s own logit plus a low-rank bilinear match
between the two tokens’ codebook embeddings, gated by the draft hidden state.
The codebooks are plain [vocab, rank] parameters rather than
nn.Embedding modules so the saved keys are candidate_selector.*_codebook
— the names the published DFlash 2 drafters and the serving runtimes use —
instead of candidate_selector.*_codebook.weight.
Parameters:
Size of the (shared with the target) token vocabulary.
Channel count of the draft hidden states.
Codebook / gate width (selector_rank; 256 in DFlash 2).
Candidates kept per position (selector_top_k; 16 in DFlash 2).
Score every (predecessor, candidate) pair for a batch of draft positions.
Fully parallel: no position depends on another’s score. Training calls this
once with the ground-truth predecessors; the decode-time walk in
:meth:walk calls it one position at a time with the token it just picked.
Parameters:
Tensor of shape […, hidden]; the draft hidden state at each scored position, with arbitrary leading dimensions.
Tensor of shape […, candidates]; U_t(b), the draft logit of
each candidate, with the same leading dimensions as hidden.
Long tensor of shape […, candidates]; the candidate
token ids, with the same leading dimensions as hidden.
Long tensor of shape […]; the token preceding each
scored position, with the same leading dimensions as hidden.
Returns: torch.Tensor
Tensor of shape […, candidates] holding S_t(a, b).
Reset the selector to a no-op: every score collapses to the draft logit.
S_t is bilinear in the two codebooks, so collapsing it needs one factor
at zero. Zeroing the successor codebook does that; it still receives
gradient immediately, while the predecessor codebook and the context gate
multiply it and therefore only start training on the second step.
Trace one coherent path through the per-position candidate lists.
Greedy (temperature == 0) follows the best successor at each step;
otherwise the step is sampled from the softmax over the candidate scores,
and the returned per-step distribution is the draft proposal q that
:func:dflash2_rejection_sample needs to stay lossless.
Parameters:
Tensor of shape [batch, draft, hidden]; the draft hidden states of the block’s predicted positions.
Tensor of shape [batch, draft, vocab]; the draft logits at those positions.
Long tensor of shape [batch]; the last verified token, i.e. the predecessor of draft position 0.
Sampling temperature; 0 selects greedily.
Returns: torch.Tensor
Tuple (path, candidate_ids, draft_probs): path is a Long tensor
Bases: Module
Two-tap dynamic depthwise convolution wrapped around one draft sublayer.
One instance covers the convolution before a sublayer (:meth:prepare) and
the one after it (:meth:finish). Both sets of dynamic coefficients are
predicted from the sublayer’s input, so :meth:prepare returns the
coefficients :meth:finish needs and the projection runs once per sublayer.
Parameters:
Channel count of the draft hidden states.
Number of taps; DFlash 2 uses 2 (self and predecessor).
Channels sharing one dynamic correction (16 in DFlash 2).
Apply the post-sublayer convolution using the coefficients from :meth:prepare.
Parameters:
Tensor of shape [batch, sequence, hidden]; the sublayer output.
Tensor of shape [batch, sequence, kernel, groups]; the second
half of :meth:prepare’s coefficients.
Draft-block length; taps never cross a block boundary.
Returns: torch.Tensor
Tensor of shape [batch, sequence, hidden].
Apply the pre-sublayer convolution and emit the post-sublayer coefficients.
Parameters:
Tensor of shape [batch, sequence, hidden]; the sublayer input.
Draft-block length; taps never cross a block boundary.
Returns: torch.Tensor
Tuple of (convolved, dynamic): convolved is a Tensor of shape
Reset the convolution to the identity: unit self-tap, no correction.
Bases: Qwen3DFlashDecoderLayer
A DFlash 2 decoder block: DFlash’s block + a two-tap conv around each sublayer.
Run the block, convolving the input and output of each sublayer.
Parameters:
Tensor of shape [batch, context, hidden]; the projected target-model context the attention keys/values are extended with.
Tensor of shape [batch, draft, hidden]; the draft
(noise-block) positions, draft being a whole number of
conv_block_size-long blocks.
Attention mask over [batch, 1, draft, context + draft],
a flex BlockMask, or None.
Long tensor of shape [batch, context + draft].
Draft KV cache, or None.
Whether to write into past_key_value.
Long tensor of shape [draft], or None.
Tuple of rotary (cos, sin) tensors of shape
[batch, context + draft, head_dim].
Draft-block length; the convolutions’ predecessor tap never crosses a block boundary.
Forwarded to the attention implementation.
Returns: torch.Tensor
Tensor of shape [batch, draft, hidden].
Bases: Qwen3DFlashDraftModel
DFlash 2 draft model: the DFlash stack plus in-block convs and a path selector.
Run the DFlash 2 draft stack over [context | noise-block].
Parameters:
Long tensor of shape [batch, context + draft].
Attention mask over [batch, 1, draft, context + draft],
a flex BlockMask, or None.
Tensor of shape [batch, draft, hidden]; the embedded
[anchor, MASK, ...] blocks laid end to end.
Tensor of shape [batch, context, layers * hidden]; the concatenated target-model context features.
Draft KV cache, or None.
Whether to write into past_key_values.
Draft-block length for the in-block convolutions; see
:meth:resolve_conv_block_size for how None is resolved.
Forwarded to the attention implementation.
Returns: torch.Tensor
Tensor of shape [batch, draft, hidden]; the normalised draft hidden
Resolve the block length the in-block convolutions must not reach across.
Parameters:
Number of draft (noise-block) query positions in this call.
Explicit block length, or None to infer it.
Returns: int
The block length to convolve within. None resolves to
Raises:
ValueError: Ifconv_block_sizedoes not dividequery_len.
Block-parallel speculative decoding with pairwise path selection.
Each cycle drafts one block in a single draft forward, walks the selector
over the per-position candidates to pick a coherent path, and verifies the
whole block with one target forward. temperature == 0 accepts the
longest exact-match prefix; temperature > 0 accepts via rejection
sampling, so the emitted tokens follow the target’s own distribution.
Parameters:
The frozen verifier; must expose model.embed_tokens,
lm_head, and an HF-style forward with output_hidden_states.
Long tensor of shape [1, prompt].
Maximum number of tokens to generate.
Token ids that end generation, or None.
Sampling temperature; 0 decodes greedily.
Returns: torch.LongTensor
Long tensor of shape [1, prompt + generated] containing the prompt
Depthwise convolution over draft-block positions with content-adaptive taps.
Tap offset reads position t - offset of the same draft block; the
leading offset positions of every block read zero padding instead of the
previous block’s tail, which is what keeps the packed blocks * block_size
training layout equivalent to drafting one block at a time.
Parameters:
Tensor of shape [batch, sequence, hidden]; sequence is a whole
number of block_size-long draft blocks laid end to end.
Tensor of shape [batch, sequence, kernel, groups]; the per-position
correction added to base_kernel, shared by the group_size
channels of each group.
Tensor of shape [kernel, hidden]; the learned base taps.
Number of channels sharing one dynamic correction.
Draft-block length; taps never cross a block boundary.
Returns: torch.Tensor
Tensor of shape [batch, sequence, hidden]; a fresh tensor that neither
Accept a prefix of the drafted block and resample the first rejected token.
Standard speculative-decoding rejection sampling, specialised to a proposal
supported on the selector’s candidate set: q(token) is read out of
draft_probs by matching candidate_ids, and the residual
clamp(p - q, 0) is formed by scattering -q into the full-vocabulary
target distribution. The accepted tokens plus the resampled one are therefore
distributed exactly as the target’s own samples.
Parameters:
Long tensor of shape [1, draft]; the selector’s path.
Tensor of shape [1, block, vocab]; the verifier’s
next-token distribution at each block position, where
block == draft + 1.
Tensor of shape [1, draft, candidates]; the proposal mass on each candidate.
Long tensor of shape [1, draft, candidates]; the candidate token ids the proposal is supported on.
Returns: int
Tuple (accepted, bonus): accepted is the number of drafted tokens