Skip to content

[PyTorch] DotProductAttention: declarative packed qkv/kv inputs#18

Open
pggPL wants to merge 22 commits into
mainfrom
dpa_packed_qkv_api
Open

[PyTorch] DotProductAttention: declarative packed qkv/kv inputs#18
pggPL wants to merge 22 commits into
mainfrom
dpa_packed_qkv_api

Conversation

@pggPL

@pggPL pggPL commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Motivation

A fused QKV projection produces one packed buffer, but callers must slice it into query_layer/key_layer/value_layer views, which DotProductAttention then reverse-engineers via pointer-based layout detection (get_qkv_layout inspecting data pointers, strides and offsets on every forward). That detection graph-breaks under torch.compile and re-derives information the caller already knows.

This PR lets callers declare the packing instead. The declared layout is truthful by construction, so no detection runs on this path — including for thd and FP8 DPA.

API

DotProductAttention.forward gains three optional arguments (existing call sites unchanged):

  • qkv_layer — fully packed QKV, e.g. [b,s,3,h,d] or [s,b,h,3,d] (thd: [t,3,h,d]).
  • kv_layer — packed KV (e.g. [b,s,2,hg,d]), used with query_layer; supports GQA.
  • qkv_interleave_dim (default -3) — where the 3/2 interleave sits (-3 or -2); explicit since shapes can be ambiguous.

Handling lives in a single helper (_unpack_packed_qkv): q/k/v come out as zero-copy select() views, the exact layout string (bs3hd, sbhd_sb2hd, th3d, ...) is built from the declaration, and inputs are strictly validated (mutual exclusion, rank/size at the interleave dim, stride(-1) == 1 so the declared layout cannot lie about memory, no inference_params).

get_qkv_layout now emits a DeprecationWarning when it detects a packed layout purely from pointers, pointing callers at the new arguments. Separate q/k/v never warn.

FP8: combine_and_quantize

For packed layouts, FP8 attention used to rebuild the packed buffer from the q/k/v views via combine_tensors (a raw set_ with a silent adjacency assumption). The original packed buffer is now threaded down as packed_qkv/packed_kv and combine_and_quantize(combined_qkv=..., combined_kv=...) quantizes it directly. Legacy call sites are unchanged (None defaults); backward combine paths untouched.

MultiheadAttention adoption

MHA hands its fused projection output straight to DPA: self-attention passes packed QKV as qkv_layer, cross-attention passes packed KV as kv_layer. The legacy sliced-views path is kept for RoPE, QK norm, KV caching, CPU offloading, GQA and FP8 projection outputs.

Tests

No new test file. _run_dot_product_attention gains a declarative_packed mode (packed buffer passed via qkv_layer/kv_layer, input grads read off the packed buffer), exercised by two small additions to the existing suite:

  • test_dpa_qkv_layout_declarative — all 8 packed dense layouts x {self causal+bias, cross padding} configs, compared cross-backend like test_dpa_qkv_layout.
  • test_dpa_qkv_layout_thd_declarative — the 4 packed thd layouts (Hopper+).

Verified on sm89 (bf16, fused+flash+unfused): declarative tests green, test_dpa_qkv_layout matrix unchanged and green, lint 10/10.

🤖 Generated with Claude Code

Fused QKV projections naturally produce one packed buffer, but
DotProductAttention forces callers to slice it into q/k/v views that TE
then reverse-engineers with pointer-based layout detection
(get_qkv_layout inspects data_ptr/storage_offset on every forward,
which graph-breaks under torch.compile and adds CPU overhead).

Let callers declare the packing instead (JAX-style):

* DotProductAttention.forward gains optional qkv_layer (fully packed
  QKV: [b,s,3,h,d]/[s,b,3,h,d]/[b,s,h,3,d]/[s,b,h,3,d] dense, [t,3,h,d]/
  [t,h,3,d] thd), kv_layer (packed KV used with query_layer), and
  qkv_interleave_dim (-3 or -2; explicit knob rather than shape
  inference since h==3 or hg==2 would be ambiguous).
* Q/K/V are derived as zero-copy select() views and the exact layout
  enum (bs3hd, bsh3d, sb3hd, bshd_bs2hd, t3hd, ...) is constructed
  declaratively -- it is truthful by construction, so get_qkv_layout is
  never called on this path, including for thd and FP8 DPA.
* combine_and_quantize no longer re-combines what is already combined:
  a new optional combined= argument carries the caller's original
  packed buffer, which is quantized directly instead of rebuilding the
  packed buffer from q/k/v views via combine_tensors (a raw set_ with
  a silent adjacency/interleave assumption). The packed original is
  threaded from DPA.forward through FusedAttention to
  FusedAttnFunc.forward; all legacy call sites are untouched
  (combined=None preserves exact behavior), and backward combine calls
  are unchanged (gradients have no pre-packed original).

Tests: dense fwd+grad bit-exactness vs separate contiguous q/k/v for
bs3hd/bsh3d/sb3hd/kv-packed/GQA (fused + flash), validation errors,
torch.compile (no data_ptr/UntypedStorage graph breaks), FP8
combined-vs-views bit equivalence, and detection-free declared t3hd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
@pggPL pggPL requested a review from cyanguwa as a code owner July 7, 2026 15:36
pggPL and others added 21 commits July 8, 2026 12:55
…claratively

Adopt the new DotProductAttention packed API inside MultiheadAttention:

* self-attention (np == ng): the fused QKV projection output, already
  viewed as [.., h, 3, d] (qkv_weight_interleaved) or [.., 3, h, d], is
  handed to DPA directly as qkv_layer with the matching
  qkv_interleave_dim (-2 / -3) -- no SplitAlongDim slicing in MHA and
  no pointer-based layout detection in DPA.
* cross-attention: the packed KV projection output is exposed as
  [.., hg, 2, d] / [.., 2, hg, d] and passed as kv_layer.
* The pass-through only engages when no per-tensor operation needs the
  individual q/k/v slices: it is skipped for RoPE, QK normalization,
  KV caching (inference_params), CPU offloading, GQA (np != ng, not a
  uniform 3-interleave) and quantized (FP8) projection outputs; those
  keep the legacy sliced-views path unchanged.

Tests: MHA self (interleaved + non-interleaved) and cross (both
interleaves) are bit-exact vs the same MHA with packed inputs converted
back to separate contiguous q/k/v (output, input grad, weight grads);
spy asserts the packed argument and interleave dim actually reach DPA;
GQA and RoPE fall back to the views path. TransformerLayer regression
suite unchanged; test_kv_cache failures on this device are pre-existing
on origin/main (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Santosh Bhavani <santosh.bhavani@live.com>
…ack_packed_qkv

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
… arguments

Rename the single layout-dependent packed_qkv plumbing argument (which held
the full QKV buffer for *3* layouts but the KV buffer for *_2* layouts) into
explicit packed_qkv/packed_kv, mirroring the public qkv_layer/kv_layer API.
combine_and_quantize's combined= is split into combined_qkv=/combined_kv=
accordingly, and _unpack_packed_qkv no longer returns the packed tensor since
the callers already hold qkv_layer/kv_layer.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Fold the tests from the new test_dpa_packed_inputs.py file into the existing
attention test suite as a dedicated section, reusing its imports. No new test
file; test logic unchanged apart from renaming the module-level constants
(_B/_S/_H/_D/_DTYPE -> _PACKED_*) to avoid collisions.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
get_qkv_layout now emits a DeprecationWarning when it recognizes q/k/v as
views of a packed buffer purely from data pointers/strides/offsets (detected
*3*/*_2* layouts), pointing callers at the declarative qkv_layer/kv_layer
API. Separate q/k/v tensors (hd_hd_hd layouts) never warn since there is
nothing to declare.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Drop the API-validation, torch.compile graph-break, combine_and_quantize
equivalence and thd no-detection spy tests; keep the dense/flash bit-exact
equivalence tests and the MHA packed pass-through/fallback tests.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Signed-off-by: Przemek Tredak <ptredak@nvidia.com>
TransformerEngine now resolves cuDNN frontend headers from the Python distribution, but GitHub Actions builds without isolation and did not install that build dependency.

Install the same >=1.25.0 requirement declared in pyproject.toml for every build variant so metadata generation can proceed.

Signed-off-by: Sudhakar Singh <sudhakars@nvidia.com>
… kernel (NVIDIA#3190)

* [PyTorch] NVFP4: emit GEMM-swizzled scales in the non-RHT 2D quantize kernel

Fixes a CUDA graph hang/corruption with NVFP4 weight caching.

Scope: the non-RHT 2D quantize path on 128-aligned shapes, which is the default cached-weight case. Other paths (1D weight scaling, non-128-aligned, etc) keep the compact + post-quantize swizzle fallback and are left for follow-ups.

- swizzle.cuh: NVFP4 128x4 GEMM-swizzled scale-index helper (byte-compatible
  with nvte_swizzle_scaling_factors).
- quantize_transpose_nvfp4_2D_kernel: add WITH_GEMM_SWIZZLED_SCALES; write
  rowwise and columnwise scales at swizzled offsets; relax the compact-only
  guard to allow the 2D swizzled path on 128-aligned dims.
- dispatch: thread with_gemm_swizzled_scales through the blockwise fallback
  (which already implements kSwizzledScale) instead of hardcoding false.
- quantizer: is_eligible_for_2d_swizzle_fusion + shared gate so the non-RHT
  2D path enables swizzled SF on 128-aligned shapes; the post-quantize swizzle
  fallback auto-skips when the flag is set.
- tests: byte-equal SF vs nvte_swizzle_scaling_factors (rowwise/columnwise/
  both, multiple shapes) + shape gate; cached-weight scale-pointer stability
  and CUDA graph capture/replay regression.

Signed-off-by: Cael Ling <caell@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Signed-off-by: Cael Ling <caell@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: vthumbe1503 <vthumbe@nvidia.com>
…3180)

* Remove fused attention workspace optimization control

Signed-off-by: Przemek Tredak <ptredak@nvidia.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* More removal

Signed-off-by: Przemek Tredak <ptredak@nvidia.com>

---------

Signed-off-by: Przemek Tredak <ptredak@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…arative param

Parametrize test_dpa_qkv_layout and test_dpa_qkv_layout_thd with
declarative={views,declarative}: the declarative mode passes the packed buffer
to DotProductAttention via qkv_layer/kv_layer (declared layout, gradients read
off the packed buffer) instead of slicing it into q/k/v views for pointer-based
detection. This reuses the whole existing config matrix (masks, bias, SWA,
cross-attention, thd, all backends) for the declarative API, replacing the
dedicated packed-input test section.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…matrix

Revert the declarative parametrization of test_dpa_qkv_layout(_thd) (which
doubled their whole config x layout product) and instead add
test_dpa_qkv_layout(_thd)_declarative covering all packed layouts on a trimmed
config dimension: one self-attention and one cross-attention config (kv_layer
path) for dense, one config for thd. Past the input handling the backend code
is identical to the views mode, so the full config matrix added no coverage.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Replace the .grad-holder objects substituted for q/k/v in declarative packed
mode with q_grad/k_grad/v_grad variables computed right after backward, used
uniformly by all return paths.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…to dpa_packed_qkv_api

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>

# Conflicts:
#	tests/pytorch/attention/test_attention.py
…plicit k_norm

- get_qkv_layout deprecation warning: add stacklevel=2 and skip it while CPU
  offloading is enabled (offloading forces MultiheadAttention onto the
  sliced-views fallback, so the caller has no migration option there).
- MultiheadAttention: gate the packed pass-through on k_norm explicitly
  instead of relying on q_norm/k_norm being created together.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants