feat(SPEC-DFLASH2): the path walk, so a DFlash2 draft drafts — on a GPU, with the walk's shape contract gated (#1314, #1501) - #1515
Merged
Conversation
…draft DRAFTS (#1314, #1501) Wave W4 of `SPEC-DFLASH2` ([#1314](#1314)): the device path walk, the refusal it retires, and the guard that replaces it pointing the other way. Rows `KERNEL-DFLASH2-PATH-WALK` (new), `KERNEL-DFLASH2-SELECTOR-EDGES` and `KERNEL-TOPK-PAIRS`. **One new `vt` op.** `Dflash2PathWalk` turns the selector's lattice into k tokens: start at the verified anchor -- already every predecessor slot of step 0 -- take the best child, then read the NEXT step's block at the predecessor row just chosen. It runs in UPSTREAM'S OWN GRID, one program per request with the step loop INSIDE it, which is spec `## Risks/decisions` D3's requirement rather than a preference: the identical sequential walk shipped host-side in DSpark and measured 28% of the 27B draft step (#436) before `SampleSequentialDevice` moved it. Unlike W3's lattice op it is specified BIT-EXACT across backends, because it performs no arithmetic at all -- only comparisons and one gather. Two contract points decide tokens and both are pinned by literals: a tie resolves to the LOWEST slot, which then selects the next step's PREDECESSOR row and so moves the whole remaining path rather than one token; and an all `-inf` row -- upstream's fully masked lane -- resolves to slot 0 rather than to "no index", which the natural parallel formulation does not give unless the seed is named. **The refusal is gone, and what replaces it points the other way.** W1 refused before any weight was read, W2 at the candidate selector, W3 at the path walk. Nothing on the greedy arm is missing now, so `RefuseDflash2PathWalk` is retired and `RefuseDflash1ArgmaxOnDflash2Block` takes its place: sampling a DFlash2 block with the DFlash1 per-slot argmax SUCCEEDS -- well-formed tokens, a lossless verify, the target's tokens still emitted, only ACCEPTANCE falling -- so losing the walk's call site has to be loud. The guard lives inside the DFlash1 sampler's own closure rather than beside the walk it defends, because a guard adjacent to the call site it protects is deleted in the same edit that deletes it. **Reachability is measured, and "it generated" is deliberately not the assertion.** A runner that dropped the DFlash2 arm entirely also generates. So `test_dflash2_runner_reach.cpp` reads the drafts off the production `VT_SPEC_TRACE` line at REAL fd 2 -- a `std::cerr` rdbuf swap cannot see a `std::fprintf(stderr, ...)`, and a capture that could not see its own line would report "the propose did not run" -- and requires them to MOVE between two engines differing ONLY in D9's `output_multiplier` and `final_logit_softcapping`. Those scalars touch nothing but the candidate values the selector reads, so the block logits are identical and the argmax would answer the same for both. Measured at 2 of 8 blocks there and 5 of 6 over the model suite's wider sweep, both counts logged rather than assumed. **`## Owed` O5's first item is discharged STRUCTURALLY, not by a gate, and the difference is stated.** What made `LoadDflashDraft`'s `conv_block_size = k + 1` dangerous was that `LoadQwen3DFlash` also wrote the field, from the checkpoint's `block_size`, so deleting the loader's line left a PLAUSIBLE block behind. The seed is gone; the field is 0 until whoever knows the resolved `k` writes it, and the first DFlash2 forward refuses by name. Deleting the loader's line is still GREEN and that is recorded as such, because the only real-engine harness enters through the in-memory `DflashDraft` overload and bypasses that function. **The PROBABILISTIC arm is deliberately NOT ported, and that is upstream's own polarity at the moved head.** `_generate_draft` calls `_cache_draft_logits` only `if self.draft_logits is not None`, set only for `draft_sample_method == "probabilistic"` -- a value `ParseSpeculativeConfigJson` refuses BY NAME here against an accept-iff-equal verify. Landing it would land two mechanisms no production entry point can reach. Its layout is recorded instead (D13, `## Owed` O12), and the half this engine can honour is honoured: `None` for greedy, so no proposal distribution is allocated on the arm that ships. **#1501, found and fixed in flow.** The row recorded DFlash2's T>0 walk as "inverse CDF" from its opening brief; it is GUMBEL-MAX at both PR heads. Blast radius is a mis-scoped wave rather than a shipped defect -- W4 ships the greedy arm -- but an implementer scoped to write an inverse-CDF walk would have written the wrong algorithm with no oracle to catch it. Corrected in place in the spec and the engine-matrix row. The head-move enumeration was re-read item by item and is correct; it was missing a seventh change in the base class (`draft_logits_spec`) and the file it lives in. Red-before, captured with its exit status: `rc=2`, 10 `error:` lines, `'Dflash2PathWalk' is not a member of 'vt'`. Green, CI recipe, one build tree, x86_64, CPU-only, at base `af25bd251`: `test_ops_dflash2_path_walk` 6 cases / 41 assertions, `test_dflash2_argmax_guard` 5 / 26, `test_qwen3_dflash2_draft` 27 / 277, `test_dflash2_runner_reach` 3 / 83, `test_dflash2_draft_routing` 12 / 39, every one `Status: SUCCESS!`. Full gate `ctest -j 4`: 578 tests, 100% passed, 0 failed, 3 skipped, rc 0. Mutation-proven 2026-08-20, each with its match count printed before the build, each with `compile_rc` printed, each restored byte-for-byte and verified by sha256 and rebuilt: dropping the walk's `previous` CARRY (2 cases / 17 assertions red); `>` to `>=`, the tie-break (2/3); the all `-inf` collapse answering `K-1` (1/1); the gather ignoring the winning slot (4/26); the walk's per-request row indexing (1/2); DELETING the runner's walk call site (2 cases / 5 assertions red); REPLACING the runner's walk with the DFlash1 argmax (1/1, the D9-scalars case, which is the GUARD-INDEPENDENT reachability proof); deleting `DflashProposeBlock`'s walk call (1 case); restoring the `conv_block_size` seed (1/2); breaking the startup notice (1/1 and 1/1); and dropping the `final_out` capture (2/5). TWO came back GREEN and are recorded as `## Owed` rather than as passes: `LoadDflashDraft`'s `conv_block_size` line (O5, above) and the argmax guard's own call, which is reachable only under another mutation and whose throwing arm is gated directly. The walk's CUDA arm is written and registered and has NEVER COMPILED: this host has no `nvcc`, so the CUDA==CPU bit-exact case (three shapes, with a forced tie group and a forced all `-inf` row layered on, because neither occurs by chance and O10 recorded what assuming one did cost) reports `no CUDA backend; skipping`. `## Owed` O11, owed to the operator's GPU lease. No throughput number is claimed and none is admissible: `## Gates` defers every ratio until acceptance reads, and a DFlash2 draft is off the paged CUDA-graph fast path because the selector needs the hidden states of the same forward its logits came from. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
`main` moved from af25bd2 to cffe59b while W4 was in flight. The merge is clean and touches nothing this branch owns; the full gate was rerun on the merged tree (581 tests, 100% passed, 0 failed, 3 skipped). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…ane scan strict (#1314) W4's fresh review returned FAIL on one MEDIUM, three LOW and one INFO. Each is repaired here rather than deferred, because each was a gate that could not say how. The MEDIUM was a written guarantee with nothing behind it. `src/vt/ops.cpp` says that BOTH trailing axes of the walk's lattice are checked, because they are the predecessor axis and the child axis, and inferring one from the other would admit a lattice indexed the wrong way round. Dropping either conjunct, and deleting the whole `VT_CHECK`, each compiled clean and left all four suites green. The mechanism is generic and worth knowing: the case built a `{B,L,K,K}` tensor with `Contig(...)` and then wrote `bad.shape[2] = K - 1`, which desynchronises the strides, so `Tensor::IsContiguous()` turned false and the CONTIGUITY guard threw first. A bare `CHECK_THROWS` cannot tell two guards apart. Both axes are now driven by genuinely contiguous wrong-extent lattices and matched on the message, and the three mutations redden 1 case / 1 assertion, 1/1 and 1/2. The sibling `KERNEL-DFLASH2-SELECTOR-EDGES` suite had the same gap, so it is repaired in the same shape: deleting its own lattice check alone used to leave the suite green, and now reddens 1 case / 2 assertions. The CUDA arm was NOT bit-exact with the CPU arm on a NaN-bearing row, against four written claims that it was. Its per-lane scan carried an equality disjunct beside its strict `>`. That disjunct is unreachable once a lane has claimed anything, and its one reachable effect was at the seed, where a lane holding `-inf` compared equal to the `-inf` seed and claimed a slot the CPU arm refuses: `[NaN,-inf]` read cpu 0 and cuda 1, while every NaN-free row agreed. The disjunct is deleted rather than the claim narrowed, because the fix is one line and the divergence is real. The lower-slot tie rule stays in the cross-lane butterfly, where lanes combine out of slot order and it is genuinely needed. That deletion has never been compiled: this host has no `nvcc`, so it is owed to the operator's lease with the rest of the arm at `## Owed` O11. The CPU half of the strictness claim IS now gated, by the one case a `>=` reduction fails. `Dflash2WalkPath`'s candidate-set check and its i32-range refusal were both ungated and are now gated by message. The `tests/CMakeLists.txt` comment above the renamed suite still described the W2 refusal and now describes the inverse guard the file holds. `docs/FEATURES.md` carried no DFlash2 entry while `STATUS.md` said a DFlash2 draft drafts, so the entry is added in the wave where it starts drafting. And `## Gates` G2 and the oracle paragraph name PR head `19c93519` while the port mirrors `66e5414c`; the greedy answer is identical at both, so the choice is defensible, and it is now stated as one with W6 named as the owner of the reconciliation. Rows touched: SPEC-DFLASH2, KERNEL-DFLASH2-PATH-WALK, KERNEL-DFLASH2-SELECTOR-EDGES, KERNEL-TOPK-PAIRS. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
`origin/main` reached a50c57d while W4 was reviewed and repaired. One conflict, in `docs/USAGE.md`, where both sides appended a NEW section at the same point: main documents fetching a checkpoint by Hugging Face repository name, and this row documents drafting with a second checkpoint. Neither replaces the other, so both are kept, main's first because it continues the paragraph it was appended to. Keyed records verified rather than assumed: both matrices carry every row main has, lose none and invent none. `check-agent-record.py` reads ENGINE=168 KERNEL=57 with anchor rot unchanged at 38, and `check-public-doc-tables.py` passes. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
localai-bot
added a commit
that referenced
this pull request
Aug 20, 2026
`docs/USAGE.md` conflicted because #1515 added the speculative-decoding section where this branch added the measured Hugging Face fetch. Resolved by taking the target branch's section whole and re-applying the scoped paragraph under the Hugging Face heading it describes, so the diff against origin/main is that one paragraph and nothing else. `.agents/issue-index.md` merged by union with no duplicate row: 490 rows at the merge base, 491 on each side, 492 after. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:Opus-5 [Claude Code] Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave W4 of
SPEC-DFLASH2(#1314):the path walk. A DFlash2 draft now DRAFTS end to end.
RefuseDflash2PathWalkisretired.
Rows touched by this change:
SPEC-DFLASH2,KERNEL-DFLASH2-PATH-WALK,KERNEL-DFLASH2-SELECTOR-EDGES,KERNEL-TOPK-PAIRS.vt::Dflash2PathWalkruns in upstream's own grid: one program per request,a slot's K scores in registers, the slot-to-slot dependency a loop INSIDE the
program rather than a kernel per slot. It runs on device by default. The
identical sequential shape in DSpark shipped host-side and measured 28% of the
27B draft step (#436) before it
had to be moved; that is not repeated here.
The row had recorded the wrong algorithm since its opening brief, and this wave
found it (#1501). Every
version of the spec said the T>0 walk uses INVERSE CDF. It is Gumbel-max at
both PR heads — at
19c93519a hand-written-log(-log1p(-u))branch, at66e5414conegumbel_noised_argmaxcall — and no inverse-CDF walk existsupstream at all. Not a shipped defect, but an implementer scoped to write it
would have written the wrong algorithm with no oracle to catch it, because the
failure would have been acceptance-only and token-invisible. Verified against
real upstream at both SHAs by a fresh reviewer, and corrected in every place the
row states it.
The probabilistic arm is deliberately NOT ported (D13,
## OwedO12). At themoved head
_cache_draft_logitsruns only whendraft_logits is not None, setonly for
draft_sample_method == "probabilistic"— whichParseSpeculativeConfigJsonalready refuses BY NAME against an accept-iff-equalverify. Porting it would land two mechanisms no entry point can reach, so
## Nothing lands deaddecides it, the same way O9 already decidedinput_embedding_scale. TheNone-for-greedy half ofdraft_logits_specIShonoured; the fp32/
-inflayout and the Philox stream a bit-parity port wouldneed are recorded in enough detail to finish later.
Reachability, and why "it generated" is not the assertion. A runner that
dropped the DFlash2 arm also generates. The gate reads the drafts off the
production
VT_SPEC_TRACEline at real fd 2 — an rdbuf swap cannot seefprintf(stderr, …)— and requires the drafts to MOVE between two enginesdiffering only in D9's output scalars, which touch nothing but the candidate
values the selector reads, so a DFlash1 argmax would answer identically for both.
Deleting the runner's walk call site and the closure guard together leaves the
engine still generating 8 blocks with
differing: 0, and the gate reds on that.Reviewed twice, FAIL then repaired. The first review found the walk's entire
[B,L,K,K]shape contract ungated: the negative case setbad.shape[2] = K-1ona contiguous tensor, desynchronising the strides so
IsContiguous()turned falseand the CONTIGUITY guard threw first — a bare
CHECK_THROWScannot tell twoguards apart. Deleting the whole
VT_CHECKleft four suites green. It is nowpinned by two genuinely contiguous wrong-extent lattices matched on message, and
all three mutations redden. The same gap was found and fixed in the sibling
test_ops_dflash2_selector_edgesrather than deferred.The second finding was that CPU and CUDA were NOT bit-exact on a NaN-bearing row,
against four written "BIT-EXACT by construction" claims: the CUDA per-lane scan
read
v > best || (v == best && j < slot)seeded atbest = -inf, slot = K, so alane holding
-infclaimed a slot the CPU's strict>refuses. Repaired byfixing the kernel rather than narrowing the claims — the disjunct is unreachable
once a lane has claimed anything, since
jonly ascends, so its sole live effectwas at the seed. The lower-slot tie rule stays in the cross-lane butterfly, where
lanes combine out of slot order, with the invariant that makes it safe stated
beside it.
Gates. Operator rerun on this head:
agent-ready.py87 ok, 0 FAIL, 0 SKIP.Full ctest 580/581 at
429153a8c, the single failuretest_ltx2_videoon a0.8%-short wall-clock ratio under load.
check-agent-record.pyENGINE=168KERNEL=57, anchor rot unchanged at 38.
On a GPU. The device gate ran on
dgx:gpu0(GB10, sm_121a, nvcc 13.0 matchedto the driver) at this branch's code:
CUDA_OBJECTS_BUILT=34,COMPILE_ERRORS=0, all seven device-bearing suites green with zerono CUDA backend; skippinglines.test_ops_dflash2_path_walkreports 83assertions on device against 49 on CPU, and the increment includes the NaN row
now chained into the parity fixture — the case the previous device run
structurally could not contain, which is why the divergence above survived a
green GB10 run once already.
test_ops_dflash2_selector_edgesreports 3859against 203.
Still owed, named.
## OwedO5 keeps two ungatedLoadDflashDraftlinesrecorded with their mutation results rather than as passes. O11 keeps the
CUDA/CPU parity argument. O12 keeps the probabilistic arm.
#1489 keeps the sibling top-k's
NaN reconciliation. No throughput number is claimed and none is admissible: the
acceptance gate has not run, and a DFlash2 draft additionally runs off the paged
CUDA-graph fast path because the selector needs the hidden states of the same
forward its logits came from.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]