nemo_automodel.components.speculative.eagle.core
nemo_automodel.components.speculative.eagle.core
Core EAGLE-3 training logic for the minimal Llama MVP.
Module Contents
Classes
Functions
Data
API
Aggregated metrics from one EAGLE-3 training step.
step_prefix_hits / step_valid are [ttt_steps] per-TTT-step
counts for the simulated accept length (:func:simulated_accept_length):
step_prefix_hits[k] counts positions whose greedy draft chain is still
fully correct through depth k + 1 (top-1 hit at every step <= k),
and step_valid[k] counts positions supervised at every step <= k
under the shifted loss/document masks, so numerator and denominator cover
the same chain population (a chain with an unsupervised earlier depth,
e.g. from a gappy multi-turn loss mask, can never be a hit and must not
count as a miss). step_valid deliberately does NOT exclude positions
whose target token falls outside the compressed draft vocabulary: serving
must reject those tokens (the draft cannot emit them), so they count as
chain breaks instead of dropping out of the denominator. Both are kept
unreduced so the recipe can accumulate them over a logging window. The
P-EAGLE trainer, whose depths are drafted in parallel from one anchor
rather than as a TTT chain, leaves them None.
Bases: Module
Draft-side EAGLE-3 trainer module with test-time-training unroll.
One TTT step of the LK acceptance-rate loss (arXiv:2602.23881).
The per-token expected acceptance under speculative sampling is the
min-overlap of the two distributions, alpha = sum_v min(p_target, p_draft),
computed over the draft vocabulary (both inputs already live there).
"alpha": the pure acceptance likelihood-mean(log alpha); the log is taken per token before the masked mean, with zero-acceptance tokens contributing zero (they carry no usable gradient direction)."lambda": the adaptive hybridkl_weight * soft_ce + (1 - kl_weight) * (1 - mean(alpha))withkl_weight = lk_kl_scale * exp(-lk_kl_decay * mean(alpha).detach()); the weight is detached so gradients flow only through the two terms, and training shifts from distillation toward direct acceptance as the draft improves. A step with zero supervised positions returns 0, matching the soft-CE path.
Under CP every masked mean is renormalized over the full sequence via
:func:_cp_global_step_loss, so the mixing weight and the loss match the
single-rank run.
Parameters:
Tensor of shape [batch, sequence, draft_vocab]; draft logits.
Tensor of shape [batch, sequence, draft_vocab]; target
distribution restricted to the draft vocabulary.
Bool tensor of shape [batch, sequence, 1]; True where
the position is supervised this step.
Returns: torch.Tensor
Scalar loss tensor for this TTT step.
Run the EAGLE-3 unrolled draft loss for one batch.
The attention layer is driven through a shared cache_hidden
list so each TTT step can attend to the K/V branches produced by
every previous step at the same position. This matches the
SpecForge llama3_eagle.py recurrence; without it, multi-step
TTT would degenerate into ttt_steps independent single-step
passes and the draft would never learn the multi-step
distribution it sees at deployment time.
attention_mask is held constant across TTT steps — only
input_ids / loss_mask / position_mask /
target_probs roll forward by one position per step.
Packing: position_ids / seq_lens make the draft’s Block-1 attention
document-level block-causal, and doc_remaining gates supervision per
step (slot t valid at step k only while k < doc_remaining[t]),
masking every cross-document TTT prediction.
Two supervision sources are accepted: the live path passes the
target’s full-vocab target_logits and the draft distribution is
derived here; the offline-cache path (precompute_eagle3) passes the
already-derived target_probs (over the draft vocab) and
position_mask directly, so the full-vocab logits never have to be
stored. Provide exactly one of the two.
Bases: Function
Differentiable sum-all-reduce across the cp group.
Forward sums per-rank inputs into a replicated total. Each rank uses that total identically, so the loss gradient w.r.t. this rank’s input is just the incoming grad (coefficient 1) — hence the identity backward.
Project target logits into draft vocabulary space and build supervision mask.
Renormalize a per-shard masked-mean loss over the full cp sequence.
step_loss is the mean over this rank’s LOCAL supervised positions. Recover the
local sum (mean * count), sum both across cp (the sum differentiably), and divide
by the global count — so the loss value equals the full-sequence loss and the
backprop’d gradient is the global gradient.
Left-shift a context-parallel-sharded sequence tensor by one position.
Each rank holds a contiguous S/cp shard. A plain left-shift would zero-fill
the boundary, but the token that rolls into rank r’s tail lives at rank
r+1’s head. Shift locally, then P2P the neighbour’s (current) head into the
tail; the last rank keeps the zero fill. Applied in lockstep each TTT step this
reproduces the global shift exactly. Used for labels / input_ids only (no grad).
Left-shift a ZIG-ZAG-sharded sequence tensor by one position.
Under zig-zag sharding rank r holds two non-contiguous global chunks —
the early chunk r and the late chunk 2*cp-1-r — laid out locally as
[early | late] (each of length c = local_len/2). A global left-shift
therefore rolls each half locally and needs two boundary tokens:
- the early half’s tail (global end of chunk
r) is the head of chunkr+1— rankr+1’s early head — except on the last rank, where chunkr+1is that rank’s OWN late chunk, so the fill is local. - the late half’s tail (global end of chunk
2*cp-1-r) is the head of chunk2*cp-r— rankr-1’s late head — except on rank 0, whose late tail is the global last position and stays zero-filled.
Two ring P2P exchanges (early head -> r-1, late head -> r+1) cover both.
Used for labels / input_ids only (no grad). Applied in lockstep each TTT step
this reproduces the global shift exactly. See :func:_cp_shift_left for the
contiguous-shard analogue.
Mean of values over the supervised positions.
Parameters:
Tensor of shape [batch, sequence]; a per-position quantity.
Bool tensor of shape [batch, sequence, 1]; True where
the position is supervised.
Returns: torch.Tensor
Scalar tensor; the masked mean (zero when no position is supervised).
Shift a batched sequence tensor left and zero-fill the tail.
Expected accepted tokens per speculative round, from prefix-hit counts.
Models greedy chain drafting: step_prefix_hits[k] / step_valid[k]
estimates the joint probability that the first k + 1 drafted tokens
all match the target’s greedy choices, i.e. that the chain survives depth
k + 1, so the expectation is 1 + sum_k P(survives depth k + 1).
Joint prefix counts keep the correlation between depths that a product of
per-step marginal accuracies discards (coincident and disjoint hits score
differently). The leading 1 counts the token the target itself emits on
every verification round, matching the accept_length convention of
the serving benchmarks (1 + accepted/drafts). A step with no
supervised positions contributes zero via the clamp_min. This is a
training-time proxy for greedy chain decoding; engine tree drafting
typically accepts more.