Repository navigation
perf(cuda): retune GQA decode flash-decoding - #999
Merged
Merged
Conversation
Increase the split-K decode occupancy target for fp16/bf16 GQA, add a rollback split-count knob, use exp2 softmax math, and parallelize split-merge output lanes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #999 +/- ##
=======================================
Coverage 83.56% 83.56%
=======================================
Files 12 12
Lines 5270 5270
Branches 5270 5270
=======================================
Hits 4404 4404
Misses 682 682
Partials 184 184
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
🔴 Benchmark Regression DetectedComparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).
Visual flags: Host infoWhat this cannot catch
|
justinchuby
marked this pull request as ready for review
August 15, 2026 08:30
justinchuby
added a commit
that referenced
this pull request
Aug 15, 2026
## Summary - stacked on #999 - attributes the fixed GQA decode aux bucket: standalone RoPE/append/transposes are already fused away; split-K merge is the dominant fixed aux cost - tiles fp16/bf16 split-merge across 4 head-dim blocks per query row - precomputes split weights once per merge CTA in shared memory ## Measurements (glm-4-9b-int4, GPU2, graph ON, ONNX_GENAI_LMHEAD_CUBLASLT=1) Before = #999 stacked baseline in this worktree. | KV | core before | merge before | prep before | core after | merge after | prep after | e2e after | |---:|---:|---:|---:|---:|---:|---:|---:| | 512 | 447.6 us | 280.8 us | 104.0 us | 448.0 us | 223.4 us | 101.8 us | 5.088 ms/tok, 196.56 tok/s | | 2048 | 1335.0 us | 280.9 us | 109.1 us | 1335.1 us | 223.2 us | 107.6 us | 5.940 ms/tok, 168.35 tok/s | Aux reduction: ~57 us/step, context-independent. Short base decode: 207.57 tok/s, captures=3 replays=474 fallbacks=0. ## Correctness / validation - glm and qwen KV512/KV2048 generated streams matched #999 exactly in measured before/after runs - capture remained intact (fallbacks=0) - cargo test -p onnx-runtime-ep-cuda --features cuda,gpu-tests --test gqa_fp16_gpu --quiet - cargo test -p onnx-runtime-ep-cuda --features cuda,gpu-tests --test group_query_attention_gpu gqa_gpu_fp16_decode_split_k_long_context_is_deterministic_and_matches_baseline --quiet - cargo fmt --all -- --check - cargo clippy -p onnx-runtime-ep-cuda --features cuda --lib -- -D warnings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 15, 2026
Promote the #991 cuBLASLt lm_head decode MatMul to default-on and harden its CUDA-graph capture guard from a single cached shape to a shape-keyed plan cache, so the out-of-box glm base decode banks the cuBLASLt logits projection without an env flag. Capture guard (Gaff's #991 blocker): `launch_dense_capturable` now keeps a shape-keyed `Vec<DenseGemmPlan>` (LRU, MRU-promote on hit, bounded by DENSE_PLAN_CACHE_CAP=8) instead of a single `Option<DenseGemmPlan>`. Every distinct (dtype, M, K, N) the node runs keeps its own preselected cuBLASLt algorithm + persistent workspace, so alternating shapes (prefill M>1, decode M==1, a speculative M=K verify width) all replay with no per-call heuristic query inside the captured region. The reject-cold-miss-during-capture contract is unchanged (now enforced per shape); eviction only ever runs off the non-capturing creation path, and the hot decode plan is kept at the MRU front, so a plan baked into a live captured graph is never freed. Strictly safer than the single-slot path (evicts less; same recapture-on-change discipline). Default-on: `lmhead_cublaslt_enabled()` inverts to default-TRUE-unless-disabled; set ONNX_GENAI_LMHEAD_CUBLASLT=0/false/off for the hand-GEMV escape hatch. #999 GQA flash-decoding is already default-on (its env vars are rollback-only knobs), so no flip is needed there. GEMV fp16 (#996) stays untouched (not in this base) and is a one-liner default flip once it merges. Measured (glm-4-9b-int4, H200, graph-on, fallbacks=0): - default-on short decode 207.72 tok/s (vs escape-hatch-off hand-GEMV 202.24) - default-on KV2048 171.69 tok/s, fallbacks=0 across 8 bucket-growth recaptures - lm_head cuBLASLt(default) == hand-GEMV(off) top-40 logprobs BYTE-IDENTICAL - qwen no regression (152.16 vs 153.60, within noise), fallbacks=0 - f64 oracle 7/7, GQA fp16 2/2, fmt + clippy clean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 15, 2026
…1004) ## Default-on: lm_head cuBLASLt + multi-shape capture guard Promotes #991's cuBLASLt `lm_head` decode MatMul to **default-on** (out-of-box glm base), after clearing Gaff's #991 blocker: the single-shape cuBLASLt plan cache is hardened into a **shape-keyed multi-shape plan cache** so plan creation never happens inside a captured CUDA-graph region across prefill/decode/verify shape switches. Rebased onto main `ad141370` (post-#996); disjoint from #996's opt-in fp16 (which stays OFF by default). ### Capture guard — path (a): shape-keyed multi-shape plan cache #991 already had a warm-once / reject-cold-miss-during-capture contract, but for a SINGLE cached shape (a shape switch evicted + re-ran the heuristic search). This PR generalizes it to a `Vec<DenseGemmPlan>` keyed by `(dtype,M,K,N)`, MRU-ordered, bounded by `DENSE_PLAN_CACHE_CAP = 8`: - **hit** → MRU-promote, launch preselected algo (no heuristic query); - **cold miss while capturing** → `Err` (reject-before-warm, now per shape); - **cold miss not capturing** → create + insert-front + LRU-truncate. - Eviction runs **only** off the non-capturing path; the hot decode plan (M==1) stays MRU-front and is never the LRU victim. Strictly safer than the single slot (evicts far less; same recapture-on-change discipline). **Shape-transition proof** (temporary `DENSE_PLAN_SEARCH` counter, glm KV2048 run = prompt → decode → **8 KV-bucket-growth recaptures**, 8623 replays): - **exactly 2 plan searches for the whole run** — `M=3` prefill (once) + `M=1` decode (once), each on the non-capturing path; - **0 searches** during any of the 8623 replays or 8 recaptures; - **fallbacks=0** (a cold-miss-inside-capture would surface as a fallback; none did). This is the "no per-transition plan search, not just steady-state" guarantee. Counter reverted (not committed). ### Default-on flip (byte-identical wins only) - `lmhead_cublaslt_enabled()` → default-TRUE-unless-disabled; escape hatch `ONNX_GENAI_LMHEAD_CUBLASLT=0|false|off` → hand fp16 GEMV. - **#999 GQA flash-decoding already default-on** (audited — its env vars are rollback-only knobs); no flip needed. - **GEMV fp16 (#996) stays PERMANENTLY opt-in — NOT flipped.** Chew's #996 verdict is land-as-opt-in: the fp16 K-accumulate is not byte-identity-safe vs fp32 (glm knife-edge argmax flips), so it is not a default-on candidate. Out-of- box GEMV stays fp32 (byte-identical); fp16 via `ONNX_GENAI_GEMV_FP16=1`. ### Measured (glm-4-9b-int4, H200, graph-on, median, no env flags) | config | short tok/s | KV2048 tok/s | fallbacks | |---|---|---|---| | **default-on (no flags)** | **~206–208** | **~166–172** | **0** | | escape-hatch `=0` (hand GEMV) | 202.24 | — | 0 | (short 207.72 GPU3 / 206.44 GPU4; KV2048 171.69 / 168.06 / 165.59 across GPU3/6/4 — cross-GPU + shared-host variance; all fallbacks=0.) - Byte-identity: cuBLASLt lm_head (default) vs hand-GEMV (off) top-40 token-0 logprobs **byte-identical** (both select token 315). - qwen no regression (152.16 vs 153.60, noise), fallbacks=0. - f64 oracle 7/7; GQA fp16 2/2; fmt + clippy clean. **Out-of-box number is ~206–208 short / ~166–172 KV2048 and that is final for this PR.** The ~232 combined figure required fp16 GEMV opt-in ON; since fp16 ships permanently opt-in, 232 is only reachable via `ONNX_GENAI_GEMV_FP16=1`.⚠️ Please gate with fresh Chew (byte-identity / f64 oracle) + Gaff (capture-safe, fallbacks=0, win real). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Measurements (glm-4-9b-int4, GPU1, graph ON, ONNX_GENAI_LMHEAD_CUBLASLT=1)
Validation
Accuracy notes