nemo_automodel.components.speculative.dflash.core
nemo_automodel.components.speculative.dflash.core
DFlash online training wrapper.
Ported from SpecForge’s specforge/core/dflash.py. DFlashTrainerModule
samples a set of anchor positions per sequence, builds one parallel draft block
per anchor (the block’s first token is the real anchor token, the rest are
MASK), runs the draft model under a bespoke block attention mask, and
computes a block-wise cross-entropy loss against the ground-truth continuation
of each anchor.
Two training objectives are supported via loss_type:
"dflash"(default): the DFlash paper’s fixed-anchor objective. Only block position 0 is a real token; positions1..block_size-1are supervised with the decay-weighted CE of Eq. 4 (w_k = exp(-(k-1)/gamma))."variable_prefix": the D2SD VP-Drafter objective (arXiv:2606.04446). Each block draws a visible-prefix lengthlfrom a truncated geometric prior (Pr(l) ~ prefix_weight_base ** l), positions< lare filled with the real tokens, and only the masked positions>= lare supervised, with the decay re-anchored at the prefix boundary (w_k = exp(-(k-l)/gamma)). This trains the draft to re-draft block suffixes behind a variable accepted prefix, the regime a variable-prefix drafter sees at inference time.
Module Contents
Classes
Functions
Data
API
Per-step training outputs for the DFlash draft.
Bases: Module
DFlash online training wrapper with block-wise CE loss.
Per-block ground-truth tokens and the supervised-position mask.
Returns (label_indices, target_ids, block_mask) each of shape
[B, N, block_size]. label_indices[..., k] is the sequence position
block position k predicts (anchor + label_start + k); block_mask
is the product of block validity, in-bounds, and the gathered loss mask.
Shared by the block-wise trainers (DFlash, JetSpec, and Domino, which passes
label_start=1 for shift_label) so the label/mask gathering lives in
one place. Under packing, doc_remaining [B, S] truncates labels at
the anchor’s document boundary: anchor sampling only keeps offsets up to
block_size - 1 inside the anchor’s document, and a shifted label window
(label_start > 0) reaches one past that guarantee.
Embed each block as [anchor_token, MASK, MASK, ...] (invalid blocks all MASK).
Position ids for the parallel draft blocks (anchor position + offset).
Without packing the anchor’s position equals its row index, so the block
positions are anchor + offset. Under packing context_position_ids
[B, S] holds per-document reset positions, so the block’s base position
is gathered from it at the anchor (context_position_ids[anchor] + offset)
to keep the draft’s RoPE phase document-local.
Embed the draft blocks with a visible prefix of real tokens, then MASK.
The variable-prefix analogue of :meth:_create_noise_embed: block
positions < prefix_lengths hold the real sequence tokens, the rest
(and every position of an invalid block) hold MASK.
Parameters:
Long tensor of shape [batch, sequence].
Long tensor of shape [batch, blocks]; each block’s
start position in the sequence.
Bool tensor of shape [batch, blocks]; invalid
(padding) blocks are embedded as all MASK.
Long tensor of shape [batch, blocks]; this block’s
visible-prefix length.
Returns:
Tensor of shape [batch, blocks * block_size, hidden].
Shared block-drafting prologue: anchors, noise embedding, positions, mask.
Centralises the sequence-packing handling for every block-wise trainer (DFlash and its Domino / JetSpec subclasses): anchors are sampled so each block stays inside one document, the block’s context prefix is restricted to its anchor’s document, and the draft RoPE uses per-document positions.
Under loss_type="variable_prefix" the block embedding shows a sampled
real-token prefix (_create_vp_noise_embed) instead of the single anchor
token, and the sampled prefix_lengths are returned for the loss; every
other loss_type returns prefix_lengths=None.
Parameters:
[B, S] context token ids (long).
[B, S] supervised-token mask.
[B, S] per-document reset positions (packing), or None.
[B, max_docs] packed document lengths, or None (unpacked).
[B, S] remaining real tokens of each position’s document.
When True, build the in-block-causal (JetSpec) mask instead of the bidirectional (DFlash / Domino) one.
Returns: torch.Tensor
“(anchor_positions [B, N], block_keep_mask [B, N], noise_embedding
Randomly sample anchor positions per sample; returns (anchors, keep_mask).
doc_remaining [B, S] (sequence packing) restricts anchors so the
whole block stays inside one document (doc_remaining >= block_size - 1),
the per-document analogue of the anchor <= seq_len - block_size bound.
This is required for correctness — _build_block_targets gathers labels
by absolute offset and does not encode document boundaries, so a block that
crossed one would be supervised on the next document’s tokens. A side effect
is that a packed document shorter than block_size yields no anchors (the
unpacked path still supervises such a short sequence’s partial block); pack
with documents at least block_size long to avoid dropping their signal.
Sample a visible-prefix length per block for variable-prefix training.
A prefix length l means block positions [0, l) are visible real
tokens and positions [l, block_size) are masked prediction targets.
Lengths follow D2SD’s truncated geometric prior Pr(l) ~ base ** l over
[min(2, block_size - 1), block_size - 1]; a base below 1 biases toward
short prefixes. The lower bound skips the degenerate fixed-anchor DFlash
case, and the upper bound keeps at least one masked target per block.
Returns: torch.Tensor
Long tensor of shape [batch, blocks].
Decay-weighted CE over each block’s masked suffix (D2SD Eq. for L_VP).
Only positions at or past the sampled visible prefix are supervised, and
the exponential decay restarts at the prefix boundary:
w_k = exp(-(k - l) / gamma) for block position k >= l
(loss_decay_gamma=None disables decay). The loss is the weighted mean
sum(nll * w) / sum(w), mirroring the fixed-anchor path’s
normalize="mean". Assumes every prefix_lengths entry is at least
min(2, block_size - 1) (what :meth:_sample_prefix_lengths produces),
so the leading always-visible positions can be sliced off before the CE.
Parameters:
Tensor of shape [batch, blocks, block_size, vocab].
Long tensor of shape [batch, blocks, block_size]; the
ground-truth token at anchor + k for block position k.
Tensor of shape [batch, blocks, block_size]; 0/1
product of block validity, in-bounds, and the loss mask.
Long tensor of shape [batch, blocks]; this block’s
visible-prefix length.
Returns: DFlashStepMetrics
DFlashStepMetrics with the weighted-mean loss, the argmax accuracy
Parallel block-wise training forward pass.
Sequence packing (position_ids [B, S] per-document reset positions,
seq_lens [B, max_docs] document lengths, doc_remaining [B, S])
keeps every block inside one document: anchors are constrained so the block
does not cross a boundary, the block’s context prefix attends only within the
anchor’s document, and the draft’s RoPE uses the per-document positions.
Bases: ValueError
Raised when a batch has no sample long enough to form a DFlash block.
A DFlash anchor needs at least block_size + 1 supervised tokens (the
anchor plus its block). Datasets always contain some short conversations;
the training loop catches this and skips the offending micro-batch rather
than aborting the run.
Per-context-token document id [B, S] from packed seq_lens [B, max_docs].
Mirrors the doc_id construction in build_block_causal_additive_mask: a
token’s id is the number of document boundaries at or before its position, so
0-length padding entries never split a real document.
Materialise a (possibly tensor-parallel) tensor as a plain local tensor.
Under tensor parallelism the target’s column-parallel lm_head and
vocab-parallel embed_tokens return DTensor outputs. The draft and the
block-wise loss consume plain tensors, so gather the full tensor. A no-op for
an already-plain (unsharded / replicated) tensor.