perf(qwen3.6): fuse decode kernel launches — shared-expert + GDN conv/L2-norm#239
perf(qwen3.6): fuse decode kernel launches — shared-expert + GDN conv/L2-norm#239minion1227 wants to merge 2 commits into
Conversation
| (wrong baseline — re-run) |
|
Auto-closed by sparkinfer eval: no single context cleared the 2% improvement gate and at least one context regressed. |
- Qwen3-MoE frontier: 493.56 (unchanged, verified) - Qwen3.6 frontier: 261.30 (both gittensor-ai-lab#230 + gittensor-ai-lab#229 merged, measured on RTX 5090) - Qwen3.6 journey: baseline(23) -> gittensor-ai-lab#230(171) -> gittensor-ai-lab#229(261) - Qwen3.6 status: 261/258/244 tok/s (128/512/4k), ~95% of llama.cpp - PR table: gittensor-ai-lab#234 updated to REJECT (was stale XL), added gittensor-ai-lab#237/gittensor-ai-lab#239/gittensor-ai-lab#241/gittensor-ai-lab#243
…lab#239/gittensor-ai-lab#241/gittensor-ai-lab#243 Each PR has a real bot eval comment with the full verdict table. Link to the PR comment as proof (runs were killed before log site POST).
🔄 Evaluation was run against a wrong baseline — re-evaluatingThe bot's same-box baseline measurement had two bugs during your PR's evaluation run:
The correct Qwen3.6 main baseline (with GDN_FAST=1) is now:
Your PR will be re-evaluated against this correct same-box main baseline once you rebase onto main. No code change needed on your side — the bot just needs a clean slate to measure against. Apologies for the confusion — this was an eval pipeline error, not an issue with your submission. |
cd383fb to
2a2ebde
Compare
|
Rebased onto |
2a2ebde to
a711cd5
Compare
✅ sparkinfer auto-eval —
|
| metric | value |
|---|---|
| label | eval:L |
| scored decode (128 ctx · 128-context · Qwen3.6) | 259.09 tok/s |
| vs same-box main | 225.24 tok/s → +15.0% (+33.9) |
| correctness (Qwen3.6 vs llama.cpp) | top-1 95.9% · KL 0.0207 |
| Qwen3.6 128-token no-regression gate | 259.09 tok/s vs main 225.24 tok/s · pass |
| Qwen3.6 512-context no-regression gate | 256.08 tok/s vs main 222.86 tok/s · pass |
| Qwen3.6 4k-context no-regression gate | 242.49 tok/s vs main 212.42 tok/s · pass |
| Qwen3-30B-A3B guard — accuracy | top-1 96.9% · KL 0.0177 · pass |
| Qwen3-30B-A3B guard — 128-token | 498.15 tok/s · pass |
| Qwen3-30B-A3B guard — 512-context | 473.08 tok/s · pass |
| Qwen3-30B-A3B guard — 4k-context | 395.62 tok/s · pass |
| Qwen3-30B-A3B guard — 16k-context | 331.83 tok/s · pass |
| Qwen3-30B-A3B guard — 32k-context | 262.0 tok/s · pass |
Verified speedup over same-box origin/main — 259.09 tok/s (main was 225.24 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.
|
The round's |
Qwen3.6 per-context: 276.26/272.93/257.73 (128/512/4k). #239 PR row: label=L -> XS (correct run24 verdict).
The Qwen3.6 shared expert ran four kernels per layer (gate GEMV, up GEMV, SwiGLU, down GEMV). Decode is CUDA-graph/launch-bound, so those extra launches cost real time: measured on an RTX 5090, removing the shared expert entirely is +8.4% decode, ~70% of which is per-kernel overhead rather than weight bandwidth. This fuses the gate+up projections and the SwiGLU into a single one-warp-per-row kernel — hn is staged into shared memory once and both dot-products are computed in one pass — cutting three launches to one per layer (down stays a separate GEMV). The per-lane reduction order matches launch_gemv, and the projections are rounded to bf16 before SwiGLU exactly as the unfused path does (sh_gate/sh_up are stored bf16), so the output is bit-identical. Qwen3.6-35B-A3B decode, RTX 5090 (sm_120): 171.3 -> 174.8 tok/s (+2.05%). Correctness: 100% top-1 agreement, KL = 0 vs the prior build (bit-identical). No-op for Qwen3-30B, which has no shared expert, so the guard cannot regress. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 30 GDN linear-attention layers ran conv_split + two l2_norm_heads kernels per layer (3 launches; the two L2-norms were tiny 16-block kernels serial on the GDN critical path). Fuse all three into one block-per-head kernel — causal conv + SiLU per lane, then the per-head L2-norm of q/k reduced in-block — and drop the two superseded kernels. Bit-identical: same conv arithmetic and per-lane conv_state shift, and the L2 reduction sees the same bf16-rounded SiLU values in the same warp/shared order. Combined with the shared-expert fusion in this PR, Qwen3.6-35B-A3B decode goes 171.3 -> 176.4 tok/s (+2.95%) on RTX 5090 (sm_120). Correctness: 100% top-1 agreement, KL=0 vs main (bit-identical). No-op for Qwen3-30B. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a711cd5 to
8868b7c
Compare
✅ sparkinfer auto-eval —
|
| metric | value |
|---|---|
| label | eval:M |
| scored decode (128 ctx · 128-context · Qwen3.6) | 277.98 tok/s |
| vs same-box main | 254.72 tok/s → +9.1% (+23.3) |
| correctness (Qwen3.6 vs llama.cpp) | top-1 98.2% · KL 0.0154 |
| Qwen3.6 128-token no-regression gate | 277.98 tok/s vs main 254.72 tok/s · pass |
| Qwen3.6 512-context no-regression gate | 274.03 tok/s vs main 251.87 tok/s · pass |
| Qwen3.6 4k-context no-regression gate | 258.85 tok/s vs main 239.0 tok/s · pass |
| Qwen3-30B-A3B guard — accuracy | top-1 96.5% · KL 0.015 · pass |
| Qwen3-30B-A3B guard — 128-token | 481.38 tok/s · pass |
| Qwen3-30B-A3B guard — 512-context | 458.09 tok/s · pass |
| Qwen3-30B-A3B guard — 4k-context | 385.21 tok/s · pass |
| Qwen3-30B-A3B guard — 16k-context | 326.97 tok/s · pass |
| Qwen3-30B-A3B guard — 32k-context | 254.61 tok/s · pass |
Verified speedup over same-box origin/main — 277.98 tok/s (main was 254.72 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.
|
The round's |
|
The round's |
Summary
Reduce decode kernel-launch count on the Qwen3.6 path with two bit-identical kernel fusions (decode here is CUDA-graph / launch-bound, so per-layer launches cost real time):
gate+upGEMVs and the SwiGLU into one one-warp-per-row kernel (hnstaged once, both dot-products in one pass): 3 launches → 1 per layer (40 layers).downstays a separate GEMV.conv_split+ the two per-headl2_norm_headskernels into one block-per-head kernel (causal conv + SiLU, then the q/k L2-norm reduced in-block): 3 launches → 1 per GDN layer.Both are bit-identical to
main(same arithmetic and reduction order; projection/SiLU values rounded to bf16 exactly as the unfused paths do), so the accuracy gate is trivial. Both are no-ops for Qwen3-30B (no shared expert, no GDN layers) → the guard cannot regress. Onlykernels/+runtime/; touches no crowded/attempted file.Proof of speedup
sm_120)Decode tok/s (end-to-end, Qwen3.6-35B-A3B-UD-Q4_K_M,
n=256,ctx=0,bs=1):cc356a5)→ +2.95% decode on the Qwen3.6 primary model.