Skip to content

perf(attn): grid-target KV-split cap for int8-MMA long-context decode#345

Closed
fansilas wants to merge 1 commit into
gittensor-ai-lab:mainfrom
fansilas:perf/nsplits-mma-cap
Closed

perf(attn): grid-target KV-split cap for int8-MMA long-context decode#345
fansilas wants to merge 1 commit into
gittensor-ai-lab:mainfrom
fansilas:perf/nsplits-mma-cap

Conversation

@fansilas

Copy link
Copy Markdown
Contributor

Summary

One decode scheduling fix for the int8 tensor-core flash-decode at long context, output-neutral to main (only the KV-work partition changes).

Grid-target n_splits cap. The depth-adaptive KV-split count jumps to 256 at >8k context — a value tuned for the older bf16 tile kernel. For the int8-MMA flash-decode (fa_split_gqa_mma_i8_kernel, 5 CTAs/SM), 256 over-splits: the num_kv_heads*256 grid spills a partial 2nd occupancy wave (tail), and the per-split chunk shrinks until the QK warps idle (gblk<8). On an nsys node trace the split kernel is ~27% of the 32k-context token and already runs at ~89% of memory bandwidth — so the win is the schedule, not the kernel.

This caps n_splits to a grid that fills ~3/4 of one occupancy wave: (num_sms*5*3/4)/num_kv_heads. The optimal grid is independent of seqlen — on the RTX 5090 (170 SMs) both 16k and 32k decode peak at grid ~640 (n_splits 159 at 4 KV heads). The cap only bites for higher KV-head counts: hd128 Qwen3-MoE (4 KV) -> 159; hd256 (2 KV -> cap 318, above the 256 tier) and short context (<8k, n_splits 32/128) are unchanged. The online-softmax combine is exact for any split count, so this is correctness-neutral.

A/B toggle: SPARKINFER_NSPLITS=256 restores the pre-cap fixed split count for the comparison below.

Proof of speedup

  • Tested on RTX 5090 (sm_120)

Decode tok/s (end-to-end, bench/scripts/bench.sh, Qwen3-30B-A3B-Q4_K_M.gguf, 128 decode tokens):

decode tok/s (32k)
before (main) 257.4
after (this PR) 287.1

Same-box A/B, both built on current main (v0.4.0), 2 reps each:

# ctx=16384: before 324.4 -> after 350.6  (+8.1%)
# ctx=32768: before 257.4 -> after 287.1  (+11.5%)

-- ctx=16384 (before / after) --
324.83 350.74
324.02 350.52
-- ctx=32768 --
257.47 287.65
257.34 286.64

128 / 512 / 4k are unchanged — the cap does not engage below 8k (int8 KV + the MMA path start at ctx>=8192). Correctness: the split count is a KV-work partition; the log-sum-exp combine is exact for any n_splits (main already ships 128 at 4k, 256 at 16k), so the attention output is unchanged up to float reduction order.

Relation to open PRs

