Skip to content

frost(sdpa): strict lse/sink/seq-lens execute contract, no torch.empty in execute - #484

Merged
Aneureka merged 5 commits into
NVIDIA:developfrom
vedaanta:vagarwalla/frost-lse-sink-strict-gh
Aug 6, 2026
Merged

frost(sdpa): strict lse/sink/seq-lens execute contract, no torch.empty in execute#484
Aneureka merged 5 commits into
NVIDIA:developfrom
vedaanta:vagarwalla/frost-lse-sink-strict-gh

Conversation

@vedaanta

@vedaanta vedaanta commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

What

Replaces GitLab MR !2305 (closed in favor of this PR). execute() must match the init-time specialization exactly, and must never allocate or convert on the hot path.

  • Strict presence checks (SM100 + SM120, both directions) for sinks, LSE, and per-batch seq lengths. Previously a required-but-missing tensor was silently replaced by a zeros dummy — numerically wrong (a zero sink logit still adds exp(0) mass to the softmax denominator; zero seq lens mask every row), and a provided-but-uncompiled tensor was silently ignored. Both now raise ValueError.
  • SM120 kernel None-specializes lse/sinks (cutlass.const_expr): no sample_lse → the LSE store is compiled out, no buffer bound at all. New Capabilities.lse_optional lets the lowering skip the dummy-LSE workspace carve (dense stats-less SM120 workspace: b·h·s·4 → 0); SM120 THD scratch drops its packed-LSE and sinks-dummy chunks.
  • SM100: kernels still write LSE unconditionally (kernel-side has_lse specialization is a follow-up), so the standalone stats-less path keeps a dummy — cached once per device instead of torch.empty per execute. Dispatch path unchanged (workspace carve).
  • Validate, never convert: _checked_lse_view / _checked_sinks_1d / _checked_seq_lens enforce dtype (fp32 sinks/LSE, int32 seq lens), shape, and contiguity, and bind true views — the implicit .to(...)/reshape-copy kernel launches are gone. The graph analyzer rejects non-fp32 sink tokens and non-int32 seq lens at probe time.
  • Rule 1 in python/cudnn/AGENTS.md: starts a numbered, growing hard-rules list for agents; Rule 1 codifies this execute-contract discipline.

Testing

SM100 box (cc 10.0): full frost SDPA fwd suites (sm100 + sm120 + fp8 + mxfp8 + integration + analyzer) — 354 passed, 37 skipped, 0 failed (one pre-existing environment-only ranking failure deselected; it fails identically on the unmodified base). New contract tests: test_dsl_sm100_execute_sink_lse_contract (passes on SM100), test_dsl_sm120_execute_contract_mismatches (needs SM120 HW), analyzer rejection tests for non-fp32 sinks / non-int32 seq lens. All SM120 sink×lse specializations + THD JIT-compile clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • SM120 attention execution can omit LSE statistics when they are not requested, reducing workspace usage.
    • Optional LSE, sink, and sequence-length inputs are supported with compile-time specialization.
  • Bug Fixes

    • Added validation for tensor dtype, shape, size, contiguity, and execution compatibility.
    • Prevented unsupported optional inputs and implicit conversions during execution.
    • Corrected workspace sizing and handling for SM100 and SM120 execution paths.
  • Tests

    • Added coverage for input contracts, invalid dtypes, optional tensors, and workspace behavior.

vedaanta and others added 4 commits August 4, 2026 21:52
Init-time flags are compile-time kernel specializations, so execute must
match them exactly — silently substituting a zeros sink changes the softmax
denominator (a zero logit still adds exp(0) mass), and sinks passed to a
sink-less kernel were silently dropped. Both directions now raise, on SM100
and SM12x alike.

The SM12x kernel None-specializes lse and sinks (cutlass.const_expr): with
no sample_lse the LSE store is compiled out and no buffer — dummy or
otherwise — is bound. The new Capabilities.lse_optional flag lets
lower_dsl_prefill skip the dummy-LSE workspace carve for such adapters
(dense stats-less SM12x workspace is now 0), and the SM12x THD scratch
drops its packed-LSE and sinks-dummy chunks.

The SM100 kernels still write an LSE unconditionally (has_lse
specialization there is a follow-up), so the standalone stats-less path
keeps a dummy — but cached once per device instead of torch.empty per
execute; the FROST dispatch path continues to carve it from the caller's
workspace.

Caller-provided lse/sinks are now shape/dtype-validated at execute;
lse_tensor must additionally be contiguous, because the kernel writes
through the bound view and a silent reshape copy would swallow the output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The implicit sinks.to(torch.float32) allocated and launched a cast kernel
on the execute hot path (and broke CUDA-graph pointer stability) whenever
a caller passed non-fp32 sinks. The kernels only consume fp32 sink logits,
so make that a contract: _checked_sinks_1d validates dtype (+ contiguity,
since the flat (H_q,) rebind must stay a view) and the graph analyzer
rejects non-fp32 sink tokens up front, keeping dispatch failures at
check_support instead of execute.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…never cast

Same treatment as sinks: the implicit seq_lens.reshape(-1).to(torch.int32)
allocated and launched a cast kernel on the execute hot path for any
non-int32 or non-contiguous input, and presence was never checked against
the compiled specialization — a seq_kv_lens_present kernel executed without
lengths ran on a zeros dummy, masking every row (silently wrong output),
while lengths passed to a specialization compiled without them were
silently ignored.

_checked_seq_lens validates dtype/numel/contiguity and binds a true view;
_check_seq_lens_contract enforces presence both ways (THD requires both
tensors outright — they source the packed cu_seqlens metadata); the graph
analyzer rejects non-int32 seq_len tensors at probe time. The dispatch
lowering forwards seq_q only when the compiled specialization consumes it,
keeping the FP8/MXFP8 padded-Q-trim gap (not plumbed in those kernels) a
documented drop instead of a new execute-time error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ule 1: execute() validates, never converts or allocates

First entry of a growing, citable rule set distilled from review findings.
Rule 1 captures the lse/sink/seq-lens execute-contract work: strict
init/execute specialization matching, checked views instead of implicit
.to()/reshape copies, and workspace carving instead of per-execute
torch.empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4c408756-81ae-4c07-b50f-33996b42e3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 5b577aa and 4fa6afb.

📒 Files selected for processing (3)
  • python/cudnn/sdpa/fwd/api_dsl.py
  • test/python/sdpa/frost/test_sdpa_fwd_dsl_sm100.py
  • test/python/sdpa/frost/test_sdpa_fwd_dsl_sm120.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/python/sdpa/frost/test_sdpa_fwd_dsl_sm100.py
  • python/cudnn/sdpa/fwd/api_dsl.py

📝 Walkthrough

Walkthrough

SDPA forward execution now validates optional tensor contracts, rejects implicit conversions, and supports LSE-free SM120 specialization. SM100 retains dummy-LSE handling where required. Tests cover dtype, specialization, workspace, and execution behavior.

Changes

SDPA forward execution contracts

Layer / File(s) Summary
Input validation contracts
python/cudnn/AGENTS.md, python/cudnn/sdpa/graph_analyzer.py, python/cudnn/sdpa/fwd/api_dsl.py, test/python/sdpa/frost/test_sdpa_graph_analyzer.py
Execution and graph analysis validate dtypes, shapes, contiguity, and compile-time optional-operand contracts.
Optional LSE kernel specialization
python/cudnn/sdpa/fwd/engines.py, python/cudnn/sdpa/fwd/kernels/prefill_f16_sm120.py, python/cudnn/frost/README.md
SM120 supports optional LSE storage. Kernel compilation conditionally creates LSE tensors and writes. Workspace documentation reflects SM100 and SM120 contracts.
SM100 execution validation
python/cudnn/sdpa/fwd/api_dsl.py, test/python/sdpa/frost/test_sdpa_fwd_dsl_sm100.py
SM100 paths validate sinks, LSE, and sequence lengths before binding or workspace copying. Tests cover mismatched specializations and successful execution.
SM120 execution and workspace integration
python/cudnn/sdpa/fwd/api_dsl.py, test/python/sdpa/frost/test_sdpa_fwd_dsl_sm120.py
SM120 rejects incompatible optional operands, removes packed-LSE and sink-dummy workspace usage, and tests zero-workspace execution.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SdpaFwdDslSm120
  participant SM120FusedMultiHeadAttentionForward
  participant SM120Kernel
  Caller->>SdpaFwdDslSm120: execute optional tensors
  SdpaFwdDslSm120->>SdpaFwdDslSm120: validate specialization and tensor contracts
  SdpaFwdDslSm120->>SM120FusedMultiHeadAttentionForward: bind validated views
  SM120FusedMultiHeadAttentionForward->>SM120Kernel: launch with optional LSE
  SM120Kernel-->>Caller: attention output and optional LSE
Loading

Possibly related PRs

Suggested labels: cat-feature

Suggested reviewers: anerudhan

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: strict execute-time contracts and removal of per-execute torch.empty allocation.
Description check ✅ Passed The description clearly covers the changes, rationale, compatibility impact, and testing, but it omits several template headings and checklist items.
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.

@vedaanta
vedaanta requested a review from Aneureka August 5, 2026 05:29
@vedaanta
vedaanta marked this pull request as ready for review August 5, 2026 05:30

@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

🧹 Nitpick comments (1)
test/python/sdpa/frost/test_sdpa_fwd_dsl_sm100.py (1)

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

Add successful optional-binding coverage.

The failure assertions do not test valid sink binding. A regression that rejects every sink or misbinds a valid FP32 sink will pass this test.

Execute the sink-enabled API with lse_tensor=lse and sinks=sink. Compare o with _ref_sdpa_full(..., sinks=sink). Also execute the API created without sample_lse with a compatible lse_tensor, as documented in this test.

Proposed test additions
     with pytest.raises(ValueError, match="sinks must be float32"):
         api.execute(q_tensor=q, k_tensor=k, v_tensor=v, o_tensor=o, lse_tensor=lse, sinks=sink.to(torch.bfloat16))

+    api.execute(q_tensor=q, k_tensor=k, v_tensor=v, o_tensor=o, lse_tensor=lse, sinks=sink)
+    torch.cuda.synchronize()
+    torch.testing.assert_close(
+        o,
+        _ref_sdpa_full(q, k, v, scale=scale, is_causal=True, sinks=sink),
+        atol=5e-2,
+        rtol=3e-2,
+    )
+
     api = SdpaFwdDslSm100(sample_q=q, sample_k=k, sample_v=v, sample_o=o, is_causal=True)
     assert api.check_support()
     api.compile()
+    api.execute(q_tensor=q, k_tensor=k, v_tensor=v, o_tensor=o, lse_tensor=lse)
🤖 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/sdpa/frost/test_sdpa_fwd_dsl_sm100.py` around lines 312 - 342,
Extend the successful execution coverage for SdpaFwdDslSm100: after the existing
sink validation, execute the sink-enabled api with lse_tensor=lse and
sinks=sink, then compare o against _ref_sdpa_full using sinks=sink. Also execute
the specialization created without sample_lse with a compatible lse_tensor,
preserving its existing no-LSE path coverage.
🤖 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/api_dsl.py`:
- Around line 783-812: Reject non-null lse_tensor for THD in the SM100 execute
path before any LSE view or execution setup, since THD stats output is
unsupported. Apply the same validation to non-null sample_lse in the SM120
adapter’s execute path, alongside its existing execute-time checks. Ensure THD
compile-time has_lse=False is enforced consistently in both directions: THD
cannot receive stats output, and non-THD behavior remains unchanged.

In `@test/python/sdpa/frost/test_sdpa_graph_analyzer.py`:
- Around line 233-252: Add an appropriate L0 test-level marker to both newly
added graph-analysis tests, including test_probe_rejects_non_int32_seq_len and
the test at the referenced additional location, since neither requires GPU
execution.

---

Nitpick comments:
In `@test/python/sdpa/frost/test_sdpa_fwd_dsl_sm100.py`:
- Around line 312-342: Extend the successful execution coverage for
SdpaFwdDslSm100: after the existing sink validation, execute the sink-enabled
api with lse_tensor=lse and sinks=sink, then compare o against _ref_sdpa_full
using sinks=sink. Also execute the specialization created without sample_lse
with a compatible lse_tensor, preserving its existing no-LSE path coverage.
🪄 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: 92b0446a-3a4e-4e3d-afd6-ded4535cc9d4

📥 Commits

Reviewing files that changed from the base of the PR and between b7ddaae and 5b577aa.

📒 Files selected for processing (9)
  • python/cudnn/AGENTS.md
  • python/cudnn/frost/README.md
  • python/cudnn/sdpa/fwd/api_dsl.py
  • python/cudnn/sdpa/fwd/engines.py
  • python/cudnn/sdpa/fwd/kernels/prefill_f16_sm120.py
  • python/cudnn/sdpa/graph_analyzer.py
  • test/python/sdpa/frost/test_sdpa_fwd_dsl_sm100.py
  • test/python/sdpa/frost/test_sdpa_fwd_dsl_sm120.py
  • test/python/sdpa/frost/test_sdpa_graph_analyzer.py

Comment thread python/cudnn/sdpa/fwd/api_dsl.py
Comment on lines +233 to +252
def test_probe_rejects_non_int32_seq_len():
"""The kernels consume per-batch lengths as int32 directly — no implicit
cast anywhere on the execute path — so an int64 seq_len is ineligible."""
g = _mk_graph()
q, k, v, dims, strides = _mk_qkv(g)
seq_kv = g.tensor(dim=(B, 1, 1, 1), stride=(1, 1, 1, 1), data_type=cudnn.data_type.INT64, name="seq_kv64")
o, _ = g.sdpa(
name="s",
q=q,
k=k,
v=v,
attn_scale=0.1,
is_inference=True,
use_padding_mask=True,
seq_len_kv=seq_kv,
)
_finish_output(o, dims, strides)
assert not _eligible(g)


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

Add a test level marker to both new tests.

Mark each test with an L0-L4 level. These graph-analysis tests should use L0 if they do not require GPU execution.

As per coding guidelines: “Mark every new Python test with a level from L0 through L4.”

Also applies to: 346-359

🤖 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/sdpa/frost/test_sdpa_graph_analyzer.py` around lines 233 - 252,
Add an appropriate L0 test-level marker to both newly added graph-analysis
tests, including test_probe_rejects_non_int32_seq_len and the test at the
referenced additional location, since neither requires GPU execution.

Source: Coding guidelines

@Aneureka Aneureka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM overall; left a minor comment regarding the behavior for user-provided LSE tensor when using THD.

Comment thread python/cudnn/sdpa/fwd/api_dsl.py
…ng them

THD stats are not plumbed: the kernels emit a head-major packed (1, H, T)
LSE into api-level scratch, which does not match cuDNN's ragged Stats
contract, so a caller's sample_lse/lse_tensor was never written. SM100 was
doubly wrong — it REQUIRED lse_tensor when sample_lse was given, then
dropped it. check_support now rejects thd + sample_lse up front on both
adapters, and the SM100 execute THD branch raises on a provided lse_tensor
(matching SM120). Dispatch is unaffected: Capabilities.thd_stats=False
already keeps THD+generate_stats graphs from reaching the adapters.

Addresses Haobin's review comment on PR NVIDIA#484.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vedaanta vedaanta added this to the Frontend 1.27.0 milestone Aug 5, 2026
@vedaanta

vedaanta commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

@cudnn-ci-bot run

@Aneureka

Aneureka commented Aug 6, 2026

Copy link
Copy Markdown
Member

@cudnn-ci-bot run frost

@cudnn-ci-bot

Copy link
Copy Markdown

🚀 Running mirror pipeline

Branch: cudnn-gh/pr-484-4fa6afb
Pipeline: 61324068
Targets: frost

@Aneureka

Aneureka commented Aug 6, 2026

Copy link
Copy Markdown
Member

No new failures found; will merge the PR. Thanks!

@Aneureka
Aneureka merged commit bce80fb into NVIDIA:develop Aug 6, 2026
1 check passed
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.

4 participants