Skip to content

sdpa: add torch custom ops cudnn::sdpa_fwd / cudnn::sdpa_bwd - #517

Open
vedaanta wants to merge 1 commit into
NVIDIA:developfrom
vedaanta:vagarwalla/sdpa-ex-torch-ops
Open

sdpa: add torch custom ops cudnn::sdpa_fwd / cudnn::sdpa_bwd#517
vedaanta wants to merge 1 commit into
NVIDIA:developfrom
vedaanta:vagarwalla/sdpa-ex-torch-ops

Conversation

@vedaanta

@vedaanta vedaanta commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

What

PyTorch custom ops — torch.ops.cudnn.sdpa_fwd / sdpa_bwd — exposing the cuDNN SDPA feature surface that torch.nn.functional.scaled_dot_product_attention's aten contract cannot express:

  • attention sinks (per-Q-head logits in the softmax denominator)
  • sliding window (window_left, cuDNN visible-tokens convention)
  • bottom-right causal alignment
  • padded batches (seq_len_q / seq_len_kv)
  • THD / varlen packing (FA-style (T, H, D) + cu_seqlens; non-contiguous K/V views of a fused KV projection are declared with their true strides)

The ops build pygraph sdpa / sdpa_backward nodes; the engine Router picks the serving plan (FROST OSS kernels or cuDNN-backend engines) per configuration. One new file + tests + docs; no existing behavior changes (the experimental dense module's backward is renamed cudnn::sdpa_bwd_legacy so both modules can coexist in one process until it is removed).

Contract

Aspect Guarantee
torch.compile register_fake meta kernels mirror the real output strides; torch.library.opcheck passes on dense and THD paths including dynamic-shape AOT dispatch, locked in by a test
autograd sdpa_fwd is differentiable on the varlen path (register_autograd); the glue converts packed TH1 stats to the padded LSE layout device-side (no host reads — tracing/capture safe). Dense + sink backward raise NotImplementedError until their engine contracts land
threading thread-local cuDNN handles, serialized graph builds, bounded (FIFO) graph cache
validation one io dtype per call; k/o/grad_out shape checks; h_k != h_v GQA head splits validated (independent K/V head counts); int32 ragged-offset overflow guards; inert causal_bottom_right rejected; base-pointer realignment uses clone() (contiguous() cannot fix a misaligned base)
backward LSE padded (B, H, max_seqlen_q, 1) fp32 — backend restriction (bprop THD rejects ragged LSE on SM8X/SM12X)

Tests (19, all L0 — test/python/test_cudnn_sdpa_torch_ops.py)

Dense: sinks (±causal) with LSE value checks, h_k != h_v GQA, query-layout adoption (4 permutations) vs an fp32 reference, sliding windows, bottom-right cross-seqlen, sinks+window, padded per-batch lengths. THD: fwd with LSE value checks, fwd+bwd (MHA + GQA), end-to-end autograd, kv-interleaved (T,2,H,D) views. Contract: opcheck (dense + THD/dynamic). All green on SM100 (cc 10.0) + previously SM120, both engine routes.

Docs: docs/fe-oss-apis/sdpa-torch-ops.md.

Consumers

torch.nn.attention.varlen and F.sdpa integration builds on these ops in #554 (the cudnn.torch provider); TE/vLLM/Megatron-style frameworks can call them directly.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added an extended scaled dot-product attention interface for dense and variable-length inputs.
    • Added support for attention sinks, sliding-window masking, causal alignment, padded sequences, grouped-query attention, and packed layouts.
    • Added optional log-sum-exp output and forward/backward operations.
  • Bug Fixes

    • Renamed the legacy attention backward operator to avoid conflicts with the new implementation.
  • Documentation

    • Added usage and capability documentation for the PyTorch attention operators.
  • Tests

    • Added CUDA coverage for extended attention behavior, layouts, masking modes, and gradient results.

@vedaanta vedaanta added cat-feature Requests for new functionality, APIs, examples, or behavior improvements. mod-frontend cuDNN frontend APIs, operation graph construction, plans, and user-facing wrappers. orig-nv-eng Reported or requested by NVIDIA engineering. mod-cutedsl CuTeDSL kernels, generated kernels, examples, or related integration work. labels Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds cuDNN PyTorch SDPA forward and THD backward operators. The implementation supports extended masking, sinks, padding, packed sequences, LSE output, graph caching, fake kernels, CUDA execution, and varlen autograd. Tests and documentation cover the new contracts.

Changes

Extended SDPA execution

Layer / File(s) Summary
Forward SDPA execution
python/cudnn/sdpa/fwd/torch_op.py, python/cudnn/__init__.py
Adds cudnn::sdpa_fwd, dense and THD execution, extended options, graph caching, optional LSE output, fake registration, lazy public exposure, and the sdpa wrapper.
Packed backward execution
python/cudnn/sdpa/fwd/torch_op.py
Adds THD-only cudnn::sdpa_bwd, padded LSE handling, deterministic plan selection, packed gradients, graph caching, fake registration, and varlen autograd routing.
Legacy backward routing
python/cudnn/experimental/ops/sdpa.py
Renames the experimental backward operator to sdpa_bwd_legacy and updates its implementation, fake registration, and dispatch paths.
Feature validation and API documentation
test/python/test_cudnn_sdpa_torch_ops.py, docs/fe-oss-apis/sdpa-torch-ops.md
Adds CUDA-gated tests for masking, sinks, packed sequences, GQA, LSE, gradients, operator contracts, and non-contiguous K/V views. Documents supported behavior and limitations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant sdpa
  participant sdpa_fwd
  participant cuDNNGraph
  Caller->>sdpa: provide q, k, v, scale, and options
  sdpa->>sdpa_fwd: pass tensors and sequence metadata
  sdpa_fwd->>cuDNNGraph: build or retrieve cached graph
  cuDNNGraph->>sdpa_fwd: return output and optional LSE
  sdpa_fwd->>Caller: return SDPA result
Loading
sequenceDiagram
  participant Autograd
  participant sdpa_fwd
  participant sdpa_bwd
  participant cuDNNGraph
  Autograd->>sdpa_fwd: run varlen forward
  sdpa_fwd->>Autograd: save output and packed LSE
  Autograd->>sdpa_bwd: pass gradients and padded LSE
  sdpa_bwd->>cuDNNGraph: execute THD backward graph
  cuDNNGraph->>Autograd: return packed DQ, DK, and DV
Loading

Possibly related PRs

Suggested reviewers: anerudhan

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the addition of PyTorch SDPA custom operators and matches the main change.
Description check ✅ Passed The description provides a detailed summary, rationale, API impact, compatibility notes, and test coverage, despite not following every template heading.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (4)
python/cudnn/sdpa/fwd/torch_op.py (4)

43-44: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Bound the graph cache.

_graph_cache grows without limit. The key includes B, S_q, S_kv, and all strides. A serving workload with variable batch or sequence shapes creates a new built cuDNN graph plus its execution plans for every distinct shape, and nothing evicts them. This retains device-side plan state for the process lifetime.

Consider an LRU bound (for example functools.lru_cache on a builder helper, or an OrderedDict with a max size) before this graduates from prototype.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 43 - 44, Bound the global
_graph_cache used by the cuDNN graph builder so it evicts least-recently-used
entries after a fixed maximum size. Update the cache access and insertion logic
around _graph_cache to refresh recency on hits and remove the oldest graph,
including its execution-plan state, when the limit is exceeded.

451-466: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The non-THD branch of _build_bwd_graph is unreachable.

_sdpa_bwd_ex_impl raises NotImplementedError for the dense path at line 542 and always passes is_thd=True at line 616. So is_thd is a constant here. The if is_thd: guard, the use_padding_mask=is_thd at line 484, and the conditional max_total_seq_len_* at lines 487-488 are dead branches.

Keeping the parameter is reasonable if dense backward lands soon. If not, drop it and simplify. Add a short comment either way so a reader does not assume the dense path is exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 451 - 466, In
_build_bwd_graph, document that the current backward implementation only reaches
the THD path because _sdpa_bwd_ex_impl rejects dense inputs and invokes it with
is_thd=True. If dense backward is not being added now, remove the unreachable
is_thd branching, including the guard and dependent use_padding_mask and
max_total_seq_len conditionals; otherwise retain the parameter but add a short
comment identifying the current restriction.

350-361: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Guard the int32 ragged offsets against overflow.

cu_seqlens_q is int32. cu_seqlens_q * q.stride(0) stays int32, so the product wraps silently for large packed buffers. With a kv-interleaved view the token stride doubles, which halves the safe token budget. Example: T = 131072, H = 64, D = 128, kv-packed token stride 2 * H * D = 16384 gives 2.1e9, past INT32_MAX. A wrapped offset produces a negative base pointer offset and reads outside the allocation.

cuDNN ragged offsets are int32 by contract, so add an explicit check rather than widening.

🛡️ Proposed guard
     if is_thd:
+        max_elem = max(T_q * q.stride(0), T_kv * k.stride(0), T_kv * v.stride(0), T_q * H_q * D_v)
+        if max_elem > 2**31 - 1:
+            raise ValueError(f"packed tensors exceed the int32 ragged-offset limit ({max_elem} elements); split the batch")
         # cuDNN ragged offsets are ELEMENT offsets per tensor, so each scales

This requires keeping T_q and T_kv bound at lines 256-257 instead of discarding them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 350 - 361, Retain the bound
T_q and T_kv values in the setup around the existing sequence-shape handling,
then validate that every computed ragged element offset fits within the signed
int32 range before populating the variant. Guard the products used by RAGGED_Q,
RAGGED_KV, RAGGED_V, RAGGED_O, and RAGGED_STATS, raising the established
argument/validation error when any packed token count and stride would exceed
INT32_MAX; keep cuDNN offsets int32 rather than widening them.

487-488: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pass the actual token counts to max_total_seq_len_q and max_total_seq_len_kv.

These attributes only size workspace. They do not define dQ, dK, or dV extents. The cuDNN documentation does not require 64-token rounding. Use total_q and total_kv directly unless a version-specific engine requires this alignment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 487 - 488, Update the SDPA
forward operation configuration to pass total_q directly as max_total_seq_len_q
and total_kv directly as max_total_seq_len_kv when is_thd, removing the _round64
alignment for these workspace-sizing attributes while preserving the existing
non-THD None behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cudnn/sdpa/fwd/torch_op.py`:
- Around line 95-102: Update _normalize_thd in
python/cudnn/sdpa/fwd/torch_op.py:95-102 to use
clone(memory_format=torch.contiguous_format) instead of contiguous(), ensuring
misaligned tensors always receive fresh storage. At
python/cudnn/sdpa/fwd/torch_op.py:561-563, extend the lse normalization guard
with lse.data_ptr() % 16 and use the same clone allocation when contiguity or
alignment fails.
- Around line 256-265: Validate k.shape on both forward paths before building
its cuDNN descriptor: in python/cudnn/sdpa/fwd/torch_op.py lines 256-265,
require k.shape to equal (v.shape[0], H_kv, D_qk); in lines 267-276, require (B,
H_kv, S_kv, D_qk) and validate matching batch sizes across q, k, and v. Raise
ValueError on mismatches, and prefix unused T_q/T_kv bindings with underscores
unless they are used for the ragged-offset overflow guard.
- Around line 391-399: Update the fake-kernel allocations in
python/cudnn/sdpa/fwd/torch_op.py:391-399 to mirror the real o_stride selection
used around lines 272-275, allocating o with torch.empty_strided for both THD
and non-THD layouts, and make empty stats placeholders use float32 on q.device.
Also update python/cudnn/sdpa/fwd/torch_op.py:660-678 to replace
torch.empty_like(k) and torch.empty_like(v) with contiguous torch.empty
allocations using q.dtype, matching the freshly allocated gradients returned
around lines 628-629.
- Around line 686-728: Complete the frontend-only API packaging for sdpa_ex: add
its APIBase subclass and wrapper around the existing sdpa_ex implementation,
register a lazy export in python/cudnn/__init__.py, add documentation under
docs/fe-oss-apis/, and move or add pytest coverage under test/python/fe_api/.
Ensure the public wrapper preserves sdpa_ex’s current arguments and return
behavior.
- Line 62: Update the O assignment in the SDPA forward implementation to add an
inline Ruff E741 suppression, keeping the existing name and value unchanged.
- Around line 170-172: Validate the `causal_bottom_right` combination before
constructing `alignment`: reject calls where `is_causal` is false and
`window_left` is negative, since both `lb` and `rb` are unset and the flag would
be inert. Raise the existing argument-validation error type with a clear
message; preserve current behavior for all other combinations.
- Around line 475-493: Update _sdpa_bwd_ex_impl to explicitly reject backward
execution when attention sinks are present, since g.sdpa_backward cannot consume
them. Add the sinks argument to the backward schema and _sdpa_bwd_ex_fake so the
guard is reachable, and raise a clear error rather than computing incorrect dq,
dk, or dv.
- Around line 21-22: Update the module docstring in torch_op.py to state that
backward support is implemented and registered through cudnn::sdpa_bwd_ex, while
documenting that the backward contract is restricted to THD inputs. Remove the
forward-only wording and preserve the existing forward behavior description.
- Around line 337-343: Update both real and fake kernel paths around the THD and
non-THD `stats` allocations so the `return_lse=False` placeholder is created on
`q.device` with the expected stats dtype, matching the device and dtype of the
`return_lse=True` output. Apply the same change to the fake kernel definitions
to keep compiled output metadata consistent with the CUDA implementation.
- Around line 561-563: Update the lse handling to call contiguous() on the
original lse tensor before reshaping it to (B, H_q, S_q, 1), preserving the
existing defensive normalization while ensuring the base-pointer alignment
requirement is applied before reshape.
- Around line 686-703: Update sdpa_ex to reject calls when query, key, or value
requires gradients, since cudnn::sdpa_fwd_ex has no registered autograd formula.
Add the guard at the start of sdpa_ex before invoking the forward operation,
while preserving inference behavior for non-grad inputs; do not wire sdpa_bwd_ex
in this change.
- Around line 627-629: Update the SDPA forward/backward dtype validation around
_build_graph and the gradient allocations so q, k, and v must share the same
supported dtype before deriving io_dtype; raise a clear validation error for
mismatches or unsupported dtypes instead of allowing a mapping KeyError.
Allocate dq, dk, and dv using the validated common dtype, preserving the
existing device and shape behavior.
- Around line 70-74: Update _get_handle and the surrounding execution path to
use a thread-local cuDNN handle per device, or otherwise serialize all handle
rebinding and g.execute calls so concurrent threads cannot share a handle.
Synchronize first-use _graph_cache construction as well, preserving correct
stream association for every execution.

In `@test/python/test_cudnn_sdpa_ex_ops.py`:
- Line 36: In test/python/test_cudnn_sdpa_ex_ops.py, update the shape unpacking
at lines 36 and 168 to use `_` for the unused D and T bindings respectively;
leave the other destructured values unchanged.
- Around line 154-160: Extend the reference path used by _ref in the causal
sdpa_fwd_ex test to produce expected fp32 LSE values, then compare the returned
packed lse tensor against that reference using the existing dtype-appropriate
tolerance conventions. Retain the current shape, dtype, and finiteness checks
while adding value validation for every LSE entry.
- Around line 23-24: Extend the module-level gate before the torch_op import to
validate the cuDNN version, GPU compute capability, and BF16 support using
cudnn.backend_version() and torch.cuda.get_device_capability(). Skip the
extended SDPA tests when these requirements are unsupported, while retaining the
existing CUDA availability check.

---

Nitpick comments:
In `@python/cudnn/sdpa/fwd/torch_op.py`:
- Around line 43-44: Bound the global _graph_cache used by the cuDNN graph
builder so it evicts least-recently-used entries after a fixed maximum size.
Update the cache access and insertion logic around _graph_cache to refresh
recency on hits and remove the oldest graph, including its execution-plan state,
when the limit is exceeded.
- Around line 451-466: In _build_bwd_graph, document that the current backward
implementation only reaches the THD path because _sdpa_bwd_ex_impl rejects dense
inputs and invokes it with is_thd=True. If dense backward is not being added
now, remove the unreachable is_thd branching, including the guard and dependent
use_padding_mask and max_total_seq_len conditionals; otherwise retain the
parameter but add a short comment identifying the current restriction.
- Around line 350-361: Retain the bound T_q and T_kv values in the setup around
the existing sequence-shape handling, then validate that every computed ragged
element offset fits within the signed int32 range before populating the variant.
Guard the products used by RAGGED_Q, RAGGED_KV, RAGGED_V, RAGGED_O, and
RAGGED_STATS, raising the established argument/validation error when any packed
token count and stride would exceed INT32_MAX; keep cuDNN offsets int32 rather
than widening them.
- Around line 487-488: Update the SDPA forward operation configuration to pass
total_q directly as max_total_seq_len_q and total_kv directly as
max_total_seq_len_kv when is_thd, removing the _round64 alignment for these
workspace-sizing attributes while preserving the existing non-THD None behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2605e9b1-7ebf-406d-a6ab-0f0fab19f8ca

📥 Commits

Reviewing files that changed from the base of the PR and between 3f17f5b and 0802143.

📒 Files selected for processing (2)
  • python/cudnn/sdpa/fwd/torch_op.py
  • test/python/test_cudnn_sdpa_ex_ops.py

Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
Comment thread python/cudnn/sdpa/fwd/torch_op.py
Comment thread python/cudnn/sdpa/fwd/torch_op.py
Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
Comment on lines +686 to +728
def sdpa_ex(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
*,
scale: Optional[float] = None,
is_causal: bool = False,
causal_bottom_right: bool = False,
window_left: int = -1,
sinks: Optional[torch.Tensor] = None,
seq_len_q: Optional[torch.Tensor] = None,
seq_len_kv: Optional[torch.Tensor] = None,
cu_seqlens_q: Optional[torch.Tensor] = None,
cu_seqlens_kv: Optional[torch.Tensor] = None,
max_seqlen_q: int = 0,
max_seqlen_kv: int = 0,
return_lse: bool = False,
):
"""cuDNN SDPA forward with the extended feature surface (see module docstring).

Returns ``o`` or ``(o, lse)`` when ``return_lse=True``.
"""
import math

attn_scale = scale if scale is not None else 1.0 / math.sqrt(query.shape[-1])
o, lse = torch.ops.cudnn.sdpa_fwd_ex(
query,
key,
value,
attn_scale,
is_causal=is_causal,
causal_bottom_right=causal_bottom_right,
window_left=window_left,
sinks=sinks,
seq_len_q=seq_len_q,
seq_len_kv=seq_len_kv,
cu_seqlens_q=cu_seqlens_q,
cu_seqlens_kv=cu_seqlens_kv,
max_seqlen_q=max_seqlen_q,
max_seqlen_kv=max_seqlen_kv,
return_lse=return_lse,
)
return (o, lse) if return_lse else o

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Missing artifacts required for a new frontend-only Python API.

The coding guidelines require every new frontend-only Python API under python/cudnn/ to ship four artifacts. sdpa_ex currently has none of them:

  • No APIBase subclass and wrapper.
  • No lazy export in python/cudnn/__init__.py. The module docstring at lines 24-25 confirms this.
  • No documentation under docs/fe-oss-apis/.
  • Tests live at test/python/test_cudnn_sdpa_ex_ops.py, not under test/python/fe_api/.

The PR description lists documentation and the lazy-table export as follow-ups. Track the APIBase wrapper and the test relocation as well, so the prototype does not graduate without them.

As per coding guidelines: "Every new frontend-only Python API must include an APIBase subclass and wrapper, a lazy export in python/cudnn/__init__.py, documentation under docs/fe-oss-apis/, and pytest coverage under test/python/fe_api/."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 686 - 728, Complete the
frontend-only API packaging for sdpa_ex: add its APIBase subclass and wrapper
around the existing sdpa_ex implementation, register a lazy export in
python/cudnn/__init__.py, add documentation under docs/fe-oss-apis/, and move or
add pytest coverage under test/python/fe_api/. Ensure the public wrapper
preserves sdpa_ex’s current arguments and return behavior.

Source: Coding guidelines

Comment thread test/python/test_cudnn_sdpa_torch_ops.py
(the cuDNN diagonal_band_left_bound convention). sinks: (H,) extra softmax
logit per query head, contributing no value."""
q, k, v = q.float(), k.float(), v.float()
B, Hq, Sq, D = q.shape

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove unused destructured values.

Ruff reports RUF059 for both bindings.

  • test/python/test_cudnn_sdpa_ex_ops.py#L36-L36: replace D with _.
  • test/python/test_cudnn_sdpa_ex_ops.py#L168-L168: replace T with _.
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 36-36: Unpacked variable D is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

📍 Affects 1 file
  • test/python/test_cudnn_sdpa_ex_ops.py#L36-L36 (this comment)
  • test/python/test_cudnn_sdpa_ex_ops.py#L168-L168
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/python/test_cudnn_sdpa_ex_ops.py` at line 36, In
test/python/test_cudnn_sdpa_ex_ops.py, update the shape unpacking at lines 36
and 168 to use `_` for the unused D and T bindings respectively; leave the other
destructured values unchanged.

Source: Linters/SAST tools

Comment thread test/python/test_cudnn_sdpa_ex_ops.py Outdated
Comment on lines +154 to +160
o, lse = torch.ops.cudnn.sdpa_fwd_ex(
q, k, v, D**-0.5, is_causal=True,
cu_seqlens_q=cu, cu_seqlens_kv=cu, max_seqlen_q=mx, max_seqlen_kv=mx, return_lse=True,
) # fmt: skip
ref, _, _, _ = self._ref(q, k, v, cu, is_causal=True)
assert (o.float() - ref).abs().max().item() < TOL
assert lse.shape == (T, H, 1) and lse.dtype == torch.float32 and torch.isfinite(lse).all()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Compare returned LSE values with the fp32 reference.

Lines 159-160 validate only metadata and finiteness. Incorrect finite LSE values will pass. Extend _ref or add an LSE reference, then compare the packed lse values with dtype-appropriate tolerances.

As per coding guidelines, “Compare test results against a reference implementation using existing reference-module patterns and dtype-appropriate tolerances.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/python/test_cudnn_sdpa_ex_ops.py` around lines 154 - 160, Extend the
reference path used by _ref in the causal sdpa_fwd_ex test to produce expected
fp32 LSE values, then compare the returned packed lse tensor against that
reference using the existing dtype-appropriate tolerance conventions. Retain the
current shape, dtype, and finiteness checks while adding value validation for
every LSE entry.

Source: Coding guidelines

@vedaanta
vedaanta force-pushed the vagarwalla/sdpa-ex-torch-ops branch 3 times, most recently from 41beced to b9e8330 Compare August 11, 2026 16:48
@vedaanta vedaanta changed the title sdpa: add extended torch custom ops (sdpa_fwd_ex / sdpa_bwd_ex) sdpa: add torch custom ops cudnn::sdpa_fwd / cudnn::sdpa_bwd Aug 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cudnn/experimental/ops/sdpa.py`:
- Around line 599-602: Update the downstream calls in
test_cudnn_sdpa_torch_ops.py to use torch.ops.cudnn.sdpa_bwd_legacy, matching
the registered operator declaration in the sdpa operation schema. Retain the
sdpa_bwd name only if an explicitly schema-compatible alias is required.

In `@test/python/test_cudnn_sdpa_torch_ops.py`:
- Around line 67-68: Add an L0 test under TestSdpaFwdExDense that supplies
unequal per-batch seq_len_q and seq_len_kv values to the dense SDPA operation,
masks invalid KV positions in the reference implementation, and compares outputs
only for valid Q positions using the suite’s existing reference-module pattern
and dtype-appropriate tolerances.
- Around line 76-78: Extend the reference helper _ref to return packed
log-sum-exp values, then update the dense and THD SDPA tests around sdpa_fwd to
request LSE output instead of disabling it. Compare each implementation’s LSE
against the reference using dtype-appropriate tolerances, while retaining the
existing output comparisons and finite-value checks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: baf0a09f-d8d9-42e7-b51f-88cdb6e6a2e7

📥 Commits

Reviewing files that changed from the base of the PR and between 0802143 and b9e8330.

📒 Files selected for processing (3)
  • python/cudnn/experimental/ops/sdpa.py
  • python/cudnn/sdpa/fwd/torch_op.py
  • test/python/test_cudnn_sdpa_torch_ops.py

Comment on lines +599 to +602
# Renamed from cudnn::sdpa_bwd: the canonical name now belongs to the
# consolidated op family in cudnn.sdpa.fwd.torch_op (this experimental
# module is slated to fold into it).
"sdpa_bwd_legacy(Tensor dO, Tensor q, Tensor k, Tensor v, Tensor o, Tensor stats, "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Update the stale downstream operator call.

The declaration now registers cudnn::sdpa_bwd_legacy, but test/python/test_cudnn_sdpa_torch_ops.py still calls torch.ops.cudnn.sdpa_bwd. Update that test to use torch.ops.cudnn.sdpa_bwd_legacy, or preserve sdpa_bwd only if a schema-compatible alias is required.

Proposed test fix
-        dq, dk, dv = torch.ops.cudnn.sdpa_bwd(
+        dq, dk, dv = torch.ops.cudnn.sdpa_bwd_legacy(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/experimental/ops/sdpa.py` around lines 599 - 602, Update the
downstream calls in test_cudnn_sdpa_torch_ops.py to use
torch.ops.cudnn.sdpa_bwd_legacy, matching the registered operator declaration in
the sdpa operation schema. Retain the sdpa_bwd name only if an explicitly
schema-compatible alias is required.

Comment thread test/python/test_cudnn_sdpa_torch_ops.py Outdated
Comment thread test/python/test_cudnn_sdpa_torch_ops.py Outdated
@vedaanta

Copy link
Copy Markdown
Collaborator Author

All CodeRabbit findings triaged and addressed in 98331fe18 (single atomic commit; ops renamed to the canonical cudnn::sdpa_fwd / sdpa_bwd per maintainer direction). Point by point:

Fixed — correctness:

  • contiguous() cannot repair a misaligned base (Critical)_normalize_thd and the LSE path use clone(memory_format=contiguous_format), which always allocates; the LSE guard also checks data_ptr() % 16 and normalizes before reshape.
  • Fake kernels reported strides the real kernels don't produce → the fwd fake mirrors the real O-stride selection (BSHD-physical detection); the bwd fake returns fresh contiguous empties in q.dtype instead of empty_like over possibly-non-contiguous inputs. Locked in by a new opcheck test (dense + THD, including dynamic-shape AOT dispatch).
  • Backward silently wrong after a sink forwardsdpa_bwd now takes sinks and raises NotImplementedError when set (dSink is a follow-up), so the failure is loud.
  • sdpa_fwd not differentiableregister_autograd wired for the varlen path; the packed-TH1→padded-LSE conversion is fully device-side (no .item()/host reads — this is what makes the dynamic-shape opcheck pass). Dense/sink autograd raise clearly. End-to-end autograd test added.
  • k never shape-validated → shape checks on both paths (plus o/grad_out in bwd).
  • Gradient dtypes vs single io_dtype → one io dtype per call enforced (_check_io_dtypes, friendly error for unsupported dtypes); dk/dv allocate in q.dtype.
  • Handle thread-safety → thread-local handles (one cuDNN handle must not be used from two threads), graph builds serialized under a lock, cached execution lock-free.
  • CPU torch.empty(0) placeholders → pinned to q.device, fp32, in real and fake kernels.
  • Inert causal_bottom_right → rejected when no diagonal band is active.
  • int32 ragged-offset overflow → host-side guards (tokens x token-stride <= INT32_MAX) on both paths, no device sync.

Fixed — hygiene/tests:

  • docstring documents the backward + autograd contract; E741 O suppressed inline (renaming would reduce clarity, as suggested); RUF059 unused bindings fixed; the unreachable dense branch of _build_bwd_graph removed; graph cache bounded (FIFO, 128); tests gated on sm80+ and cuDNN ≥ 9.6; LSE value comparisons added vs the fp32 reference (dense incl. sinks-in-denominator, and packed THD); padded dense-batch test added; docs page added (docs/fe-oss-apis/sdpa-torch-ops.md).

Skipped, with reasons:

  • max_total_seq_len_q/kv should be actual token counts — they already are: the actual packed totals, rounded up to the backend's 64-token accumulator granularity (now commented).
  • Stale torch.ops.cudnn.sdpa_bwd call in the test — not stale: the test targets THIS module's cudnn::sdpa_bwd; it is the experimental module's op that was renamed (sdpa_bwd_legacy) to free the canonical name.
  • APIBase subclass + fe_api/ test location — deliberate deviation: these are torch.library custom ops, not an OSS eager-API; torch.library (schema/fake/autograd/opcheck) is the wrapper layer here, and the tests exercise torch dispatch. Docs + gating + reference-comparison guidelines are all now met.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/fe-oss-apis/sdpa-torch-ops.md`:
- Around line 21-35: Add a lazy public export for the SDPA torch wrapper in the
cudnn package initializer, ensuring torch and cutlass are imported only when the
exported API is accessed. Update the documentation example to import and invoke
that public entry point instead of importing cudnn.sdpa.fwd.torch_op directly,
while preserving the existing operator usage.

In `@python/cudnn/sdpa/fwd/torch_op.py`:
- Around line 662-682: Update the backward graph cache key in the sdpa_bwd key
construction to include the rounded packed token totals for T_q and T_kv, using
the same 64-token bucket calculation as _build_bwd_graph. Preserve the existing
key fields and ensure both totals participate in cache identity before the plan
is reused.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 570da5ff-3749-4643-9dc8-0545fcf56e6f

📥 Commits

Reviewing files that changed from the base of the PR and between b9e8330 and 98331fe.

📒 Files selected for processing (3)
  • docs/fe-oss-apis/sdpa-torch-ops.md
  • python/cudnn/sdpa/fwd/torch_op.py
  • test/python/test_cudnn_sdpa_torch_ops.py

Comment thread docs/fe-oss-apis/sdpa-torch-ops.md
Comment thread python/cudnn/sdpa/fwd/torch_op.py
@vedaanta

Copy link
Copy Markdown
Collaborator Author

Addendum — two more contract fixes landed in the same commit after re-running the upstream PyTorch suites against these ops (they were previously masked by the experimental dense module, which the provider no longer uses):

  • h_k != h_v support: cuDNN allows K and V to carry independent head counts (each dividing H_q); the ops previously borrowed H_kv from v for the K descriptor. K/V/dK/dV now use separate H_k/H_v end to end, with divisibility validation. Covered by test_gqa_hk_ne_hv (mirrors upstream test_cudnn_attention_gqa, which passes again).
  • Output adopts the query's layout permutation: the dense O-stride selection generalized from a binary BSHD/BHSD choice to Q's full dim-permutation (any B/H/S order, D innermost; broadcast inputs fall back to contiguous), with the fake kernel mirroring it — upstream test_cudnn_attention_preserves_query_layout (all 6 permutations × eager/compile) passes. Covered by test_output_adopts_query_layout.

Upstream verification on the final tip: test_transformers.py -k cudnn 23 pass / 0 fail; test_varlen_attention.py through the #554 provider 140 pass / 29 fail (all impl-identity spy asserts or paged cross-impl comparisons). Op suite: 19/19 incl. opcheck (dense + THD dynamic-shape).

@vedaanta
vedaanta force-pushed the vagarwalla/sdpa-ex-torch-ops branch from 98331fe to 1877d67 Compare August 11, 2026 19:07
@vedaanta
vedaanta requested a review from Anerudhan August 11, 2026 20:39


@torch.library.register_fake("cudnn::sdpa_bwd")
@torch.library.register_fake("cudnn::sdpa_bwd_legacy")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you fix the name about legacy ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in be0cf75 — the implementation identifiers now match the registered name: _sdpa_bwd_legacy_impl / _sdpa_bwd_legacy_fake. (The whole experimental module is slated for removal once dense backward lands in cudnn::sdpa_bwd.)

Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
if handles is None:
handles = _tls.handles = {}
if device not in handles:
handles[device] = cudnn.create_handle()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be we should guard it with the device contenxt. It silently creates on current GPU

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in be0cf75 — handle creation is pinned with with torch.cuda.device(device): so a tensor on cuda:1 never gets a handle silently created against the caller's current device.

Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
# cuDNN ragged offsets are ELEMENT offsets per tensor, so each scales
# its token prefix sums by that tensor's OWN token stride. Small
# on-stream int ops — CUDA-graph-capture safe.
variant[int(_UIDs.RAGGED_Q)] = _int32_col(cu_seqlens_q * q.stride(0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should confirm cu_seqlen_q is on device ? And _int64 as pointer ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both done in be0cf75: _check_cu_seqlens validates that cu_seqlens_q/kv live on q's device before they are bound (a CPU tensor would hand cuDNN a host address), and the offsets are int64 pointers now (INT64 graph declarations + _int64_col binding).

Comment thread python/cudnn/sdpa/fwd/torch_op.py Outdated
S_q, S_kv = max_seqlen_q, max_seqlen_kv
# cuDNN ragged offsets are int32 ELEMENT offsets: the largest offset
# (total tokens x token stride) must fit.
for name, t, total in (("q", q, T_q), ("k", k, T_kv), ("v", v, T_kv)):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We fixed it for int64? ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in be0cf75 — ragged offsets are now int64 end to end: the graph declares all 12 ragged-offset tensors as INT64 and the variant binds _int64_col columns (matching the test_mhas harness convention). The int32 overflow guards are gone — int64 element offsets cannot realistically overflow. Verified on both engine routes (FROST + backend), 19/19.

Family-local torch contract for the features
torch.nn.functional.scaled_dot_product_attention cannot express: attention
sinks, sliding window, bottom-right causal, padded batches, and THD/varlen
packing (FA-style (T,H,D) + cu_seqlens). The ops build pygraph
sdpa/sdpa_backward nodes; the Router picks the serving plan (FROST OSS
kernels or backend engines) per config.

Contract highlights:
- register_fake meta kernels mirror the real kernels' output strides;
  torch.library.opcheck passes on both paths, including dynamic-shape AOT
  dispatch (torch.compile contract), and is locked in by a test.
- sdpa_fwd is differentiable on the varlen path via register_autograd; the
  glue converts packed TH1 stats to the padded LSE layout device-side (no
  host reads, capture/tracing-safe). Dense and sink backward raise
  NotImplementedError until their engine contracts land.
- Thread-safe: thread-local cuDNN handles (a handle must not be used from
  two threads), serialized graph builds, bounded (FIFO) graph cache.
- Validation: one io dtype per call, k/o/grad_out shape checks, int32
  ragged-offset overflow guards, inert-flag rejection
  (causal_bottom_right without an active band), clone() not contiguous()
  for base-pointer realignment (contiguous() cannot fix a misaligned base).

cudnn::sdpa_fwd / cudnn::sdpa_bwd are the canonical names; the experimental
dense module's backward is renamed cudnn::sdpa_bwd_legacy so both modules
coexist in one process until it is removed.

Tests (14, L0): sinks/window/bottom-right/padded dense with LSE value
checks against an fp32 reference; THD fwd/bwd incl. GQA, kv-interleaved
views, end-to-end autograd; opcheck. Docs: docs/fe-oss-apis/sdpa-torch-ops.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vedaanta
vedaanta force-pushed the vagarwalla/sdpa-ex-torch-ops branch from 1877d67 to be0cf75 Compare August 12, 2026 19:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/cudnn/__init__.py (1)

328-337: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Register jax in _LAZY_OPTIONAL_IMPORTS.

The dedicated jax branch bypasses _load_optional_symbol, which adds _OPTIONAL_DEPENDENCY_INSTALL_HINT to import failures. Add "jax": (".jax", None) to _LAZY_OPTIONAL_IMPORTS and remove the dedicated branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/__init__.py` around lines 328 - 337, Update
`_LAZY_OPTIONAL_IMPORTS` to include `"jax": (".jax", None)`, then remove the
dedicated `if name == "jax"` branch so `__getattr__` routes JAX through
`_load_optional_symbol` and preserves the existing lazy import behavior and
installation hint handling.

Sources: Coding guidelines, Learnings

🧹 Nitpick comments (3)
python/cudnn/sdpa/fwd/torch_op.py (2)

648-658: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Backward accepts an inert causal_bottom_right.

_sdpa_fwd_impl rejects causal_bottom_right=True when neither is_causal nor window_left >= 0 is set (line 333). _sdpa_bwd_impl accepts it. alignment then re-anchors no band, so the flag has no effect and the two ops disagree on the same argument set. Repeat the forward check here so a mismatched backward call fails instead of computing a different mask than intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 648 - 658, The _sdpa_bwd_impl
validation must reject causal_bottom_right=True when neither is_causal nor
window_left >= 0 is enabled, matching the existing _sdpa_fwd_impl check. Add
this guard alongside the other backward argument validations so unsupported
alignment does not proceed with an unanchored mask.

364-377: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

The dense path skips the alignment and dense-innermost guard.

_normalize_thd enforces stride(-1) == 1 and a 16B-aligned base pointer for THD inputs. The dense branch passes q, k, and v through unchecked. A sliced dense view with an odd element storage offset then reaches the kernels with a misaligned address, which is the exact failure _normalize_thd documents at lines 163-167.

Apply an alignment-only normalization here, or validate and raise.

♻️ Proposed guard
         B, H_q, S_q, D_qk = q.shape
         _, H_v, S_kv, D_v = v.shape
         H_k = k.shape[1]
+        for name, t in (("q", q), ("k", k), ("v", v)):
+            if t.data_ptr() % 16:
+                raise ValueError(f"{name} base pointer must be 16B-aligned for the cuDNN descriptors; got offset {t.storage_offset()}")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 364 - 377, Update the
dense-path handling around q_stride, k_stride, and v_stride to enforce
dense-innermost layout and 16-byte base-pointer alignment for q, k, and v,
matching the guarantees provided by _normalize_thd. Normalize eligible views or
raise a clear validation error before kernel dispatch when alignment or stride
requirements are not met.
python/cudnn/__init__.py (1)

251-251: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Expose only explicit GEMM symbols through cudnn.

Remove the attr_name=None entries for grouped_gemm and discrete_grouped_gemm. Returning the package module exposes internal scheduler and utility submodules through cudnn; __all__ does not restrict direct attribute access. Register only the public classes and wrappers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/__init__.py` at line 251, Update the `cudnn` registration
entries for `grouped_gemm` and `discrete_grouped_gemm` to specify only their
explicit public GEMM classes or wrappers, removing any `attr_name=None` values.
Preserve the existing module paths while preventing direct exposure of internal
scheduler and utility submodules through `cudnn`.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cudnn/sdpa/fwd/torch_op.py`:
- Around line 774-780: Update the backward ragged-offset construction around the
_UIDs.RAGGED_Q through _UIDs.RAGGED_DV entries by converting cu_seqlens_q and
cu_seqlens_kv to torch.int64 once before these multiplications. Reuse the
widened prefix-sum tensors for all seven entries, preserving the existing
_int64_col conversions and stride/shape calculations.

---

Outside diff comments:
In `@python/cudnn/__init__.py`:
- Around line 328-337: Update `_LAZY_OPTIONAL_IMPORTS` to include `"jax":
(".jax", None)`, then remove the dedicated `if name == "jax"` branch so
`__getattr__` routes JAX through `_load_optional_symbol` and preserves the
existing lazy import behavior and installation hint handling.

---

Nitpick comments:
In `@python/cudnn/__init__.py`:
- Line 251: Update the `cudnn` registration entries for `grouped_gemm` and
`discrete_grouped_gemm` to specify only their explicit public GEMM classes or
wrappers, removing any `attr_name=None` values. Preserve the existing module
paths while preventing direct exposure of internal scheduler and utility
submodules through `cudnn`.

In `@python/cudnn/sdpa/fwd/torch_op.py`:
- Around line 648-658: The _sdpa_bwd_impl validation must reject
causal_bottom_right=True when neither is_causal nor window_left >= 0 is enabled,
matching the existing _sdpa_fwd_impl check. Add this guard alongside the other
backward argument validations so unsupported alignment does not proceed with an
unanchored mask.
- Around line 364-377: Update the dense-path handling around q_stride, k_stride,
and v_stride to enforce dense-innermost layout and 16-byte base-pointer
alignment for q, k, and v, matching the guarantees provided by _normalize_thd.
Normalize eligible views or raise a clear validation error before kernel
dispatch when alignment or stride requirements are not met.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: dbe6014a-d59d-4b4d-b977-c37e5415e439

📥 Commits

Reviewing files that changed from the base of the PR and between 1877d67 and be0cf75.

📒 Files selected for processing (4)
  • docs/fe-oss-apis/sdpa-torch-ops.md
  • python/cudnn/__init__.py
  • python/cudnn/experimental/ops/sdpa.py
  • python/cudnn/sdpa/fwd/torch_op.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/fe-oss-apis/sdpa-torch-ops.md
  • python/cudnn/experimental/ops/sdpa.py

Comment on lines +774 to +780
int(_UIDs.RAGGED_Q): _int64_col(cu_seqlens_q * q.stride(0)),
int(_UIDs.RAGGED_KV): _int64_col(cu_seqlens_kv * k.stride(0)),
int(_UIDs.RAGGED_V): _int64_col(cu_seqlens_kv * v.stride(0)),
int(_UIDs.RAGGED_O): _int64_col(cu_seqlens_q * o.stride(0)),
int(_UIDs.RAGGED_DQ): _int64_col(cu_seqlens_q * (H_q * D_qk)),
int(_UIDs.RAGGED_DK): _int64_col(cu_seqlens_kv * (H_k * D_qk)),
int(_UIDs.RAGGED_DV): _int64_col(cu_seqlens_kv * (H_v * D_v)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Same int32 ragged-offset overflow on the backward path.

All seven ragged-offset entries multiply an int32 cu_seqlens_* tensor by a stride before _int64_col widens the result. Cast the two prefix-sum tensors with .to(torch.int64) once, then multiply.

[skip_comment]

⛔ Skipped due to learnings
Learnt from: YangXu1990uiuc
Repo: NVIDIA/cudnn-frontend PR: 509
File: python/cudnn/sdpa/fwd/kernels/prefill_fp8_sm120.py:1218-1224
Timestamp: 2026-08-09T19:32:05.011Z
Learning: In `python/cudnn/sdpa/fwd/kernels/prefill_fp8_sm120.py`, `cutlass.Vector` vectorizes `prims.mul_packed_f32x2` when `fmul2` receives the eight-element `cutlass.Array` slice `o_regs[o_off:8]`. Do not assume that the `Tuple[Float32, Float32]` annotation limits this operation to one pair.
Learnt from: YangXu1990uiuc
Repo: NVIDIA/cudnn-frontend PR: 509
File: python/cudnn/sdpa/fwd/api_dsl.py:2018-2024
Timestamp: 2026-08-09T19:32:34.987Z
Learning: In `python/cudnn/sdpa/fwd/api_dsl.py`, FP8 Amax buffers are written in place by kernel atomics. The shared `SdpaFwdDsl._amax_slot()` helper must use `tensor.view(-1)[:1]`, not `reshape()`, so non-contiguous caller buffers raise `ValueError` instead of creating a detached copy. The SM100 and SM120 per-tensor FP8 paths use this helper.
Learnt from: CR
Repo: NVIDIA/cudnn-frontend PR: 0
File: python/cudnn/AGENTS.md:0-0
Timestamp: 2026-07-31T21:15:22.165Z
Learning: Applies to python/cudnn/**/*.py : For FP4x2 packing, use `_tensor_shape` and `_tensor_stride`; when `interpret_uint8_as_fp4x2` is enabled, the innermost dimension is doubled.
Learnt from: YangXu1990uiuc
Repo: NVIDIA/cudnn-frontend PR: 509
File: python/cudnn/sdpa/fwd/api_dsl.py:1985-1991
Timestamp: 2026-08-09T19:32:36.684Z
Learning: In `python/cudnn/sdpa/fwd/engines.py`, `lower_dsl_prefill` clears `seq_q_lens_present` for FP8/MXFP8 before adapter-level checks. Therefore, FP8 handling for dense per-batch Q lengths must validate at execute time: allow `seq_len_q == S_q`, and reject shorter Q lengths to prevent writes of O and finite LSE values past the valid query range.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudnn/sdpa/fwd/torch_op.py` around lines 774 - 780, Update the
backward ragged-offset construction around the _UIDs.RAGGED_Q through
_UIDs.RAGGED_DV entries by converting cu_seqlens_q and cu_seqlens_kv to
torch.int64 once before these multiplications. Reuse the widened prefix-sum
tensors for all seven entries, preserving the existing _int64_col conversions
and stride/shape calculations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cat-feature Requests for new functionality, APIs, examples, or behavior improvements. mod-cutedsl CuTeDSL kernels, generated kernels, examples, or related integration work. mod-frontend cuDNN frontend APIs, operation graph construction, plans, and user-facing wrappers. orig-nv-eng Reported or requested by NVIDIA engineering.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants