Commit ab79f56
committed
feat: anchored Eagle3 training
Add training.eagle3_num_anchors. When set, the TTT unroll is evaluated at N
sampled anchor positions per sample instead of at every supervised position,
so each depth costs O(N) rather than O(sequence length). Off by default; the
dense Eagle3 path is untouched.
The draft is a single layer, so its depth-0 keys and values are a pointwise
projection of the layer input and can be built for the whole sequence without
attention. Only the queries are gathered. KV is laid out as
[context | chain depth 1 | ...] with N slots per depth; an anchor sees the
context up to and including its own position plus its own slot in each chain
block, and anchors never see each other. test_context_rows_match_the_dense_
eagle3_mask asserts a row equals the dense Eagle3 row for that position.
Masking is a materialised [B, 1, N, KV] boolean passed to
scaled_dot_product_attention. Anchored attention is small enough that a
BlockMask costs more to build than the density it removes: at S=4096 with 4
depths the build alone is 2.1ms against 1.2ms for the whole SDPA path.
Anchor sampling and the per-depth mask use the target position mask when the
draft vocabulary is pruned, matching the dense path -- those positions have no
representable argmax, so training on them fits a renormalized target the draft
cannot emit. Anchoring rejects drafts whose attention has no k_proj/v_proj
(MLA projects through kv_a_proj_with_mqa/kv_b_proj and needs its own context-KV
path).
Anchors are sampled with a spacing cap. training.eagle3_anchor_max_gap is how
many supervised positions may sit between neighbours, so 0 puts them side by
side. It matters because anchor a supervises token a+d at depth d: a token is
seen at depth d only if the anchor d positions behind it was also picked, so
anchors g apart leave each token ttt_length/(g+1) of its depths, and once g
reaches ttt_length every token is pinned to a single depth. None (the default)
means block_size, which spreads anchors as evenly as the budget allows. Gaps
count supervised positions rather than raw tokens, so no slot is spent on an
unsupervised region and anchors stay token-adjacent inside a span.
Qwen3-8B, 10k PerfectBlend, 5 epochs, N=512: max_gap=0 reaches the dense
simulated acceptance length of 0.78 at 1.09x the samples/s, where sampling
without a cap plateaus at 0.47 from a third of the way in. The gap is not data
volume -- both see the same 512 slots and the same tokens -- it is that
isolated anchors never show a token more than one depth.
The anchor sampler moves out of models/dflash.py to models/ops/anchors.py so
both paths share it. DFlash picks up the default cap, which replaces its
uniformly random draw with an evenly strided one at the same density: same
expected depth coverage, without the isolated anchors that random gaps
produce.
Attention cost against dense TTT (TTT=4, N=256, one GB300, ms):
seq 4096 dense 1.78 anchored 1.12
seq 6144 dense 3.82 anchored 1.10
seq 32768 dense 75.95 anchored 4.24
seq 131072 dense 1134.5 anchored 15.89
test_dflash's test_accuracy_improves trained for 30 steps, which left the accuracy
in the noise: it improved on only 9 of 12 seeds, so the assertion turned on which
anchors happened to be drawn rather than on whether the model learned. Both the
old and new samplers score 9/12 there, so this is not a regression, but changing
the sampler shifts the RNG stream and moves which seeds land where. At 100 steps
every seed improves by at least 0.18 under either sampler.
Claude-Session: https://claude.ai/code/session_01CqPZAxkfxK7vTCbBQniBni1 parent 5f217f9 commit ab79f56
9 files changed
Lines changed: 1016 additions & 65 deletions
File tree
- configs
- tests
- tools
- torchspec
- config
- models
- ops
- training
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
0 commit comments