Repository navigation
Conversation
Add cum_seqlen_q / cum_seqlen_k (and max_seqlen_q for the grid) to the Hopper FMHA example, following the Blackwell example's interface. Q/K/V/O/LSE of a variable-length side are packed along the sequence dimension. - The grid covers the longest query sequence; all warp groups skip tiles that start past the end of their query sequence or have no keys. - Q/K/V and LSE are offset to the current sequence per tile, and the mask helpers use its lengths. Variable-length keys use the residual mask when no window is set, so K/V rows of the next sequence in a tail tile are masked. - With variable-length queries, O is stored with a row predicate instead of TMA, so a tail tile does not overwrite the next sequence. - run() and the CLI accept per-sequence lengths, e.g. --q_shape "3,(1,129,700),8,128", and check each sequence against the reference. - can_implement requires fp8 key lengths divisible by 16 (fp8 V is s-major, and its key offsets and head stride must be 16-byte aligned). Add test/examples/CuTeDSL/hopper/test_fmha_varlen.py. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
Collaborator
Collaborator
|
The varlen path looks correct to me. A few things I'd want to settle before merging:
The rest I saw are efficiency/cleanup. follow-ups, not blockers. |
… store The default softmax scale is 1/sqrt(head_dim); a non-square S_q != head_dim must keep it, so route the default through a named helper and pin it in a host test. The varlen row store also asserts the epilogue-tile and warp-group divisibility and D contiguity its 128-bit copy assumes. Verified on GH200 (SM90, DSL 4.8.1): test_fmha_varlen.py 33 passed.
Author
|
Thanks — addressed both points in c6cfa35:
Validation on GH200 (SM90, CuTe DSL 4.8.1): |
This branch has not been deployed
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.
Closes #3773.
Adds variable sequence lengths to the Hopper CuTe DSL FMHA example
(
examples/python/CuTeDSL/cute/hopper/kernel/attention/fmha.py), following theinterface of the Blackwell example.
Interface
__call__takescum_seqlen_q,cum_seqlen_k(int32,[0, s_0, s_0 + s_1, ...])and
max_seqlen_q. PassNonefor a fixed-length side. Q/K/V/O/LSE of avariable-length side are packed along the sequence dimension with batch size 1.
run()and the CLI accept per-sequence lengths as in the Blackwell example,e.g.
--q_shape "3,(1,129,700),8,128" --k_shape "3,(65,300,700),2,128".Q and K can be variable-length independently. GQA/MQA works as before.
Implementation
All warp groups skip the same tiles: those that start past the end of their
query sequence, or whose key sequence is empty. Rows of a query sequence
with no keys are therefore not written, as in the Blackwell example.
domain_offset,and the mask helpers get that sequence's lengths.
TMA zero fill only applies past the end of the whole buffer. Extra K columns
are masked: variable-length keys use the residual mask when no window is
set, and the causal/window masks already cover the last K tile. Extra Q rows
are computed but not stored.
variable-length queries the epilogue copies the staged shared-memory tile to
global memory with 128-bit stores and a row predicate instead of the TMA
store (the Blackwell example also drops its TMA store for variable-length
queries).
are behind
const_expr.can_implementnow requires key lengths divisible by 16. FP8 V iss-major, and its head stride is s_k bytes, which TMA needs to be a multiple
of 16 bytes, so fixed-length shapes that fail this check could not run
before either. With variable lengths, key offsets not divisible by 16 made
the kernel fault on GH200.
run(): the default softmax scale uses the head dimension (q_shape[3]),not
q_shape[1], which is a tuple for variable-length input. [Hopper CuTeDSL] Add tests for the dense GEMM, FMHA and CTA norm examples #3770 makes thesame one-line change. This is a behavior change for a caller that passes
scale_softmax=0.0with a fixed-length, non-squareS_q != head_dim: thatgraph now uses
1/sqrt(head_dim).default_softmax_scalenames the value anda host test pins it for non-square shapes.
divisibility, and (for a static layout) D contiguity, that its 128-bit copy
assumes, so an unsupported (out_dtype, head_dim) fails at trace time instead
of dropping rows.
Tests
test/examples/CuTeDSL/hopper/test_fmha_varlen.py(separate from thefixed-length tests in #3770). Output and LSE are checked per sequence against
a PyTorch reference (fp16/bf16 atol 1e-2, fp8 atol 0.13, rtol 1e-5):
(1, 0, 63, 64, 65, 128, 129, 700): empty and length-1 sequences,both sides of the 64- and 128-row tiles, unaligned sequence starts;
non-persistent;
variable-length Q only and K only;
On GH200: 32/32 pass, and the #3770 fixed-length suite (53 cases) passes on
this branch.
Performance (GH200)
fp16, h=16, d=128,
mma_tiler_mn=(64, 128), 16384 total tokens, TFLOPS, meanof two runs of 50 iterations. FLOPs count the attended (q, k) pairs of each
sequence. Neither branch includes #3399.
persistent causal case, fixed length on
mainruns below non-persistent(365 vs 395), and the varlen path does not show that gap. I haven't looked
into why.
(455 / 312) because its static round-robin assignment over the max-length
grid balances the work poorly. A length-aware persistent schedule could be
a follow-up.