[common/PyTorch] Fused attention: pass real tensor strides to cuDNN (opt-in, v2 C API)#17
Open
pggPL wants to merge 3 commits into
Open
[common/PyTorch] Fused attention: pass real tensor strides to cuDNN (opt-in, v2 C API)#17pggPL wants to merge 3 commits into
pggPL wants to merge 3 commits into
Conversation
cuDNN fused attention reconstructs Q/K/V strides from the NVTE_QKV_Layout enum (generateMatrixStrides), so the enum has to encode memory geometry and Python must detect it by inspecting data_ptr, which is torch.compile-hostile. cudnn-frontend itself is stride-based, so pass the real torch strides instead. Prototype, opt-in via NVTE_FUSED_ATTN_REAL_STRIDES=1 (default off = old behavior): - new experimental C API nvte_fused_attn_set_strides(q, k, v, dO): a thread-local side channel carrying strides in cuDNN dim order [b, h, s, d]; NULL clears it. No NVTETensor/NVTEShape ABI change. - F16 arbitrary-seqlen fwd/bwd graph builders use the provided strides for the Q/K/V (and dO in bwd) set_stride instead of the enum-derived ones. Dense (non-THD, non-paged) layouts only; O and dQ/dK/dV outputs keep the enum-derived strides since TE allocates them. - FADescriptor_v1 gains a real_strides array included in operator< so cached plans built for different strides are not reused. - PyTorch extension reads q/k/v (and dO) .strides() from the incoming at::Tensors, permutes them from bshd/sbhd/bhsd torch order to [b, h, s, d], sets the side channel around the nvte_fused_attn_fwd/bwd calls and clears it afterwards. Verified on sm89 (bf16, b=2 s=128 h=8 d=64, no_mask, deterministic bwd): packed bs3hd/sbh3d views passed with the *separate* layout enum (bshd_bshd_bshd / sbhd_sbhd_sbhd) are bit-exact vs contiguous baselines in forward and backward with the flag on, and wrong with the flag off (enum strides mismatch memory), demonstrating the enum no longer needs to encode memory geometry. test_dot_product_attention passes with the flag off and on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…licit v2 API
Convert the real-stride prototype plumbing to a production shape: instead of
the thread-local nvte_fused_attn_set_strides side channel, thread the strides
through an explicit, additive C API.
- new C struct NVTEQKVStrides {q, k, v, d_o}: each member points to 4 int64
element-strides in cuDNN dim order [b, h, s, d], or is NULL to derive the
strides from the NVTE_QKV_Layout enum as before.
- new exported functions nvte_fused_attn_fwd_v2 / nvte_fused_attn_bwd_v2:
identical to the v1 entry points plus an NVTEQKVStrides parameter. The old
nvte_fused_attn_fwd/bwd become thin wrappers calling _v2 with all-NULL
strides, so existing callers (JAX, external) are untouched and behavior is
unchanged.
- strides-first core: the parameter is threaded explicitly through
fused_attn_arbitrary_seqlen_fwd/bwd into the F16 arbitrary-seqlen
fwd/bwd_impl graph builders (no globals). Provided strides are used for the
Q/K/V (and dO in bwd) set_stride on dense (non-THD, non-paged) layouts;
otherwise the enum-derived strides are used exactly as before. Outputs
(O, dQ/dK/dV) keep enum-derived strides since TE allocates them. FP8 and
max-512 backends ignore the parameter (documented in the header).
- FADescriptor_v1 keeps the real_strides cache-key extension, now populated
from the threaded parameter; the RealStrideOverride TLS machinery and
nvte_fused_attn_set_strides are removed entirely. Since the strides are an
explicit argument, both phases of the two-phase (workspace-size + execute)
call sequence trivially receive the same values.
- PyTorch extension builds NVTEQKVStrides from the incoming at::Tensor
strides (bshd/sbhd/bhsd -> [b, h, s, d] permutation as before) and calls
_v2 directly; still gated by NVTE_FUSED_ATTN_REAL_STRIDES=1 (default off).
- add tests/pytorch/attention/test_fused_attn_real_strides.py covering
flag-on == flag-off for contiguous inputs, bit-exact packed bs3hd/sbh3d
strided views declared with separate layout enums, and the unchanged
packed-enum path.
Verified on sm89: full prototype matrix bit-exact (packed views declared
separate == contiguous baselines, fwd+bwd; negative control still wrong with
the flag off), test_dot_product_attention 48 passed/48 skipped with the flag
off and on, and old C symbols still exported.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…op the opt-in flag Remove NVTE_FUSED_ATTN_REAL_STRIDES: the PyTorch extension now always passes the live torch strides of Q/K/V (and dO) to the cuDNN fused-attention graphs for plain 4-D torch tensors; quantized tensors, THD and non-4D inputs silently fall back to NULL strides (enum-derived). The v2 C API contract (NVTEQKVStrides, NULL = enum-derived) is unchanged. With real strides always on, DotProductAttention skips the pointer-based (untyped_storage().data_ptr() / storage_offset()) qkv layout detection for dense bshd/sbhd non-FP8 inputs and declares the format-derived separate layout instead. This removes the torch.compile graph breaks on UntypedStorage.data_ptr inside get_qkv_layout and the per-step CPU overhead of the detection. The old detection's stride normalization is preserved: tensors with stride(-1) != 1 are made contiguous in the bypass path (plain tensor metadata, still traceable). thd, FP8 DPA and KV-caching paths keep the existing detection since those backends do not consume real strides. Tests: DPA-level bit-exactness of packed-QKV strided views vs separate contiguous tensors (fused and flash backends), stride(-1) normalization, thd detection retention, and a torch.compile graph-break check with a forced-detection negative control. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.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.
Problem
TE's cuDNN fused-attention integration loses tensor stride information at the pybind boundary: torch tensors are wrapped into
NVTETensor(shape+ptr+dtype — no strides), and the C++ side reconstructs strides from theNVTE_QKV_Layoutenum via hard-coded formulas (generateMatrixStrides). cudnn-frontend itself is stride-based (Tensor_attributes::set_stride; NVIDIA's own SDPA sample expresses bs3hd-interleaved QKV purely as strides).Consequences of the enum channel:
data_ptr()/storage_offset()(get_qkv_layout) — untraceable undertorch.compile(gb0156) and pure per-step CPU overhead;Change (2 commits)
1. Opt-in real-stride plumbing. The PyTorch extension reads the live
at::Tensorstrides of Q/K/V (and dO in backward), permutes them to cuDNN dim order[b,h,s,d], and the F16 arbitrary-seqlen backend uses them for the cuDNN graphset_strideinstead of enum formulas — for dense (non-THD, non-paged) layouts. The graph/plan cache key (FADescriptor_v1) is extended with the stride values. Gated byNVTE_FUSED_ATTN_REAL_STRIDES=1(default off → behavior unchanged).2. Additive v2 C API. Strides travel as an explicit parameter, no hidden state:
nvte_fused_attn_fwd/bwdremain as thin wrappers calling_v2with NULL strides — JAX and external callers are untouched byte-for-byte. (_v2naming followsnvte_cublas_gemm_v2/nvte_quantize_v2.) Outputs (O, dQ/dK/dV) keep enum-derived strides. FP8/max-512 and THD/paged paths ignore the parameter (documented in the header).Why this matters
With real strides, the enum no longer needs to encode memory geometry: q/k/v that are strided views into a packed buffer (
bs3hd,sbh3d, kv-packed) work correctly even when declared as the plain separate layout. This removes the need for pointer-based layout detection in Python (torch.compile-hostile) and eliminates the silent-corruption failure mode.Verification (RTX Ada sm89, bf16, F16 arbitrary-seqlen, fwd+bwd grads)
[b,s,3,h,d]views declared separatesbh3dviews declaredsbhd_sbhd_sbhdtest_dot_product_attention(flag OFF and ON)New focused test:
tests/pytorch/attention/test_fused_attn_real_strides.py(5 tests).Notes / limitations
cu_seqlens/ragged offsets remain the channel there).🤖 Generated with Claude Code
Flag lifecycle (commitment)
NVTE_FUSED_ATTN_REAL_STRIDESis a transitional rollout vehicle, scheduled for removal:get_qkv_layout) at the same time.The code is structured so step 3 is mechanical: the flag is read in exactly two places (the pybind glue in
attention.cppand one Python gate helper indot_product_attention.py) — removal deletes those two checks and the detection path they guard. Nothing else depends on the flag.Rationale for not shipping always-on immediately: today's detection also normalizes odd stride patterns via a
.contiguous()retry; with detection gone those patterns reach cuDNN raw (loud plan-build failure, not corruption — but a new input class that deserves bake time), and the cuDNN support surface for general strides across archs/versions is broader than the enum set we could test on sm89.