Skip to content

perf(ds4): accelerate ROCm decode and long-context selection - #667

Open
davide221 wants to merge 2 commits into
codex/ds4-pflash-long-contextfrom
codex/ds4-rocm-moe-wave32
Open

perf(ds4): accelerate ROCm decode and long-context selection#667
davide221 wants to merge 2 commits into
codex/ds4-pflash-long-contextfrom
codex/ds4-rocm-moe-wave32

Conversation

@davide221

@davide221 davide221 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Merge-ready, stacked on #664 (codex/ds4-pflash-long-context). This PR accelerates DeepSeek V4 Flash verification on gfx1151 without reducing the model's six routed experts. Here, q=4 means seed-inclusive speculative verification width four, not four experts.

Cleaned scope

  • Use MMVF for the qualified gfx1151 q4 hyper-connection projection.
  • Reuse activations across four ROCmFP2 output rows and tune ROCmFP2/3 Wave32 loads/launches.
  • Update large verifier masks by device-clear plus negative ranges instead of transferring the full mask.
  • Split indexed MLA KV work across more Wave32 blocks, then combine online-softmax state.
  • Replace full long-context top-k sorting with an exact two-stage block-radix selector.
  • Default the qualified sparse verifier-attention path on for gfx1151 DSpark, with kill switches for every new default.
  • Keep the shared adaptive-width controller available for linear-chain drafters, but opt-in. Fixed width is the production default.

The draft-only fused MoE combine and direct-visible attention experiments were removed: neither cleared the qualification threshold, and the latter's shape contract was not broad enough for merge.

Real-workflow A/B

Matched on lucebox8's Strix Halo (gfx1151), ROCm 7.2.4, the same target and DSpark GGUFs, q4 verification, six routed experts, q4 KV, sparse prefill, and 32K server context. Sparse verifier attention was explicitly enabled on the exact base (5452b4a) to match the head (5eb4fbe) default.

Workflow Exact base This PR Change Quality
HumanEval 27.33 tok/s 35.77 tok/s +30.9% 2/2 -> 2/2
GSM8K 24.77 tok/s 32.28 tok/s +30.3% 2/2 -> 2/2
Agent, 2K/8K/24K prompts 22.29 tok/s 30.09 tok/s +35.0% 6/6 successful in both

The two 24K agent cases averaged 22.42 -> 29.71 tok/s (+32.5%). Prefill was essentially unchanged: HumanEval 43.5 -> 44.7 tok/s, GSM8K 31.3 -> 32.5 tok/s, and agent 94.1 -> 93.9 tok/s. This PR closes a substantial decode gap; it does not claim to close the remaining raw-prefill gap to Vulkan.

On matched requests, verifier time fell from roughly 125-133 ms/step to 84-101 ms/step. Benchmark artifacts and logs are retained under /data/pr667-merge-ready/ on lucebox8.

The sparse/quantized production path is not an end-to-end byte-identity mode, so task correctness is the workflow quality gate. Kernel-level gates remain strict where applicable: the new top-k returns the exact reference selected set, and ROCmFP2/3 tests validate every qualified width against the host reference.

Adaptive width result

Adaptive width remains opt-in via DFLASH_ADAPTIVE_SPEC_WIDTH=1 or the DS4-specific DFLASH_DS4_ADAPTIVE_WIDTH=1. On the same real workflows it was slower than fixed q4: HumanEval -4.7%, GSM8K -15.9%, and agent -3.5%. The implementation is retained for future backend-specific qualification, while all non-DS4 backends preserve their previous default policies.

Verification

  • Release dflash_server build on ROCm/gfx1151.
  • test_server_unit: 431/431 passed after final cleanup.
  • deepseek4_unit, including long-context top-k and indexed-attention GPU coverage: passed.
  • test_rocmfp_mix_gateup_glu and test_rocmfpx_mmq: all parity cases passed.
  • Exact block-radix selector: 2.36-4.55x faster than full sort across DS4-relevant sizes, exact selected set for every case.
  • Real target tokenizer harness: passed.
  • Codex Responses API and Open WebUI streaming probes: passed.
  • git diff --check: clean.

An unrelated all-target HIP build still hits the pre-existing cudaErrorNoDevice compatibility issue in unchanged test_rms_norm_hip.cpp; the production server and all PR-focused targets build cleanly.

Controls

  • DFLASH_DS4_SPARSE_DECODE_FLASH=0: restore explicit verifier attention.
  • GGML_CUDA_MLA_NO_SPLIT_KV=1: disable split-KV MLA.
  • GGML_DS4_TOPK_BLOCK_RADIX=0: restore hipCUB full sort.
  • DFLASH_ROCMFP2_ROW4=0: restore the two-row ROCmFP2 kernel.
  • DFLASH_GFX1151_HC_MMVF_Q4=0: restore generic q4 projection dispatch.
  • DFLASH_DS4_INCREMENTAL_VERIFY_MASK=0: restore full verifier-mask transfer.

Review in cubic

@davide221

Copy link
Copy Markdown
Contributor Author

lucebox8 gfx1151 A/B (ecae461)

Matched cold-process runs; only DFLASH_MOE_FUSED_COMBINE=0|1 changed. Both
used the 0731 ROCmFPX MIX target, DSpark draft, q4 K/V, sparse prefill, full
6/6 routing, q=4 cap, and 256 output tokens.

Prompt Metric OFF ON Delta
32,768 server prefill (median, n=2) 184.56 191.23 tok/s +3.62%
32,768 client decode (median, n=2) 31.771 31.938 tok/s +0.53% (noise)
122,879 server prefill 129.46 133.43 tok/s +3.07%
122,879 client decode 26.875 26.540 tok/s -1.25% (no gain)

Correctness/integrity:

  • All normal runs returned the identical expected response hash
    a91b12613458d22a3fbf6d66ff2fba42124b46ea0e032f68c3564cc0c506ec43.
  • Captures completed with zero dropped steps, requests, or token bursts.
  • 431/431 server tests and 5/5 focused profiling/GPU tests passed, including
    GPU numerical equivalence for ggml_moe_combine.

Matched ROCprof at 32K confirms the intended replacement: roughly 3,675 old
reduce/multiply launches disappear and 3,643 moe_combine_kernel launches are
added, saving about 1.36 s of sampled GPU work. Normal runs—not ROCprof—are the
throughput source.

Conclusion: reusable prefill win, decode-neutral. Keep this draft and
opt-in
while the next gfx1151 work targets dominant quantized matmul and
grouped compact attention kernels.

@pepuscz

