Skip to content

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
localai-bot merged 4 commits into
mainfrom
row/SPEC-DFLASH2-W4
Aug 20, 2026
Merged

feat(SPEC-DFLASH2): the path walk, so a DFlash2 draft drafts — on a GPU, with the walk's shape contract gated (#1314, #1501)#1515
localai-bot merged 4 commits into
mainfrom
row/SPEC-DFLASH2-W4

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Wave W4 of SPEC-DFLASH2 (#1314):
the path walk. A DFlash2 draft now DRAFTS end to end. RefuseDflash2PathWalk is
retired.

Rows touched by this change: SPEC-DFLASH2, KERNEL-DFLASH2-PATH-WALK,
KERNEL-DFLASH2-SELECTOR-EDGES, KERNEL-TOPK-PAIRS.

vt::Dflash2PathWalk runs 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 19c93519 a hand-written -log(-log1p(-u)) branch, at
66e5414c one gumbel_noised_argmax call — and no inverse-CDF walk exists
upstream 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, ## Owed O12). At the
moved head _cache_draft_logits runs only when draft_logits is not None, set
only for draft_sample_method == "probabilistic" — which
ParseSpeculativeConfigJson already refuses BY NAME against an accept-iff-equal
verify. Porting it would land two mechanisms no entry point can reach, so
## Nothing lands dead decides it, the same way O9 already decided
input_embedding_scale. The None-for-greedy half of draft_logits_spec IS
honoured; the fp32/-inf layout and the Philox stream a bit-parity port would
need 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_TRACE line at real fd 2 — an rdbuf swap cannot see
fprintf(stderr, …) — and requires the drafts to MOVE between two engines
differing 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 set bad.shape[2] = K-1 on
a contiguous tensor, desynchronising the strides so IsContiguous() turned false
and the CONTIGUITY guard threw first — a bare CHECK_THROWS cannot tell two
guards apart. Deleting the whole VT_CHECK left four suites green. It is now
pinned 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_edges rather 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 at best = -inf, slot = K, so a
lane holding -inf claimed a slot the CPU's strict > refuses. Repaired by
fixing the kernel rather than narrowing the claims
— the disjunct is unreachable
once a lane has claimed anything, since j only ascends, so its sole live effect
was 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.py 87 ok, 0 FAIL, 0 SKIP.
Full ctest 580/581 at 429153a8c, the single failure test_ltx2_video on a
0.8%-short wall-clock ratio under load. check-agent-record.py ENGINE=168
KERNEL=57, anchor rot unchanged at 38.

On a GPU. The device gate ran on dgx:gpu0 (GB10, sm_121a, nvcc 13.0 matched
to the driver) at this branch's code: CUDA_OBJECTS_BUILT=34,
COMPILE_ERRORS=0, all seven device-bearing suites green with zero
no CUDA backend; skipping lines
. test_ops_dflash2_path_walk reports 83
assertions 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_edges reports 3859
against 203.

Still owed, named. ## Owed O5 keeps two ungated LoadDflashDraft lines
recorded 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]

mudler added 4 commits August 20, 2026 20:17
…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
localai-bot merged commit 141402e into main Aug 20, 2026
0 of 15 checks passed
@localai-bot
localai-bot deleted the row/SPEC-DFLASH2-W4 branch August 20, 2026 22:28
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants