Skip to content

fix(xqa): read the runtime draft mask on the SWAP_AB spec-dec path - #4229

Closed
Smallfu666 wants to merge 1 commit into
flashinfer-ai:mainfrom
Smallfu666:fix/4198-swap-ab-runtime-mask
Closed

Smallfu666 wants to merge 1 commit into
flashinfer-ai:mainfrom
Smallfu666:fix/4198-swap-ab-runtime-mask

Conversation

@Smallfu666

@Smallfu666 Smallfu666 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

📌 Description

Partially addresses #4198: fixes the SWAP_AB runtime-mask and sliding-window gaps on the SM90 fp8 XQA spec-decode path. Ragged Q and the PDL scale-load hazard remain open (follow-ups).

On the SWAP_AB layout (q_seq_len * head_group_ratio <= 32), warpGrpApplyMask synthesized a causal mask from the column index and never read specDec.params.mask; the tok0WinBeg argument was accepted but unused, so sliding-window begin-masking was also lost on this path.

Changes:

  • csrc/xqa/mha_sm90.cu — rewrite the SWAP_AB && SPEC_DEC variant of warpGrpApplyMask:
    • read the runtime draft mask through the same SpecDec::needMask / loadTileMaskRow helpers as the non-SWAP_AB variant, with the lookup transposed for this layout (mask row selected by the q-token column, bit index = the thread's KV row); TileMaskRow loads are deduplicated across adjacent columns sharing a q token;
    • apply the per-column sliding-window begin mask (globalRow < tok0WinBeg + maskCol), with signed comparisons (tok0WinBeg can be deeply negative) and explicit shift clamping instead of relying on PTX shl.b64 clamp behavior;
    • fix the early-exit condition so window-edge tiles far from the draft region are still masked (previously the function returned early based on the draft region alone, which is one structural reason tok0WinBeg was dead);
    • keep the hard-coded causal behavior as a fallback when params.mask == nullptr.
  • flashinfer/xqa.py — remove the use_sliding_window and swap_ab_eligible conditions from the SM90 fp8 MHA fallback guard (the kernel now handles both); keep the use_ragged_q and sinks fallbacks; update the note.
  • flashinfer/decode.py — drop the now-stale docstring sentence saying sliding-window falls back to the generic kernel.
  • tests/attention/test_xqa_batch_decode.py — comment-only: label the deterministic-test parametrizations ((2,4) → SM90 fp8 SWAP_AB path, (2,16) → non-SWAP_AB path).

The kernel change also clamps maskStartRow in the null-mask fallback (cacheSeqLen >= SPEC_Q_SEQ_LEN ? cacheSeqLen - SPEC_Q_SEQ_LEN : 0) so a cacheSeqLen < SPEC_Q_SEQ_LEN call from direct C++ users cannot underflow and skip masking in release builds.

🔍 Related Issues

Partially addresses #4198 (gaps (a) and (b); ragged Q and the qScalePtr/kvScalePtr PDL load-ordering item are intentionally left for follow-ups). Builds on the deterministic tests introduced in #4199.

🚀 Pull Request Checklist

✅ Pre-commit Checks

  • I have installed pre-commit by running pip install pre-commit (or used your preferred method).
  • I have installed the hooks with pre-commit install.
  • I have run the hooks manually with pre-commit run --all-files and fixed any reported issues.

🧪 Tests

Validation on a matching reported CUDA/PyTorch stack — H100 (SXM) / SM90, CUDA 13.0 (nvcc 13.0.88), torch 2.12.0+cu130, source build (JIT), on current main + this patch. Notes: the original report is from an H100 PCIe; the node's kernel driver here is 550.127.08 with the CUDA 13.0 forward-compatibility package, not a native 580-series driver.

test (H100) before (unpatched, SM90 fp8 MHA forced on) after (patched, default dispatch)
spec_dec_sliding_window[300-full-False-…-63-…-2-32-2-4] FAILED PASSED
spec_dec_sliding_window[300-full-False-…-63-…-4-64-4-2] FAILED PASSED
spec_dec_sliding_window[300-full-False-…-127-…-2-32-2-4] FAILED PASSED
spec_dec_sliding_window[300-full-False-…-127-…-4-64-4-2] FAILED PASSED
mask_mode_deterministic[2-4-full-fp8] FAILED PASSED
causal controls (deterministic causal + all causal window cases) PASSED PASSED
full pytest tests/attention/test_xqa_batch_decode.py -k fp8 1380 passed / 480 skipped / 0 failed

The 16 remaining before-failures in the forced-on targeted subset are the known sinks + spec-dec accuracy TODO (unchanged by this PR; sinks still fall back in normal dispatch).

Primary development validation — H200 / SM90, CUDA 13.0, torch 2.13.0, source build (JIT): same 5 cases fail before / pass after; post-rebase targeted regression 68 passed / 0 failed; full -k fp8 (pre-rebase tree) 1378 passed / 0 failed.

Reviewer Notes

Summary by CodeRabbit

  • Bug Fixes

    • Improved speculative decoding mask handling for custom masks, causal masking, sequence boundaries, and sliding-window limits.
    • Prevented incorrect accumulator values when tokens fall outside valid masking or sequence ranges.
    • Improved SM90 FP8 speculative-decoding kernel selection, including clearer handling for ragged queries and sink values.
  • Documentation

    • Updated XQA documentation to reflect current FP8 fallback behavior.
    • Clarified test coverage comments for SM90 head-group configurations.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f57dbe29-38ce-4af5-ac67-adea24111a1f

📥 Commits

Reviewing files that changed from the base of the PR and between 13a161e48890634d74142b94da63d0f7aed8e05b and 66d0f09.

📒 Files selected for processing (4)
  • csrc/xqa/mha_sm90.cu
  • flashinfer/decode.py
  • flashinfer/xqa.py
  • tests/attention/test_xqa_batch_decode.py
💤 Files with no reviewable changes (1)
  • flashinfer/decode.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • flashinfer/xqa.py
  • csrc/xqa/mha_sm90.cu

📝 Walkthrough

Walkthrough

SM90 speculative-decoding masking now supports synthesized causal masks when no device mask is provided and combines runtime mask rows with causal or sliding-window boundaries. FP8 dispatch fallback now targets ragged-Q inputs and provided sinks instead of swap_ab_eligible.

Changes

SM90 speculative decoding

Layer / File(s) Summary
Runtime speculative-decoding mask construction
csrc/xqa/mha_sm90.cu, tests/attention/test_xqa_batch_decode.py
warpGrpApplyMask adds null-mask causal synthesis, runtime causal/sliding-window boundaries, per-tile mask loading, and masked accumulator initialization; the SM90 path-selection test comment is updated.
SM90 FP8 dispatch routing and documentation
flashinfer/xqa.py, flashinfer/decode.py
FP8 fallback logic removes swap_ab_eligible and disables the SM90 path for ragged Q or provided sinks; related docstrings are narrowed accordingly.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant xqa
  participant warpGrpApplyMask
  participant SpecDec
  participant AccumulatorTiles
  xqa->>warpGrpApplyMask: select SM90 speculative-decoding path
  warpGrpApplyMask->>SpecDec: load per-tile mask rows
  warpGrpApplyMask->>warpGrpApplyMask: compute causal and sliding-window masks
  warpGrpApplyMask->>AccumulatorTiles: initialize masked elements to safeInitRowMax
Loading

Possibly related issues

  • flashinfer-ai/flashinfer issue 4198 — Covers SWAP_AB speculative-decoding mask handling and sliding-window behavior addressed by these changes.

Possibly related PRs

Suggested reviewers: yzh119, saltyminty, bkryu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing runtime draft-mask handling on the SWAP_AB spec-decode path.
Description check ✅ Passed The description follows the template and includes the required summary, related issues, checklist, tests, and reviewer notes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@csrc/xqa/mha_sm90.cu`:
- Around line 1768-1806: The null-mask fallback in the shown masking path must
reject or safely handle cacheSeqLen values below SPEC_Q_SEQ_LEN at runtime, not
only via assert. Add an appropriate runtime guard before computing maskStartRow
so underflow cannot bypass masking in release builds, while preserving the
existing fallback behavior for valid lengths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a6d1c03-3c28-4e2d-903c-74461ecfc60d

📥 Commits

Reviewing files that changed from the base of the PR and between 6812ddb and 13a161e48890634d74142b94da63d0f7aed8e05b.

📒 Files selected for processing (2)
  • csrc/xqa/mha_sm90.cu
  • flashinfer/xqa.py

Comment thread csrc/xqa/mha_sm90.cu
…infer-ai#4198)

The #if SWAP_AB && SPEC_DEC variant of warpGrpApplyMask synthesized a
causal mask from bit position ((1<<(maskCol+1))-1) and never read
specDec.params.mask, and ignored its tok0WinBeg parameter, so full
draft masks and per-row sliding windows were silently wrong whenever
the SM90 fp8 kernel took the SWAP_AB specialization
(q_seq_len * head_group_ratio <= 32).

Rework it to mirror the non-SWAP_AB version with the axes transposed
(acc rows = KV positions in tile, acc cols = q heads): each q-token
column selects one TileMaskRow via specDec.needMask/loadTileMaskRow
(bit index = KV row), the sliding window masks rows with
globalRow < tok0WinBeg + idxQTokInCta, and the end mask handles the
cacheSeqLen tail. 64-bit shifts are explicitly guarded and window
comparisons stay signed (tok0WinBeg can be deeply negative). The
legacy synthesized-causal path is kept as the params.mask == nullptr
fallback, with its cacheSeqLen - SPEC_Q_SEQ_LEN subtraction clamped so
a direct C++ caller with cacheSeqLen < SPEC_Q_SEQ_LEN cannot underflow
it and silently skip masking. TileMaskRow loads are deduplicated per q
token across the unrolled column loop.

Python side: drop the SM90 fp8 fallbacks for sliding window and
SWAP_AB eligibility in flashinfer/xqa.py (the kernel now handles
both); ragged Q and sinks fallbacks stay. Sync the flashinfer.decode
docstring and the deterministic-test path comments accordingly.

Verified on H200 and H100 (JIT, FLASHINFER_CUDA_ARCH_LIST=9.0a):
the 5 failing numeric cases (deterministic full-fp8 + 4 sliding-window
full-mask SWAP_AB cases) now pass on both; full -k fp8 regression
1380 passed / 0 failed; spec-dec bf16+fp8 matrix all green. The 16
sinks+spec-dec cases fail only when the SM90 kernel is forced and are
a pre-existing separate TODO.

Signed-off-by: Han-Yin Chang <nick20350@gmail.com>
@Smallfu666
Smallfu666 force-pushed the fix/4198-swap-ab-runtime-mask branch from 13a161e to 66d0f09 Compare July 29, 2026 02:45
@Smallfu666

Copy link
Copy Markdown
Contributor Author

Closing this PR. This is not a claim that the underlying problem is fixed.

We are not continuing this work, so the PR should not stay open as though we were. #4198
stays open and is not affected by this: it tracks four gaps, this PR addressed two of them
(the SWAP_AB runtime mask and the sliding-window begin mask), and ragged Q and the PDL
scale-load ordering were always left as follow-ups. Please feel free to unassign me there, so it is
not waiting on me.

Closing changes nothing in-tree. The dispatch guard from #4199 is still live in
flashinfer/xqa.py, so the affected shapes keep falling back to the generic kernel exactly
as they do today.

The work stays on the PR for whoever picks it up — the rewritten SWAP_AB warpGrpApplyMask,
and the before/after validation in the description on H100 and H200. One caveat: my check
that the defect still survives on current main is a source-level reading of
csrc/xqa/mha_sm90.cu, not a re-run of those tests.

@Smallfu666 Smallfu666 closed this Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants