Repository navigation
Conversation
The planner test imported the top-level flash_attn_v100_cuda name. With an
editable install that name can resolve to another tree's build, and after any
test that loaded the extension through flash_attn_v100 (as the QSA routes do)
every planner case failed with "type PreparedPolicy is already registered".
Import it through flash_attn_v100.flash_attn_interface instead.
The mixed grouped/XQA case still patched _SM70_QSA_XQA_PAGE4,
_SM70_QSA_XQA_PAGE4_MIN_ROWS and _SM70_QSA_GROUPED_PAGE4, which no longer exist
since the page4 switches moved into the sm70 sparse policy; it now asserts the
policy defaults it relies on.
Tests (Tesla V100):
pytest tests/kernels/attention/test_sm70_qsa_grouped_page4.py
tests/kernels/test_sm70_qsa_page4_plan.py
before: 2 passed, 38 errors; after: 40 passed
Co-authored-by: Claude
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJxVXkjSHhfq2D71DoYSK7
Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
The native Flash-V100 grouped/XQA page4 QSA routes accept only six query
heads per row (the Flash-Next TP4 rank). TP2 ranks (12 heads) and single-rank
pipeline stages (24 heads) fell back to the Triton split-K kernel, which on
SM70 and SM75 runs tl.dot as FP32 FMA (Triton has no tensor-core MMA before
SM80) and is L1/shared-memory bound.
All query heads of a QSA row share one latent KV head and one selection, so a
rank with 6*R heads now runs as R consecutive six-head pseudo-rows that repeat
the row's selection, position and request. A grouped tile of eight pseudo-rows
is then 8/R rows x 6*R heads and loads the union of only 8/R selections. The
kernels are unchanged; the shape check accepts multiples of six heads and the
minimum-row threshold counts pseudo-rows.
Tesla V100, one 1616-row prefill chunk, real top-k selections dumped from a
Flash-Next 24k and 150k prefill (13 QSA layers each), ms per call:
12 heads, one KV head (TP2 rank): Triton 22.6-23.5, native 7.0-9.2
(2.5-3.3x)
max |native - Triton| 1.2e-4 to 2.4e-4 (one FP16 ulp at the output scale)
The 24-head stages of Flash-Next carry two KV heads; they reach the routes
with the KV-head change that follows.
Tests:
pytest tests/kernels/attention/test_sm70_qsa_page4_head_groups.py
tests/kernels/attention/test_sm70_qsa_grouped_page4.py
tests/kernels/test_sm70_qsa_page4_plan.py
tests/models/qwen4_exp/test_qsa_xqa_page4_workspace.py
tests/models/qwen4_exp/test_qsa_xqa_page4_workspace_cpu.py
tests/models/qwen4_exp/test_qsa_ops.py
tests/models/qwen4_exp/test_sm70_qsa_tuning.py
tests/config/test_sm70_provider_lifecycle.py
Tesla V100: 123 passed; Quadro RTX 8000: 79 passed, 44 skipped
Co-authored-by: Claude
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YaG8VjXn5eN3Aq7eZrA3dp
Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
The grouped page4 and XQA page4 QSA prefill routes were built for sm_70 only and refused every other device. Their kernels run on Turing (sm_75) as well, with three changes: - fused_mma.h knows the 16x16x16 FP32 accumulator layout of both architectures: accumulator_16x16_rows() names the two rows a lane holds (Volta HMMA.884 quad pairs r and r + 2, Turing m16n8k8 tiles lane / 4 and lane / 4 + 8). The two row-scaling helpers of the grouped kernel used the Volta formula inline and now ask this function. All other fragment accesses are uniform and layout independent. - The planner hash table follows the device's opt-in shared memory: 8192 entries (96 KiB, unchanged on Volta) or 5120 entries (60 KiB) on 64 KiB devices. A group of eight QSA rows holds at most 8 x 513 microblocks, so both tables are complete; the plan order depends only on category and logical owner, not on the hash slot. - Host checks test the shared memory the kernels need instead of compute capability 7.0, and grouped_sparse_page4_device_supported() reports whether this build has a kernel image and enough shared memory for a device. The QSA gate asks it per device (cached), so a build without sm_75 keeps Turing on Triton instead of failing. setup.py of Flash-V100 and the wheel bundling accept 7.5. The page4 route logs and the new device warning use per-process scope: in a mixed pipeline the affected stage is often not the first local rank. Tests on a Quadro RTX 8000 and a Tesla V100: the page4 plan, grouped page4 and head-group tests now also run on Turing (device gate shared in tests/kernels/flash_v100_page4.py). A probe that loads a row-indexed matrix into an accumulator fragment confirms accumulator_16x16_rows() for all 32 lanes on both GPUs. Co-authored-by: Claude Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJxVXkjSHhfq2D71DoYSK7 Signed-off-by: Peuqui <43776522+Peuqui@users.noreply.github.com>
…ge4 routes The page4 gate required one KV head per rank. Qwen3.8-Flash-Next has 24 query heads on 2 KV heads, so a TP1 stage (every PP4 stage) carries both KV heads and always fell back to the Triton split-k kernel; only TP2 and TP4 ranks reached the native routes. The route now runs each KV head's block of query heads against that head's slice of the K/V-interleaved cache, and the existing 6-head pseudo-row split handles the 12 heads of each block. The kernels already take block, token and head strides separately; the page4 view and the microblock IDs now use the cache's token stride (num_kv_heads x 256) instead of assuming 256 (_qsa_xqa_page4_page_stride). For one KV head nothing changes. The head-group test uses the serving layout [blocks, 2, page, kv, 256] and the rank shapes 6/1 (TP4), 12/1 (TP2) and 24/2 (TP1), plus 24/1, against Triton and an FP32 reference on a Quadro RTX 8000 and a Tesla V100. Co-authored-by: Claude Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJxVXkjSHhfq2D71DoYSK7 Signed-off-by: Peuqui <43776522+Peuqui@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.
Purpose
The QSA prefill attention of Qwen4Exp takes the native Flash-V100 grouped and XQA page4 routes only on sm_70 devices and only on ranks with six query heads on one KV head, the Flash-Next TP4 rank. Every other rank falls back to the Triton split-k kernel. Triton 3.6 has no tensor-core MMA before SM80, so on SM70 and SM75 its tl.dot runs as FP32 FMA. Flash-Next has 24 query heads on 2 KV heads, so on our TP2xPP2 entry (12/1 per rank) and our PP4 entry (24/2 per stage) no rank reached the native routes, neither the Tesla V100 stages nor the Quadro RTX 8000 stages.
Four commits:
Decode is not affected: MTP decode has 5 rows per request, below the route's minimum of 64 rows, and stays on Triton.
Not a duplicate: gh pr list --repo 1CatAI/1Cat-vLLM --state all --search with "page4 Turing", "grouped page4", "Flash-V100 sm_75", "Flash-V100 Turing", "QSA Turing", "QSA prefill attention", "page4 KV heads", "accumulator layout", "sm75 flash-attention-v100" and "QSA 24 heads", and the same searches over issues, find no PR or issue that admits these rank shapes or Turing to the page4 routes. Related: #828 (Leonccaa) uses the grouped page4 route for QSA DCP prefill attention, a different path; both PRs change the cache page-stride condition of the same gate, which gives one conflict hunk in vllm/models/qwen4_exp/nvidia/ops/qsa.py. #1229 (ours) admits the cuBLAS QSA indexer and exact top-k on Turing; it changes other lines of qsa.py, this PR applies with and without it, and both arms of the serving A/B below had it. #1205 changes the QSA selector and staging in qsa.py without a conflict. git merge-tree against all 122 open PR heads adds conflicts with #828 (qsa.py, see above), #903 (fused_mha.h, flash_decode_paged.cu, fused_mha_api.cpp: new declarations and bindings next to its new split route) and the draft series #1179-#1198 (flash_decode_paged.cu, where the new device query sits next to functions the series renames; #1194-#1198 also qsa.py and test_qsa_ops.py).
AI assistance was used for this change. I reviewed every line and ran the tests and measurements below.
Test Plan
The tests ran on main 6f3557c plus this PR (Flash-V100 built from this tree with TORCH_CUDA_ARCH_LIST="7.0;7.5") and on main with its own build (Flash-V100 sm_70), once on a Tesla V100 and once on a Quadro RTX 8000.
Kernel benchmark: one 1,616-row prefill chunk with the real top-k rows, positions, requests and block table dumped from a Flash-Next prefill at 24,240 and 150,288 tokens of context, 13 QSA layers each; random FP16 K/V in the serving layout [blocks, 2, 16, kv_heads, 256]; both routes through qsa_sparse_paged_attention; median of 20 calls.
Serving A/B on our fork (main 6f3557c plus our commits): fork commit eb203d0 (this change) against its parent ee261cc, Flash-Next PP4 (stages on two Quadro RTX 8000 and two Tesla V100) and TP2xPP2 (one TP2 pair per GPU type), torch.compile with CUDA graphs, MTP with 4 speculative tokens. Boots fix cold, base, fix, base, fix; the cold boot is not counted. The base arm runs the production tree, the fix arm the same entries with PYTHONPATH on the fork tree, its own compile cache and its Flash-V100 build for sm_70 and sm_75. Per boot: four ~29k-token prompts (the first long request after a boot is not counted), a decode benchmark (prose, code, edit), quality prompts, and prompts of 184,134 and 249,950 tokens.
Path coverage
Acceleration and benchmark contract (required for performance changes)
Test Result
Max |native - Triton| 1.22e-4 to 2.44e-4 on every dump (FP16 output).
🤖 Generated with Claude Code
https://claude.ai/code/session_01YaG8VjXn5eN3Aq7eZrA3dp