pepuscz commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Thank you, @davide221, for your work on Lucebox, for pushing this optimization series, and for acting on the earlier Strix Halo feedback. We qualified PR #667 head 2f136181b0b387592e413a81dedc0cd79991e128.

Test configuration

  • AMD Ryzen AI Max+ 395, 128 GiB unified memory
  • Lucebox ROCm/ROCmFPX MIX target
  • Q4RMFP4 DSpark drafter, six routed experts, speculative verification width q=4
  • Q4_0 K/V cache, sparse prefill, 131,072-token context, one prefix-cache slot
  • 120/120/120 W package limits
  • Baseline: our previous Lucebox ROCm ROCmFPX setup, built from Lucebox f686c447f067a04ea100a996e4c826e8cc4decc1 with four pinned patches for 128K bounds, gfx1151 MMQ tuning, ROCmFPX dense matvec, and current DeepSeek V4 reasoning behavior. The exact Ansible configuration pins every patch and artifact hash, build input, runtime setting, and hardware policy.
  • Candidate source: PR #667 head 2f136181b0b387592e413a81dedc0cd79991e128
  • Identical model, prompts, generation limits, runtime options, and hardware profile

Matched results

Workload Previous Lucebox ROCm ROCmFPX setup PR #667 Change
2,048-token prompt, up to 510 output tokens — median decode 30.50 tok/s 38.60 tok/s +26.6%
122,879-token prompt — cold prefill 133.223 tok/s 145.682 tok/s +9.4%
122,879-token prompt, 256 output tokens — decode 18.10 tok/s 26.50 tok/s +46.4%
Deterministic quality suite 30/30 29/30 one regression

The PR #667 build also fit the full 131,072-token configuration in 128 GiB. Minimum effective non-CMA headroom was 4.984 GiB. There was no OOM, GPU reset, server restart, or kernel memory-pressure event. For comparison, the stacked PR #664 base could not complete the same exact-128K fit because of a global-memory OOM.

Quality finding

Quality was evaluated with client_test_runner.py and all 30 fixtures in the Lucebox harness at commit 90f85fa. The only failed fixture was math_10: it asks for f(84) in the specified recursive integer function, with expected boxed answer 997.

The previous setup stopped naturally after 1,974 generated tokens and returned \boxed{997}. PR #667 reached the 2,048-token output limit and ended mid-formula without returning the answer. The other 29 fixtures passed the corrected quality gate.

For independent reproduction, our v1.5.0 release packages the exact Ansible configurations for both tested Strix Halo systems together with the benchmark definitions, pinned revisions, result data, and comparison chart.

@davide221

Copy link
Copy Markdown
Contributor Author

Split-KV MLA follow-up (0f1264c), still draft and opt-in.

  • Adds a native HIP two-way split-KV schedule for indexed D512 MLA verification (q <= 8), enabled with GGML_CUDA_MLA_SPLIT_KV=1.
  • Keeps all six routed experts and the same adaptive DSpark configuration.
  • 8K normal, 3-run medians: 36.239 -> 36.996 tok/s (+2.1%); target verify 108.1 -> 105.8 ms (-2.1%). The final decode-only build separately confirmed 37.628 tok/s and 104.0 ms.
  • 32K normal, 3-run medians: 33.676 -> 34.671 tok/s (+3.0%); target verify 116.7 -> 113.4 ms (-2.8%). The final decode-only build separately confirmed 34.415 tok/s and 115.7 ms against the 33.645 tok/s / 118.3 ms matched control.
  • Every run produced the identical response SHA-256 0f785a7f...d104d194.
  • Decode-only guard leaves prefill flat: 8K 207.11 vs 207.39 tok/s; 32K 183.51 vs 183.86 tok/s, and removes the prototype extra ~0.9 GB allocation.
  • Matched ROCprof: indexed-attention cost per layer fell 0.293 -> 0.127 ms (-56.5%), saving ~3.5 ms per verification round. Median marked verify range fell 106.30 -> 102.60 ms. Split launches were exactly 672 = 32 rounds x 21 attention layers; none occurred in prefill.

Retained captures:

  • /data/lucegraph-ds4-splitkv-runs/rocprof-control-8k
  • /data/lucegraph-ds4-splitkv-runs/rocprof-split2-decodeonly-8k

Rejected: four-way split (repeat client deltas were only +0.17%/+0.32%) and the earlier 2-wave ROCmFP MMQ tile (+0.07%).

@davide221

Copy link
Copy Markdown
Contributor Author

Strix Halo follow-up at fb2f462 (PR remains draft)\n\nResult\n- Default gfx1151 path: 39.403 tok/s at exact 8K versus 37.935 tok/s with split-KV explicitly disabled: +3.87%.\n- Exact 32K observation: 36.195 tok/s.\n- All runs used DS4 model-default six routed experts, fixed q=4 verification, 128 output tokens, and the same response SHA256: 0f785a7ffa406498aafb14553966eaed0f52220fed0f7cc016b66921d104d194.\n- Split-KV is now automatic only on gfx1151. Other devices remain opt-in; GGML_CUDA_MLA_NO_SPLIT_KV and GGML_DS4_FA_NO_SPLIT_KV are opt-outs.\n\nMatched ROCprof, exact 8K, baseline to combined candidate\n- speculative wall: 4303.159 to 4019.416 ms (-6.59%)\n- verifier marker median: 104.531 to 96.781 ms (-7.41%)\n- summed GPU kernels per round: 91.441 to 83.405 ms (-8.79%)\n- ROCmFP2 q106 expert kernels: 1071.001 to 938.965 ms (-12.33%)\n- ROCmFP3 q105 expert kernels: 347.337 to 336.596 ms (-3.09%)\n- indexed attention: 191.650 to 82.393 ms (-57.01%)\n\nThe new ROCmFP2 and ROCmFP3 helpers load each activation once for two independent output rows. ROCmFP3 uses explicit FMA to preserve the previous raw float hashes; its fused-GLU specialization keeps the lower-register path because pairing regressed occupancy.\n\nValidation\n- relevant HIP targets build on ROCm 7.2.4 / gfx1151\n- test_rocmfp_mix_gateup_glu: pass\n- learned and fixed ROCmFP2/3 microbench hashes match their controls\n- full response hash matches at 8K and 32K\n- artifacts: /data/lucegraph-ds4-splitkv-runs/pairloop-q3-exact-rocprof-fixedq4-8k-r1, /data/lucegraph-ds4-splitkv-runs/pairloop-q3-exact-default-split-8k-r1, /data/lucegraph-ds4-splitkv-runs/pairloop-q3-exact-fixedq4-32k-r1

@davide221

Copy link
Copy Markdown
Contributor Author

q5 qualification: adaptive width regresses real-code decode throughput

I completed the q5 variable-acceptance evaluation requested in the PR description.

Scope

  • Exact DS4 controller from commit 5eacd1f3962950615668ef54c9acd268b99309f3, minimally overlaid on the exact PR58 private engine. This isolates adaptive width; it does not include PR667's unrelated kernel changes.
  • Same binary in both arms: SHA-256 79590a5b467b14564bbf1e432987941599c80ea851796d1260ab6bd63d3de691.
  • Dense bundled q5 profile, top-k 4, adaptive fixed with both switches 0 versus enabled with both switches 1.
  • Normalized effective-environment diff was empty after excluding the port and adaptive switches.
  • ABBA crossover with a fresh server process per arm: two distinct six-turn cumulative coding workflows per process, 512 output tokens per turn, 24 measured requests per mode. No request was selected or rerun based on acceptance.
24-request aggregate Fixed q5 PR667 adaptive q5 Change
Server decode median 42.000 tok/s 38.650 tok/s -7.98%
Server decode weighted 39.988 tok/s 37.102 tok/s -7.22%
Server decode p10 32.000 tok/s 31.460 tok/s -1.69%
Server decode minimum 29.200 tok/s 29.800 tok/s +2.05%
Client end-to-end median 26.361 tok/s 26.537 tok/s +0.67% (flat)
Median TTFT 5.588 s 5.772 s +3.29% (slower)
Median acceptance 0.895 0.870 -2.79%
Completed requests 24/24 24/24 stable

The two adaptive process replicates were unusually consistent at 37.112 and 37.091 weighted tok/s. Fixed-width replicates were 39.892 and 40.084 weighted tok/s. The decode regression therefore does not look like a single-process outlier.

The server explicitly logged adaptive width policy=acceptance. No measured request had 100% acceptance. All response hashes differed in the first fixed/adaptive pair, so this is not a quality-equivalence result; the approximate bundled verifier still needs a separate semantic evaluation before any policy promotion.

Conclusion: the current PR667 q5 controller is not a speed win for real coding traffic on this Lucebox setup and should remain disabled in the private profile. It sometimes improves an individual low-acceptance turn, but the additive probe/backoff policy is not Pareto-better across the workload.

This was a development-only matched A/B on lucebox6 with the appliance and memory-clock qualification gates bypassed because the host remains on BIOS 03.04 balanced/auto/auto. That prevents an absolute product claim, but it does not explain the matched relative regression.

Artifacts:

/home/lucebox6/pr667-codebench.1cztp0/
  aggregate.json
  fixed-result.json
  fixed-r2-result.json
  adaptive-result.json
  adaptive-r2-result.json
  *-effective.env
  *-server.log
  results-sha256.txt

Maintained harness: lucebox_bench/real_code_workflow.py, SHA-256 ba39537cddfae7ad4eaa3bac409dd97c51c7d18243ab174bd2fe8a358990a4ad.

@davide221

Copy link
Copy Markdown
Contributor Author

Follow-up/correction on the code-workflow A/B above: all 48 measured responses across the four runs had and (the four warmups likewise hit their 128-token cap).\n\nSo the result is valid evidence for sustained decode on cumulative coding prompts with natural, non-100% acceptance, but it should not be described as completed natural-EOS workflow performance or as a quality-equivalent user-task comparison. The checked-in benchmark is being hardened to report length-limited request counts explicitly and to retain outputs/hashes for separate semantic review.\n\nThe controller conclusion for that measured regime is unchanged: PR667 q5 adaptive was slower than fixed width (37.102 vs 39.988 weighted server tok/s, -7.22%). A natural-EOS/targeted-patch follow-up is needed before generalizing beyond sustained decode.

@davide221

Copy link
Copy Markdown
Contributor Author

Natural-completion follow-up to the token-capped cumulative-workflow result above.

Scope

  • Exact PR58 engine base plus only the PR667 DS4 q5 acceptance-feedback controller.
  • One identical binary in every arm: 79590a5b467b14564bbf1e432987941599c80ea851796d1260ab6bd63d3de691.
  • Maintained HumanEval fixture: c7e294317b3a5e478d9419255c962c2fd9ee8d58e2b54609ab1c1b0d2126e9bd.
  • Launcher: d533619c1e47b0dcd26dcba5fdc026517c4c9758eeba283699c84680f65681f4.
  • C1, temperature 0, seed 0, max 512; fresh server for each A-B-B-A arm.
  • Fixed sets both adaptive switches to 0. Adaptive sets both to 1; every adaptive request logged adaptive width policy=acceptance.
  • Normalized effective environments are identical after excluding the expected adaptive switches, port, and SSH/session metadata.

Natural-EOS result

Two 10-prompt repeats per mode, 20 requests per mode. All 40 measured requests completed with finish_reason=stop; none had 100% acceptance.

Metric Fixed PR667 adaptive Delta
Server decode, weighted 34.538 tok/s 35.125 tok/s +1.70%
Server decode, median 34.4 tok/s 35.7 tok/s +3.78%
Server decode, p10 31.67 tok/s 30.54 tok/s -3.57%
Client aggregate (includes TTFT) 26.814 tok/s 26.917 tok/s +0.38%
Client output-window 36.847 tok/s 36.453 tok/s -1.07%
Median TTFT 2.421 s 2.226 s -8.06%
p95 TTFT 2.644 s 2.387 s -9.72%
Median acceptance 0.755 0.795 +0.040

Replicates were stable:

  • fixed client aggregate: 26.835, 26.793 tok/s;
  • adaptive client aggregate: 26.943, 26.892 tok/s;
  • fixed server weighted: 34.560, 34.516 tok/s;
  • adaptive server weighted: 35.164, 35.086 tok/s.

However, only 1/10 response hashes matched between fixed and adaptive in each paired repeat. Each mode was internally deterministic (10/10 repeat hashes matched), so the controller changes outputs on this approximate sparse-prefill/top-k4 profile. HumanEval gold tests were retained but deliberately not executed by the serving client; this run makes no quality-equivalence claim.

Report SHA-256:

  • fixed: 6a7454d9cfff4c7109b4b75faf8330700909c082291c08451a1f0508bb035acc
  • adaptive: 01156f9fbcb274ae95e08de0b8aaf11068ffe6d8a31414a8f238b21192451027
  • adaptive-r2: 8365b68171d0ba4f9b97bf9666c3fbf1dd1917efb563936fbc81ddcacee78be5
  • fixed-r2: 0b91e812459277e870c553024aef279761452cccbd73c9f70a80e3e8d372a6d8

