Skip to content

[feat] VSA-256 fastpath on Blackwell via FA4 CuTe block-sparse attention - #1354

Merged
SolitaryThinker merged 8 commits into
hao-ai-lab:mainfrom
FoundationResearch:256vsa
May 26, 2026
Merged

[feat] VSA-256 fastpath on Blackwell via FA4 CuTe block-sparse attention#1354
SolitaryThinker merged 8 commits into
hao-ai-lab:mainfrom
FoundationResearch:256vsa

Conversation

@alexzms

@alexzms alexzms commented May 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a 256-element VSA tile path that dispatches to the FA4 CuTe block-sparse
attention forward kernel on Blackwell, on top of the existing 64-element
TK/Triton path which remains the default and is unchanged.

Measured ~1.4 PFLOPs forward kernel on a single GB200 (bf16, head_dim=128,
seq_len=40960, 8 heads) for the sparse kernel itself; the 64-tile path is
byte-identical to current behavior at the default settings.

Design

VSA tile shape flows through the same channel that already carries
VSA_sparsity: FastVideoArgs.VSA_tile_size (CLI: --VSA-tile-size T H W)
attn_metadata_builder.build()VideoSparseAttentionMetadata.VSA_tile_size
→ backend forward().

  • Default (4, 4, 4) keeps the existing 64-tile path untouched.
  • Setting (4, 8, 8) (256-element blocks) triggers the new CuTe BSHD
    fastpath inside forward() automatically (dispatch by
    block_elements = prod(tile_size)).

Kernel-level backend overrides for debugging / perf comparison (all unset by
default, no behavior change):

  • FASTVIDEO_VSA_TRITON=1 forces Triton (route-A 256→64 expansion for the
    256 path).
  • FASTVIDEO_VSA_TK=1 prefers the sm_90 TK kernel (64 path only).
  • FASTVIDEO_VSA_CUTEDSL=1 prefers CuTe (256 path default).
  • FASTVIDEO_KERNEL_VSA_FORCE_TRITON=1 kept as a backward-compat alias for
    FASTVIDEO_VSA_TRITON.

New dependencies (256-tile path only)

  • Submodule fastvideo-kernel/include/flash-attention
    FoundationResearch/flash-attention @ vsapad. Python-only (CuTe DSL
    JIT); no C++ build step.
  • Runtime: nvidia-cutlass-dsl>=4.3 and quack-kernels.

The default 64-tile path adds no new dependencies and is unaffected if the
submodule / CuTe DSL is absent.

Test plan

  • pytest fastvideo-kernel/tests/test_vsa256_forward.py — CuTe forward vs dense torch reference (2/2)
  • pytest fastvideo-kernel/tests/test_vsa256_forward_vbs.py — CuTe variable KV block size (1/1)
  • pytest fastvideo-kernel/tests/test_vsa256_triton.py — Triton fwd+bwd vs torch reference (1/1)
  • pytest fastvideo-kernel/tests/test_vsa256_forward_cross.py — three-way torch/CuTe/Triton parity (1/1)
  • Verified the (4,4,4) default path is unchanged vs main.
  • SSIM regression suite on Hopper (recommended pre-merge; the 64-tile
    path is untouched but worth confirming).

Hardware tested

NVIDIA GB200 (sm_100), CUDA 12.9, torch 2.9.1+cu128, aarch64 Linux.

Draft: opening for early review of the dispatch design and the submodule /
dependency story before backward (autograd) support for the CuTe path lands.

alexzms added 3 commits May 14, 2026 23:02
Adds FoundationResearch/flash-attention on the vsapad branch under
fastvideo-kernel/include/flash-attention. This carries the CuTe-DSL
block-sparse attention forward kernel (flash_attn.cute.block_sparsity)
required by the upcoming VSA-256 fastpath on Blackwell.

The submodule is Python-only (CuTe DSL JIT); no C++ build step needed.
New fastvideo_kernel.block_sparse_attn_cute_fwd. Provides a thin Python
wrapper around flash_attn.cute.interface._flash_attn_fwd that adapts
VSA's (block_map, variable_block_sizes) inputs into FA4's
BlockSparseTensorsTorch + per-KV-block validity mask.

Exposes BHSD and BSHD entrypoints (block_sparse_attn_cute_fwd and
block_sparse_attn_cute_fwd_bshd respectively); the BSHD variant is
intended for the upcoming VSA-256 fastpath which keeps inputs in BSHD
layout to avoid hot-path transposes.

Forward only; no autograd registration yet. Requires the FoundationResearch
flash-attention submodule plus nvidia-cutlass-dsl and quack-kernels.
Adds VSA's 256-token block path on top of the existing 64-token path.
The new path is intended for FA4's CuTe block-sparse attention forward
kernel on Blackwell, where 256-token KV tiles give ~1.4 PFLOPs forward
throughput on GB200.

`video_sparse_attn(block_size=...)` now auto-dispatches on
`block_elements = prod(block_size)`:
  - 64  -> existing index-native TK/Triton path (unchanged behavior).
  - 256 -> new `block_sparse_attn_256` wrapper, which expands the
           logical 256-block map into the FA4 kernel's 128-token KV
           layout and calls into the CuTe BSA forward.

`video_sparse_attn_bshd` is added for callers that already have BSHD
tensors and want to skip the BHSD<->BSHD round-trip on the CuTe hot
path; it is defined only for block_elements=256.

Backend selection honors a small set of opt-in env vars (all unset by
default):
  - FASTVIDEO_VSA_TRITON=1  forces Triton in either path. The 256 path
                            uses a route-A 256->64 expansion.
  - FASTVIDEO_VSA_TK=1      prefers the sm_90 TK kernel in the 64 path
                            (no-op if the extension isn't available).
  - FASTVIDEO_VSA_CUTEDSL=1 prefers CuTe in the 256 path (default).
  - FASTVIDEO_KERNEL_VSA_FORCE_TRITON=1 is kept as a backward-compat
                            alias for FASTVIDEO_VSA_TRITON.
@mergify mergify Bot added type: feat New feature or capability scope: training Training pipeline, methods, configs scope: inference Inference pipeline, serving, CLI scope: attention Attention backends (VSA, STA, Flash, etc.) scope: kernel CUDA kernels, fastvideo-kernel labels May 15, 2026
@mergify

mergify Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

Your pull request matches the following merge protections and will not be merged until they are valid.

🔴 PR merge requirements

Waiting for

  • check-success=fastcheck-passed
  • check-success=full-suite-passed
  • check-success~=pre-commit
This rule is failing.
  • check-success=fastcheck-passed
  • check-success=full-suite-passed
  • check-success~=pre-commit
  • #approved-reviews-by>=1
  • title~=(?i)^\[(feat|feature|bugfix|fix|refactor|perf|ci|doc|docs|misc|chore|kernel|new.?model|skill|skills|infra)\]

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a 256-block sparse attention path optimized for Blackwell GPUs, utilizing the CuTe FA4 block-sparse attention kernel. It introduces new wrappers for 256-block attention, adds a BSHD-native fastpath to reduce memory transposes, and integrates these into the existing VSA backend with configurable tile sizes. Review feedback identifies high-severity issues including a potential JIT compilation error in the CuTe DSL mask logic and shape inconsistencies in the Log-Sum-Exp (LSE) tensor across different layouts. Additionally, the reviewer suggested simplifying redundant backend selection logic and optimizing tensor expansion operations.

kv_blk = n_idx // block_size_ssa
kv_off = n_idx % block_size_ssa
kv_sizes = aux_tensors[0]
valid = utils.scalar_to_ssa(kv_sizes[kv_blk[0]], cutlass.Int32)

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.

high

In the CuTe DSL, kv_blk is a scalar SSA value derived from n_idx // block_size_ssa. Indexing it with [0] is likely incorrect and may cause a JIT compilation error. It should be used directly as the index for kv_sizes.

Suggested change
valid = utils.scalar_to_ssa(kv_sizes[kv_blk[0]], cutlass.Int32)
valid = utils.scalar_to_ssa(kv_sizes[kv_blk], cutlass.Int32)

Comment on lines +203 to +210
if lse_bshd is None:
lse = torch.empty(
(q.shape[0], q.shape[1], q.shape[2]),
dtype=torch.float32,
device=q.device,
)
else:
lse = lse_bshd.transpose(1, 2).contiguous()

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.

high

There is a shape inconsistency for the Log-Sum-Exp (LSE) tensor. Flash Attention typically returns LSE in [B, H, S] layout. Since this function transposes the output to BHSD, the LSE should remain in [B, H, S] layout to be consistent. Transposing it to [B, S, H] on line 210 conflicts with the fallback shape on line 205.

Suggested change
if lse_bshd is None:
lse = torch.empty(
(q.shape[0], q.shape[1], q.shape[2]),
dtype=torch.float32,
device=q.device,
)
else:
lse = lse_bshd.transpose(1, 2).contiguous()
if lse_bshd is None:
lse = torch.zeros(
(q.shape[0], q.shape[1], q.shape[2]),
dtype=torch.float32,
device=q.device,
)
else:
lse = lse_bshd.contiguous()

Comment on lines +236 to +243
if lse_bshd is None:
lse = torch.empty(
(q.shape[0], q.shape[2], q.shape[1]),
dtype=torch.float32,
device=q.device,
)
else:
lse = lse_bshd.transpose(1, 2).contiguous()

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.

high

In the BSHD variant, the fallback LSE shape on line 238 is [B, H, S] (since q.shape[2] is H and q.shape[1] is S), but the transposed return value on line 243 is [B, S, H]. The fallback should match the expected BSHD-consistent layout [B, S, H].

Suggested change
if lse_bshd is None:
lse = torch.empty(
(q.shape[0], q.shape[2], q.shape[1]),
dtype=torch.float32,
device=q.device,
)
else:
lse = lse_bshd.transpose(1, 2).contiguous()
if lse_bshd is None:
lse = torch.zeros(
(q.shape[0], q.shape[1], q.shape[2]),
dtype=torch.float32,
device=q.device,
)
else:
lse = lse_bshd.transpose(1, 2).contiguous()

Comment on lines +401 to +404
elif _force_tk():
use_sm90 = sm90_available
else:
use_sm90 = sm90_available

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.

medium

The elif _force_tk(): block is redundant because it performs the exact same assignment as the else: block. This can be simplified to improve maintainability.

    else:
        use_sm90 = sm90_available

Comment on lines +46 to +47
if os.environ.get("FASTVIDEO_VSA_CUTEDSL", "0") == "1":
return "cutedsl"

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.

medium

This explicit check for FASTVIDEO_VSA_CUTEDSL is redundant as it returns the same value as the default case on line 48. Removing it simplifies the logic.

    return "cutedsl"

Comment on lines +66 to +72
expanded_sizes = torch.empty(
(sizes_i32.numel() * 2,),
dtype=torch.int32,
device=sizes_i32.device,
)
expanded_sizes[0::2] = child0
expanded_sizes[1::2] = child1

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.

medium

The construction of expanded_sizes can be performed more efficiently and concisely using torch.stack and flatten.

    expanded_sizes = torch.stack([child0, child1], dim=1).flatten()

alexzms added 2 commits May 15, 2026 19:13
forward() now derives block_elements = math.prod(VSA_TILE_SIZE) and,
when it is 256 and the CuTe entrypoint is importable, calls
video_sparse_attn_bshd directly (inputs already arrive in [B,S,H,D],
so the BHSD transpose round-trip is skipped). The default (4,4,4)
tile keeps the existing 64-element TK/Triton path byte-for-byte.

VSA_TILE_SIZE stays the single module-level constant it already was on
main -- no env var, no per-pipeline plumbing, no metadata/config
threading. Switching to the Blackwell fastpath is a one-line change to
that constant; everything else (build/tile/construct_variable_block_sizes)
reads it unchanged.
Four GPU correctness tests covering the new VSA-256 path:

- test_vsa256_forward.py:        CuTe forward vs dense torch reference
                                 (qk_equal and qk_diff shapes).
- test_vsa256_forward_vbs.py:    CuTe forward with variable KV block sizes
                                 (<256) vs token-masked torch reference.
- test_vsa256_triton.py:         Route-A Triton fwd + bwd vs token-masked
                                 torch reference (forces FASTVIDEO_VSA_TRITON).
- test_vsa256_forward_cross.py:  Three-way parity: torch ref vs CuTe vs
                                 Triton on the same inputs.

All tests skip cleanly when CUDA is unavailable. They exercise the public
fastvideo_kernel.video_sparse_attn entrypoint with block_size=(4, 8, 8)
and rely on its built-in block_elements=256 dispatch.
@alexzms
alexzms marked this pull request as ready for review May 17, 2026 01:52
@alexzms
alexzms requested a review from Davids048 May 18, 2026 19:49
@alexzms alexzms added the ready PR is ready to merge label May 18, 2026
Comment thread .gitmodules

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you check if this fork is needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think I will need to merge the kernel level change of FA4 into our trusted repo https://github.com/hao-ai-lab/flash-attention-fp4

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@Davids048 Good catch — confirmed the fork is not needed, and it's been removed.

I traced it through: the submodule pointed at our fork's vsapad branch, which turned out to be Dao-AILab/flash-attention @ c19cd20e plus a single line adding torchvision to cute/pyproject.toml — i.e. zero kernel changes. The flash_attn.cute we build against (interface.py, block_sparsity.py) is byte-identical to that upstream revision, and the VSA-256 parity tests pass against it. (The only real kernel work in the fork is an unrelated dev/blocksize64 experiment that this PR doesn't use — it ships the 256→128 expansion path, i.e. native block=128.)

So this supersedes my earlier note about merging into flash-attention-fp4 — there's nothing to merge. The FA4 CuTe backend is now an optional, lazily-imported dependency (VSA falls back to Triton when it's absent), documented in fastvideo-kernel/README.md (e9320eb) with a pinned upstream install:

pip install "git+https://github.com/Dao-AILab/flash-attention.git@c19cd20e#subdirectory=flash_attn/cute"

Pinned to c19cd20e because that revision's _flash_attn_fwd takes m_block_size/n_block_size (what the wrapper calls); later upstream reshaped it into a tile_mn tuple.

alexzms added 2 commits May 19, 2026 01:42
…module

Review feedback: the VSA-256 fastpath vendored FA4 CuTe via a git
submodule pointing at a personal-org SSH fork
(git@github.com:FoundationResearch/flash-attention.git @ vsapad), making
it a hard build/clone dependency of the public repo.

- Remove the flash-attention submodule (.gitmodules + gitlink).
- Lazily import flash_attn.cute (_load_fa4_cute) with a clear actionable
  error; it is no longer pulled at module load.
- Default the VSA-256 backend to Triton; the FA4 CuTe fastpath is opt-in
  via FASTVIDEO_VSA_CUTEDSL=1 (same optional-dependency model as the
  NVFP4 FA4 path already on main).
- VSA-256 CuTe tests skip cleanly when the optional FA4 CuTe build is
  absent and explicitly opt into CuTe when present.

Follow-up: upstream the block-sparsity delta into
hao-ai-lab/flash-attention-fp4 so the CuTe fastpath can depend on it the
same optional-pip way as NVFP4.
The VSA-256 fastpath's optional flash_attn.cute dependency is provided
upstream by Dao-AILab/flash-attention @ c19cd20e: the installed cute
(interface.py + block_sparsity.py) is byte-identical to that revision, so
no FoundationResearch fork or kernel patch is required. Document the
pinned install in the kernel README. Pin rationale: that revision's
_flash_attn_fwd uses m_block_size/n_block_size; later upstream moved to a
tile_mn tuple and is not drop-in compatible.
@SolitaryThinker

Copy link
Copy Markdown
Collaborator

Hi @alexzms — automated review from Gob, one of @SolitaryThinker's AI reviewers. Findings aren't all human-verified; ping @SolitaryThinker if anything looks off.

TL;DR

Substantial, well-scoped feature PR: clean block_elements == 256 dispatch, the 64-element TK/Triton path is structurally untouched, the FA4 CuTe dep is correctly lazy + optional, and @Davids048's earlier fork-submodule concern is fully resolved in commits 9560e648e8 + e9320ebf2b. Two real things to chase before merge: (1) the CuTe-256 forward has no autograd registration / requires_grad guard while the PR carries scope: training, and (2) the PR body is stale relative to the post-submodule-removal reality. Both are small surfaces.

Verdict: approve-with-followup

  • S0: 0 S1: 0 S2: 3 (3 surfaced) S3: not shown; see review.md

Findings

[S2-important] CuTe-256 forward has no autograd; training silently broken if 256-tile + CuTe is enabled

What. block_sparse_attn_cute_fwd._cute_forward calls _flash_attn_fwd directly without torch.library.custom_op + register_autograd (the pattern both block_sparse_attn_triton and block_sparse_attn_sm90 follow in block_sparse_attn.py). There's no requires_grad guard in the kernel and no self.training check in fastvideo/attention/backends/video_sparse_attn.py:forward().

Why it matters. PR carries scope: training label. If a user sets VSA_TILE_SIZE=(4,8,8) and FASTVIDEO_VSA_CUTEDSL=1 together (both opt-in, but both can be set by anyone), forward succeeds but .backward() will either raise ("element 0 of tensors does not require grad and does not have a grad_fn") or silently drop sparse-branch gradients — and the BSHD path does out_view.add_(out_c_blk.unsqueeze(2) * gate_view) in-place on the CuTe output (ops.py:226), which makes the failure mode harder to debug. The Triton-256 fallback (_triton_via_route_ablock_sparse_attn_triton) IS autograd-safe, so the structural problem is CuTe-only.

Suggested fix (pick one):

  1. Easiest: add if q.requires_grad or k.requires_grad or v.requires_grad: raise NotImplementedError("VSA-256 CuTe fastpath is forward-only; unset FASTVIDEO_VSA_CUTEDSL for the Triton route-A autograd path.") at the top of _cute_forward.
  2. Better: wrap _cute_forward in torch.library.custom_op and register_autograd raising NotImplementedError from the backward — the error then surfaces at .backward() with a stack trace pointing here.
  3. Drop the scope: training label from this PR if CuTe-training is genuinely deferred.

Evidence: fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn_cute_fwd.py:138-184 (no custom_op); contrast fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn.py:104-131,230 (Triton path's autograd registration); fastvideo/attention/backends/video_sparse_attn.py:271-279 (no requires_grad check on the dispatch).

[S2-important] kv_blk[0] indexing in CuTe _vbs_mask_mod — please resolve gemini-bot's high-priority flag explicitly

What. gemini-bot flagged on block_sparse_attn_cute_fwd.py:139 (commit e9320ebf2b) that kv_blk is already a scalar SSA value from n_idx // block_size_ssa, and indexing it with [0] is likely incorrect. The line: valid = utils.scalar_to_ssa(kv_sizes[kv_blk[0]], cutlass.Int32).

Why it matters. If gemini-bot is right, either (a) this fails at first JIT compile when FASTVIDEO_VSA_CUTEDSL=1 is exercised, or (b) it silently picks the wrong KV-block size for partial blocks — which corrupts masking only when variable_block_sizes < kv_block_size. The test_vsa256_forward_vbs.py test would catch (b) but only on Blackwell hardware with the CuTe build installed. PR body claims Blackwell verification but doesn't cite a vbs test run.

Suggested fix. Reply to the gemini-bot thread either: (1) "tested on Blackwell, pytest fastvideo-kernel/tests/test_vsa256_forward_vbs.py -v passes, here's the log" (and leave the code as-is — kv_blk[0] may be a benign scalar SSA shape quirk), OR (2) change to kv_sizes[kv_blk] if the bot is correct. Either resolution is fine; the open thread is what makes this S2.

Evidence: fastvideo-kernel/python/fastvideo_kernel/block_sparse_attn_cute_fwd.py:138-141; fastvideo-kernel/tests/test_vsa256_forward_vbs.py; gemini-bot PR comment, file block_sparse_attn_cute_fwd.py line 139, SHA e9320ebf2b, high-priority.

[S2-persistent] PR description is stale relative to commits 9560e648e8 and e9320ebf2b

What. The PR body describes a FoundationResearch/flash-attention@vsapad submodule and a "draft for early review of dispatch design and dependency story" framing. Neither is true at HEAD:

  • .gitmodules at HEAD has no flash-attention entry (removed in 9560e648e8).
  • Install path is now pip install "git+https://github.com/Dao-AILab/flash-attention.git@c19cd20e#subdirectory=flash_attn/cute", documented in fastvideo-kernel/README.md (added in e9320ebf2b).
  • PR is not marked draft AND label ready is set, contradicting the "draft for early review" body text.

Why it matters. Future reviewers (and automated review pipelines) read the body for context. Stale body content keeps generating already-resolved concerns ("submodule pin mutable" / "vsapad branch needed") and obscures the current architectural decision (Dao-AILab upstream pin at an immutable SHA).

Suggested fix. Edit the PR body to (1) drop the "draft for early review" framing or genuinely re-mark draft and remove ready; (2) replace the submodule sentence with the upstream-pin install snippet from fastvideo-kernel/README.md; (3) optionally add an explicit "Verified on Blackwell with pytest fastvideo-kernel/tests/test_vsa256_forward*.py" line if true.

Evidence: PR body vs commits 9560e648e8, e9320ebf2b; .gitmodules at HEAD; fastvideo-kernel/README.md:30-50.


S3 findings (redundant _force_tk branch, redundant FASTVIDEO_VSA_CUTEDSL explicit check, 3-way parity tolerance justification, torch.stack idiom) are recorded in the local review file but not surfaced here — none are blocking.

Strengths: clean 64-path isolation (single block_elements == 256 branch, no shared mutation, default (4,4,4) preserves the existing path byte-for-byte structurally); lazy optional-import for flash_attn.cute with an actionable error message naming all three required deps and the env-var; @Davids048's fork-submodule concern fully resolved in 9560e648e8 + e9320ebf2b; four new pytest files with pytest.importorskip guards on the optional CuTe build; consistent FASTVIDEO_VSA_ env-var prefix with the legacy alias correctly mapped in exactly one place.

— Gob (@SolitaryThinker's AI reviewer). Full review archived locally.

@SolitaryThinker
SolitaryThinker merged commit be548a7 into hao-ai-lab:main May 26, 2026
2 of 9 checks passed
@SolitaryThinker
SolitaryThinker deleted the 256vsa branch May 26, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready PR is ready to merge scope: attention Attention backends (VSA, STA, Flash, etc.) scope: inference Inference pipeline, serving, CLI scope: kernel CUDA kernels, fastvideo-kernel scope: training Training pipeline, methods, configs type: feat New feature or capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants