nemo_automodel.components.speculative.dflash.draft_qwen3
nemo_automodel.components.speculative.dflash.draft_qwen3
DFlash draft model (Qwen3-style).
Ported from SpecForge’s specforge/modeling/draft/dflash.py. DFlash drafts a
whole block of block_size tokens in parallel: the block’s first position
holds the real anchor token and the rest are MASK tokens, and the draft
predicts the whole block in a single non-causal forward conditioned on the
target model’s context hidden states.
The draft attention is therefore not causal — a draft block’s queries
attend to (a) the projected target-hidden context strictly before its anchor and
(b) bidirectionally to the other (noise) tokens of the same block. The attention
mask that enforces this is built by the trainer wrapper in
nemo_automodel.components.speculative.dflash.core.
Module Contents
Classes
Functions
Data
API
Bases: Module
Non-causal attention whose keys/values are [context | noise-block].
Queries come from the draft (noise) tokens only; keys and values are the
concatenation of the projected target-hidden context and the noise tokens.
The bidirectional/block structure is supplied entirely by attention_mask.
Bases: GradientCheckpointingLayer
A DFlash decoder block: non-causal attention over [context | noise] + MLP.
Bases: Qwen3PreTrainedModel
DFlash draft model: a small non-causal Qwen3 stack over [context | noise].
Keep the RoPE inv_freq buffer in fp32 across dtype casts.
Qwen3RotaryEmbedding computes the rotary angles in fp32 but reads the
frequencies from a stored inv_freq buffer. model.to(bfloat16) — the
training build path — rounds that buffer to bf16, whereas the serving
runtime (SGLang keeps an fp32 RoPE cache) and HF’s from_pretrained reload
keep it in fp32. The resulting train/inference RoPE mismatch grows with
absolute position (the bf16 frequencies dephase) and erodes draft
acceptance, so inv_freq must stay fp32 on both the training and reload
paths. A bf16 round-trip cannot be undone by upcasting, so when a cast
rounds the buffer we recompute fresh fp32 frequencies from the rotary
config (the same values HF derives on the fp32 paths) instead of upcasting
the corrupted ones.
Project draft hidden states to logits, applying the target’s output transform.
Some targets do not read their lm_head output raw: Muse Glimmer scales
it by output_multiplier and squashes it through
final_logit_softcapping. The draft is trained against, and verified
by, those transformed logits, so both training and decoding have to apply
them — otherwise the draft learns one distribution and is served another.
Both fields live in dflash_config; absent (every Qwen3-family target),
this is just the head.
Parameters:
Tensor of shape […, hidden]; draft hidden states with arbitrary leading dimensions.
The frozen target’s output projection.
Returns: torch.Tensor
Tensor of shape […, vocab].
Embed a draft block with the target’s (frozen, shared) input embedding.
Applies input_embedding_scale from dflash_config — 1.0, hence a
no-op, for every Qwen3-family target. Unlike the reference, this calls the
embedding module rather than indexing its weight table directly: under
tensor parallelism the target’s embed_tokens is vocab-parallel and only
the module call carries the DTensor plan.
Parameters:
The frozen verifier.
Long tensor of shape [batch, draft]; the block’s token ids,
position 0 holding the verified anchor and the rest MASK.
Returns: torch.Tensor
Tensor of shape [batch, draft, hidden].
Block-parallel speculative decoding: draft a block, verify with the target, accept the matching prefix.
top_p / top_k truncate the target’s distribution, which is what
defines the emitted tokens; the draft proposes from plain temperature.
Parameters:
The frozen verifier.
Long tensor of shape [1, prompt].
Maximum number of tokens to generate.
Token ids that end generation, or None.
Sampling temperature; 0 decodes greedily.
Nucleus mass to keep, in (0, 1].
Candidates to keep, 0 for the whole vocabulary.
Returns: torch.LongTensor
Long tensor of shape [1, prompt + generated].
Band mask keeping keys within sliding_window of each query’s position.
The draft’s queries are the trailing q_len positions of the concatenated
[context | noise-block] key axis, so a query at row i sits at key
position k_len - q_len + i. Both bounds are strict, matching transformers’
sliding_window_overlay and the reference DFlash decode mask. The draft is
non-causal, so the band is symmetric; the forward half is inert in practice
because the block is far shorter than any real window.
Parameters:
Tensor of shape [batch, heads, query, head_dim].
Tensor of shape [batch, kv_heads, key, head_dim], where key
spans the context followed by the draft block.
Maximum absolute position distance, exclusive.
Returns: torch.Tensor
Floating-point tensor of shape [1, 1, query, key], in query.dtype;
Apply RoPE where queries (draft block) are a suffix of the key positions.
The keys span [context | noise-block] while the queries are only the
noise block, so q is rotated with the trailing q_len slice of the
rotary tables and k with the full table.
Reject targets whose cache cannot be rewound after a rejected block.
Speculative decoding verifies a whole block and then rewinds the target to
the accepted prefix. For attention layers that is just dropping KV rows, but
a linear-attention layer keeps a recurrent state that has already absorbed
the rejected tokens; Cache.crop truncates the KV entries and leaves that
state where it is. The target then predicts from a corrupted state, so the
“lossless” guarantee quietly stops holding — greedy decoding drifts away
from the target’s own output instead of reproducing it.
Training is unaffected: it is a single forward with no cache and no rewind.
Parameters:
The frozen verifier.
Raises:
ValueError: If the target has any layer whose state cropping cannot rewind.
Build the DFlash draft config for a Qwen3-shaped target.
The draft is a small non-causal Qwen3 stack that reuses the target’s architecture defaults (head_dim, rope_theta, rms_norm_eps, …) and only shrinks the depth.
Parameters:
The target’s config.
Number of draft decoder layers.
Depth of the target; recorded so a reloaded draft config still describes which target it was trained on.
DFlash block size.
The recipe’s DFlash block (mask_token_id,
target_layer_ids, and any subclass extras such as Domino’s
projector fields).
The draft’s attention implementation; must agree with the mask format the trainer builds.
Returns: Qwen3Config
The draft Qwen3Config.
Pick num_draft_layers target layers spread across the target’s depth.
Concatenate the selected target layers’ hidden states along the feature dim.
hidden_states follows HF’s output_hidden_states convention where
index 0 is the embedding output, so layer i’s output is at index
i + 1.
Return the target’s output projection.
Prefers lm_head and falls back to get_output_embeddings(), which is
what a *ForConditionalGeneration target exposes instead.
Parameters:
The frozen verifier.
Returns: nn.Module
The module projecting hidden states to vocabulary logits.
Greedy (temperature == 0) or truncated-distribution sampling over the last dim.
Parameters:
Tensor of shape […, vocab]; raw scores, with arbitrary leading dimensions.
Sampling temperature; 0 selects greedily.
Nucleus mass to keep, in (0, 1].
Candidates to keep, 0 for the whole vocabulary.
Returns: torch.Tensor
Long tensor of shape […]; the sampled token id per position.
Full-vocabulary sampling distribution after temperature, top-k, and top-p.
Speculative decoding needs the distribution, not just a draw: rejection
sampling compares the target’s probability of a drafted token against the
draft’s, so both sides must be built the same way. Filtered-out tokens keep a
probability of exactly 0 so the residual clamp(p - q, 0) stays correct.
Parameters:
Tensor of shape […, vocab]; raw scores, with arbitrary leading dimensions.
Sampling temperature; must be > 0.
Nucleus mass to keep, in (0, 1].
Candidates to keep, 0 for the whole vocabulary.
Returns: torch.Tensor
Tensor of shape […, vocab] in float32; a probability distribution over
Reject sampling parameters the decode path cannot honor.
Parameters:
Sampling temperature; 0 means greedy.
Nucleus mass to keep, in (0, 1].
Candidates to keep, 0 for the whole vocabulary.
Raises:
ValueError: If any parameter is outside its supported range.