Repository navigation
[CuTe, SM90] Fit forward tile to the block-sparse block size - #2903
Merged
drisspg merged 2 commits intoSep 24, 2026
Merged
Conversation
flash_attn_func takes no tile sizes, and the SM90 forward heuristic ignored the sparse KV block (and the sparse Q block for head_dim > 96). Any BlockSparseTensorsTorch.block_size that differed from the heuristic tile was rejected with a ValueError, even when the kernel supports that tile: e.g. flex's default 128 block at hdim 192, and every block at hdim 256. After the SM90 heuristic, shrink tile_m to 64 when the sparse Q block is not a multiple of tile_m, and shrink tile_n to the sparse KV block when it is smaller. Only configurations that previously raised are affected. Also fix the KV-subtile error message, which printed tile_n as the sparse block size.
LiRunGuo
force-pushed
the
sm90-fwd-tile-fit-block-sparsity
branch
from
September 21, 2026 21:31
27dc185 to
37e0898
Compare
Contributor
Author
|
@drisspg could you take a look when you have a moment? You own most of the block-sparsity code and the typed-config stack (#2732/#2733) that moves Suggested path, whichever you prefer:
Either way, the follow-up I'd like to send next is the matching change to the SM90 backward tile selection, so that more of the 40 (head_dim x block size) combinations work end to end. Right now 11 do. I'll keep that separate, since the backward tiles are more tightly tuned. I don't think FA4 CI runs on Hopper (it's B200 only), so I can post H200 results for any revision of this. |
liuyang-05
added a commit
to liuyang-05/flash-attention
that referenced
this pull request
Oct 25, 2026
Follow-up to Dao-AILab#2903, which made the SM90 forward pick a tile that matches the block-sparse block size. The SM90 backward still used its own heuristic tile (tile_n 128 for hdim <= 128, 96 for 192, 64 for block-sparse 256; tile_m 128 for hdim 64) and SM90 has no KV subtiling, so any other block size raised a ValueError in normalize_block_sparse_config_bwd, e.g. flash_attn_func with block_size (128, 64) for hdim 128 or (64, 128) for hdim 64. _fit_sm90_bwd_tile_to_block_sparsity runs after _tile_size_bwd_sm90 when the caller supplies block_size and only fires when that tile would be rejected: tile_m drops to 64 for a sparse Q block that is a multiple of 64 but not of tile_m, and tile_n shrinks to a sparse KV block of 32..tile_n in steps of 16. A smaller tile_n moves it off WGMMA's 64-row M axis, so the MMA layout follows: - head dims that round to a multiple of 128 (96, 128, 256): S/dP unswapped, dK/dV swapped with head_dim split across the two warp groups; - otherwise tile_n 64 with the swapped S/dP layout (hdim 64), or tile_n split across the warp groups of swapped dK/dV when each slice is a multiple of 16 (hdim 64 and 192). Slices that are not compile but produce wrong dK/dV, so those sizes (hdim 64/192 with tile_n 48/80/112) stay rejected, as does 16. Configurations accepted today keep the same tile and the same compile key. On Dao-AILab#2903's grid (hdim 64/96/128/192/256 x 8 block sizes), forward + backward goes from 11/40 to 27/40. Test Plan: New test_sm90_block_sparse_bwd_tile_fits_block_size (10 block sizes, with and without mask_mod) fails 20/20 on main and passes on this branch (H200): ``` pytest tests/cute/test_mask_mod.py -k tile_fits_block_size pytest tests/cute/test_mask_mod.py tests/cute/test_block_sparsity.py tests/cute/test_score_mod.py pytest tests/cute/test_flash_attn.py -k test_flash_attn_output ``` Per-test-id outcomes match main for all suites except the 20 new tests; newly accepted configurations match the fp32 flex reference under causal, sliding-window and document masks, MHA and GQA; previously accepted ones keep their compile keys and give bitwise-identical out/lse/dK/dV (MHA) vs main. This change was authored with an AI assistant (Claude Code). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.
Motivation
flash_attn_func/flash_attn_varlen_func(and flex attention's FLASH backend, which calls them) take no tile sizes. On SM90 the forward tile comes from_tile_size_fwd_sm90, which only adjuststile_mfor the sparse Q block when head_dim <= 96. It never looks at the sparse KV block. SM90 has no KV subtiling (kv_subtile_factormust be 1), and a sparse Q block smaller thantile_mcannot be subtiled either. So anyBlockSparseTensorsTorch.block_sizethat differs from the heuristic tile raises aValueErrorin the forward, even when the kernel runs that tile fine through_flash_attn_fwd(..., tile_mn=...):BLOCK_SIZE=128fails (tile_n=112).tile_n=80). The only accepted size, (128, 80), is then rejected by the backward (tile_n=64), so SM90 hdim-256 block sparsity through autograd is not usable at all.It also accounts for all 120 SM90 failures of
test_mask_mod.py::test_parameterized_masks[True-128-112-*]/[True-64-128-*](use_autograd=True with block sparsity). Those have failed on Hopper since the autograd variant was added in #2485. FA4 CI runs only on B200, which skips non-128x128 tiles, so nothing caught them.Change
_fit_sm90_fwd_tile_to_block_sparsityruns after the SM90 heuristic when the caller suppliesblock_size:tile_mbut is a multiple of 64, usetile_m=64. Larger sparse Q blocks still useq_subtile_factor.tile_nand a multiple of 16, usetile_n = sparse KV block.The tile only shrinks, which never adds smem or register pressure. The rule fires only when the heuristic tile would already have been rejected by
normalize_block_sparse_config, so configurations that work today are unchanged: same tiles, same kernels, same performance. Also fixes the SM90 KV-subtile error message, which printedtile_nwhere it meant the sparse KV block size.Results (H200, bf16, B=2 H=4 S=1024, block-aligned causal BlockMask vs fp32 flex)
(head_dim x block size) combinations accepted by
flash_attn_func, out of 40 (hdim 64/96/128/192/256 x 8 block sizes):Forward max abs error vs the fp32 flex reference is <= 0.0039 for every newly accepted case. The remaining backward rejections come from the SM90 backward's own tile_n (128 for hdim <= 128, 96 for 192, 64 for 256). That is a separate heuristic and is left for a follow-up.
Tests
test_sm90_block_sparse_fwd_tile_fits_block_size(7 cases, forward and backward where supported, viaflash_attn_func): 7/7 fail on main, 7/7 pass here.tests/cute/test_mask_mod.py(H200): main 1256 passed / 120 failed -> this PR 1383 passed / 0 failed (+7 new).tests/cute/test_block_sparsity.py(H200): 4883 passed / 0 failed.tests/cute/test_mask_mod_varlen.py+tests/cute/test_flash_attn_fast.py(H200): 784 passed, 2 failed. The two failures,test_varlen_static_masks[block_diagonal-mha-*], were CUDA OOMs while four suites shared the node. Rerun alone, they pass on both main and this PR (17/17).FLASH_ATTENTION_FAKE_TENSOR=1compile, then GPU), separate cache dir for the patched tree.ruff check: no new findings.This overlaps the function that #2733 moves into typed configs. The change is a self-contained post-pass and is easy to rebase onto that stack, whichever lands first.