Skip to content

CUDA: enable sparse-fa for dsv4 prefill (again) - #29298

Merged
ServeurpersoCom merged 3 commits into
masterfrom
aman/sparse-fa-batch-gate
Sep 23, 2026
Merged

ServeurpersoCom merged 3 commits into
masterfrom
aman/sparse-fa-batch-gate

Conversation

@am17an

@am17an am17an commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Overview

#28770 switched off sparse-fa for DSV4/GLM because of the batch-dependent gate. This PR adds a clamp on the condition that allows DSV4 prefill to activate again (no change for decode). Also adds a preference for the wide kernel (i.e. 8 ncols1) if it exists at large batch sizes.

Performance on DGX spark

Model Test t/s master t/s aman/sparse-fa-batch-gate Speedup
deepseek4 ?B IQ2_XXS - 2.0625 bpw pp2048 382.33 ± 0.87 384.39 ± 0.95 1.01
deepseek4 ?B IQ2_XXS - 2.0625 bpw tg32 17.31 ± 0.06 17.19 ± 0.07 0.99
deepseek4 ?B IQ2_XXS - 2.0625 bpw pp2048 @ d32768 295.59 ± 0.99 294.01 ± 1.90 0.99
deepseek4 ?B IQ2_XXS - 2.0625 bpw tg32 @ d32768 16.08 ± 0.04 15.95 ± 0.04 0.99
deepseek4 ?B IQ2_XXS - 2.0625 bpw pp2048 @ d65536 238.24 ± 0.76 293.35 ± 1.51 1.23
deepseek4 ?B IQ2_XXS - 2.0625 bpw tg32 @ d65536 15.49 ± 0.01 15.39 ± 0.04 0.99
deepseek4 ?B IQ2_XXS - 2.0625 bpw pp2048 @ d131072 172.75 ± 0.92 244.46 ± 2.10 1.42
deepseek4 ?B IQ2_XXS - 2.0625 bpw tg32 @ d131072 14.49 ± 0.03 14.41 ± 0.03 0.99
Model Test t/s master t/s aman/sparse-fa-batch-gate Speedup
qwen4exp A3B IQ1_S - 1.5625 bpw pp2048 964.02 ± 5.22 961.50 ± 6.48 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw tg32 36.79 ± 0.28 36.74 ± 0.28 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw pp2048 @ d16384 767.69 ± 5.12 770.17 ± 2.83 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw tg32 @ d16384 32.23 ± 0.24 32.27 ± 0.25 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw pp2048 @ d32768 685.52 ± 4.24 684.04 ± 5.58 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw tg32 @ d32768 29.36 ± 0.16 29.24 ± 0.19 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw pp2048 @ d65536 522.68 ± 2.17 525.21 ± 2.70 1.00
qwen4exp A3B IQ1_S - 1.5625 bpw tg32 @ d65536 23.39 ± 0.13 23.38 ± 0.13 1.00

Additional information

Requirements

@am17an
am17an requested a review from a team as a code owner September 23, 2026 08:03
@github-actions github-actions Bot added ggml changes relating to the ggml tensor library for machine learning CUDA Related to the CUDA backend labels Sep 23, 2026
The query loop of flash_attn_mask_to_sparse_indices has a runtime trip
count, which keeps the unrolled scan over the values of a lane from
issuing its loads together. Template the kernel on ncols1 so the loop
is bounded at compile time: batch one decodes compile to straight line
code and the scan drops from 46 to 17 us at 49k columns on sparse
decode shapes.
@ServeurpersoCom

ServeurpersoCom commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

I pushed the compile time unroll of the mask scan query loop and retested on the PR, before/after: sparse decode goes from 54.75 to 25.56 us at 49k

Comment thread ggml/src/ggml-cuda/fattn.cu Outdated
Comment on lines +46 to +47
#pragma unroll
for (int q = 0; q < ncols1 && q < q1 - q0 && !selected; ++q) {

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.

This loop cannot actually be unrolled beyond the first iteration. I would recommend checking in host code whether or not an out-of-bounds check is needed and then launching a template specialization with/without one accordingly.

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.

Right, it only unrolled for ncols1 == 1: the kernel is now templated on whether the last query group is partial, chosen in host code, with the column bound hoisted out of the loop, which brings the ncols1 == 8 scan from 110 to 14 branches and halves the batched sparse op at 49k context (586 -> 244 us), decode unchanged.

The query loop of the ncols1 == 8 scan keeps a runtime bound and an
early exit, so it does not unroll past its first iteration. Template the
kernel on whether the last group of queries is partial, decided on the
host from n_queries, and hoist the column bound out of the loop: the
loop becomes straight line code and the batched sparse op at 49k
context drops from 586 to 244 us.
@ggerganov

Copy link
Copy Markdown
Member

@ServeurpersoCom Can you confirm this change fixes the issue?

@ServeurpersoCom

Copy link
Copy Markdown
Contributor

@ServeurpersoCom Can you confirm this change fixes the issue?

Yes, confirmed on my RTX PRO 6000: sparse decode is back to the b11047 level (54.89 -> 26.01 us at 49k context), the batched sparse op drops from 603 to 237 us, and FLASH_ATTN_EXT passes 3982/3982.

@ServeurpersoCom
ServeurpersoCom merged commit dc9879c into master Sep 23, 2026
12 checks passed
@ggerganov
ggerganov deleted the aman/sparse-fa-batch-gate branch September 23, 2026 15:41
gagallo7 pushed a commit to gagallo7/qvac-fabric-llm.cpp that referenced this pull request Sep 30, 2026
* CUDA: enable sparse-fa for dsv4 prefill (again)

* CUDA: unroll the query loop of the sparse mask scan

The query loop of flash_attn_mask_to_sparse_indices has a runtime trip
count, which keeps the unrolled scan over the values of a lane from
issuing its loads together. Template the kernel on ncols1 so the loop
is bounded at compile time: batch one decodes compile to straight line
code and the scan drops from 46 to 17 us at 49k columns on sparse
decode shapes.

* CUDA: pick the out of bounds check of the sparse mask scan in host code

The query loop of the ncols1 == 8 scan keeps a runtime bound and an
early exit, so it does not unroll past its first iteration. Template the
kernel on whether the last group of queries is partial, decided on the
host from n_queries, and hoist the column bound out of the loop: the
loop becomes straight line code and the batched sparse op at 49k
context drops from 586 to 244 us.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
(cherry picked from commit dc9879c)
gagallo7 pushed a commit to gagallo7/qvac-fabric-llm.cpp that referenced this pull request Sep 30, 2026
* CUDA: enable sparse-fa for dsv4 prefill (again)

* CUDA: unroll the query loop of the sparse mask scan

The query loop of flash_attn_mask_to_sparse_indices has a runtime trip
count, which keeps the unrolled scan over the values of a lane from
issuing its loads together. Template the kernel on ncols1 so the loop
is bounded at compile time: batch one decodes compile to straight line
code and the scan drops from 46 to 17 us at 49k columns on sparse
decode shapes.

* CUDA: pick the out of bounds check of the sparse mask scan in host code

The query loop of the ncols1 == 8 scan keeps a runtime bound and an
early exit, so it does not unroll past its first iteration. Template the
kernel on whether the last group of queries is partial, decided on the
host from n_queries, and hoist the column bound out of the loop: the
loop becomes straight line code and the batched sparse op at 49k
context drops from 586 to 244 us.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
(cherry picked from commit dc9879c)
(cherry picked from commit e59b5a7)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CUDA Related to the CUDA backend ggml changes relating to the ggml tensor library for machine learning

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants