Repository navigation
Add XQA group size 3 for GQA decode; default on for fp16 on SM 8.0 and 9.0 - #34235
Draft
Selena Yang (selenayang888) wants to merge 1 commit into
Draft
Selena Yang (selenayang888) wants to merge 1 commit into
Selena Yang (selenayang888) wants to merge 1 commit into
Conversation
This was referenced Nov 15, 2026
This branch has not been deployed
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.
Draft. This branch applies the Round-2 iteration 5 + 7 changes onto
main1f53982. It was built, tested and measured at this head together with the other Round-2 PRs on A100 and H200 (see Reported Validation and Performance At This Head). Additional evidence comes from staged runtimes built on ORT main 2bf400a.Summary
IsSupportedGQAXqaGroupSizecases. Before this change such models never used XQA.ORT_ENABLE_XQA_GROUP3=0/1overrides the device default in both directions;ORT_ENABLE_XQA=0still disables XQA entirely. bf16 and SM 8.6/8.9/10.x keep the previous behavior because they were not measured.Phi-4-mini-instruct-cuda-gpu:5) at this head, group 3 off → on: A100 decode +1.5% to +26.3% as shipped and +3.2% to +28.8% with capture; H200 +7.2% to +39.7% as shipped and +9.2% to +44.4% with capture. TTFT unchanged. Logits move by ~1e-3 relL2 (same order as switching to the memory-efficient attention backend); WikiText-2 perplexity within 0.03%.Previous PR: GenAI #2979 (GenAI: skip the legacy Phi-3 LongRoPE rewind under graph capture). Source files are disjoint from #34234 and from the Round-1 PRs, so this PR targets
main; its accumulated numbers were measured with all of them applied.Series (Round 2, Phi-4-mini): B1 baseline → #34234 constant-branch
If+ capture → GenAI #2979 GenAI capture rewind → this PR (XQA group 3).Companions: #34234 lets the Phi-4-mini package run with CUDA graph capture (its LongRoPE
Ifblocked capture). Wide GEMV (w48) = #34212 INT4 + #33728 INT8 (opt-in).Scope:
group_query_attention.cc(default gate),group_query_attention_workspace.h(geometry predicate),xqa_loader_{fp16,bf16}_impl.cuh(instantiation and dispatch), C++ geometry test, newTestXQAGroup3Parityintest_gqa.py.Description structure follows ORT #33932; performance tables use the columns of Mobius #813.
Reported Validation
onnxruntime_provider_test) and GenAI main da29a76b + GenAI #2979.test_gqa.py -k "TestXQAGroup3Parity or TestXQAHeadSinkParity or TestXQASlidingWindowParity": 112/112 passed on A100 and H200 (24 new group-3 cases: fp16/bf16, head 64/128/256, past 4/1000, with/without rotary).onnxruntime_provider_test --gtest_filter=If.*:GroupQueryAttentionWorkspaceTest.*: 14/14 passed on A100 and H200.ORT_ENABLE_XQA_GROUP3=1on A100 and on H200;=0differs by relL2 1.6e-3.git clang-formatandruffclean.Performance At This Head: Phi-4-mini
Runtime
rt-pr= ORT main 1f53982 + #34234 + this PR; GenAI main da29a76b + GenAI #2979. Baseline = the same runtime withORT_ENABLE_XQA_GROUP3=0(main's behavior for this model); optimized = the device default (group 3 on). The wide GEMV (#34212 / #33728) is not onmain, sow48is not available at this head. Change = Optimized decode relative to Baseline (first table) or llama.cpp (second table). One cohort, 3 interleaved rounds, llama.cpp in the same rounds; cores 32-39, A100 GPU 2, H200 GPU 1.A100-SXM4-80GB
Package as shipped (no capture). Baseline:
a100-phi4-pr0; optimized:a100-phi4-prdef.Decoder capture on. Baseline:
a100-phi4-prcap0; optimized:a100-phi4-prcap.Same captured optimized series versus llama.cpp
a100-phi4-llama(identical input IDs, same rounds):H200
Package as shipped. Baseline:
h200-phi4-prdef(group 3 off at that time); optimized:h200-phi4-pr1(=1, identical to the updated default, see the gate check).Decoder capture on. Baseline:
h200-phi4-prcap; optimized:h200-phi4-prcap1.Same captured optimized series versus llama.cpp
h200-phi4-llama:ORT_ENABLE_XQA_GROUP3=0 ORT_ENABLE_CUDNN_FLASH_ATTENTION=1) is still 7.5% / 6.1% faster than group-3 XQA at 128 / 4096 tokens (captured smoke, 2 rounds), so revisiting [CUDA] Accelerate Nemotron Bolt large-chunk prefill across contrib ops #34164's decode policy for group-3 models is worth a separate look.Staged evidence below adds the Round-1 wide GEMV and shows where this model stands against llama.cpp with all Round-1 and Round-2 changes.
Staged Evidence: ORT 2bf400a + Round-1 PRs
Baseline =
it6: B1 + #34234 + GenAI #2979 + this PR's kernels with the previous opt-in default (group 3 off). Optimized =it7: same plus a device default (SM 8.0 at the time). Only the group-3 default differs (onelibonnxruntime_providers_cuda.so).A100-SXM4-80GB (staged)
Package as shipped (no capture, no flags). Baseline:
a100-phi4-it6def; optimized:a100-phi4-it7def.Decoder capture on (runtime option, package files unchanged). Baseline:
a100-phi4-it6cap; optimized:a100-phi4-it7cap.Best A100 configuration (capture +
w48) versus llama.cppa100-phi4-llama(identical input IDs, same rounds):it7capture +w48(device default) vs the previous opt-init6capture +w48+ORT_ENABLE_XQA_GROUP3=1: same kernels, greedy outputs 128/128 identical at every length, decode -0.24% to +0.07% at 128-2048 and -1.6% at 4096/7680 (shared-host noise).H200 (staged)
On the staged base the H200 decode default was cuDNN SDPA, and forcing group 3 on (capture +
w48, it6 cohort) cost -4.34 / -3.83 / -5.07 / -6.81 / -5.61% at 128 / 512 / 2048 / 4096 / 7680 tokens, which is why the staged default was SM 8.0 only (the same switch on A100: +4.81 / +5.56 / +10.24 / +30.18 / +33.05%). Best staged H200 configuration (capture +w48, group 3 off, cuDNN SDPA) versus llama.cpph200-phi4-llama:Shared-host medians are descriptive, not statistical significance; negative rows are kept.
Controls And Configuration
Runtimes (all source-built):
rt-pr): ORT main 1f53982 + Stage constant If branches on device and allow CUDA graph capture #34234 + this PR; GenAI main da29a76b + GenAI #2979. Release, SM 80/90, CUDA 13.0, cuDNN 9.8 runtime.rt-it6/rt-it7, differ only inlibonnxruntime_providers_cuda.so): ORT main 2bf400a + Round-1 iterations (Add opt-in wide INT4 M1 GEMV with uint8 zero points #34212-Add opt-in chunked LinearAttention prefill using GatedDeltaNet #34215, with [CUDA] MatMulNBits 8-bit: wide fp16 M=1 kernel for decode GEMVs #33728's INT8 wide kernel) + Harden CUDA graph ownership, retained-buffer lifetime, and failed-capture recovery #33932 + [CUDA] Add opt-in single-warp INT4 GEMV for FP16 decode #33952 (= B1) + Stage constant If branches on device and allow CUDA graph capture #34234 + XQA group 3 (rt-it6: opt-in;rt-it7: SM 8.0 default). GenAI main faa19c7a + Weird requested input shape after runningbert_model_optimization.py#2889 + building ONNXruntime with DNNL cause an error #2899 + Add section for distro installations #2900 + GenAI #2979 (0.20.0.dev0).Phi-4-mini-instruct-cuda-gpu:5(CLI 0.11.0), unchanged: INT4 + INT8 block 32 with uint8 zero points, INT8 LM head, LongRoPEIf, GQA 24 / 8 heads, head size 128, fp16 KV.--captureturns on the decoder'senable_cuda_graphat runtime.microsoft/Phi-4-mini-instruct@cfbefacb converted to Q4_K_M (2,493,840,224 B), CUDA graphs, flash attention, fp16 KV, the same prompt token IDs. Quantization is not quality-matched.Correctness Evidence And Limits
Teacher-forced logits, group 3 on vs off (same build, staged, A100):
This is the same magnitude as switching to the existing memory-efficient attention backend (max relL2 1.1e-3 / 3.3e-3 at prompts 128 / 2048, same harness).
WikiText-2 (raw, test split) decode-path perplexity on A100: prefill
prefixtokens, then append one token per step and score it with the previous step's logits, so every scored token goes through decode attention.Greedy outputs (first measured request, 128 tokens), group 3 on vs off at this head: A100 shipped first differs at token 10 for the 128-token prompt, identical at the other lengths; A100 captured differs at tokens 39 (2048) and 90 (4096); H200 shipped differs at token 25 (512); H200 captured at token 90 (4096). Staged: A100
it7capture +w48vsit6capture +w48+ switch 128/128 at all lengths (same kernels).Known Limitations
enable_cuda_graph, so the capture rows require the runtime option.Required Tests
TestXQAGroup3Parity(new): fp16 + bf16, head 64/128/256, past 4/1000, with and without rotary, group 3 forced on, against the PyTorch reference.GroupQueryAttentionWorkspaceTest.XqaGeometryPredicatesCoverSupportedDomainupdated for group 3.Resource Observations
it7default (shipped)it7capture +w48it7default (shipped)it7capture +w48Separate one-round resource pass (2 warmups + 1 measured request per length, fresh process); own-process NVML peak sampled every 20 ms, RSS high-water mark; excluded from the timing tables. XQA group 3 adds no persistent allocation; peak VRAM is dominated by the arena. llama.cpp resources were not sampled in this round (GGUF 2,493,840,224 B). The new instantiations add to
libonnxruntime_providers_cuda.sosize and compile time (fp16 + bf16 x supported head sizes).Existing Work And Duplication
GitHub search (open and merged) for XQA group sizes: #32108 (merged) adds group 6 to paged XQA; #33759 (open) covers group 8 in a different split-decode kernel; #32071 (open) integrates the CUDA workspace with activation memory patterns and touches the same workspace header. None adds non-paged group 3. Round-1 #34214 changes
xqa/mha_impl.cuhonly (disjoint).WORK.md Acceptance Criteria
ORT_ENABLE_XQA_GROUP3=0restores the previous behavior. Experimental per WORK.md until quality is validated.Quality Results
Remaining Before Ready For Review
Evidence
Round-2 iteration log (ITERATIONS.md, iterations 5 and 7 and the PR-head section), rebased-head cohort
prh-{a100,h200}.tar.gz, staged cohortsit6/it7-{a100,h200}.tar.gz(timing JSONL with token IDs, logs, runtime hashes),it7-{a100,h200}-checks.tar.gz(gate and perplexity logs), PR-head verification logs, resource passres2. Kept in the author's review package; available on request.