Skip to content

perf(draft): batch append projections across lanes - #656

Draft
Graffioh wants to merge 19 commits into
Luce-Org:mainfrom
Graffioh:codex/qwen38-dflash2-append-pack
Draft

perf(draft): batch append projections across lanes#656
Graffioh wants to merge 19 commits into
Luce-Org:mainfrom
Graffioh:codex/qwen38-dflash2-append-pack

Conversation

@Graffioh

@Graffioh Graffioh commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Depends on #651. While #651 is open, this draft includes its commits. The diff collapses to this follow-up when #651 lands.

Profiling campaign and rerunnable evidence: Luce-Org/luce_box#69.

Summary

Batches the fixed-width draft append projections across active lanes. The graph packs each lane's append feature columns before the shared fc, wk, and wv matrix multiplications. It slices the projected columns before lane-local normalization, RoPE, destination rows, and KV-cache writes.

  • replaces the singular append builder with build_draft_kv_appends()
  • sends one lane through the normal and exact-bulk graphs, preserving the C1 graph shape
  • sends C1-C6 lanes through the packed batch graph
  • keeps a_step fixed, so padded rows still target only each lane's trash slot
  • preserves the existing graph-cache key over the backend and ordered lane-state pointers
  • leaves build_draft_kv_steps(), dynamic-convolution history and apply, attention, masks, and selector ownership unchanged

Isolated draft result on R9700

Qwen3.8-27B-UD-IQ4_XS, DFlash2 Q8 width 8, Q8 K/V cache, ten fixed-width HumanEval code-only waves, 256 forced tokens, temperature 0:

C Matched baseline draft p50 Packed draft p50 Improvement Append dispatches/round
1 5.1955 ms 5.1784 ms 0.33% 22 → 22
2 7.1411 ms 6.5494 ms 8.28% 44 → 22
3 9.4684 ms 8.2753 ms 12.60% 66 → 22
4 11.3953 ms 9.7563 ms 14.38% 88 → 22

The dispatch reduction is exact. Eleven append projections each avoid one Q8 quantize and one MMQ launch per additional lane. The remaining block-16 Q8 launches belong to lane-local dynamic convolution and remain out of scope.

End-to-end code-prompt result

This reruns the PR #651 HumanEval-style protocol on the current #651 head and this candidate. Each cell uses a fresh server, a same-C eight-token warmup, temperature 0, natural EOS after DFLASH_MIN_TOKENS=8, a 256-token cap, five server slots, K8 prefill admission, and two measured repetitions. C1-C4 use disjoint slices of the same ten checked-in raw prompts. llama.cpp was not rerun.

The published #651 column is context. The causal comparison uses a fresh build of current #651 at 22b65ba0 because the published table came from the earlier 8ba84d3d restack.

Radeon AI PRO R9700

C Published #651 tok/s Current #651 tok/s Candidate tok/s (range) vs current Decode vs current TTFT vs current Output parity
1 110.02 110.97 110.57 (110.31-110.83) -0.36% -0.73% -1.21% yes
2 153.67 154.06 154.00 (153.48-154.53) -0.03% +0.04% -0.85% yes
3 200.33 201.77 203.68 (203.28-204.08) +0.94% +0.77% -1.68% yes
4 188.85 190.63 191.80 (191.75-191.85) +0.61% +0.65% -1.73% yes

Ryzen AI Max+ 395 / Strix Halo

C Published #651 tok/s Current #651 tok/s Candidate tok/s (range) vs current Decode vs current TTFT vs current Output parity
1 40.63 47.97 47.75 (47.66-47.84) -0.45% -0.42% +0.60% yes
2 58.69 65.80 65.92 (65.70-66.14) +0.18% +0.81% +0.36% yes
3 63.52 70.05 70.71 (70.66-70.76) +0.95% +1.65% +0.77% yes
4 63.20 68.56 69.30 (69.30-69.31) +1.08% +1.53% -0.78% yes

The end-to-end goodput gain is marginal. This change remains worthwhile because the production implementation is net +29 lines, deletes the singular runtime path, removes 66 append-projection dispatches at C4, and cuts isolated C4 draft compute by 14.38%. TTFT is not a general justification because the Strix C1-C3 result regressed slightly.

All published, current-baseline, candidate, and repeat output sets have identical completion-token counts and output digests at C1-C4.

Correctness

The multilane qualification compares three independent single-lane reference graphs with one packed graph at append counts 0, 9, and 18 for a_step=18. It checks all five K/V cache layers and explicitly verifies padded trash-row writes.

Verified locally:

  • HIP build of dflash_server and test_draft_swa_multilane for gfx1151;gfx1201
  • test_draft_swa_multilane ... dflash2-q8_0.gguf 0 --append-only
  • test_qwen35_roctx
  • test_seq_engine_contract
  • test_inference_profile
  • test_observability
  • two-GPU C1-C4 HumanEval output parity and repeat audit
  • product and campaign git diff --check
  • independent final audit with no P0-P3 findings

Dynamic-convolution coefficient packing and the device-resident selector handoff remain separate measured PRs.

Review in cubic

Restore the paged-attention C ABI through a separate tree entry point. Reject unsupported GDN variants, initialize root-only chain trees, and size draft metadata from graph capacity.
Keep GDN journals inside the result buffer, restore the 10-source tensor layout, validate every tree destination before mutation, and allow selector widths that use the CPU top-k fallback.
Validate singleton selector vocabularies and token IDs before codebook gathers. Reject unsupported GDN output layouts on Vulkan, and use overflow-safe ceil division in paged attention.
Preserve paged-KV allocation order across rollback and destroy draft graphs before park frees their weights.

Reject invalid GDN replay layouts and malformed SWA metadata. Exercise both grouped and scalar GDN dispatch paths.
Add fixed-width DFlash2 concurrent decoding for Qwen3.8 and batch the shared dense draft projections across active lanes.

Keep convolution, cache updates, RoPE, masks, and attention lane-local. Preserve transactional state promotion and fixed-chain validation through C6.
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.

1 participant