perf(qwen36): decode fuses + FAGQA4 restore + n_splits=160 (+8.6% @32k)#294
perf(qwen36): decode fuses + FAGQA4 restore + n_splits=160 (+8.6% @32k)#294jimcody1995 wants to merge 2 commits into
Conversation
04d3094 to
8f5ac05
Compare
8f5ac05 to
84a5d1c
Compare
|
Hi, @skyrocket2026 Thanks |
84a5d1c to
06a3b7f
Compare
❌ sparkinfer auto-eval —
|
| metric | value |
|---|---|
| label | eval:REJECT |
| Qwen3.5 score | eval-qwen35:REJECT (fail) |
| vs same-box main | 385.65 tok/s → +1.9% (+7.4) |
| Qwen3.6 score | eval-qwen36:REJECT (fail) |
| scored decode (16384 ctx · 16k-context · bidir) | 393.04 tok/s |
| correctness (bidir vs llama.cpp) | top-1 98.3% · KL 0.02 |
| 128-token no-regression gate | 426.8 tok/s vs main 426.5 tok/s · pass |
| 512-context no-regression gate | 421.99 tok/s vs main 419.81 tok/s · pass |
| 4k-context no-regression gate | 405.07 tok/s vs main 403.0 tok/s · pass |
| 16k-context no-regression gate | 393.04 tok/s vs main 385.65 tok/s · pass |
| 32k-context no-regression gate | 370.4 tok/s vs main 363.62 tok/s · pass |
| Qwen3.5 optimize | eval:REJECT · 262.85 tok/s · fail |
| Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy | top-1 98.3% · KL 0.02 · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 128 | 426.59 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 512 | 421.76 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 4k | 405.04 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 16k | 393.15 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 32k | 370.43 tok/s · pass |
| Qwen3.6 optimize | eval:REJECT · 393.04 tok/s · fail |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy | top-1 55.8% · KL 1.7049 · FAIL |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 128 | 265.69 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 512 | 262.58 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 4k | 249.67 tok/s · pass |
Rejected — no-regression guard: Qwythos-9B (Q4_K_M) accuracy broke (top1=0.5575, kl=1.7049).
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.
❌ sparkinfer auto-eval —
|
| metric | value |
|---|---|
| label | eval:REJECT |
| Qwen3.5 score | eval-qwen35:REJECT (fail) |
| Qwen3.6 score | eval-qwen36:REJECT (fail) |
| Qwen3.5 vs same-box main | 270.13 tok/s → -5.3% (-14.4) |
| Qwen3.5 scored decode (512 ctx · 512-context) | 255.74 tok/s |
| Qwen3.5 correctness | top-1 96.5% · KL 0.0125 |
| Qwen3.5 128-token no-regression gate | 258.37 tok/s vs main 273.0 tok/s · fail |
| Qwen3.5 512-context no-regression gate | 255.74 tok/s vs main 270.13 tok/s · fail |
| Qwen3.5 4k-context no-regression gate | 243.63 tok/s vs main 263.02 tok/s · fail |
| Qwen3.6 vs same-box main | 353.02 tok/s → +5.1% (+17.9) |
| Qwen3.6 scored decode (32768 ctx · 32k-context) | 370.91 tok/s |
| Qwen3.6 correctness | top-1 97.1% · KL 0.01 |
| Qwen3.6 128-token no-regression gate | 426.88 tok/s vs main 426.56 tok/s · pass |
| Qwen3.6 512-context no-regression gate | 422.2 tok/s vs main 421.57 tok/s · pass |
| Qwen3.6 4k-context no-regression gate | 405.51 tok/s vs main 404.98 tok/s · pass |
| Qwen3.6 16k-context no-regression gate | 393.58 tok/s vs main 380.69 tok/s · pass |
| Qwen3.6 32k-context no-regression gate | 370.91 tok/s vs main 353.02 tok/s · pass |
| Qwen3.5 optimize | eval:REJECT · 255.74 tok/s · fail |
| Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy | top-1 97.1% · KL 0.01 · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 128 | 426.77 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 512 | 422.13 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 4k | 405.46 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 16k | 393.45 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 32k | 370.45 tok/s · pass |
| Qwen3.6 optimize | eval:REJECT · 370.91 tok/s · fail |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy | top-1 96.5% · KL 0.0125 · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 128 | 258.29 tok/s · fail |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 512 | 255.69 tok/s · fail |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 4k | 243.67 tok/s · fail |
| regressions | regression-qwen35-128, regression-qwen35-512, regression-qwen35-4k, regression-qwen35-128, regression-qwen35-512, regression-qwen35-4k |
Rejected — no-regression guard: Qwythos-9B (Q4_K_M) decode regressed at: 128, 512, 4k.
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.
…ressing gittensor-ai-lab#318/gittensor-ai-lab#324 Re-apply Qwen3.6 decode optimizations on top of current main (GDN_QKVZ_FUSE, famma graph recapture, GDN_FUSE, ffn_down requant preserved). Adds banded n_splits, dual-row MMVQ, qknorm+int8 KV fuse, attn QKV/GDN quad paths, gated attn combine→Q8, and shared-gate mmvq+sigmoid. Co-authored-by: Cursor <cursoragent@cursor.com>
f9065a6 to
a36d27d
Compare
|
Hi, @skyrocket2026 Thanks |
✅ sparkinfer auto-eval —
|
| metric | value |
|---|---|
| label | eval:S |
| Qwen3.5 score | eval-qwen35:S (pass) |
| Qwen3.6 score | eval-qwen36:S (pass) |
| Qwen3.5 vs same-box main | 271.54 tok/s → +4.4% (+12.0) |
| Qwen3.5 scored decode (128 ctx · 128-context) | 283.5 tok/s |
| Qwen3.5 correctness | top-1 98.4% · KL 0.0095 |
| Qwen3.5 128-token no-regression gate | 283.5 tok/s vs main 271.54 tok/s · pass |
| Qwen3.5 512-context no-regression gate | 280.52 tok/s vs main 268.79 tok/s · pass |
| Qwen3.5 4k-context no-regression gate | 266.02 tok/s vs main 261.0 tok/s · pass |
| Qwen3.6 vs same-box main | 354.19 tok/s → +3.8% (+13.6) |
| Qwen3.6 scored decode (32768 ctx · 32k-context) | 367.82 tok/s |
| Qwen3.6 correctness | top-1 97.3% · KL 0.0102 |
| Qwen3.6 128-token no-regression gate | 423.77 tok/s vs main 427.68 tok/s · pass |
| Qwen3.6 512-context no-regression gate | 419.48 tok/s vs main 422.79 tok/s · pass |
| Qwen3.6 4k-context no-regression gate | 402.51 tok/s vs main 405.72 tok/s · pass |
| Qwen3.6 16k-context no-regression gate | 390.09 tok/s vs main 381.81 tok/s · pass |
| Qwen3.6 32k-context no-regression gate | 367.82 tok/s vs main 354.19 tok/s · pass |
| Qwen3.5 optimize | eval:S · 283.5 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 | 423.6 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 512 | 419.54 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 4k | 402.54 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 16k | 390.16 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 32k | 367.66 tok/s · pass |
| Qwen3.6 optimize | eval:S · 367.82 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy | top-1 98.4% · KL 0.0095 · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 128 | 283.12 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 512 | 279.98 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 4k | 265.99 tok/s · pass |
Verified speedup over same-box origin/main — 367.82 tok/s (main was 354.19 tok/s).
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.
❌ sparkinfer auto-eval —
|
| metric | value |
|---|---|
| label | eval:REJECT |
| Qwen3.5 score | eval-qwen35:REJECT (fail) |
| Qwen3.6 score | eval-qwen36:REJECT (fail) |
| Qwen3.5 vs same-box main | 282.83 tok/s → +0.2% (+0.6) |
| Qwen3.5 scored decode (128 ctx · 128-context) | 283.42 tok/s |
| Qwen3.5 correctness | top-1 95.8% · KL 0.0301 |
| Qwen3.5 128-token no-regression gate | 283.42 tok/s vs main 282.83 tok/s · pass |
| Qwen3.5 512-context no-regression gate | 280.46 tok/s vs main 279.9 tok/s · pass |
| Qwen3.5 4k-context no-regression gate | 266.08 tok/s vs main 272.03 tok/s · fail |
| Qwen3.6 vs same-box main | 353.86 tok/s → +3.9% (+13.9) |
| Qwen3.6 scored decode (32768 ctx · 32k-context) | 367.72 tok/s |
| Qwen3.6 correctness | top-1 99.1% · KL 0.0295 |
| Qwen3.6 128-token no-regression gate | 423.84 tok/s vs main 427.39 tok/s · pass |
| Qwen3.6 512-context no-regression gate | 419.62 tok/s vs main 422.47 tok/s · pass |
| Qwen3.6 4k-context no-regression gate | 402.6 tok/s vs main 405.72 tok/s · pass |
| Qwen3.6 16k-context no-regression gate | 390.22 tok/s vs main 381.8 tok/s · pass |
| Qwen3.6 32k-context no-regression gate | 367.72 tok/s vs main 353.86 tok/s · pass |
| Qwen3.5 optimize | eval:REJECT · 283.42 tok/s · fail |
| Qwen3.5 optimize — Qwen3.6-35B-A3B guard accuracy | top-1 99.1% · KL 0.0295 · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 128 | 423.68 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 512 | 419.55 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 4k | 402.53 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 16k | 390.09 tok/s · pass |
| Qwen3.5 optimize — Qwen3.6-35B-A3B 32k | 367.73 tok/s · pass |
| Qwen3.6 optimize | eval:REJECT · 367.72 tok/s · fail |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) guard accuracy | top-1 95.8% · KL 0.0301 · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 128 | 283.09 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 512 | 280.02 tok/s · pass |
| Qwen3.6 optimize — Qwythos-9B (Q4_K_M) 4k | 265.88 tok/s · fail |
| regressions | regression-qwen35-4k, regression-qwen35-4k |
Rejected — no-regression guard: Qwythos-9B (Q4_K_M) decode regressed at: 4k.
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.
…1% @8k-32k) fa_split_gqa_mma_i8_kernel<HEAD_DIM,GQA> is a single template shared by the hd128 (Qwen3-30B) and hd256 (Qwen3.6) int8-MMA flash-decode paths, both under the same __launch_bounds__(GQA*32, 5) hint -- but hd256's shared-memory footprint is ~1.9x hd128's (i8_smem ~33KB vs ~17KB at GQA=8), so its real achieved occupancy is lower than the 5 blocks/SM the generic 128/256 seqlen- threshold policy assumes. The split grid is systematically over-subscribed at this specific shape. Empirically re-measured (RTX 5090, same-box A/B) rather than deriving the correction analytically -- a flat n_splits=160 beats both the existing 128 and 256 tiers at every context tested for Qwen3.6 specifically: 4096 (tier 128): 405.80 -> 406.90 tied (~0%) 8192 (tier 128): 389.61 -> 402.19 +3.2% 16384 (tier 256): 381.56 -> 392.26 +2.8% 32768 (tier 256): 354.34 -> 372.03 +4.9% Gated strictly to Qwen3.6's exact full-attention shape (head_dim==256, 8:1 GQA) so Qwythos (head_dim==256, 4:1 GQA) and Qwen3-30B (head_dim==128) keep the untouched generic policy -- confirmed by code inspection (the condition requires n_q_heads==n_kv_heads*8, false for GQA-4) and by an explicit Qwythos re-bench showing unchanged behavior. Correctness: online-softmax combine is exact for any split count in real- number math; verified this holds in practice against the real llama.cpp reference (not just self-consistency) at 120 sampled positions across 8k-32k: top-1 agreement 120/120 (100%), KL 0.0659 nats vs baseline's 0.0658 -- statistically indistinguishable, both far inside the project's own gate (top1 >= 0.90, KL <= 0.20). Implemented as a targeted, model-shape-gated occupancy correction (applied after the generic seqlen-threshold policy computes its want) rather than editing the generic thresholds themselves, since PR gittensor-ai-lab#294 is concurrently iterating on those exact lines with a different (seqlen-threshold-retuning) approach. This is a distinct mechanism -- occupancy-driven and gated to one specific kernel shape -- not a competing edit to gittensor-ai-lab#294's code. Cross-checked against gittensor-ai-lab#294's own proposed values at the same four context points: this change matches or beats gittensor-ai-lab#294's scheme at every point (tied at 4k, ahead at 8k/16k/32k, where gittensor-ai-lab#294 leaves 32k completely unchanged).
The rebase accidentally dropped main's FAGQA4 hd256 flash-decode kernel, causing Qwythos 4k regression (257 vs 263 tok/s). Restore it with gated combine_hd256. Add n_splits=160 for Qwen3.6 GQA-8 only (+15 tok/s @32k). Co-authored-by: Cursor <cursoragent@cursor.com>
…1% @8k-32k) fa_split_gqa_mma_i8_kernel<HEAD_DIM,GQA> is a single template shared by the hd128 (Qwen3-30B) and hd256 (Qwen3.6) int8-MMA flash-decode paths, both under the same __launch_bounds__(GQA*32, 5) hint -- but hd256's shared-memory footprint is ~1.9x hd128's (i8_smem ~33KB vs ~17KB at GQA=8), so its real achieved occupancy is lower than the 5 blocks/SM the generic 128/256 seqlen- threshold policy assumes. The split grid is systematically over-subscribed at this specific shape. Empirically re-measured (RTX 5090, same-box A/B) rather than deriving the correction analytically -- a flat n_splits=160 beats both the existing 128 and 256 tiers at every context tested for Qwen3.6 specifically: 4096 (tier 128): 405.80 -> 406.90 tied (~0%) 8192 (tier 128): 389.61 -> 402.19 +3.2% 16384 (tier 256): 381.56 -> 392.26 +2.8% 32768 (tier 256): 354.34 -> 372.03 +4.9% Gated strictly to Qwen3.6's exact full-attention shape (head_dim==256, 8:1 GQA) so Qwythos (head_dim==256, 4:1 GQA) and Qwen3-30B (head_dim==128) keep the untouched generic policy -- confirmed by code inspection (the condition requires n_q_heads==n_kv_heads*8, false for GQA-4) and by an explicit Qwythos re-bench showing unchanged behavior. Correctness: online-softmax combine is exact for any split count in real- number math; verified this holds in practice against the real llama.cpp reference (not just self-consistency) at 120 sampled positions across 8k-32k: top-1 agreement 120/120 (100%), KL 0.0659 nats vs baseline's 0.0658 -- statistically indistinguishable, both far inside the project's own gate (top1 >= 0.90, KL <= 0.20). Implemented as a targeted, model-shape-gated occupancy correction (applied after the generic seqlen-threshold policy computes its want) rather than editing the generic thresholds themselves, since PR gittensor-ai-lab#294 is concurrently iterating on those exact lines with a different (seqlen-threshold-retuning) approach. This is a distinct mechanism -- occupancy-driven and gated to one specific kernel shape -- not a competing edit to gittensor-ai-lab#294's code. Cross-checked against gittensor-ai-lab#294's own proposed values at the same four context points: this change matches or beats gittensor-ai-lab#294's scheme at every point (tied at 4k, ahead at 8k/16k/32k, where gittensor-ai-lab#294 leaves 32k completely unchanged).
|
Hi @skyrocket2026 — thanks for the detailed eval on Qwythos 4k regression (265.88 vs main 272.03)
Qwen3.6 scored speed (+3.9% @32k, below 5% target)
Updated PR body with 32k scored numbers. Please re-eval when convenient — thanks! |
…1% @8k-32k) (#338) fa_split_gqa_mma_i8_kernel<HEAD_DIM,GQA> is a single template shared by the hd128 (Qwen3-30B) and hd256 (Qwen3.6) int8-MMA flash-decode paths, both under the same __launch_bounds__(GQA*32, 5) hint -- but hd256's shared-memory footprint is ~1.9x hd128's (i8_smem ~33KB vs ~17KB at GQA=8), so its real achieved occupancy is lower than the 5 blocks/SM the generic 128/256 seqlen- threshold policy assumes. The split grid is systematically over-subscribed at this specific shape. Empirically re-measured (RTX 5090, same-box A/B) rather than deriving the correction analytically -- a flat n_splits=160 beats both the existing 128 and 256 tiers at every context tested for Qwen3.6 specifically: 4096 (tier 128): 405.80 -> 406.90 tied (~0%) 8192 (tier 128): 389.61 -> 402.19 +3.2% 16384 (tier 256): 381.56 -> 392.26 +2.8% 32768 (tier 256): 354.34 -> 372.03 +4.9% Gated strictly to Qwen3.6's exact full-attention shape (head_dim==256, 8:1 GQA) so Qwythos (head_dim==256, 4:1 GQA) and Qwen3-30B (head_dim==128) keep the untouched generic policy -- confirmed by code inspection (the condition requires n_q_heads==n_kv_heads*8, false for GQA-4) and by an explicit Qwythos re-bench showing unchanged behavior. Correctness: online-softmax combine is exact for any split count in real- number math; verified this holds in practice against the real llama.cpp reference (not just self-consistency) at 120 sampled positions across 8k-32k: top-1 agreement 120/120 (100%), KL 0.0659 nats vs baseline's 0.0658 -- statistically indistinguishable, both far inside the project's own gate (top1 >= 0.90, KL <= 0.20). Implemented as a targeted, model-shape-gated occupancy correction (applied after the generic seqlen-threshold policy computes its want) rather than editing the generic thresholds themselves, since PR #294 is concurrently iterating on those exact lines with a different (seqlen-threshold-retuning) approach. This is a distinct mechanism -- occupancy-driven and gated to one specific kernel shape -- not a competing edit to #294's code. Cross-checked against #294's own proposed values at the same four context points: this change matches or beats #294's scheme at every point (tied at 4k, ahead at 8k/16k/32k, where #294 leaves 32k completely unchanged).
Summary
Rebased decode stack on current
main(preserves #318 GDN/FFN fuse, #323 down requant, #324 int8 graph recapture). Adds banded adaptiven_splits, dual-row MMVQ (K=2048), qknorm+int8 KV fuse, fused full-attn QKV MMVQ, gated-attn combine→Q8, and shared-gate Q4_K mmvq+sigmoid.Fixes eval regression from
a36d27d: the rebase accidentally dropped main'sFAGQA4hd256 GQA-4 flash-decode path, causing Qwythos 4k to fall back to scalarfa_split_kernel<256>. Restored in7d37962. Also addsn_splits=160occupancy correction for Qwen3.6 GQA-8 hd256 int8-MMA only (Qwythos GQA-4 keeps generic policy).Proof of speedup
sm_120)Decode tok/s (end-to-end, from
bench/scripts/bench.sh— required for evaluation):