Artifacts remain on lucebox6 at /home/lucebox6/pr667-humaneval.sC8okv/. No model server remains running. As in the earlier campaign, absolute qualification is limited by the development BIOS/power state; the matched relative A/B is valid.

Decision

This is not a material product-speed win and is nowhere near 2x. Natural short code completions are effectively flat end to end (+0.38%) with a slightly worse p10, while the cumulative 512-token coding workload regressed weighted server decode by 7.22%. Keep PR667 adaptive q5 disabled in the Lucebox customer profile unless a future controller passes a broader quality-scored workload battery with a clear Pareto improvement.

@davide221

Copy link
Copy Markdown
Contributor Author

Draft qualification update — not ready to merge.

This commit adds two narrowly scoped gfx1151 defaults for DeepSeek V4 Flash DSpark:

  • ROCmFP2 verification reuses each activation across four output rows per Wave32 wave (DFLASH_ROCMFP2_ROW4=0 restores the two-row kernel).
  • Decode attention stays explicit at short history and switches per layer to the model's sparse top-k flash path only when at least half of the compressed rows can be removed (DFLASH_DS4_SPARSE_DECODE_FLASH=0 restores explicit attention).

Matched fixed-q4, six-routed-expert results on lucebox8, ROCm 7.2.4, Strix Halo gfx1151:

Prompt Server decode Client decode Vulkan chart Output
8K 36.1 tok/s 41.93 tok/s 39.55 tok/s identical hash
32K 33.8 tok/s 39.30 tok/s 38.41 tok/s identical hash
123K 28.3 tok/s 33.17 tok/s 32.60 tok/s identical hash

The final 32K and 123K runs left the sparse-attention environment variable unset and logged the gfx1151 default activation. Both completed 128 tokens with SHA-256 0f785a7ffa406498aafb14553966eaed0f52220fed0f7cc016b66921d104d194.

LuceGraph/ROCprof attribution on the matched 32K verifier showed:

  • ROCmFP2 kernel time: about 1.949 s -> 1.780 s (-8.7%).
  • Marked verify span: 7.645 s -> 7.413 s (-3.0%) from row reuse alone.
  • Adaptive sparse flash supplies the remaining long-context gain by avoiding physical raw+compressed KV concatenation and dense attention over rows that the model's indexer will discard.

Correctness/rollback checks:

  • test_rocmfp_mix_slice_matvec: pass with the new default and with DFLASH_ROCMFP2_ROW4=0.
  • test_rocmfp_mix_gateup_glu: pass with the new default and with the kill switch.
  • test_deepseek4_unit: pass.
  • Rejected FP3/FP4 variants are not included.

The retained disposable captures and generated host/GPU reports are under /data/lucegraph-ds4-gap-fb2f-runs. Raw 123K prefill remains below the Vulkan chart (122,879 tokens in 1,162.7 s, about 105.7 tok/s), so this closes the measured decode gap rather than the full cold-retrieval gap. Keep this PR draft-only until prefill, long-context quality, clocks/power, and broader checkpoint coverage are improved/repeated.

@davide221
davide221 force-pushed the codex/ds4-rocm-moe-wave32 branch from f456d8b to 5eb4fbe Compare August 29, 2026 23:27
@davide221
davide221 marked this pull request as ready for review August 29, 2026 23:34

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 issues found across 24 files

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="server/deps/llama.cpp/ggml/src/ggml-cuda/rocmfp3_mix.cu">

<violation number="1" location="server/deps/llama.cpp/ggml/src/ggml-cuda/rocmfp3_mix.cu:519">
P2: The paired path changes the per-term rounding sequence: `mix_block_accum2` uses explicit `fmaf`, while `mix_block_accum` uses the chained multiply expression. On a build that does not contract the reference expression, this changes dot products and can change greedy tokens; keep both helpers' arithmetic sequence identical.</violation>
</file>

<file name="server/src/deepseek4/deepseek4_fused_verify.inc">

<violation number="1" location="server/src/deepseek4/deepseek4_fused_verify.inc:1270">
P2: When `q - 1 - i` exceeds `w.n_swa`, this wrap calculation extends past the raw-ring segment and masks compressed or preserved rows that should remain visible. Handle `future >= w.n_swa` as a full raw-ring mask, and only apply the two wrapped ranges when `future < w.n_swa`.</violation>
</file>

<file name="server/docs/ENVIRONMENT.md">

<violation number="1" location="server/docs/ENVIRONMENT.md:51">
P3: Two of the eight newly documented DFLASH_* variables — `DFLASH_GFX1151_HC_MMVF_Q4` (table line 51, defined in ggml-cuda/mmvf.cu) and `DFLASH_ROCMFP2_ROW4` (table line 65, defined in ggml-cuda/rocmfp2_mix.cu) — are missing from the "Full inventory (generated)" section, while the other new variables in this PR were added to it. Anyone searching the index finds no cross-reference for these kill switches. Add both to the inventory with their defining file to keep the documented table and the index consistent.</violation>
</file>

<file name="server/test/bench_ds4_topk.cpp">

<violation number="1" location="server/test/bench_ds4_topk.cpp:162">
P3: This standalone bench re-implements the long-context TOP_K selected-set parity check (full sort vs block-radix) and timing that already lives in the established framework, server/tests/test_deepseek4_unit.cpp (~lines 3020-3125), which registers in ctest and is what the PR cites as passing. Keeping a separate executable with its own correctness gate means the selected-set contract must be maintained in two places and can silently drift. Move the exact-set verification into the existing framework's long-context top-k test and keep this binary for perf-only measurement over the tile sweep.</violation>
</file>

<file name="server/src/qwen35/qwen35_backend.cpp">

<violation number="1" location="server/src/qwen35/qwen35_backend.cpp:4002">
P2: When the DSpark confidence gate shortens `v_len` while shared adaptive width is off, this branch still divides by the configured `verify_cap` instead of the actual offered candidates. That under-reports acceptance and can mis-steer the PFlash residency bandit; use `n_spec_offered_sum` in all three exit paths.

