Repository navigation
[Perf] [NVIDIA] qwen3: Fuse prefill QK norm and RoPE with contiguous Q - #47785
Conversation
Produce contiguous query output and update keys in packed storage. Share the standalone half-warp normalization arithmetic, retain gated BF16 decode and native fallbacks, and cover mutation, numerical parity, dispatch and graph replay. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preview approval requiredThis pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit |
Observe the real wrapped prefill operator for eligible EXTEND and require no prefill calls for the other tested modes. Assert Hopper prefill eligibility so a disabled optimization cannot silently pass the existing dispatch fixture. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/tag-and-rerun-ci |
a6ef5b6 to
cc39bef
Compare
|
/tag-and-rerun-ci |
|
/tag-and-rerun-ci |
1 similar comment
|
/tag-and-rerun-ci |
|
/rerun-failed-ci |
1 similar comment
|
/rerun-failed-ci |
Supply torch.int64 on the mock decode backend so real draft-buffer allocation receives a torch.dtype rather than a child Mock. Preserve the existing capture and replay assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Fixed the unrelated CI failure - Inkling checkpoint replay fixture in d6f3d18. After #47173, the draft graph runner reads The open fix #47858 addresses this by setting Python syntax, the isolated before/after mock dtype contract check, and |
|
/tag-and-rerun-ci |
1 similar comment
|
/tag-and-rerun-ci |
|
/rerun-failed-ci |
|
/rerun-failed-ci |
Preserve upstream MRoPE table-decode support while retaining the standard-RoPE-only dense prefill scope. Combine MRoPE decode and dense prefill dispatch and graph-replay coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/tag-and-rerun-ci |
1 similar comment
|
/tag-and-rerun-ci |
|
/rerun-failed-ci |
|
@BBuf Base CI passed. Can you help take a look?? Thank you! |
|
/rerun-failed-ci try again |
Apply the one-line test correction proposed in sgl-project#48223 so operator registration does not depend on transitive imports. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Applied the exact one-line Arm64 test fix proposed in #48223 in commit 093cf61 to address the unrelated CI regression tracked in #48221.
|
|
/tag-and-rerun-ci |
|
/rerun-failed-ci |
|
/rerun-failed-ci |
1 similar comment
|
/rerun-failed-ci |
Motivation
Dense Qwen3 prefill launches QK normalization and cached RoPE separately, then FA3 or FlashInfer materializes contiguous Q. This change combines normalization, the same cached RoPE, and contiguous Q output in one CUDA launch. K is normalized and rotated in its original packed QKV storage; Q input and V remain unchanged.
It uses the existing
--enable-fused-qk-norm-ropeopt-in and remains disabled by default on CUDA. Attention kernels and KV-cache handling are unchanged.Modifications
attention.qk_norm_ropewith FP16/BF16 support, tensor/device/dtype/stride/alignment checks, and explicit K/output mutation metadata. D128 processes two rows per warp; D64/D256 use one. Share the existing standalone half-warp normalization arithmetic, round normalized values before FP32-table rotation, and preserve shuffle participation for an unmatched final row.EXTENDon Hopper, head dimension 128, TP1, unquantized weights, FA3/FlashInfer, full-head unscaled NeoX RoPE, and matching activation/weight dtypes. Keep speculative, mixed, verification, inherited/non-Qwen3, deterministic, compiled, and other unvalidated configurations on the separate path. Preserve existing BF16 table decode guards.Disabling the existing flag or reverting this contribution rolls the change back; there is no checkpoint, schema, or persisted-data migration. On upstream base-versus-PR comparisons, keep the existing flag enabled in both trees so BF16 decode treatment is identical.
Accuracy Tests
Current contribution-source checks, run on H200 and the checkout's Python path:
python -m pytest test/registered/kernels/ops/attention/test_table_qk_norm_rope.py -qpython -m pytest test/registered/unit/models/test_qwen3_qk_norm_rope.py -qpython -m pytest test/registered/kernels/ops/layernorm/test_qknorm.py -k two_rows -xqcompute-sanitizer --tool memcheck --error-exitcode 99 python -m pytest test/registered/kernels/ops/attention/test_table_qk_norm_rope.py -k hybrid -xqcompute-sanitizer --tool synccheck --error-exitcode 99python -m pytest test/registered/unit/kernels/test_kernels_namespace.py -qFresh compatible-serving validation: one H200, TP1, SGLang v0.5.20 serving paths, Torch
2.13.0.2+cu130, Triton3.8.0, FlashInfer0.6.18, and kernel0.4.7.0+cu130. The QKV producer, cached NeoX RoPE and attention consumer paths used by these cases were checked; the exact PR CUDA/shared-normalization files from01b927bwere loaded through a compatibility adapter. The CUDA header SHA256 iseb5c6486fdfd265bf8aa9a54ea52be72ce126516f50802b747ea951e4bf430e0. The comparison uses optimized #45214 standalone normalization in the baseline and preserves identical original decode treatment in both arms.Paired real-model smoke outputs are exact at 128, 4096 and 8192 input tokens for Qwen3-4B BF16/FA3 and Qwen3-Embedding-0.6B FP16/FlashInfer. A direct compatible-runtime FP16/BF16 contract check also matches Q/K exactly, with Q input/V unchanged, K updated and contiguous Q.
Paired Qwen3-4B GSM8K-200 with sgl-eval 0.1.3:
The same 200 questions, expected answers, generation texts and per-question grades match exactly. The existing verified canonical corpus was reused; no prompts, grading or tolerances were changed.
Checkpoints:
Qwen/Qwen3-4Brevision1cfa9a7208912126459214e8b04321603b3df60cQwen/Qwen3-Embedding-0.6Brevision97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3Accuracy command/settings (
MODELis the verified local snapshot of the pinned checkpoint):Speed Tests and Profiling
Fresh compatible-serving comparison: baseline A1 → candidate B → baseline A2, with fresh servers, identical checkpoint/runtime/seed/cache/chunking/graph settings, and 200 measured requests per window at concurrency 4 after warmup. This is six server phases and nine serving windows, not a repeat of the broader candidate matrix. All 1800 measured requests completed without errors.
For each metric, the baseline below is the arithmetic mean of A1 and A2's window metric. Change is
100 * (B / mean(A1, A2) - 1); these are descriptive bracketed comparisons, not confidence intervals or pooled percentiles.Absolute per-window latency:
Generation latency is the complete non-streaming HTTP request through response-body read/JSON parse with one generated token: a one-token request-latency/TTFT proxy, not first-streamed-token TTFT. Embedding latency is complete embedding response latency. Throughput is completed requests per second over each measured window.
Serving reproduction protocol
For an independent source-pair reproduction, compare base
b50d4394945d3dcacea05ee4afd334a9b225cbdcwith PR01b927b0ba6992c18b6dd185f0490ff528eeb0feon one Hopper GPU. Enable the existing flag in both source trees; use the pinned model revisions above. The recorded compatible-v0.5.20 measurements use the adapter described above, so these source-pair instructions specify the workload rather than package that adapter.Common launch settings in every arm:
For Qwen3-4B use
DTYPE=bfloat16,BACKEND=fa3, and append--return-hidden-states-mode last. For Qwen3-Embedding-0.6B useDTYPE=float16,BACKEND=flashinfer, and append--is-embedding. Use local model snapshots with offline Hugging Face/Transformers access and only one visible GPU.The HTTP client uses explicit token IDs, not tokenizer-generated random text:
POST /generatewithinput_ids=ids(4096, seed),sampling_params={"temperature": 0, "max_new_tokens": 1, "ignore_eos": True},return_logprob=True, andreturn_hidden_states="last". Hidden-state/logprob response serialization is included in the measured latency.POST /v1/embeddingswith one token-ID array ininput=ids(length, seed)andencoding_format="float"; measure lengths 4096 and 8192 separately.700 + i(i=0..7), wait for all eight, thenPOST /flush_cache?timeout=60once. Read that response as text, not JSON. Submit 200 measured requests with seeds1000 + i(i=0..199) through a semaphore of four, with no flush between requests.200 / wall_seconds.bench_serving --disable-streamreproduces the non-streaming timing class, but ordinaryrandom-idsmode does not guarantee the same IDs, diagnostic fields or JSON-read boundary. Use the explicit HTTP protocol above when matching this workload.Untimed actual-server profiles confirm
qk_norm_rope_kernelengagement in the candidate:The activation-dtype Q-copy kernel family disappears; other copy kernels remain. Native decode normalization remains in the generation candidate trace. Separate current-source preparation profiles show three preparation kernels → one, with no
aten::contiguousin the hybrid path. D128 BF16/FP16 int32/int64 specializations compile with 56 registers and zero spills on SM90a. Profiles are untimed engagement/copy-removal evidence and are not mixed into serving measurements.Current-source cold-L2 preparation microbenchmark:
python test/registered/kernels/benchmark/attention/bench_qk_norm_rope.pypassed all six token/dtype shapes. At 4096 tokens, 32 Q heads / 8 KV heads / D128, split norm + cached RoPE + Q conversion measured 79.456 → 33.455 us (BF16) and 79.331 → 31.787 us (FP16). The marker framework rotates inputs to defeat L2 reuse.Checklist
Ruff 0.15.1, isort 7.0.0, clang-format 20.1.7, registered-test admission, pytest entrypoint lint and
git diff --checkpassed. Repository pre-commit checks passed for all 10 contribution files withpre-commit run --show-diff-on-failure --files $(git diff --name-only b50d439..HEAD); no files were changed. Both local Claude and Copilot GPT complete-diff reviewers covered all 10 files and returned no actionable findings; contribution code remains at the reviewed01b927b.Final audit reran the 7 hybrid cases, 21 model dispatch/graph cases and 3 shared half-warp regressions: all 31 passed without code changes. Renewed complete-diff Standards and Spec/functionality reviews returned no actionable findings. Base CI currently stops at the draft gate ("PR is draft. Blocking CI."); this is not a GPU-test failure or a CI pass.
Review and Merge Process
/tag-and-rerun-ci,/tag-run-ci-label,/rerun-failed-ciImplementation and local validation were assisted by GitHub Copilot; automated authorship is disclosed in the commit trailer.
CI States
Latest PR Test (Base): ✅ Run #41798402706
Latest PR Test (Extra): ❌ Run #41798402657
Latest PR Test (AMD ROCm 10): ⏳ Run #41798402707
Tip
Base CI only covers the core cases, and its workload may not exercise your change. Use
/rerun-testto run the tests your change touches, or copy this prompt for an agent: