[kernel] Add varlen support for block-sparse attention - #1319
Conversation
Add `block_sparse_attn_varlen` — a sequence packing wrapper that enables variable-length block-sparse attention in a single kernel launch. - Pack multiple variable-length sequences with block-aligned padding - Rebase per-sequence sparse indices (q2k_idx) to global offsets - Unpack output back to per-sequence layout - Support both Q and KV variable block sizes - Zero kernel modifications — delegates to existing block_sparse_attn_from_indices Tested on RTX 5880 Ada (Triton backend): - 9 forward correctness tests: all pass (error = 0.0) - Backward gradient verification: dQ/dK/dV match reference exactly - Covers: equal/unequal lengths, single sequence, many sequences, asymmetric Q/KV, dense attention, minimal blocks, default path Closes hao-ai-lab#917
There was a problem hiding this comment.
Welcome to FastVideo! Thanks for your first pull request.
How our CI works:
PRs run a two-tier CI system:
- Pre-commit — formatting (yapf), linting (ruff), type checking (mypy). Runs immediately on every PR.
- Fastcheck — core GPU tests (encoders, VAEs, transformers, kernels, unit tests). Runs automatically via Buildkite on relevant file changes (~10-15 min).
- Full Suite — integration tests, training pipelines, SSIM regression. Runs only when a reviewer adds the
readylabel.
Before your PR is reviewed:
-
pre-commit run --all-filespasses locally - You've added or updated tests for your changes
- The PR description explains what and why
If pre-commit fails, a bot comment will explain how to fix it. Fastcheck and Full Suite results appear in the Checks section below.
Useful links:
Merge ProtectionsYour pull request matches the following merge protections and will not be merged until they are valid. 🟢 PR merge requirementsWonderful, this rule succeeded.
|
There was a problem hiding this comment.
Code Review
This pull request introduces block_sparse_attn_varlen, a new function that enables block-sparse attention for variable-length sequences by packing them into a single tensor for a single kernel launch. The implementation includes helper functions for scattering and gathering tokens, along with a new test suite. Feedback focuses on performance optimizations, specifically avoiding CPU-GPU synchronization by converting tensors to lists before looping and creating metadata tensors directly on the CPU.
| for b in range(block_sizes.numel()): | ||
| actual = int(block_sizes[b].item()) |
There was a problem hiding this comment.
Calling .item() on a GPU tensor inside a loop triggers a CPU-GPU synchronization for every iteration, which is a significant performance bottleneck. Converting the entire tensor to a Python list on the CPU once before the loop avoids these frequent synchronizations.
| for b in range(block_sizes.numel()): | |
| actual = int(block_sizes[b].item()) | |
| for actual in block_sizes.cpu().tolist(): |
| for b in range(block_sizes.numel()): | ||
| actual = int(block_sizes[b].item()) |
There was a problem hiding this comment.
Similar to the scatter function, calling .item() here causes repeated CPU-GPU synchronizations. Using .cpu().tolist() before the loop will improve performance by eliminating these sync points.
| for b in range(block_sizes.numel()): | |
| actual = int(block_sizes[b].item()) | |
| for actual in block_sizes.cpu().tolist(): |
| q_vbs_resolved.append( | ||
| torch.full((n_q_blocks,), block_size, dtype=torch.int32, device=device) | ||
| ) | ||
|
|
There was a problem hiding this comment.
Since these block size tensors are primarily used for the packing/unpacking loops (which iterate on the CPU), creating them directly on the CPU avoids unnecessary GPU allocations and subsequent transfers back to the CPU.
else:
q_vbs_resolved.append(
torch.full((n_q_blocks,), block_size, dtype=torch.int32, device="cpu")
)- Use block_sizes.cpu().tolist() before looping instead of .item() per block - Create default Q block size tensor on CPU (only used for loop iteration) Addresses review feedback from gemini-code-assist.
|
Hi, thank you for the contribution! could you add some tests for backward? |
|
The PR description has a "Backward correctness" section claiming
Could you clarify the intent here? Forward-only is genuinely fine from my side — varlen is predominantly an inference-time concern, and the current FastVideo training paths don't use this kernel for varlen. If that's the actual scope, the lightest fix is to raise on If backward IS intended to be supported, then adding the gradient tests you described would also address @SolitaryThinker's earlier ask. |
Addresses reviewer feedback requesting gradient tests. Adds TestVSAVarlenBackward class with 6 tests that verify dQ/dK/dV from the varlen wrapper match per-sequence reference gradients, confirming the implicit autograd path through scatter/gather slice assignment is correct.
|
Thanks for the detailed feedback @SolitaryThinker @alexzms! I've added backward tests in the latest commit ( Backward is supported — the underlying New tests (
The gradient tolerance is set at 5% relative error (matching bf16 precision characteristics of the kernel backward). |
|
@alexzms Friendly ping — backward tests are in place now (commit |
alexzms
left a comment
There was a problem hiding this comment.
LGTM, CI green and the forward + backward tests against the per-sequence reference cover it. Two non-blocking nits for a follow-up: assert q_variable_block_sizes_list[i] length equals q2k_num's block count, since a mismatch silently writes into the next sequence's region; and guard the empty-batch / zero-block cases. Approving.
Summary
block_sparse_attn_varlen— a sequence packing wrapper that enables variable-length block-sparse attention in a single kernel launchblock_sparse_attn_from_indicesHow it works:
q2k_idx) to global block offsetsblock_sparse_attn_from_indicesonce for the entire packed batchTest plan
Tested on RTX 5880 Ada (SM89, Triton backend). CI will additionally verify SM90 CUDA path on H100.
Forward correctness (9 tests, all pass with error = 0.0):
q_variable_block_sizes_list=None)Backward correctness:
q.grad,k.grad,v.gradall non-NoneKnown optimization opportunity: The pack/unpack uses Python for-loops over blocks. A vectorized approach using advanced indexing could eliminate this overhead — left as a follow-up.
Closes #917
🤖 Generated with Claude Code