Skip to content

feat(dflash): scheduler/server verify-step glue — raw-argmax bypass, T>0 stochastic accept, EAGLE ctx-fix default-on#297

Open
Sujimoshi wants to merge 13 commits into
Avarok-Cybersecurity:mainfrom
Sujimoshi:feat/dflash-scheduler-cluster
Open

feat(dflash): scheduler/server verify-step glue — raw-argmax bypass, T>0 stochastic accept, EAGLE ctx-fix default-on#297
Sujimoshi wants to merge 13 commits into
Avarok-Cybersecurity:mainfrom
Sujimoshi:feat/dflash-scheduler-cluster

Conversation

@Sujimoshi

Copy link
Copy Markdown
Contributor

Summary

Next cluster from the #218 split. Wires the scheduler/server side of DFlash verify: threads a dflash_verify_raw_argmax flag from CLI through scheduler::run / mtp_step into all four verify dispatchers (K2/K3/K4/dflash), adds T>0 stochastic (rejection-sampling) acceptance for DFlash in the K=2 path, and flips ATLAS_DFLASH_EAGLE_FIX to default-on across both this branch's server-side consumers and #294's model-side capture logic.

Stacked on #294 (dflash_head core) → #292#290#258.

Contents

  • scheduler/mod.rs, mtp_step.rs, main_modules/serve.rs — thread dflash_verify_raw_argmax through to all verify dispatchers.
  • verify_dflash_step.rs / verify_k2_step.rs / verify_k3_step.rs / verify_k4_step.rs — when DFlash is active, compare the drafter's raw argmax directly against the verify result instead of routing through the rep_pen/DRY sampling pipeline first (the pipeline-processed token can legitimately differ from the draft even when the target agrees, silently collapsing accept rate).
  • verify_k2_step.rs — new dflash_stochastic_accept (rejection sampling against the draft distribution) for T>0 DFlash sampling.
  • ATLAS_DFLASH_EAGLE_FIX now defaults on (both here and via a paired commit on feat(dflash): drafter head core — forward_block split, NVFP4-safe lm_head, γ-1 noise0-skip fix #294's branch): fixes a real ctx-undercount where propose() was appending exactly 1 ctx slot per verify step regardless of num_accepted, instead of one slot per accepted position. ATLAS_DFLASH_EAGLE_FIX=0 still opts back into the legacy single-row-0 capture for A/B comparison.

Dropped from the upstream #218 diff as debug/perf scaffolding, not feature content: an env-gated per-position acceptance diagnostic table in verify_dflash_step.rs, and an unconditional first-20-steps timing log in verify_k4_step.rs.

Why the EAGLE-fix default flip

A tester flagged seeing K=γ accept rates commonly >50%, some >90%, and asked to hold off on #294 pending investigation. GPU validation on the identical prompt showed:

EAGLE_FIX off (old default) EAGLE_FIX on (new default)
verify steps 183 46-71
mean accept 14.5% 27.8-35.5%
>50% steps 5.5% 18.3-26.1%
≥90% steps 0 1-2
max accept 73% 93-100%
output clean, finish_reason=length clean, finish_reason=length

The tester's report reproduces closely once this flag is on — but it's not a regression, it's the fix working as intended: correct per-position ctx accrual gives the drafter an accurate context, so it proposes better multi-token drafts and needs fewer verify iterations for the same output length. No corruption, no repetition, no degenerate loops observed in any run.

Test plan

  • cargo check --workspace
  • cargo fmt --all -- --check
  • cargo clippy --workspace --tests
  • file-size-cap (0 violations)
  • GPU validation on GB10 (Qwen3.6-27B-NVFP4 + DFlash draft, T=0, ATLAS_DFLASH_DRAFT_CAP=15, no other env overrides — exercising the new defaults as-shipped): 71 K=γ verify steps, mean accept 27.8%, max 100%, finish_reason=length, output coherent (valid Python + docstrings), cut off cleanly at the token limit.

🤖 Generated with Claude Code

Sujimoshi and others added 13 commits July 5, 2026 01:18
…4 lm_head

Split out of Avarok-Cybersecurity#218 (DFlash speculative decoding for Qwen3.6-27B-NVFP4).
Adds the model-level plumbing DFlash's draft/verify path needs: trait
default no-op hooks (prefill_phase1_verify, prefill_gdn_wy16) for future
verify-path PRs, a row-major K-row dflash_hidden_save capture buffer
(dflash_hidden_save_rows, try_dflash_capture_draft/_all) so every
accepted verify position gets its own target hidden instead of only
rows 0/1, ctx_slot_positions on DflashProposerState to track true
sequence positions across thinking-token gaps, an FP32 GDN output
sibling buffer (gdn_buf_out_f32 / ATLAS_DFLASH_VERIFY_GDN_F32 prototype)
and ssm_verify_h_tmp scratch, a dense_gemm_pipelined kernel for the
draft head lm_head, and a RoPE fix for the drafter (config.json has
rope_scaling=null; the prior YaRN-interpolated inv_freq corrupted
positions and caused 0% accept rate — replaced with the standard
1/theta^(2j/dim) formula).

Conflicts against main (which has evolved independently since Avarok-Cybersecurity#218
branched, most notably via Avarok-Cybersecurity#229's NVFP4 MMQ FFN + FlashInfer-GDN work)
were resolved by keeping main's own additions (default_loops module,
VARLEN-FLA batched methods, NVFP4 lm_head) intact and layering the
DFlash-specific fields/methods alongside them, rather than reapplying
the original patch wholesale. impl_a1.rs's misc-scratch block was
extracted into impl_a1_init::build_misc_scratch (shared with the new
gdn_buf_out_f32/ssm_verify_h_tmp allocations) to keep the constructor
readable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ot drafter's

Found while GPU-testing this PR end-to-end on GB10: the DFlash lm_head
NVFP4 fast path (w4a16_gemm) used the drafter's own (uncapped)
vocab_size as the GEMM's N, but lm_head_nvfp4 is quantized against the
target model's tokenizer-capped vocab_size, which can be smaller. On a
checkpoint where the tokenizer caps vocab from 248320 to 248077, this
read ~243 rows past the end of the packed weight/scale buffers,
producing CUDA_ERROR_ILLEGAL_ADDRESS on the first DFlash proposal and
poisoning the CUDA context for the rest of the process.

Threads the target's capped config.vocab_size through
build_model -> BlockDiffusionDraftHead::from_weights as a new
lm_head_nvfp4_vocab_size field, and fixes both the GEMM call and the
subsequent argmax loop (which shares the same row stride and had the
same latent bug, though it wouldn't itself crash — just read the wrong
row data).

Re-verified end-to-end on GB10 with the same drafter checkpoint and
target checkpoint that reproduced the original crash: 3 consecutive
requests including the one that previously triggered the CUDA-700
poisoning now all complete cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Soft-load dense_gemm_bf16_pipelined via try_kernel (unused in this PR,
  shouldn't hard-fail model load)
- Make drafter RoPE config-driven (standard vs YaRN) based on
  config.json's rope_scaling field instead of hardcoding standard-only
- Guard ctx_slot_positions push against max_ctx_len to stay in lockstep
  with ctx_len clamping; downgrade expected-case "ctx_len update skipped"
  log from warn to trace
- Remove LM_HEAD_GEMM_LOGGED debug-session leftover (static + one-shot
  GEMM pointer dump)
- Gate gdn_buf_out_f32 (FP32 GDN verify buffer) allocation behind
  ATLAS_DFLASH_VERIFY_GDN_F32 env var instead of always allocating

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The two new DFlash verify-scaffolding hooks pushed transformer_layer.rs
to 535 LoC, tripping the file-size-cap CI gate. Condensed verbose
doc comments (no logic changes) back to 492 LoC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…1 conv, opt-in F32 GDN output

Extracted from Avarok-Cybersecurity#218 (DFlash speculative decoding), stacked on Avarok-Cybersecurity#258
(model glue). SSM/GDN layer-level pieces of the K=γ verify path:

- gated_delta_rule_wy16.cu: fused K=16 WY-chunkwise GDN verify kernel
  (+ F32-output variant) writing final h_state and Hi_0..Hi_14
  intermediates in one pass; wired via prefill_gdn_wy16.
- causal_conv1d_update_prefill_inter: batched phase-1 conv that also
  writes per-token conv_state intermediates for partial-accept rollback;
  wired via prefill_phase1_verify.
- ATLAS_DFLASH_VERIFY_QKVZ_FIX (default ON): route the M=1 verify QKVZ
  projection through the same w4a16_gemv kernel serial decode uses
  (~2.29% rel_diff otherwise).
- ATLAS_DFLASH_VERIFY_OUTPROJ_FIX (default ON): per-row w4a16_gemv
  out_proj for verify-window calls (≤17 tokens, native-NVFP4 builds
  only) to keep batched verify numerically aligned with serial decode
  (~2.26% rel_diff otherwise). Normal prefill chunks keep the shared
  GEMM dispatch.
- ATLAS_DFLASH_VERIFY_GDN_F32 (opt-in): F32 GDN output in the split4 and
  wy16 verify branches, removing a BF16-truncation source.
- decode_batched sequential K∉{2,3,4,17} fallback extracted to
  conv_gdn_sequential.rs; prof!-instrumented decode GDN/norm launches.
- mod.rs: TransformerLayer impl moved to trait_transformer_impl.rs
  (500-LoC cap).

Deviations from Avarok-Cybersecurity#218 as-authored, due to main's independent evolution:
qkvz/out_proj projection ladders were replaced on main by shared
dispatch helpers (prefill_qkvz_proj / prefill_out_proj_dispatch) that
now carry the CUTLASS-NVFP4 and FP8 branches, so the verify fixes are
layered as guarded pre-branches instead of reapplying Avarok-Cybersecurity#218's inline
ladders; the out_proj fix is additionally gated to ≤17-token calls
because phase3 is now also the hot batched-prefill path. NVTX
instrumentation is left to Avarok-Cybersecurity#255. 218's trait_prefill_gdn_batched.rs and
qkvz_projection.rs extractions were dropped — main since made its own
supersets of both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contiguity fix

Extracted from Avarok-Cybersecurity#218 (DFlash speculative decoding), stacked on Avarok-Cybersecurity#290
(SSM-layer verify kernels). Model-level orchestration of the K=γ
verify path plus a correctness fix for the drafter's context
accumulator:

- verify_d_kgamma_layers.rs / verify_d_kgamma_attn_meta.rs: per-layer
  K=γ forward loop and batched attention metadata, extracted from
  verify_d.rs to keep it under the 500-LoC cap.
- dflash_append.rs: EAGLE ctx-slot append dispatchers
  (dflash_accept_append / dflash_eagle_accept_append /
  dflash_eagle_kgamma_append) plus dflash_decode_ctx_append_dispatch,
  called unconditionally at the end of every decode() step so the
  drafter's ctx accumulator stays contiguous even across stretches
  where DFlash itself is gated off (e.g. inside a <think> block).
  Wires the accompanying `skip_next_decode_append` one-shot flag on
  `DflashProposerState` (new field) so propose()'s own internal
  decode-append doesn't double-append a row already appended by one
  of the eagle dispatchers.
- verify_a.rs / verify_b.rs / verify_d.rs: wiring for the above plus
  the K=γ dispatch path (decode_verify_graphed_kgamma_dispatch).

Deviations from Avarok-Cybersecurity#218 as-authored, due to main's independent evolution:
NVTX instrumentation (verify_step, per-layer/per-phase ranges,
d2h_argmax_readback) stripped throughout — left to Avarok-Cybersecurity#255, not yet
merged into this stack. prefill_b/batch_kernel.rs and prefill_c.rs
had their own `output_f32`/`output_f32_written` GdnPrefillBuffers
fields already (from Avarok-Cybersecurity#290's F32-GDN-output work); Avarok-Cybersecurity#218's patch tried
to add the same fields again verbatim — kept the existing (real, in
prefill_c.rs's case non-null) values and dropped the duplicates.

Test plan: cargo check/fmt/clippy/LoC-cap all green on this branch
tip. GPU validation on GB10 (Qwen3.6-27B-NVFP4 + DFlash draft, T=0)
pending — will report accept-rate/divergence vs the Avarok-Cybersecurity#290 baseline
(byte 634) and whether this fixes the previously-observed 0/16
accept collapse on the forced K=γ path (ATLAS_DFLASH_DRAFT_CAP
override), since that collapse predates this ctx-accumulator fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…FP4-safe lm_head, γ-1 noise0-skip argmax

Next cluster from the Avarok-Cybersecurity#218 split, stacked on Avarok-Cybersecurity#292 (verify orchestration),
which stacks on Avarok-Cybersecurity#290 (SSM verify kernels), which stacks on Avarok-Cybersecurity#258 (model glue).

Contents:
- `dflash_head/forward_block_layer.rs` — Q/K/V/O/MLP projections switched to
  `dense_gemm_bf16_pipelined` (tensor-core pipelined GEMM, ~faster than the
  scalar dense_gemm for the drafter's per-step M).
- `dflash_head/forward_block_step0.rs` (new) — fc-projection step extracted
  from `forward_block.rs` to stay under the 500-LoC cap; batches the GEMM
  over all `eff_ctx` ctx slots in one launch instead of a per-slot loop
  (avoids O(eff_ctx) weight re-reads of the 262 MB fc matrix).
- `dflash_head/forward_block.rs` — final logits GEMM now correctly skips
  noise0 (the conditioning slot, untrained per the block-diffusion paper —
  its logits are garbage) when extracting argmax drafts: `γ-1` valid drafts
  from noise1..noise{γ-1}, matching SGLang's reference behavior. Preserved
  the NVFP4-aware lm_head GEMM dispatch (w4a16_gemm for NVFP4 lm_head
  targets e.g. Holo) that already existed on this stack — Avarok-Cybersecurity#218's own
  version of this file predates that support and would have regressed it.
- `dflash_head/propose.rs` — timing instrumentation (`t_propose_start`,
  per-step forward_us/total_us debug log) and optional `ATLAS_DFLASH_T=2`
  iterative denoising (off by default; tested upstream as acceptance-neutral,
  not recommended).

Deviations from Avarok-Cybersecurity#218 as-authored:
- Left `dense_ffn.rs`'s diff out entirely — its `prof!`-instrumentation hunk
  is superseded by later perf work already on this stack (split_silu /
  single-warp GEMV dispatch), and its other two hunks are NVTX (out of scope,
  see Avarok-Cybersecurity#255). Same policy applied to `qwen3_attention/prefill/paged_oproj.rs`
  and `paged_qkv.rs`: their only changes were NVTX `NvtxRange` tags around
  GEMM dispatch that's already more advanced on this stack (cutlass_nvfp4
  routing) than Avarok-Cybersecurity#218's version — kept the current dispatch unchanged.
- `dflash_head/from_weights.rs`: kept this stack's existing YaRN-aware RoPE
  inv_freq table (handles both `rope_scaling: yarn` and standard/null per
  config.json) over Avarok-Cybersecurity#218's blanket revert-to-standard-RoPE fix — the
  YaRN-aware version already supersedes it.
- Did NOT adopt Avarok-Cybersecurity#218's flip of `ATLAS_DFLASH_DRAFT_CAP`'s default from 1 to
  `gamma - 1` (i.e. defaulting to the full K=γ path). Avarok-Cybersecurity#218's commit message
  cites the BF16 embed-stride fix (2e3a26a, already present in this stack's
  `verify_d.rs` since Avarok-Cybersecurity#292) as resolving K=γ's garbage-output bug — but our
  own GPU validation on this exact stack (GB10, Qwen3.6-27B-NVFP4+DFlash,
  forced `ATLAS_DFLASH_DRAFT_CAP=16`) still measures 0/16 accepted. The
  stride fix was real but not sufficient; flipping the default would silently
  degrade every DFlash request to full-γ speculation with 0% acceptance.
  Kept the safe default (1, forces step_verify_k2) with an updated comment.

Test plan:
- [x] cargo check -p spark-model / cargo check --workspace
- [x] cargo fmt --all -- --check
- [x] cargo clippy --workspace --tests
- [x] file-size-cap (0 violations)
- [ ] GPU validation on GB10 (Qwen3.6-27B-NVFP4 + DFlash draft, T=0) —
      pending, will report divergence/accept-rate vs Avarok-Cybersecurity#292's baseline
      (byte 899) before merge.
The comment written before GPU testing claimed K=γ still measures 0/16
accepted (true for Avarok-Cybersecurity#292, before this cluster's noise0-skip argmax fix).
Actual validation on this commit shows real, non-zero acceptance (0-93%
per step) with no corruption — update the comment to match, and record
why the safe default (1) is still kept: verify cost outweighs measured
acceptance on this hardware/model, a throughput question, not correctness.
… fix)

GPU validation this session: on the identical docstring-template prompt,
enabling the flag dropped verify steps 183→46 and raised mean K=γ accept
14.5%→35.5% (max 73%→93%) for the same output length, with no output
regression (finish_reason=length, coherent text in both runs). The flag
fixes a real ctx-undercount (propose() was appending exactly 1 ctx slot
per verify step regardless of num_accepted) rather than being a risky
experimental toggle, so it should be on by default. ATLAS_DFLASH_EAGLE_FIX=0
still opts back into the legacy single row-0 capture for A/B testing.

This also explains a tester report of frequently seeing >50% (and some
>90%) accept at wide gamma: they were very likely running with this
ctx-accurate path already active.
…T>0 stochastic accept

Threads a `dflash_verify_raw_argmax` flag from CLI args through
scheduler::run and mtp_step into all four verify dispatchers
(K2/K3/K4/dflash): when DFlash is active, the drafter's own raw argmax
output is compared directly against the verify result instead of going
through the rep_pen/DRY sampling pipeline first — the pipeline-processed
token can legitimately differ from what the drafter proposed even when
the target model agrees with the draft, which was silently collapsing
accept rate.

verify_k2_step.rs additionally gains a T>0 stochastic-acceptance path
(rejection sampling against the draft distribution) for DFlash, used
when sampling temperature > 0.

Dropped from the upstream diff (fork/optimizations @ a7100e0) as
debug/perf scaffolding, not feature content: an env-gated per-position
acceptance diagnostic table in verify_dflash_step.rs, and an
unconditional first-20-steps timing log in verify_k4_step.rs.

Stacked on Avarok-Cybersecurity#294 (dflash_head core) -> Avarok-Cybersecurity#292 -> Avarok-Cybersecurity#290 -> Avarok-Cybersecurity#258.
…ers)

Matches the model-side default flip in feat/dflash-head-core (c1bb0ad):
verify_dflash_step.rs's dflash_eagle_kgamma_append call and
verify_k2_step.rs's dflash_eagle_accept_append call now run by default.
ATLAS_DFLASH_EAGLE_FIX=0 restores the legacy per-step single row-0
ctx-append path for A/B comparison.
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