The n_splits grid-target cap is original — no open PR does it. The only file touched is runtime/src/models/qwen35.cpp, shared with several open PRs (#329, #308, #294, #285, #283, #272, #267), but the change is a small self-contained addition to the depth-adaptive split block. Added-line containment against every one of those (and every merged/closed PR on this file) is <=14.3% — a single shared }. No kernel changes.

@skyrocket2026 skyrocket2026 added area:runtime subsystem (emission weight 0.26) test-on-5090 Maintainer-approved to evaluate on RTX 5090 (greenlight) eval:none sparkinfer auto-eval verdict: none eval-qwen35:none eval-qwen36:none 128-context UI-only: strongest measured context in sparkinfer eval labels Jul 11, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

⚪ sparkinfer auto-eval — 0954339

metric value
label eval:none
Qwen3.5 score eval-qwen35:none (pass)
Qwen3.6 score eval-qwen36:none (pass)
Qwen3.5 vs same-box main 282.88 tok/s → +0.6% (+1.8)
Qwen3.5 scored decode (128 ctx · 128-context) 284.66 tok/s
Qwen3.5 correctness top-1 96.8% · KL 0.0195
Qwen3.5 128-token no-regression gate 284.66 tok/s vs main 282.88 tok/s · pass
Qwen3.5 512-context no-regression gate 281.53 tok/s vs main 280.04 tok/s · pass
Qwen3.5 4k-context no-regression gate 272.64 tok/s vs main 271.88 tok/s · pass
Qwen3.6 vs same-box main 427.46 tok/s → -0.5% (-2.2)
Qwen3.6 scored decode (128 ctx · 128-context) 425.22 tok/s
Qwen3.6 correctness top-1 96.1% · KL 0.0174
Qwen3.6 128-token no-regression gate 425.22 tok/s vs main 427.46 tok/s · pass
Qwen3.6 512-context no-regression gate 420.41 tok/s vs main 422.8 tok/s · pass
Qwen3.6 4k-context no-regression gate 403.36 tok/s vs main 405.75 tok/s · pass
Qwen3.6 16k-context no-regression gate 379.27 tok/s vs main 381.9 tok/s · pass
Qwen3.6 32k-context no-regression gate 351.92 tok/s vs main 354.31 tok/s · pass
Qwen3.5 optimize eval:none · 284.66 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy top-1 96.1% · KL 0.0174 · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 128 425.17 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 512 420.35 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 4k 403.29 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 16k 379.78 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 32k 352.01 tok/s · pass
Qwen3.6 optimize eval:none · 425.22 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy top-1 96.8% · KL 0.0195 · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 128 284.36 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 512 281.0 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 4k 272.81 tok/s · pass

Within the significance gate — no verified speedup over same-box main.

RTX 5090 (sm_120) · 128/512/4k/16k/32k guarded · scored vs same-box main · strongest context scores · built from source · correctness vs llama.cpp. Automated — not merged; merge manually after review.

@fansilas fansilas closed this Jul 11, 2026
@fansilas fansilas reopened this Jul 11, 2026
@fansilas
fansilas marked this pull request as draft July 11, 2026 23:06
@fansilas
fansilas force-pushed the perf/nsplits-mma-cap branch from 0954339 to 66650c4 Compare July 12, 2026 09:05
@fansilas
fansilas marked this pull request as ready for review July 12, 2026 09:12
@fansilas

Copy link
Copy Markdown
Contributor Author

Hi @skyrocket2026

The speedup here is entirely long-context (≥8k) — the split cap is a deliberate no-op below 8k, so the 128-context scored decode reads it as none and the real gains land only in the 16k/32k guards.

Rebased onto current main (e59c0de) and re-verified same-box on RTX 5090 (170 SM), 3 reps each (bench/scripts/bench.sh, Qwen3-30B-A3B-Q4_K_M.gguf, 128 decode tokens):

ctx main this PR gain
16k 323.4 350.6 +8.4%
32k 258.7 289.2 +11.8%

Would it be possible to run a longctx eval on this one so the 32k result is scored rather than only guarded? The change is correctness-neutral — the log-sum-exp combine is exact for any split count, and the 128/512/4k guards are unchanged — and SPARKINFER_NSPLITS=256 restores the pre-cap split count for a clean same-box A/B.

@skyrocket2026

Copy link
Copy Markdown
Member

⚠️ sparkinfer auto-eval errored for 66650c4 — re-run manually.

log tail
kernelEPK13__nv_bfloat16S3_S3_PS1_S4_iif
    0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info    : Used 40 registers, used 1 barriers, 128 bytes smem
ptxas info    : Compile time = 28.334 ms
ptxas info    : Compiling entry function '_ZN10sparkinfer7kernels14rmsnorm_kernelILi1EEEvPK13__nv_bfloat16S4_S4_PS2_iif' for 'sm_120'
ptxas info    : Function properties for _ZN10sparkinfer7kernels14rmsnorm_kernelILi1EEEvPK13__nv_bfloat16S4_S4_PS2_iif
    0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info    : Used 40 registers, used 1 barriers
ptxas info    : Compile time = 31.208 ms
ptxas info    : Compiling entry function '_ZN10sparkinfer7kernels14rmsnorm_kernelILi0EEEvPK13__nv_bfloat16S4_S4_PS2_iif' for 'sm_120'
ptxas info    : Function properties for _ZN10sparkinfer7kernels14rmsnorm_kernelILi0EEEvPK13__nv_bfloat16S4_S4_PS2_iif
    0 bytes stack frame, 0 bytes spill stores, 0 bytes spill loads
ptxas info    : Used 40 registers, used 1 barriers
ptxas info    : Compile time = 23.025 ms
>> using local build
>> WARN: could not lock GPU clocks (no permission?) — falling back to warmup-only
>> [primary-qwen35] model Qwythos-9B-Claude-Mythos-5-1M-Q4_K_M.gguf ...
>> [1/3] reusing pre-built submission (66650c4) (SI_SKIP_BUILD) ...
>> model sha256 OK
>> (re)building llama.cpp reference (CUDA sm_120) ...

!! no RESULT_JSON; stderr tail:
>> [polaris-judge] ERROR: no RESULT_JSON in stdin

>> bare-metal box left running (ssh root@91.224.44.227:40058)

@skyrocket2026 skyrocket2026 added the needs-rebase Verified speedup but not the round winner — rebase after merge-first lands label Jul 12, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

⏸ Merge conflict — rebase before eval

This branch conflicts with main, so the RTX 5090 eval is skipped until you rebase.

Please rebase onto main and push — the bot re-evaluates on the next poll once the PR is cleanly mergeable.

@skyrocket2026 skyrocket2026 removed the test-on-5090 Maintainer-approved to evaluate on RTX 5090 (greenlight) label Jul 12, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

The round's merge-first PR was just merged. Please rebase this branch onto main — once you push the rebase the bot re-evaluates it against the new frontier (crediting your marginal gain on top of what merged).

@skyrocket2026

Copy link
Copy Markdown
Member

The round's merge-first PR was just merged. Please rebase this branch onto main — once you push the rebase the bot re-evaluates it against the new frontier (crediting your marginal gain on top of what merged).

@fansilas
fansilas force-pushed the perf/nsplits-mma-cap branch from 66650c4 to 911e280 Compare July 12, 2026 19:11
@fansilas

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (a3b63f8) — the #294 conflict is resolved (the cap sits after #294's banded policy, gated to head_dim==128, so #294's hd256/qwen36 tuning is untouched). Should be cleanly mergeable now.

Re-verified same-box on RTX 5090 (170 SM); SPARKINFER_NSPLITS=256 reproduces main's long-context split count:

ctx main this PR gain
16k 321.6 348.2 +8.3%
32k 257.8 288.4 +11.9%

The win is entirely ≥8k, so 128-context bidir scoring reads it as none — it only shows up in the 16k/32k guards. Would it be possible to run a longctx eval here so the 32k result is scored rather than only guarded? It's correctness-neutral (the log-sum-exp combine is exact for any split count; 128/512/4k guards unchanged). Note the previous run errored at the polaris judge (no RESULT_JSON), so it likely needs a manual re-run regardless.

@skyrocket2026 skyrocket2026 added test-on-5090 Maintainer-approved to evaluate on RTX 5090 (greenlight) eval:none sparkinfer auto-eval verdict: none eval-qwen35:none eval-qwen36:none and removed eval-qwen36:none eval:none sparkinfer auto-eval verdict: none eval-qwen35:none needs-rebase Verified speedup but not the round winner — rebase after merge-first lands labels Jul 12, 2026
@skyrocket2026 skyrocket2026 added the re-evaluate Winner merged — rebase onto main; bot re-evaluates on push label Jul 12, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

⚪ sparkinfer auto-eval — 911e280

metric value
label eval:none
Qwen3.5 score eval-qwen35:none (pass)
Qwen3.6 score eval-qwen36:none (pass)
Qwen3.5 vs same-box main 293.34 tok/s → +0.4% (+1.2)
Qwen3.5 scored decode (512 ctx · 512-context) 294.55 tok/s
Qwen3.5 correctness top-1 96.3% · KL 0.0152
Qwen3.5 128-token no-regression gate 298.85 tok/s vs main 298.46 tok/s · pass
Qwen3.5 512-context no-regression gate 294.55 tok/s vs main 293.34 tok/s · pass
Qwen3.5 4k-context no-regression gate 283.55 tok/s vs main 283.3 tok/s · pass
Qwen3.6 vs same-box main 412.09 tok/s → +0.1% (+0.2)
Qwen3.6 scored decode (128 ctx · 128-context) 412.32 tok/s
Qwen3.6 correctness top-1 97.3% · KL 0.0102
Qwen3.6 128-token no-regression gate 412.32 tok/s vs main 412.09 tok/s · pass
Qwen3.6 512-context no-regression gate 418.02 tok/s vs main 418.61 tok/s · pass
Qwen3.6 4k-context no-regression gate 402.53 tok/s vs main 402.5 tok/s · pass
Qwen3.6 16k-context no-regression gate 398.8 tok/s vs main 399.25 tok/s · pass
Qwen3.6 32k-context no-regression gate 382.33 tok/s vs main 382.52 tok/s · pass
Qwen3.5 optimize eval:none · 294.55 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy top-1 97.3% · KL 0.0102 · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 128 412.31 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 512 418.05 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 4k 402.54 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 16k 398.76 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B 32k 382.34 tok/s · pass
Qwen3.6 optimize eval:none · 412.32 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy top-1 96.3% · KL 0.0152 · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 128 298.4 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 512 294.21 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 4k 283.33 tok/s · pass

Within the significance gate — no verified speedup over same-box main.

RTX 5090 (sm_120) · 128/512/4k/16k/32k guarded · scored vs same-box main · strongest context scores · built from source · correctness vs llama.cpp. Automated — not merged; merge manually after review.

@fansilas
fansilas marked this pull request as draft July 12, 2026 21:47
skyrocket2026 added a commit that referenced this pull request Jul 12, 2026
* dashboard: PR #345 -> eval:none (412.32 tok/s)

* dashboard: PR #353 -> eval:XL (427.54 tok/s)

* dashboard: PR #353 merged -> bidir frontier update

* dashboard: use Hugging Face logo for Qwen3.5 model link

Replace the org avatar with the official yellow HF icon so the Qwythos
repo link is recognizable on file:// and GitHub Pages.
@fansilas
fansilas marked this pull request as ready for review July 14, 2026 07:29
@fansilas
fansilas marked this pull request as draft July 14, 2026 07:33
@skyrocket2026

Copy link
Copy Markdown
Member

Closed: draft open for 4+ days

This PR has remained a draft for 5 days (threshold: 4 days). Recent commits or comments do not reset this timer.

Mark ready for review or reopen when you're ready to continue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

128-context UI-only: strongest measured context in sparkinfer eval area:runtime subsystem (emission weight 0.26) eval:none sparkinfer auto-eval verdict: none eval-qwen35:none eval-qwen36:none re-evaluate Winner merged — rebase onto main; bot re-evaluates on push test-on-5090 Maintainer-approved to evaluate on RTX 5090 (greenlight)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants