spec : add DFlash2 support (local convolution + candidate selector) - #27342
spec : add DFlash2 support (local convolution + candidate selector)#27342SubSir wants to merge 1 commit into
Conversation
|
Hi @SubSir, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
|
I've revised the PR description. It's fully hand-written now. |
|
Hi, there. After some testing in with qwen 3.8 in llama.cpp webui, I found out that vision support (with PDFs) is broken, while the native mtp is not. checkpoint from https://huggingface.co/incoai/Qwen3.8-27B-DFlash2-GGUF |
This comment was marked as low quality.
This comment was marked as low quality.
…and DFlash v1 Adds docs/dflash2-strix.md with the four-arm llama-benchy comparison on Qwen3.8-27B UD-Q4_K_XL, and vendors the unedited tool output under benchmarks/results/dflash2-20260819/. Headline, decode t/s at depth 0 / 8k / 32k: base (no spec decode) 11.81 11.44 10.54 DFlash v1 (n=5) 21.09 12.87 10.75 DFlash2 (n=4) 26.39 21.58 21.11 DFlash2 (n=7) 25.18 21.46 16.32 DFlash2 holds about 2x over base at every depth measured, while DFlash v1 decays to 1.02x by 32k, i.e. by then it is worth nothing over plain decode. Draft width matters at depth: at 32k, width 4 beats width 7 by 29 percent, and the two tie at shallow depth. DFlash2 engine support is upstream ggml-org/llama.cpp#27342 by Jian Chen and is not our work; the page says so. What is ours is the gfx1151/Vulkan validation and the draft-width tuning. Drafter weights are inco's published Apache-2.0 GGUFs. Measured with llama-benchy 0.4.0, which draws prompts from a Gutenberg book so acceptance reflects real prose. Content matters more than expected here: a code-corpus prompt set decays about 47 percent from shallow to 32k where this corpus decays about 20 percent, so the page carries that caveat rather than presenting one number as definitive. --runs 2, so the spec arms carry +/- 1.0 to 2.5 and only the 32k width gap is comfortably outside the noise. Prefill was taken at the server default -ub 512; -ub 256 is the measured dense optimum on these weights, so that column is a few percent below the ceiling. Noted in the page, rerun deferred. Assisted-by: Claude Opus 5
|
Very impressive for single parallelism: on my b70 I get almost a 3x performance boost under my testing scenarios with 5 tokens (regression for higher values). |
Vision requests fail with
|
| Case | Before | After |
|---|---|---|
| Image prompt | rc=-1, request dropped |
described correctly |
| Text | 63 t/s, acceptance 103/132 | 63 t/s, acceptance 103/132 |
| Text right after an image prompt | n/a | 62.5 t/s, acceptance 132/184 |
The third row is the one I was most worried about — the draft KV survives a multimodal
prefill, acceptance stays in the normal range, and no rc=-1 or CUDA errors appear in the log.
Caveat: only tested single-sequence (parallel = 1) on one target/drafter pair, so a
second opinion on the multi-sequence path would be welcome.
Unrelated data point: --spec-draft-n-max 7 is bad on Volta
The README of the GGUF repo suggests --spec-draft-n-max 7, which matches the H200
numbers. On a V100 it is a sizeable regression; a sweep over three prompts (3 prompts x 2
runs, mean TG) gave:
| n-max | 2 | 3 | 4 | 5 | 7 | built-in MTP (n4) |
|---|---|---|---|---|---|---|
| TG, t/s | 51.9 | 53.1 | 55.5 | 52.2 | 44.9 | 52.1 |
At n-max 7 a long generation drafted 1356 tokens for 315 accepted and dropped to 27 t/s
against 39.8 for the built-in MTP. Q8_0 of the drafter was consistently slower than
Q4_K_M while costing ~0.9 GiB more VRAM, matching the acceptance-length table in the
model card.
Written by Claude (Anthropic's Claude Code), posted from the account of the human who ran
the hardware. The investigation, the patch and every number above come from actual runs on
the V100 box described at the top — nothing here is inferred or estimated. Please review
the patch on its merits; I can rerun any check you would like to see.
|
@Shamish There is already a fix for the reported problem at z-lab#1 As for the Performance on a single RTX3090, it seems to be not significantly better than MTP: MTP: DFlash2 (using maxn=4 because 7 was slower): |
|
A test with 2 / 4 / etc. concurrenc (-np) would be nice - MTP has some issues there currently and if this also scales with parallel requests, that would be a totally different story :) |
|
Thanks for the PR. It would be great if you could run the benchmark on SpeedBench using the corresponding checkpoints and report the performance numbers. @SubSir |
|
Tested this branch on AMD Strix Halo (gfx1151) with the Vulkan backend. Builds clean and the DFlash2 drafter loads and runs correctly. Sharing numbers since this looks like an untested configuration — it's an integrated GPU on unified memory, which behaves quite differently from discrete VRAM. Setup
Measured over Results (tok/s ± σ)
Observations1. Gain over MTP is workload-dependent. DFlash2 Q8_0 is +12.6% over MTP on code generation (clears 3–4σ) but only +3% on prose (inside noise). A single prose prompt would have shown the two as equivalent — worth noting for anyone benchmarking this. 2. The smaller drafter wins here. BF16 trails Q8_0 by ~9–10% on both prompts. On unified memory the drafter and target contend for one memory channel, and the extra 1.7 GB of weight traffic appears to cost more than the improved acceptance rate returns. I'd expect this to invert on a discrete GPU. 3.
Throughput climbs to block_size 5 and then stops. block_size 8 matched block_size 5 sample-for-sample across seven deterministic runs (21.47/21.70/21.11/21.12/23.68/21.31/23.01 vs 21.39/21.71/21.12/21.13/23.69/21.32/23.02). The startup line explains it — Two things follow. It might be worth clamping or warning when 4. Minor: a For context on the platformThe ROCm/HIP backend on the same machine (packaged LimitationsTwo prompts, five samples per cell (seven for the block_size sweep). Acceptance rate is content-dependent and the prose/code gap is already 12 points, so other workloads may land elsewhere. The block_size sweep used the code prompt on the Q8_0 drafter only. Happy to run additional configurations if useful. |
Typically, this type of thing doesn't help. Now you're presenting a thing that you don't even know works and someone has to waste time trying to figure out if it even works? You're just adding noise and no signal. It's great that you want to help out with OSS projects. Just find a different way to contribute. |
|
Blackwell numbers (sm_120). RTX PRO 4000 Blackwell SFF, 24 GB, 70 W cap, CUDA 12.8. Built at 5ecbe1a with Target Qwen3.8-27B NVFP4, draft
Separately, a single 1100-token code generation at |
Thats kind of strange. In my setup with either running it with/without layer split on R9700's (2x) i dont see the "near linear" scaling for multiple sessions. np=1 is higher with ~45-48TPS, but it dops to 23/24 per slot for np=2, and falls below no speculation for long contexts and long generations (above 130k). Thats on vulkan though - I need to retest ROCM. Will try the PR the upcoming days on the dual card config... |
|
I am (non-exhaustively and non-scientifically) testing on R9700 + Vulkan. At Those gains are not as spectacular as claimed, but are an improvement. My settings (MTP settings also included, commented out): ./llama-server --jinja --port 8085 -fa on -np 1 --log-colors on --no-webui --metrics --models-max 1 --no-warmup --models-preset models/config.inimodel = AI/LLM/Models/Qwen3.8-27B-UD-Q4_K_XL.gguf
ctx-size = 131072
ctk = iq4_nl
ctv = iq4_nl
ub = 2048
b = 1024
temp = 1.0
top-p = 0.95
top-k = 20
repeat-penalty = 1.0
presence-penalty = 0.0
dev = Vulkan0
chat-template-file=AI/LLM/Models/qwen3.8_chat_template.jinja
#spec-default =
#spec-type = draft-mtp
#spec-draft-n-max = 4
fit = off
spec-type = draft-dflash
spec-draft-n-max = 4
model-draft = AI/LLM/Models/Qwen3.8-27B-DFlash2-Q4_K_M.ggufThe JINJA is froggeric's v22.2 |
Could you maybe also try with two parallel requests in np=2 or np=3? would be nice to have a second reference if the scaling is an issue on my side... |
I'm not familiar with client-side apps that make parallel requests 🫤 But if you've got suggestions on how to try parallel requests I'll gladly take it. |
think thats the easiest and most straight forward. Also tried to reproduce gabrielcosis results on ROCM and did a second MTP run for sanity, but nowhere near. The dropoff at higher fills above 100k is even heftier than vulkan, down to ~18TPS per slot for two filled to 110k each. Vulkan stays at around 22TPS then, scales to 3x15TPS or the same fill degree. Still need to establish a reliable baseline before the Dflash2 test makes sense... |
|
Thanks for this PR. I built it to try DFlash2 on Windows and hit a load failure I could not resolve, so here is a precise repro in case it is useful. Summary: with this branch, the z-lab DFlash2 drafter fails to load when paired with a target, with EnvironmentReproDrafter: What I ruled out
I also chased One possibly useful detailMSVC's The drafter's own metadata, in case a shape assumption is involved: Happy to run a patched build or add instrumentation if that would help narrow it down. |
cache q4? LOL |
…on + candidate selector) Upstream PR: ggml-org/llama.cpp#27342 Author: Zihan Zhang (SubSir) <z-lab/dflash2> Commit: 5ecbe1a "support DFlash2" Adds grouped dynamic depthwise convolution and a candidate selector on top of DFlash, enabled automatically when the checkpoint is DFlash2.
|
Following up on my earlier Strix Halo numbers with the concurrency data @KaruroChori and @treo asked about. Same machine (Ryzen AI MAX+ 395, gfx1151, Vulkan/RADV), same build (this PR @ Server started with Aggregate throughput (tok/s)
Per-request throughput (tok/s)
What this shows1. The baseline scales, the drafted paths don't. Without a drafter, aggregate throughput goes 11.11 → 20.14 → 34.14, close to linear. With either drafter it drops from np=1 to np=2 and only recovers past the single-stream figure at np=4. So the server's batching itself is healthy — the regression is specific to speculative decoding. 2. Speculation is a net loss at np≥2 here. At np=4 both drafters land at 25–26 tok/s aggregate against 34.14 with no drafter at all. The crossover sits between np=1 and np=2. On this hardware speculative decoding is purely a single-stream optimization — it buys 2× there and costs ~25% by np=4. 3. MTP degrades the same way DFlash2 does. @KaruroChori reported that MTP doesn't show this behaviour on Intel B70. On AMD/Vulkan the two are within noise of each other at every concurrency level (np=2: 16.52 vs 15.87; np=4: 26.35 vs 25.32). Whatever causes it here is not specific to DFlash2, so it may be worth checking whether the B70 difference is backend-specific rather than drafter-specific. Worth noting the failure mode is milder than what was reported on B70 — per-request throughput falls to 6.5–8.3 tok/s rather than collapsing to ~1 tok/s. It degrades rather than falls over. CaveatsThree rounds per cell, one prompt type, concurrency only up to 4. Happy to extend this to np=8, sweep |
|
Tested with folowing enviroment: Baseline MTP: DFlash2 Test command: |
|
I've tried this PR with my Ryzen 395+ with claude assistant. Following is the patch and report written by claude code: fixdflashmultigpudeviceinherit.patch DFlash2 on Ryzen AI Max+ 395 (Strix Halo, Radeon 8060S) — test report + multi-GPU crash fixTested PR #27342 on a Ryzen AI Max+ 395 / Radeon 8060S ("Strix Halo") box, also equipped with a Bug: crash on multi-GPU systems when
|
| Backend | Method | Decode speed | Notes |
|---|---|---|---|
| CUDA (RTX 5050, 8GB) | autoregressive | 6.8 t/s | VRAM-starved, spills to CPU |
| CUDA (RTX 5050, 8GB) | DFlash2 | 11.6 t/s (~1.7×) | |
| HIP/ROCm (8060S) | autoregressive | 11.1 t/s | |
| HIP/ROCm (8060S) | DFlash2 | 12.2 t/s (~1.1×) | |
| Vulkan/RADV (8060S) | autoregressive | 12.7 t/s | |
| Vulkan/RADV (8060S) | DFlash2 (post-fix) | 17.6 t/s (~1.4×) |
DFlash2's relative speedup is highly hardware-dependent: it helps most when the baseline is
compute/VRAM-starved (RTX 5050 with an 8GB card holding an 18GB model), and least when the
baseline is already fully GPU-resident and bandwidth-efficient (Strix Halo's 128GB unified
memory holds the whole model comfortably, so there's less headroom left to reclaim).
For reference, on the same 8060S/Vulkan setup, MTP (--spec-type draft-mtp, self-speculative,
no separate draft model) reached ~1.5–1.8× over the same autoregressive baseline — noticeably
ahead of DFlash2 here, likely because MTP's draft head shares almost the entire trunk with the
target (near-free verification), whereas DFlash2's separate draft network pays a full second
forward pass every block. Worth noting as a data point for where DFlash2 is and isn't the
right tool, not a criticism of the PR — different architectures trading off differently across
hardware is expected.
Environment
- CPU/APU: AMD Ryzen AI Max+ 395 (Strix Halo), Radeon 8060S Graphics (gfx1151), 128GB GTT
- Discrete GPU: NVIDIA GeForce RTX 5050, 8GB VRAM
- Backends tested: CUDA 13.3, ROCm/HIP 7.14, Vulkan (RADV, mesa 26.1.4, glslc 2026.1)
- Base commit:
5ecbe1ac1(PR spec : add DFlash2 support (local convolution + candidate selector) #27342 head at time of testing)
I see the same scaling issue. Slightly elevated performance compared to MTP on 2xR9700 @ Vulkan and ROCM (50/50 Split) of Q3.8-27B-UD-Q4-XL. Maybe 10-15%, and I also noticed a crazy spike to almost 4x when all of the predictions matched. Well done :) However, with parallel sessions, I see exactly the same steep drop as with MTP with vulkan and ROCM. Which is really cumbersome for any real-world workload. But thats not subject to the PR i guess but a general bug right now somewhere in the server or seculation implementation. I did not test the mtmd path. |
Incompatibility between
|
|
This errors out with more than 1 GPU and tensor parallelism. Tried 2x 3x 4x 5x GPUs: |
|
relates to #27396 |
A patch set for review: ngram cache overlay, loader bug, p_min wiring (measured notes)First — thank you for this PR. DFlash2 on Qwen3.8-27B is excellent We've been running it hard on a single RTX 5090 (Qwen3.8-27B Q5_K_M 1. An ngram cache overlay inside the dflash impl (the main addition)
Rationale (measured on GPU-resident targets): verify batches are Config: requires
Greedy outputs byte-identical on our merge probe (one caveat below). 2. Bug: n_max=4 crashes the draft loaderDeterministic repro: 3. Smaller items
4. One honest caveat + what we could NOT doAt draft width > trained width, the wider verify batch changes CUDA We also attempted cross-stream pre-drafting (worker thread decoding Repro commands and the full falsification table (truncation, EWMA |
(cherry picked from commit 5ecbe1ac17ec0484c5b44af0bd580cdc9c428ed4) Not a verbatim cherry-pick: ggml-org/llama.cpp#27342 sits on an upstream about one day ahead of this branch's base, so four files conflicted on context that belongs to other upstream commits rather than to DFlash2. Resolved by taking only the DFlash2 lines, leaving this commit a pure DFlash2 delta: - gguf-py/gguf/constants.py, gguf_writer.py: dropped SAMPLE_FROM_ANCHOR and add_sample_from_anchor - src/models/dflash.cpp: kept the logit-scale and final-logit-softcapping block, dropped the d2t reduced-draft-vocab scatter - common/speculative.cpp: kept the dflash.selector_top_k read, dropped the dflash.sample_from_anchor read Measured on gfx1151 / RADV (Radeon 8060S, Vulkan), Qwen3.8-27B UD-Q4_K_XL target with the published Qwen3.8-27B-DFlash2-Q8_0 drafter, f16 KV, one llama-server launch per request, arms counterbalanced, decode t/s: prompt AR DFlash DFlash2 v2/AR v2/v1 code 11.43 24.01 37.38 3.27x 1.56x prose 11.85 15.25 22.61 1.91x 1.48x reasoning 11.95 23.17 38.65 3.23x 1.67x mean 11.75 20.81 32.88 2.80x 1.58x Acceptance rises on every content type: code 0.406 -> 0.618, prose 0.177 -> 0.316, reasoning 0.375 -> 0.649, with fewer drafts issued to get there. Note the baseline is bandwidth-pinned here (11.75 t/s x 17.9 GB = 210 GB/s, at the board's ceiling), so this ratio is not comparable to the author's Apple-silicon figures. All three arms ran the same binary, which carries this branch's fix stack, so it does not isolate DFlash2's own contribution either. test-backend-ops passes on RADV for every op the new graph adds: TOP_K 445 cases, CONCAT 193, PAD 28, REPEAT 18, FILL 4. Assisted-by: Claude Opus 5
|
llama.cpp crashed when testing this branch, not sure if it is related to your changes or useful (core was not produced), just fyi. build done from commit 5ecbe1a ROCm 7.14, Ubuntu 24.04, 2x W7800 48GB cards I have not had any crashes running latest master with mtp/ngram. |
|
Root cause for the TL;DR
It is a VRAM-pressure bug, not a checkpoint bug — which is why it reproduces for some people and not others with the identical GGUF. EnvironmentNarrowing it downPhase markers inside Adding a bounds check in front of One device, and The mechanism// src/llama-model.cpp, load_tensors
splits[i] = free; // 1338 - free VRAM in bytes
float split_sum = 0.0f;
for (size_t i = 0; i < n_devices(); ++i) {
split_sum += splits[i];
splits[i] = split_sum;
}
for (size_t i = 0; i < n_devices(); ++i) {
splits[i] /= split_sum; // 1351 - 0/0 -> NaN when free == 0
}
...
const int layer_gpu = std::upper_bound(splits.begin(), splits.begin() + n_devices(),
float(il - i_gpu_start)/act_gpu_layers) - splits.begin();
auto * dev = devices.at(layer_gpu).dev; // 1363 - throwsWith a single device and The existing fallback just above does not catch this: if (free == 0 && total == 0) { // 1335
ggml_backend_dev_memory(cpu_dev, &free, &total);
}
This is reachable by any second model loaded onto a saturated device, but speculative decoding is the case that hits it routinely, since the drafter loads last by construction. Fix- for (size_t i = 0; i < n_devices(); ++i) {
- splits[i] /= split_sum;
+ if (split_sum > 0.0f) {
+ for (size_t i = 0; i < n_devices(); ++i) {
+ splits[i] /= split_sum;
+ }
+ } else {
+ // every device reported zero free memory - this happens when a draft model is
+ // loaded after the target has already filled VRAM. dividing by zero here would
+ // poison the split points with NaN and send upper_bound() past the last device.
+ for (size_t i = 0; i < n_devices(); ++i) {
+ splits[i] = float(i + 1) / float(n_devices());
+ }
}and defensively at 1363: - auto * dev = devices.at(layer_gpu).dev;
+ auto * dev = devices.at(std::min<size_t>(layer_gpu, n_devices() - 1)).dev;Either half alone stops the crash; the first is the actual bug, the second keeps a NaN from any other source from turning into an out-of-range access. Happy to open this as a separate PR if you prefer it split out from the DFlash2 work, since it is really a pre-existing loader issue that DFlash2 only exposes. After the fixLoads and runs.
Absolute numbers are low because half the target sits on CPU on a 16 GB card — worth noting that speculative decoding holds up well under partial offload, since the verify pass batches tokens and amortizes the slow layers. The relative gain on code is larger than what I get on a fully GPU-resident setup would suggest is the ceiling. Unrelated observation while tracing this
llama_set_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k], true);with no bounds check, while the EAGLE3 path at line 516 guards the same call with |
@ix-yuzhoulu can you elaborate more on these pipeline bubbles? I ried to narrow it down myself but only got so far that is somehow tied to the number of extra tokens generated in relation to the number of slots, thats why currently MTP with draft n max = 1 scales and others dont. I saw the same here with Dflash2. But it seems like CUDA is not affected at all, while vulkan is badly struck and ROCM kind of - at least with MTP, i have subpar scaling. it scales however somewhat, and funny enough, for longer token sequences ,it becomes faster. I could not test due to the model loading crash, but I'm willing to retest after the fix. Heres the scaling for ROCM with MTP and vulkan, current master, for MTP. so I expect it to be roughly the same for dflash2: Details
The external CUDA datapoint in the comments supplies the comparison this section previously lacked. Placing
ROCM
Run R2 —
|
| metric | value |
|---|---|
| prompt eval | 300.74 ms / 14 tok — 21.48 ms/tok — 46.55 t/s |
| eval | 21648.87 ms / 712 tok — 30.45 ms/tok — 32.84 t/s |
| graphs reused | 276 |
| draft acceptance | 0.52278 (436/834), mean len 2.57 |
| acc per pos | (0.719, 0.496, 0.353) |
R2b — 2 parallel (tasks 281, 282)
| slot | task | eval | ms/tok | t/s | graphs reused | acceptance | mean len |
|---|---|---|---|---|---|---|---|
| 2 | 281 | 22204.66 ms / 374 tok | 59.53 | 16.80 | 445 | 0.393 (203/516) | 2.18 |
| 1 | 282 | 27441.32 ms / 505 tok | 54.45 | 18.37 | 514 | 0.365 (265/726) | 2.10 |
R2c — 3 parallel (tasks 527, 528, 529)
| slot | task | eval | ms/tok | t/s | graphs reused | acceptance | mean len |
|---|---|---|---|---|---|---|---|
| 0 | 529 | 17070.45 ms / 455 tok | 37.60 | 26.60 | 703 | 0.462 (265/573) | 2.39 |
| 2 | 528 | 20411.33 ms / 461 tok | 44.37 | 22.54 | 726 | 0.381 (247/648) | 2.14 |
| 1 | 527 | 20822.56 ms / 465 tok | 44.88 | 22.28 | 730 | 0.367 (243/663) | 2.10 |
Aggregate TG: single 32.84 → dual 35.17 (1.07×) → triple 71.42 (2.18×).
Tick time: width 4 = 78.3 ms · width 8 = 122.0 ms · width 12 = 93.4 ms.
Width 12 costing less than width 8 is the opposite of Vulkan's width-12 point (Vulkan Run 2, same
config: single 37.52, triple 40.78, ratio 1.09×, tick time 170.0 ms at width 12 — rising, not
falling, from its own width-8-adjacent points). Task 529 (slot 0) alone ran at 37.60 ms/tok, well
below the other two triple-leg slots (44.4–44.9 ms/tok) despite all three submitting into the same
verify batch each tick — a per-slot spread not seen in the Vulkan runs at this width.
Run R1 — spec-draft-n-max = 1 (L = 2), parallel = 3
R1a — single (slot 2, task 0)
| metric | value |
|---|---|
| prompt eval | 357.63 ms / 14 tok — 25.55 ms/tok — 39.15 t/s |
| eval | 17343.49 ms / 563 tok — 30.86 ms/tok — 32.40 t/s |
| graphs reused | 327 |
| draft acceptance | 0.70821 (233/329), mean len 1.71 |
| acc per pos | (0.708) |
R1b — 2 parallel (tasks 332, 333)
| slot | task | eval | ms/tok | t/s | graphs reused | acceptance | mean len |
|---|---|---|---|---|---|---|---|
| 2 | 333 | 21937.01 ms / 498 tok | 44.14 | 22.66 | 606 | 0.762 (215/282) | 1.76 |
| 1 | 332 | 22918.86 ms / 503 tok | 45.66 | 21.90 | 626 | 0.657 (199/303) | 1.66 |
R1c — 3 parallel (tasks 639, 640, 641)
| slot | task | eval | ms/tok | t/s | graphs reused | acceptance | mean len |
|---|---|---|---|---|---|---|---|
| 0 | 641 | 23564.45 ms / 400 tok | 59.06 | 16.93 | 860 | 0.691 (163/236) | 1.69 |
| 2 | 639 | 23807.62 ms / 393 tok | 60.73 | 16.47 | 860 | 0.658 (156/237) | 1.66 |
| 1 | 640 | 24874.73 ms / 426 tok | 58.53 | 17.09 | 879 | 0.658 (169/257) | 1.66 |
Aggregate TG: single 32.40 → dual 44.56 (1.38×) → triple 50.49 (1.56×).
Tick time (ms/tok × mean_len): width 2 = 52.8 ms · width 4 = 76.8 ms · width 6 = 99.3 ms.
ulkan comparison at the same config (Runs 1/4): single 34–35 t/s, triple 52.7–55.8 t/s (1.50–1.64×),
tick time 49.7 ms (width 2) → 85.5–89.3 ms (width 6). **ROCm tracks the Vulkan collapse pattern
Vulkan
Sweep summary (raw)
| run | n_max |
L | N | N·L | single TG | parallel TG (sum) | parallel/single | single acc | parallel acc (range) |
|---|---|---|---|---|---|---|---|---|---|
| 6a | 1 | 2 | 2 | 4 | 33.72 | 42.96 | 1.27× | 0.644 | 0.661 – 0.686 |
| 1 | 1 | 2 | 3 | 6 | 35.11 | 52.65 | 1.50× | 0.699 | 0.656 – 0.780 |
| 4 | 1 | 2 | 3 | 6 | 34.07 | 55.78 | 1.64× | 0.693 | 0.643 – 0.675 |
| 6b | 3 | 4 | 2 | 8 | 35.42 | 41.12 | 1.16× | 0.416 | 0.415 – 0.425 |
| 5 | 1 | 2 | 4 | 8 | 33.87 | 55.58 | 1.64× | 0.632 | 0.632 – 0.683 |
| 3 | 2 | 3 | 3 | 9 | 35.95 | 38.52 | 1.07× | 0.529 | 0.530 – 0.560 |
| 2 | 3 | 4 | 3 | 12 | 37.52 | 40.78 | 1.09× | 0.458 | 0.393 – 0.500 |
Runs 6b and 5 are the same batch width (8 tokens) with different L — 4 vs 2 — and differ by
1.16× vs 1.64×. Recorded here without interpretation.
Config
[qwen3-8-27b]
cache-ram = 14336
ctx-checkpoints = 4
checkpoint-min-step = 8192
main-gpu = 0
parallel = 3
batch-size = 4096
ubatch-size = 512
kv-unified = false
spec-type = draft-mtp
spec-draft-n-max = 1
hf = unsloth/Qwen3.8-27B-GGUF:UD-Q4_K_XL
ctx-size = 491520
temp = 1.0
top-p = 0.95
top-k = 20
min-p = 0.0
presence-penalty = 0.0
repeat-penalty = 1.0
cache-type-k = q8_0
cache-type-v = q8_0
flash-attn = true
split-mode = layer
tensor-split = 50,50,0
jinja = true
reasoning-preserve = true
reasoning-effort = medium
reasoning-budget = 20000
image-min-tokens = 1024
no-mmproj-offload = true



Overview
DFlash2 adds two more modules compared to DFlash, grouped dynamic depthwise convolution and candidate selector.
The formula is for reference.
out[i,c] = Σ_t (base[t,c] + δ[i,t,g(c)]) · x[i−t,c].baseis the static kernel,δis the dynamic part predicted from the input.xis divided into groupsg(c)andδis shared across a group. Another one isedge(p→c) = ⟨A[p] ⊙ project(h), B[c]⟩ + unary[c].A/Bare the codebooks for the predecessor and the candidate. This position's hidden is projected byproject(h)to the same rank to multiplyA. Andunary[c]is the draft's own score forc.DFlash2 is enabled when the checkpoint is DFlash2; no need to use extra flag.
We evaled
Qwen3.8-27BatQ4_K_Mon Apple M5 Pro 64 GB. And the test data is first 8 problems of GSM8K.Concurrency is set to 1 and temperature is 1.0, top-p 0.95, top-k 20. We use default
xhighreasoning and set2,048 maximum new tokens.
Requirements