(Based on your team's feedback about offered-candidate acceptance-rate denominators.)</violation>
</file>

<file name="server/src/deepseek4/deepseek4_backend.cpp">

<violation number="1" location="server/src/deepseek4/deepseek4_backend.cpp:238">
P2: When `DFLASH_DS4_SPEC=1` but the drafter is absent or fails to load, `init()` falls back to autoregressive decoding while this guard still enables SparseFlash attention. Enable the default only after a DSpark drafter loads successfully, or gate it on the actual speculative-decoder state.</violation>
</file>

<file name="server/deps/llama.cpp/ggml/src/ggml-cuda/mmvf.cu">

<violation number="1" location="server/deps/llama.cpp/ggml/src/ggml-cuda/mmvf.cu:852">
P2: On gfx1150 and other RDNA3.5 devices, this defaults q4 hyper-connection projections to MMVF despite qualification being gfx1151-only. Gate this branch with the exact gfx1151 compute capability so unmeasured devices retain the generic crossover.</violation>
</file>

<file name="server/test/test_rocmfp_mix_gateup_glu.cpp">

<violation number="1" location="server/test/test_rocmfp_mix_gateup_glu.cpp:125">
P3: The new comment and the ntok=3 change assume ntok=3 always exercises the two-pass GLU finalizer, but that only holds on a gfx1151 strix-tuned device. In rocmfp2_mix.cu's ggml_cuda_rocmfp2_mix_mul_mat_id_glu, the one-pass kernel is selected when `!strix_tuned || n_tokens <= 2` where `strix_tuned = eu.gfx1151 && eg.gfx1151`. On any non-gfx1151 HIP device, ntok=3 still takes the one-pass path, so the two-pass finalizer is never covered there and the new one_tok block duplicates the same path (both use the `<1,2>` kernel). The test only validates the two-pass branch when run on the gfx1151 target; consider gating this test (or the ntok=3 case) on the gfx1151 device so the coverage claim matches behavior.</violation>
</file>

<file name="server/test/bench_rocmfp_mix_gateup_glu.cpp">

<violation number="1" location="server/test/bench_rocmfp_mix_gateup_glu.cpp:233">
P3: The new verification block claims it "catches mode-specific numerical drift," but it never compares any of the hashes against a reference or against each other — it only prints them. It also does not check the actual fusion invariant: fused_result (swiGLU output) is never compared to anything recomputed from the raw up/gate projections, so a wrong fusion still passes silently. For the claims in the comment to hold, compute silu(up_result)*gate_result on host and compare (with tolerance) to fused_result, or assert the raw/fused hashes match an expected value; otherwise reword the comment. This matters because the PR relies on these benchmarks to gate the fusion.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +519 to +520
acc0 = fmaf(rs0 * mix_fp3_fixed(mix_fp3_code(qs0, j)), x, acc0);
acc1 = fmaf(rs1 * mix_fp3_fixed(mix_fp3_code(qs1, j)), x, acc1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The paired path changes the per-term rounding sequence: mix_block_accum2 uses explicit fmaf, while mix_block_accum uses the chained multiply expression. On a build that does not contract the reference expression, this changes dot products and can change greedy tokens; keep both helpers' arithmetic sequence identical.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/deps/llama.cpp/ggml/src/ggml-cuda/rocmfp3_mix.cu, line 519:

<comment>The paired path changes the per-term rounding sequence: `mix_block_accum2` uses explicit `fmaf`, while `mix_block_accum` uses the chained multiply expression. On a build that does not contract the reference expression, this changes dot products and can change greedy tokens; keep both helpers' arithmetic sequence identical.</comment>

<file context>
@@ -435,7 +476,65 @@ __device__ __forceinline__ void mix_block_accum(
+            const float x = xc[col0 + j];
+            const float rs0 = (j < MIX_QK/2) ? s00 : s01;
+            const float rs1 = (j < MIX_QK/2) ? s10 : s11;
+            acc0 = fmaf(rs0 * mix_fp3_fixed(mix_fp3_code(qs0, j)), x, acc0);
+            acc1 = fmaf(rs1 * mix_fp3_fixed(mix_fp3_code(qs1, j)), x, acc1);
+        }
</file context>
Suggested change
acc0 = fmaf(rs0 * mix_fp3_fixed(mix_fp3_code(qs0, j)), x, acc0);
acc1 = fmaf(rs1 * mix_fp3_fixed(mix_fp3_code(qs1, j)), x, acc1);
acc0 += rs0 * mix_fp3_fixed(mix_fp3_code(qs0, j)) * x;
acc1 += rs1 * mix_fp3_fixed(mix_fp3_code(qs1, j)) * x;

Comment on lines +1270 to +1275
if (future > 0) {
const int begin = (pos_i + 1) % w.n_swa;
const int first = std::min(future, w.n_swa - begin);
add_range(col + (size_t) begin,
col + (size_t) (begin + first));
add_range(col,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When q - 1 - i exceeds w.n_swa, this wrap calculation extends past the raw-ring segment and masks compressed or preserved rows that should remain visible. Handle future >= w.n_swa as a full raw-ring mask, and only apply the two wrapped ranges when future < w.n_swa.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/deepseek4/deepseek4_fused_verify.inc, line 1270:

<comment>When `q - 1 - i` exceeds `w.n_swa`, this wrap calculation extends past the raw-ring segment and masks compressed or preserved rows that should remain visible. Handle `future >= w.n_swa` as a full raw-ring mask, and only apply the two wrapped ranges when `future < w.n_swa`.</comment>

<file context>
@@ -1249,34 +1255,97 @@ static int ds4_try_fused_verify_step(
+                              col + (size_t) w.n_swa);
+                } else {
+                    const int future = e - 1 - pos_i;
+                    if (future > 0) {
+                        const int begin = (pos_i + 1) % w.n_swa;
+                        const int first = std::min(future, w.n_swa - begin);
</file context>
Suggested change
if (future > 0) {
const int begin = (pos_i + 1) % w.n_swa;
const int first = std::min(future, w.n_swa - begin);
add_range(col + (size_t) begin,
col + (size_t) (begin + first));
add_range(col,
if (future > 0) {
if (future >= w.n_swa) {
add_range(col, col + (size_t) w.n_swa);
} else {
const int begin = (pos_i + 1) % w.n_swa;
const int first = std::min(future, w.n_swa - begin);
add_range(col + (size_t) begin,
col + (size_t) (begin + first));
add_range(col,
col + (size_t) (future - first));
}
}

cache_.last_tok = out_tokens.empty() ? last_tok : out_tokens.back();
const int total_draft_pos = std::max(1, n_spec_steps * verify_cap);
const int total_draft_pos = shared_feedback_width
? std::max(1, n_spec_offered_sum)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When the DSpark confidence gate shortens v_len while shared adaptive width is off, this branch still divides by the configured verify_cap instead of the actual offered candidates. That under-reports acceptance and can mis-steer the PFlash residency bandit; use n_spec_offered_sum in all three exit paths.

(Based on your team's feedback about offered-candidate acceptance-rate denominators.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/qwen35/qwen35_backend.cpp, line 4002:

<comment>When the DSpark confidence gate shortens `v_len` while shared adaptive width is off, this branch still divides by the configured `verify_cap` instead of the actual offered candidates. That under-reports acceptance and can mis-steer the PFlash residency bandit; use `n_spec_offered_sum` in all three exit paths.

(Based on your team's feedback about offered-candidate acceptance-rate denominators.) </comment>

<file context>
@@ -3983,7 +3998,9 @@ bool Qwen35Backend::do_spec_decode(int committed, int n_gen,
             cache_.last_tok = out_tokens.empty() ? last_tok : out_tokens.back();
-            const int total_draft_pos = std::max(1, n_spec_steps * verify_cap);
+            const int total_draft_pos = shared_feedback_width
+                ? std::max(1, n_spec_offered_sum)
+                : std::max(1, n_spec_steps * verify_cap);
             out_accept_rate =
</file context>

// Only the DSpark verifier configuration has completed model-level
// qualification. Preserve an explicit user choice, including the burn-in
// kill switch value 0.
if (!env_flag_enabled("DFLASH_DS4_SPEC") ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When DFLASH_DS4_SPEC=1 but the drafter is absent or fails to load, init() falls back to autoregressive decoding while this guard still enables SparseFlash attention. Enable the default only after a DSpark drafter loads successfully, or gate it on the actual speculative-decoder state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/deepseek4/deepseek4_backend.cpp, line 238:

<comment>When `DFLASH_DS4_SPEC=1` but the drafter is absent or fails to load, `init()` falls back to autoregressive decoding while this guard still enables SparseFlash attention. Enable the default only after a DSpark drafter loads successfully, or gate it on the actual speculative-decoder state.</comment>

<file context>
@@ -230,6 +230,32 @@ static bool configure_dspark_mmvq_defaults(int gpu) {
+    // Only the DSpark verifier configuration has completed model-level
+    // qualification. Preserve an explicit user choice, including the burn-in
+    // kill switch value 0.
+    if (!env_flag_enabled("DFLASH_DS4_SPEC") ||
+        std::getenv("DFLASH_DS4_SPARSE_DECODE_FLASH") != nullptr) {
+        return;
</file context>

"DFLASH_GFX1151_HC_MMVF_Q4");
return !value || std::atoi(value) != 0;
}();
if (gfx1151_hc_q4_mmvf && GGML_CUDA_CC_IS_RDNA3_5(cc) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: On gfx1150 and other RDNA3.5 devices, this defaults q4 hyper-connection projections to MMVF despite qualification being gfx1151-only. Gate this branch with the exact gfx1151 compute capability so unmeasured devices retain the generic crossover.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/deps/llama.cpp/ggml/src/ggml-cuda/mmvf.cu, line 852:

<comment>On gfx1150 and other RDNA3.5 devices, this defaults q4 hyper-connection projections to MMVF despite qualification being gfx1151-only. Gate this branch with the exact gfx1151 compute capability so unmeasured devices retain the generic crossover.</comment>

<file context>
@@ -834,6 +836,23 @@ bool ggml_cuda_should_use_mmvf(enum ggml_type type, int cc, const int64_t * src0
+                        "DFLASH_GFX1151_HC_MMVF_Q4");
+                    return !value || std::atoi(value) != 0;
+                }();
+                if (gfx1151_hc_q4_mmvf && GGML_CUDA_CC_IS_RDNA3_5(cc) &&
+                    src0_ne[0] == 16384 && src0_ne[1] == 24 && ne11 == 4) {
+                    return true;
</file context>
Suggested change
if (gfx1151_hc_q4_mmvf && GGML_CUDA_CC_IS_RDNA3_5(cc) &&
if (gfx1151_hc_q4_mmvf && cc == GGML_CUDA_CC_OFFSET_AMD + 0x1151 &&

| `DFLASH_DS4_VERIFY_FORCE_GRAPH_REPLAY` | unset | OPT-IN: bypass graph property scans only after warmup; scheduler-generation checks remain mandatory. |
| `DFLASH_DS4_ROCTX` | unset | DEBUG: on HIP builds, dynamically load ROCTX and emit semantic DS4 prefill, speculative-decode, and layer-range markers for external rocprof traces. No events, timing, or device synchronization are added. |
| `DFLASH_QWEN35_ROCTX` | unset | DEBUG: on HIP builds, dynamically load ROCTX and mark Qwen concurrent steps, graph compute, and argmax readback with live, padded, and packed-prefill shape metadata. |
| `DFLASH_GFX1151_HC_MMVF_Q4` | 1 on gfx1151 for the DS4 `[16384,24]` q4 projection | BURN-IN KILL SWITCH: =0 restores the generic hipBLAS dispatch decision. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Two of the eight newly documented DFLASH_* variables — DFLASH_GFX1151_HC_MMVF_Q4 (table line 51, defined in ggml-cuda/mmvf.cu) and DFLASH_ROCMFP2_ROW4 (table line 65, defined in ggml-cuda/rocmfp2_mix.cu) — are missing from the "Full inventory (generated)" section, while the other new variables in this PR were added to it. Anyone searching the index finds no cross-reference for these kill switches. Add both to the inventory with their defining file to keep the documented table and the index consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/docs/ENVIRONMENT.md, line 51:

<comment>Two of the eight newly documented DFLASH_* variables — `DFLASH_GFX1151_HC_MMVF_Q4` (table line 51, defined in ggml-cuda/mmvf.cu) and `DFLASH_ROCMFP2_ROW4` (table line 65, defined in ggml-cuda/rocmfp2_mix.cu) — are missing from the "Full inventory (generated)" section, while the other new variables in this PR were added to it. Anyone searching the index finds no cross-reference for these kill switches. Add both to the inventory with their defining file to keep the documented table and the index consistent.</comment>

<file context>
@@ -44,6 +48,9 @@ consolidation of this list into CLI flags is tracked as follow-up work.
 | `DFLASH_DS4_VERIFY_FORCE_GRAPH_REPLAY` | unset | OPT-IN: bypass graph property scans only after warmup; scheduler-generation checks remain mandatory. |
 | `DFLASH_DS4_ROCTX` | unset | DEBUG: on HIP builds, dynamically load ROCTX and emit semantic DS4 prefill, speculative-decode, and layer-range markers for external rocprof traces. No events, timing, or device synchronization are added. |
 | `DFLASH_QWEN35_ROCTX` | unset | DEBUG: on HIP builds, dynamically load ROCTX and mark Qwen concurrent steps, graph compute, and argmax readback with live, padded, and packed-prefill shape metadata. |
+| `DFLASH_GFX1151_HC_MMVF_Q4` | 1 on gfx1151 for the DS4 `[16384,24]` q4 projection | BURN-IN KILL SWITCH: =0 restores the generic hipBLAS dispatch decision. |
+| `GGML_CUDA_MLA_SPLIT_KV` / `GGML_DS4_FA_SPLIT_KV` | 1 on gfx1151 indexed decode; unset elsewhere | BURN-IN: force the reusable split-KV MLA schedule. Set `GGML_CUDA_MLA_NO_SPLIT_KV=1` (or legacy `GGML_DS4_FA_NO_SPLIT_KV=1`) to disable it. |
+| `GGML_DS4_TOPK_BLOCK_RADIX` | 1 on gfx1151 | BURN-IN KILL SWITCH: =0 restores hipCUB full sort for DS4-shaped 512-row top-k selection. |
</file context>

backend, ncols, nrows, k, false, 2, iterations, full_sort) &&
compute_topk(
backend, ncols, nrows, k, true, 2, iterations, hierarchical);
const bool exact = ran && same_selected_set(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This standalone bench re-implements the long-context TOP_K selected-set parity check (full sort vs block-radix) and timing that already lives in the established framework, server/tests/test_deepseek4_unit.cpp (~lines 3020-3125), which registers in ctest and is what the PR cites as passing. Keeping a separate executable with its own correctness gate means the selected-set contract must be maintained in two places and can silently drift. Move the exact-set verification into the existing framework's long-context top-k test and keep this binary for perf-only measurement over the tile sweep.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/test/bench_ds4_topk.cpp, line 162:

<comment>This standalone bench re-implements the long-context TOP_K selected-set parity check (full sort vs block-radix) and timing that already lives in the established framework, server/tests/test_deepseek4_unit.cpp (~lines 3020-3125), which registers in ctest and is what the PR cites as passing. Keeping a separate executable with its own correctness gate means the selected-set contract must be maintained in two places and can silently drift. Move the exact-set verification into the existing framework's long-context top-k test and keep this binary for perf-only measurement over the tile sweep.</comment>

<file context>
@@ -0,0 +1,184 @@
+                backend, ncols, nrows, k, false, 2, iterations, full_sort) &&
+            compute_topk(
+                backend, ncols, nrows, k, true, 2, iterations, hierarchical);
+        const bool exact = ran && same_selected_set(
+            full_sort.indices, hierarchical.indices, ncols, nrows, k);
+        const double speedup = hierarchical.milliseconds > 0.0
</file context>

// in must be a multiple of 128: the wide block load reads 128 weights at a time and would
// read past the tensor on the final block (register_host enforces this).
const int in = 256, out = 64, n_experts = 6, n_used = 3, ntok = 2;
// Three tokens exercises the low-register two-pass GLU finalizer. q <= 2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new comment and the ntok=3 change assume ntok=3 always exercises the two-pass GLU finalizer, but that only holds on a gfx1151 strix-tuned device. In rocmfp2_mix.cu's ggml_cuda_rocmfp2_mix_mul_mat_id_glu, the one-pass kernel is selected when !strix_tuned || n_tokens <= 2 where strix_tuned = eu.gfx1151 && eg.gfx1151. On any non-gfx1151 HIP device, ntok=3 still takes the one-pass path, so the two-pass finalizer is never covered there and the new one_tok block duplicates the same path (both use the <1,2> kernel). The test only validates the two-pass branch when run on the gfx1151 target; consider gating this test (or the ntok=3 case) on the gfx1151 device so the coverage claim matches behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/test/test_rocmfp_mix_gateup_glu.cpp, line 125:

<comment>The new comment and the ntok=3 change assume ntok=3 always exercises the two-pass GLU finalizer, but that only holds on a gfx1151 strix-tuned device. In rocmfp2_mix.cu's ggml_cuda_rocmfp2_mix_mul_mat_id_glu, the one-pass kernel is selected when `!strix_tuned || n_tokens <= 2` where `strix_tuned = eu.gfx1151 && eg.gfx1151`. On any non-gfx1151 HIP device, ntok=3 still takes the one-pass path, so the two-pass finalizer is never covered there and the new one_tok block duplicates the same path (both use the `<1,2>` kernel). The test only validates the two-pass branch when run on the gfx1151 target; consider gating this test (or the ntok=3 case) on the gfx1151 device so the coverage claim matches behavior.</comment>

<file context>
@@ -122,7 +122,9 @@ TEST_CASE(RocmfpMixGateupGluFixture, fused_gateup_glu) {
     // in must be a multiple of 128: the wide block load reads 128 weights at a time and would
     // read past the tensor on the final block (register_host enforces this).
-    const int in = 256, out = 64, n_experts = 6, n_used = 3, ntok = 2;
+    // Three tokens exercises the low-register two-pass GLU finalizer. q <= 2
+    // uses the one-pass kernel and is checked separately below.
+    const int in = 256, out = 64, n_experts = 6, n_used = 3, ntok = 3;
</file context>


ggml_cuda_rocmfp2_mix_unregister(d_gate);
ggml_cuda_rocmfp2_mix_unregister(d_up);
// Hash both raw projections and the fused output after timing. This catches

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new verification block claims it "catches mode-specific numerical drift," but it never compares any of the hashes against a reference or against each other — it only prints them. It also does not check the actual fusion invariant: fused_result (swiGLU output) is never compared to anything recomputed from the raw up/gate projections, so a wrong fusion still passes silently. For the claims in the comment to hold, compute silu(up_result)*gate_result on host and compare (with tolerance) to fused_result, or assert the raw/fused hashes match an expected value; otherwise reword the comment. This matters because the PR relies on these benchmarks to gate the fusion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/test/bench_rocmfp_mix_gateup_glu.cpp, line 233:

<comment>The new verification block claims it "catches mode-specific numerical drift," but it never compares any of the hashes against a reference or against each other — it only prints them. It also does not check the actual fusion invariant: fused_result (swiGLU output) is never compared to anything recomputed from the raw up/gate projections, so a wrong fusion still passes silently. For the claims in the comment to hold, compute silu(up_result)*gate_result on host and compare (with tolerance) to fused_result, or assert the raw/fused hashes match an expected value; otherwise reword the comment. This matters because the PR relies on these benchmarks to gate the fusion.</comment>

<file context>
@@ -170,20 +214,51 @@ int main() {
 
-    ggml_cuda_rocmfp2_mix_unregister(d_gate);
-    ggml_cuda_rocmfp2_mix_unregister(d_up);
+    // Hash both raw projections and the fused output after timing. This catches
+    // mode-specific numerical drift even when the generated-token hash happens
+    // to remain unchanged.
</file context>

@davide221

Copy link
Copy Markdown
Contributor Author

Corrected Lucebox6 result: adaptive is workload-dependent; the full performance commit does not speed up the private q5 bundle

Correction to my earlier conclusion: I had generalized from one high-acceptance workload and initially tested the adaptive controller in isolation, not the complete PR 667 performance stack.

Adaptive q5 controller

The width traces confirm that adaptive mode was active (fixed offered 4 candidates/step; adaptive averaged roughly 3.3–3.8 and starts each request at q3).

On natural-EOS HumanEval, adaptive was slightly faster by the server metric:

metric adaptive vs fixed q5
server weighted decode +1.70%
client aggregate +0.38%
client output-window -1.07%
p10 server rate about -3.6%

On the capped, high-acceptance coding workload, adaptive was -7.22% by server weighted decode. Fixed q5 amortizes more work when acceptance is already high. Therefore the adaptive result is not universally negative, but it is not a robust default win either; keeping it opt-in is justified.

Full PR 667 performance commit on the PR 58 private bundle

I then rebuilt the exact PR 58 package on private base 136ad9c1, applied only PR 667's current performance commit 5eb4fbe, and ran an ABBA comparison against the PR 58 baseline. Both arms used fixed q5 (DFLASH_ADAPTIVE_VERIFY=0, DFLASH_DS4_ADAPTIVE_VERIFY=0), identical model/package/prompts, temperature 0, seed 0, and max_tokens=512.

combined two-run result PR 58 baseline PR 58 + 5eb4fbe delta
server weighted decode 34.537 tok/s 34.438 tok/s -0.29%
client aggregate 26.810 tok/s 26.744 tok/s -0.25%

All 40 measured requests ended naturally (finish_reason=stop), each repeat generated 2,384 tokens, and candidate outputs matched baseline 10/10. Internal weighted timing moved from 114.844 to 115.176 ms/spec-step, so this is noise/slightly slower rather than a hidden gain.

Correctness tests passed: server 432/432, DeepSeek4 unit suite, GPU top-k parity, and gfx1151 ROCmFP. The block-radix top-k microbenchmark is genuinely faster on gfx1151 (2.33–4.31x across tested 8K–32K widths), but that kernel win does not translate to this private q5 end-to-end path. Most of this commit is gated to gfx1151/q4 or paths bypassed by the bundled private operator.

Recommendation: do not stack 5eb4fbe into the private PR 58 package as-is. Keep adaptive opt-in and evaluate a path-specific controller with a real-workload/semantic gate. A matched ROCprof kernel attribution is still outstanding because the current LuceGraph concurrency-emitter patch needs porting to this engine layout; I am not claiming an exact kernel-level cause without that capture.

Reproducibility artifacts retained on lucebox6:

  • result: /home/lucebox6/pr667-performance-humaneval-profile-20260830
  • source: /home/lucebox6/pr58-pr667-full-20260830
  • candidate commit: 109aa4215027c337803ea7f4668fa6aae6509791
  • candidate binary SHA-256: 1675d63788ade5ab4c98aa3f612814cc95812a879a1acb1a8bd5275f0d0c31de
  • baseline binary SHA-256: 79590a5b467b14564bbf1e432987941599c80ea851796d1260ab6bd63d3de691
  • source bundle SHA-256: 099c058f6e96ad35283c8d42547794e65900f74a73706dc30a711c1562d9bcea

@pepuscz

pepuscz commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Thank you, @davide221. We independently qualified the current PR #667 head, 5eb4fbe95e13944ad964bd7e42980bca518e3d5c, against the previously qualified Lucebox commit 2f136181b0b387592e413a81dedc0cd79991e128.

Test configuration

  • AMD Ryzen AI Max+ 395 / Radeon 8060S (gfx1151), 128 GiB unified memory
  • Ubuntu 26.04, kernel 7.0.0-29-generic, ROCm 7.1
  • DeepSeek V4 Flash 0731 ROCmFPX MIX target (98.29 GB)
  • Q4RMFP4 dense-F16 DSpark draft (10.65 GB)
  • Six routed experts, fixed speculative verification width q=4
  • Q4_0 K/V, sparse prefill, 131,072-token allocation, one prefix-cache slot
  • CPU performance governor with boost enabled, GPU DPM high, 120/120/120 W package limits
  • Identical model files, prompts, request limits, runtime options, and hardware policy for both arms
  • One fresh process per arm; every retrieval request was cold with zero restored prefix tokens

The 2,048-token workload used one warmup followed by three measured 510-token generations. Each retrieval point used one deterministic five-key request with a 256-token output ceiling.

Matched results

Workload Lucebox 2f13618 PR #667 5eb4fbe Change
2,048-token prompt — median generation 38.8 tok/s 41.7 tok/s +7.5%
7,680-token cold input processing 214.34 tok/s 213.91 tok/s -0.2%
7,680-token generation 34.8 tok/s 36.9 tok/s +6.0%
30,720-token cold input processing 202.11 tok/s 201.68 tok/s -0.2%
30,720-token generation 31.4 tok/s 32.0 tok/s +1.9%
122,879-token cold input processing 143.13 tok/s 142.82 tok/s -0.2%
122,879-token generation 28.2 tok/s 29.6 tok/s +5.0%

Both commits recovered all five values exactly at 7,680, 30,720, and 122,879 input tokens. The result confirms a decode-side improvement while input-processing throughput remains unchanged within 0.3%.

Quality and compatibility

The candidate passed:

  • HumanEval-style: 10/10
  • GSM8K-style: 10/10
  • MATH-style: 9/10 after correcting the math_08 fixture
  • Hermes tool-result contract: 12/12
  • PR-focused server, DeepSeek 4, ROCmFPX, fused GLU, dense matvec, and exact top-k tests

The unmodified harness labels math_08 incorrect because its fixture expects 20; the generated answer 20/3 is algebraically correct. With that established correction, the candidate scores 29/30, matching the previous Lucebox setup. The remaining miss is math_10: the model returned 998, while the expected answer is 997.

The complete 131,072-token configuration fit in 128 GiB. Minimum effective non-CMA memory headroom was 4,374,508 KiB, with no OOM, GPU reset, service restart, or safety-watcher event.

Reproduction

The public Ansible release now builds the unmodified 5eb4fbe source and pins the source, submodule, rocWMMA, package, model, and output-binary identities. We now list this as the leading Lucebox ROCm ROCmFPX configuration. Strix Halo llama.cpp Vulkan IQ3_XXS v0.7.0 remains the default because its 122,879-token result is still higher overall (226.82 tok/s input processing, 35.73 tok/s generation, 30/30 quality).

Thanks again for implementing and iterating on this optimization.

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