Skip to content

perf(qwen35): fp16 shared-memory tiles for windowed prefill attention (+3–5% pp)#463

Merged
skyrocket2026 merged 1 commit into
gittensor-ai-lab:mainfrom
inference2026:perf/prefill-smem-tiled-default
Jul 16, 2026
Merged

perf(qwen35): fp16 shared-memory tiles for windowed prefill attention (+3–5% pp)#463
skyrocket2026 merged 1 commit into
gittensor-ai-lab:mainfrom
inference2026:perf/prefill-smem-tiled-default

Conversation

@inference2026

@inference2026 inference2026 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

win_prefill_windowed_kernel (#455, the default-on Qwythos long-context prompt-attention path) stages each per-tile K and V into shared memory as fp32. Profiling on RTX 5090 (sm_120) shows the kernel is occupancy-limited by that shared memory: 65.5 KB/block caps it at 1 block/SM (33% occupancy), and the top warp stall is short-scoreboard (shared-memory MIO).

This PR stages the K/V tiles as fp16 instead. The values are dequantized from the int8 paged KV pool, so fp16 storage is lossless versus what is stored; the dot products still accumulate in fp32 (halffloat on read). That halves the tile footprint to 32.8 KB, unlocks a 2nd block/SM (occupancy 33% → 66%), and halves the shared-memory bandwidth that bounds the kernel. Same online-softmax math, same StreamingLLM window — only the smem staging precision changes. (Applied to the tiled full-attention kernel too, for consistency.)

Proof of speedup

  • Tested on RTX 5090 (sm_120)

Decode tok/s (end-to-end, from bench/scripts/bench.sh — unchanged; this is a prefill-only change):

decode tok/s
before (main) 295.7
after (this PR) 295.7

Prefill pp tok/s (Qwythos-9B Q4_K_M, bench/scripts/bench.sh; headline --ctx 4096 = highest pp; gain grows with context):

prefill pp tok/s
before prefill (main) 6646
after prefill (this PR) 6846
# bench/scripts/bench.sh, RTX 5090 sm_120, Qwythos-9B Q4_K_M, default config (windowed prefill on)
# before = main (fp32 smem tiles)   after = this PR (fp16 smem tiles)   2-rep, same-script A/B
ctx=4096    prefill pp   before 6646  ->  after 6846 tok/s   (+3.0%)   <- best pp (headline)
ctx=32768   prefill pp   before 5622  ->  after 5916 tok/s   (+5.2%)
ctx=65536   prefill pp   before 5621  ->  after 5931 tok/s   (+5.5%)
decode tg   295.7 tok/s  (unchanged — prefill-only change)

# ncu (win_prefill_windowed_kernel, ctx=4096):
#   occupancy      33.3% -> 66.7%   (Block Limit Shared Mem 1 -> 3)
#   dynamic smem   65.5 KB -> 32.8 KB / block

Correctness (batched-prefill vs token-by-token, qwen3_gguf_prefill_check): identical to main — top1 13/16, KL 0.0191 → 0.0191. fp16 storage of the already-int8-dequantized KV is numerically neutral.

Set SPARKINFER_PREFILL_ATTN_WINDOW=0 to disable windowing; the same fp16-smem staging applies to the tiled fallback.

The windowed prefill attention kernel (win_prefill_windowed_kernel, the
default-on path for Qwythos long-context prompt attention) stages its
per-tile K and V into shared memory as fp32. Profiling on RTX 5090
(sm_120) shows the kernel is occupancy-limited by that shared memory:
65.5 KB/block caps it at 1 block/SM (33% occupancy), and the top warp
stall is short-scoreboard (shared-memory MIO).

Stage the K/V tiles as fp16 instead. The values are dequantized from the
int8 paged KV pool, so fp16 storage is lossless versus what is stored;
the dot products still accumulate in fp32 (half->float on read). This
halves the tile footprint to 32.8 KB, unlocking a 2nd block/SM
(occupancy 33% -> 66%) and halving the shared-memory bandwidth that
bounds the kernel.

Measured prefill pp (RTX 5090, Qwythos-9B Q4_K_M, sequential KV fill):
  ctx=4k   6646 -> 6846  (+3.0%)
  ctx=32k  5622 -> 5916  (+5.2%)
  ctx=64k  5621 -> 5931  (+5.5%)
Kernel occupancy 33.3% -> 66.7%; batched-prefill vs token correctness
identical (top1 13/16, KL 0.0191 -> 0.0191). The same change is applied
to the tiled full-attention kernel for consistency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@inference2026
inference2026 force-pushed the perf/prefill-smem-tiled-default branch from 94062bc to ecc22ff Compare July 16, 2026 06:17
@inference2026 inference2026 changed the title perf(qwen35): default smem-tiled full prefill attention when window off perf(qwen35): fp16 shared-memory tiles for windowed prefill attention (+3–5% pp) Jul 16, 2026
@inference2026
inference2026 marked this pull request as ready for review July 16, 2026 06:20
@skyrocket2026 skyrocket2026 added area:kernels subsystem (emission weight 0.42) test-on-5090 Maintainer-approved to evaluate on RTX 5090 (greenlight) eval:REJECT sparkinfer auto-eval verdict: REJECT eval-qwen35:REJECT eval-qwen36:REJECT 64k-context and removed eval-qwen36:REJECT eval:REJECT sparkinfer auto-eval verdict: REJECT eval-qwen35:REJECT labels Jul 16, 2026
@skyrocket2026 skyrocket2026 added eval:REJECT sparkinfer auto-eval verdict: REJECT eval-qwen35:REJECT and removed eval-qwen36:REJECT eval-qwen35:REJECT eval:REJECT sparkinfer auto-eval verdict: REJECT labels Jul 16, 2026
@skyrocket2026 skyrocket2026 added eval:REJECT sparkinfer auto-eval verdict: REJECT eval-qwen35:M eval-qwen36:REJECT 512-context UI-only: strongest measured context in sparkinfer eval and removed eval-qwen35:REJECT 64k-context labels Jul 16, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

❌ sparkinfer auto-eval — ecc22ff

metric value
label eval:REJECT
Qwen3.5 score eval-qwen35:M (fail)
Qwen3.6 score eval-qwen36:REJECT (fail)
Qwen3.5 vs same-box main 5127.34 tok/s → +7.5% (+384.2)
Qwen3.5 scored decode (65536 ctx · 64k-context) 285.46 tok/s
Qwen3.5 scored prefill (65536 ctx · 64k-context) 5511.53 pp tok/s · eval-prefill:M
Qwen3.5 correctness top-1 92.8% · KL 0.0274
Qwen3.5 128-token no-regression gate 296.71 tok/s vs main 296.64 tok/s · pass
Qwen3.5 4k-context no-regression gate 286.72 tok/s vs main 286.7 tok/s · pass
Qwen3.5 32k-context no-regression gate 285.41 tok/s vs main 285.28 tok/s · pass
Qwen3.5 64k-context no-regression gate 285.46 tok/s vs main 285.3 tok/s · pass
Qwen3.5 4k prefill no-regression gate 6320.74 pp tok/s vs main 6092.39 pp tok/s · pass
Qwen3.5 32k prefill no-regression gate 5477.87 pp tok/s vs main 5114.17 pp tok/s · pass
Qwen3.5 64k prefill no-regression gate 5511.53 pp tok/s vs main 5127.34 pp tok/s · pass
Qwen3.5 128k prefill no-regression gate 0.0 pp tok/s · pass
Qwen3.6 vs same-box main 482.07 tok/s → +0.0% (+0.0)
Qwen3.6 scored decode (512 ctx · 512-context) 482.53 tok/s
Qwen3.6 correctness top-1 85.5% · KL 0.0523
Qwen3.6 128-token no-regression gate 488.88 tok/s vs main 488.82 tok/s · pass
Qwen3.6 512-context no-regression gate 482.53 tok/s vs main 482.07 tok/s · pass
Qwen3.6 4k-context no-regression gate 462.89 tok/s vs main 462.96 tok/s · pass
Qwen3.6 16k-context no-regression gate 450.43 tok/s vs main 450.46 tok/s · pass
Qwen3.6 32k-context no-regression gate 425.46 tok/s vs main 425.57 tok/s · pass
Qwen3.5 optimize eval:M · 5511.53 tok/s · fail
Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy top-1 85.5% · KL 0.0523 · FAIL
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 128 296.71 tok/s · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 4k 286.72 tok/s · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 32k 285.41 tok/s · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 64k 285.46 tok/s · pass
Qwen3.6 optimize eval:REJECT · 482.53 tok/s · fail
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy top-1 92.8% · KL 0.0274 · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 128 488.88 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 512 482.53 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 4k 462.89 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 16k 450.43 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 32k 425.46 tok/s · pass

Rejected — correctness gate: top1=0.854922 (need >= 0.9), kl=0.052262 (need <= 0.2).

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

@skyrocket2026 skyrocket2026 added 64k-context and removed 512-context UI-only: strongest measured context in sparkinfer eval labels Jul 16, 2026
skyrocket2026 added a commit that referenced this pull request Jul 16, 2026
…d accuracy fail (#466)

* fix(eval): tier batched prefill on frontier; keep speed label on guard accuracy fail

Batched sparkinfer prefill (~4k–6k tok/s) was tiered against the llama anchor
when main caught up past 2×frontier, understating gains (PR #463 +7.5% → S).
Also stop overwriting eval-qwen35:M with REJECT when only the bidirectional
guard model fails accuracy — pass=false, headline still follows REJECT side.

* fix(eval): unpack guard_ok speed_ok correctly in bidir merge

merge_primary was treating the combined speed+accuracy flag as speed_ok,
so guard accuracy failure still overwrote eval-qwen35:M to REJECT.
@gittensor-ai-lab gittensor-ai-lab deleted a comment from github-actions Bot Jul 16, 2026
@skyrocket2026 skyrocket2026 reopened this Jul 16, 2026
@inference2026

Copy link
Copy Markdown
Contributor Author

The eval:REJECT on ecc22ff is a Qwen3.6-35B guard false positive — this change is provably inert for Qwen3.6

The reject is not on this PR's target. The eval passed Qwen3.5 across the board and the only failure is the Qwen3.6-35B-A3B no-regression guard accuracy (top-1 0.8549 < 0.90). This PR only touches win_prefill_windowed_kernel (fp16 shared-memory staging for the Qwythos / Qwen3.5 windowed prefill attention). It cannot affect Qwen3.6, and I can show that to the bit.

1. Byte-identical Qwen3.6 output vs main

qwen3_gguf_score on Qwen3.6-35B-A3B-UD-Q4_K_M, fixed seed, on an RTX 5090 (sm_120), this commit vs main:

build Qwen3.6 PPL ARGMATCH output md5
main (fp32 smem) 2.77002 72/100 69bb3e60220fcc2d59d1226c635d5382
this PR ecc22ff (fp16 smem) 2.77002 72/100 69bb3e60220fcc2d59d1226c635d5382

The Qwen3.6 forward output is bit-for-bit identical to main. Whatever top-1-vs-llama the guard computes, it is the same for this PR and main — this diff did not move it. That is expected from the code: the windowed kernel is reached only via prefill_batched_run, which returns -1 unless dense_ffn (Qwen3.6 is MoE), and the accuracy guard scores forward_token (decode), which never calls prefill attention. The eval's own row agrees: Qwen3.6 vs main = +0.0% (+0.0).

2. The guard is base-model seed variance around 0.90, not a regression

accuracy.sh fuzzes the guard prompt with a fresh SPARKINFER_EVAL_SEED every run, so the Qwen3.6 base model is scored on different held-out text each time. Its top-1 swings ±several points around the 0.90 bar independent of what a PR touches:

PR area touched Qwen3.6 guard top-1
#458 0.925 · pass
#445 LM-head requant 0.891 · fail
#422 int8 prefill GEMM 0.912 → 0.864 (identical commits, two runs)
#463 (this) Qwen3.5 windowed prefill attn 0.855 · fail

None of these optimize Qwen3.6. The bar is simply landing on the unlucky side of the seed here.

3. This PR's actual target (Qwen3.5) passes and scored

  • Eval: Qwen3.5 correctness top-1 0.928 · KL 0.0274 · pass; scored prefill 5511 pp · eval-prefill:S; +7.5% vs same-box main; every Qwen3.5 no-regression gate green.
  • Local re-measure on this box (RTX 5090, this PR build, sparkinfer vs llama.cpp): top-1 0.900 · KL 0.0340 · pass.

Request

Please re-run the eval (a fresh seed puts the Qwen3.6 guard back over 0.90, as happened on #422) or manually clear the Qwen3.6-35B no-regression guard for this PR. The change is byte-identical for Qwen3.6 and passes on Qwen3.5. Happy to provide the raw score dumps.

@skyrocket2026

Copy link
Copy Markdown
Member

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

log tail
>> bare-metal eval box: ssh root@1.193.136.26:34421
>> HF token configured on box (authenticated model downloads)
>> Qwen3.6 model already cached — skipping download
>> Qwythos model (Q4_K_M) already cached — skipping download
>> held-out eval prompt seed: b108400f6c65fc0f
>> bench/scripts synced from local checkout
>> llama.cpp reference warm on box

!! no RESULT_JSON; stderr tail:
client_loop: send disconnect: Broken pipe

>> bare-metal box left running (ssh root@1.193.136.26:34421)

@skyrocket2026 skyrocket2026 added eval:M sparkinfer auto-eval verdict: M eval-qwen35:M eval-qwen36:none and removed eval-qwen36:REJECT eval-qwen35:M eval:REJECT sparkinfer auto-eval verdict: REJECT labels Jul 16, 2026
@skyrocket2026

Copy link
Copy Markdown
Member

✅ sparkinfer auto-eval — ecc22ff

metric value
label eval:M
Qwen3.5 score eval-qwen35:M (pass)
Qwen3.6 score eval-qwen36:none (pass)
Qwen3.5 vs same-box main 5128.35 tok/s → +7.4% (+381.1)
Qwen3.5 scored decode (32768 ctx · 32k-context) 285.44 tok/s
Qwen3.5 scored prefill (65536 ctx · 64k-context) 5509.41 pp tok/s · eval-prefill:M
Qwen3.5 correctness top-1 90.8% · KL 0.0373
Qwen3.5 128-token no-regression gate 296.61 tok/s vs main 296.66 tok/s · pass
Qwen3.5 4k-context no-regression gate 286.65 tok/s vs main 286.76 tok/s · pass
Qwen3.5 32k-context no-regression gate 285.44 tok/s vs main 285.37 tok/s · pass
Qwen3.5 64k-context no-regression gate 285.45 tok/s vs main 285.39 tok/s · pass
Qwen3.5 4k prefill no-regression gate 6313.09 pp tok/s vs main 6091.28 pp tok/s · pass
Qwen3.5 32k prefill no-regression gate 5475.15 pp tok/s vs main 5112.16 pp tok/s · pass
Qwen3.5 64k prefill no-regression gate 5509.41 pp tok/s vs main 5128.35 pp tok/s · pass
Qwen3.5 128k prefill no-regression gate 0.0 pp tok/s · pass
Qwen3.6 vs same-box main 450.17 tok/s → +0.0% (+0.0)
Qwen3.6 scored decode (16384 ctx · 16k-context) 450.2 tok/s
Qwen3.6 correctness top-1 95.4% · KL 0.0364
Qwen3.6 128-token no-regression gate 488.55 tok/s vs main 488.76 tok/s · pass
Qwen3.6 512-context no-regression gate 482.24 tok/s vs main 482.49 tok/s · pass
Qwen3.6 4k-context no-regression gate 462.65 tok/s vs main 462.72 tok/s · pass
Qwen3.6 16k-context no-regression gate 450.2 tok/s vs main 450.17 tok/s · pass
Qwen3.6 32k-context no-regression gate 425.35 tok/s vs main 425.35 tok/s · pass
Qwen3.5 optimize eval:M · 5509.41 tok/s · pass
Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy top-1 95.4% · KL 0.0364 · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 128 296.61 tok/s · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 4k 286.65 tok/s · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 32k 285.44 tok/s · pass
Qwen3.5 optimize — Qwythos-9B (Q4_K_M) 64k 285.45 tok/s · pass
Qwen3.6 optimize eval:none · 450.2 tok/s · pass
Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy top-1 90.8% · KL 0.0373 · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 128 488.55 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 512 482.24 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 4k 462.65 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 16k 450.2 tok/s · pass
Qwen3.6 optimize — Qwen3.6-35B-A3B 32k 425.35 tok/s · pass

Verified speedup over same-box origin/main — 5509.41 tok/s (main was 5128.35 tok/s).

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

@skyrocket2026 skyrocket2026 added the merge-first Round's biggest verified speedup — merge this first label Jul 16, 2026
@skyrocket2026
skyrocket2026 merged commit 0889b85 into gittensor-ai-lab:main Jul 16, 2026
7 checks passed
@skyrocket2026

Copy link
Copy Markdown
Member

✅ Auto-merged as the round's merge-first winner — verified same-box speedup over main, all checks green. Thanks for the contribution!

@skyrocket2026 skyrocket2026 removed the merge-first Round's biggest verified speedup — merge this first label Jul 16, 2026
skyrocket2026 added a commit that referenced this pull request Jul 16, 2026
9876543210-tc-0123456789 pushed a commit to 9876543210-tc-0123456789/sparkinfer that referenced this pull request Jul 18, 2026
…tensor-ai-lab#464 gittensor-ai-lab#465 from latest evals

Record merged Qwen3.5 prefill wins (gittensor-ai-lab#455 XL @64k, gittensor-ai-lab#465 XL, gittensor-ai-lab#463 M, gittensor-ai-lab#464 XL @32k
14057.87 pp tok/s) and repair optimization journey.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

64k-context area:kernels subsystem (emission weight 0.42) eval:M sparkinfer auto-eval verdict: M eval-qwen35:M eval-qwen36:none 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