[perf] avoid full-sequence materialization in VSA coarse/sparse combine - #1813
Open
boxwrench wants to merge 1 commit into
Open
[perf] avoid full-sequence materialization in VSA coarse/sparse combine#1813boxwrench wants to merge 1 commit into
boxwrench wants to merge 1 commit into
Conversation
The compression branch computed a per-block coarse output and then expanded it across the full sequence with repeat() before combining it with the sparse output. The gated combine then allocated two more [B, H, S, D] tensors for the product and the sum. Keep the coarse result at block resolution, [B, H, n_blocks, 1, D], and let it broadcast over the intra-block axis during the combine. This mirrors the BSHD 128/256 path, which already broadcasts out_c_blk.unsqueeze(2) rather than repeating. Under no_grad the combine accumulates into the sparse output with addcmul_, removing the remaining two full-sequence temporaries; with grad enabled it stays out-of-place, for the same reason the BSHD path documents (the sparse output is saved by FA4's autograd node for backward). Ungated the result is bit-exact. Gated, addcmul_ fuses the multiply-add instead of rounding the intermediate product to bf16, so the rounding differs from the old path; in the added tests the fused result is the more accurate of the two when both are compared against fp32. Top-k routing, shapes and dtypes are unchanged, and 64/128/256 dispatch is unaffected. Isolated combine microbenchmark at an H3-like shape (bf16, 56 heads, dim 128, 15488 tokens, block 64), gated: 4.06 -> 2.12 ms and 848 -> 212 MiB peak allocated. This measures the combine alone, not an end-to-end workload. Adds tests/test_vsa_combine.py (17 cases, CI-sized, no model or pipeline dependency) and benchmarks/bench_vsa_combine.py.
Contributor
Merge Protections🔴 1 of 1 protections blocking · waiting on 👀 reviews and 🤖 CI
🔴 PR merge requirementsWaiting for
This rule is failing.
|
There was a problem hiding this comment.
Welcome to FastVideo! Thanks for your first pull request.
How our CI works:
PRs run a three-tier CI system:
- Pre-commit — formatting (yapf), linting (ruff), type checking (mypy). Runs immediately on every PR.
- Fastcheck — six core GPU lanes run automatically via Buildkite (~10-15 min).
- Merge gate — a reviewer adds
ready; changed paths select only the relevant integration, training, golden, or SSIM coverage.
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 merge-gate results appear in the Checks section below.
Useful links:
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.
Problem
The VSA compression branch computes a per-block coarse output, then expands it across the full sequence with
repeat()before combining it with the sparse output:The coarse value is constant within a block, so the expansion is avoidable. Gated, the combine then allocates two further
[B, H, S, D]tensors for the product and the sum — three full-size temporaries where none is needed.Change
Keep the coarse result at block resolution,
[B, H, n_blocks, 1, D], and let it broadcast over the intra-block axis during the combine.This mirrors the BSHD 128/256 combine, which already broadcasts
out_c_blk.unsqueeze(2)instead of repeating. Underno_gradthe combine accumulates into the sparse output withaddcmul_, removing the remaining two full-sequence temporaries. With grad enabled it stays out-of-place, for the same reason the BSHD path documents in its comment — the sparse output is saved by FA4's autograd node for backward, so mutating it there would invalidate the graph.The combine is shared by the 64/128/256 dispatch (the
repeat()happened before the block-size branch), so all three paths benefit.Correctness
New
tests/test_vsa_combine.py, 17 cases, all CI-sized synthetic tensors with no model, checkpoint or pipeline dependency:video_sparse_attn.addcmul_fuses the multiply-add instead of rounding the intermediate product to bf16, so the rounding differs from the old path and the two disagree on a fraction of elements. In these tests the fused result is the more accurate of the two when both are compared against an fp32 reference (mean error 1.52e-3 → 1.29e-3, max 2.29e-2 → 1.56e-2). The test asserts accuracy against fp32 rather than agreement with the old rounding.block_sparse_attn, and the 128/256 kernels are still dispatched.Performance
benchmarks/bench_vsa_combine.py. These numbers are an isolated microbenchmark of the combine alone — not an end-to-end workload. At an H3-like shape (bf16, 56 heads, head dim 128, 15488 tokens, block size 64):The 636 MiB saved in the gated case is exactly the three full
[B, H, S, D]bf16 tensors that are no longer materialized (3 × 211.8 MiB). The residual 212 MiB is the benchmark's own input clone.For a full-workload data point, MiniMax H3 at 864×480/124f with topk 0.20 measured end-to-end:
That end-to-end measurement is from an AMD Radeon AI PRO R9700 (gfx1201, ROCm 7.2.1, PyTorch 2.9.1, Triton 3.5.1) and is hardware-specific — it should not be extrapolated to other GPUs. The change itself is not platform-specific; the mechanism is allocation count and memory traffic.
Notes
video_sparse_attn_bshdstill uses.float().sum(dim=2)for its block means, which materializes an fp32 copy of q/k/v. That is a separate concern on a 128/256-only path and is left alone here to keep this diff minimal.test_vmoba_correctness, 2test_attn_qat_train, and one intermittenttest_vsa_varlen.py::TestVSAVarlenBackward::test_backward_different_lengthsthat also fails on unmodifiedmain). Those are unrelated to this change and appear to be ROCm-environment failures.