Skip to content

sdpa: validate the derived O layout after inference, not in pre_validate_node - #1139

Open
0z5a wants to merge 1 commit into
NVIDIA:developfrom
0z5a:claim/703-validation-order
Open

0z5a wants to merge 1 commit into
NVIDIA:developfrom
0z5a:claim/703-validation-order

Conversation

@0z5a

@0z5a 0z5a commented Sep 18, 2026

Copy link
Copy Markdown

Problem

CompositeSDPANode's forward pre_validate_node() validates the shape and layout of an output that its own infer_properties_node() is responsible for producing.

scaled_dot_product_flash_attention.h (base L285) runs CUDNN_FE_SDPA_VALIDATE_DIM_STRIDE(output_names::O, ...), which requires rank-4 dim AND stride on O. But O is created by the Graph::sdpa factory as a virtual output (graph_interface.h:379), and the very same node already treats that contract as "absent means inferred" for Stats, Max and Sum_exp. A caller who requests O exactly as the factory hands it back is therefore rejected on a property that has not been materialized yet:

V01  O dim+stride omitted (factory default) -> ATTRIBUTE_NOT_SET "The dim for output_names::O is invalid"
V03a O dim omitted, stride explicit         -> ATTRIBUTE_NOT_SET (same class)
V03b O dim explicit, stride omitted         -> ATTRIBUTE_NOT_SET "The stride for output_names::O is invalid"

Change

Only the derived-output check moves; nothing else about ordering changes.

  • pre-validation now judges the caller's declaration: dim and stride must be given together or not at all, and a partial declaration still fails with ATTRIBUTE_NOT_SET.
  • infer_properties_node() materializes the packed BHSD layout.
  • post-validation keeps the original rank-4 plus last-dimension-stride check, with its original error code and message.
  • INode::pre_validate_node() gains the rule a node author needs: a derivable output attribute can only be validated after its materialization, and pre must not read "not yet derived" as "unsupported".

Regression tests cover both validate_subtree() and expand_subtree(), and were seen RED before the change and GREEN after.

Verification

Run on an L20 with two real cuDNN installs, cudnnGetVersion() self-reporting each, because the check itself carries no version gate:

case base 9.19 base 9.26 patched 9.19 patched 9.26
V01 O dim+stride omitted ATTRIBUTE_NOT_SET ATTRIBUTE_NOT_SET OK OK
V02 explicit equivalent declaration OK OK OK OK
V03a/b partial declaration ATTRIBUTE_NOT_SET ATTRIBUTE_NOT_SET still refused still refused
V04 explicitly unsupported layout GRAPH_NOT_SUPPORTED GRAPH_NOT_SUPPORTED unchanged unchanged
V05 invalid required input (K) GRAPH_NOT_SUPPORTED GRAPH_NOT_SUPPORTED unchanged unchanged
V06a/b optional output not requested / requested without dims OK / ATTRIBUTE_NOT_SET OK / ATTRIBUTE_NOT_SET unchanged unchanged

An explicitly-declared O is not rewritten: the caller's layout survives inference byte for byte. On GPU, an omitted-O graph and an explicitly-declared one both validate, build and execute, agreeing element-wise (max |A-B| = 0 over 32768 elements) even when the two declare different layouts.

C++ suite, reading the runner's own summary line:

  • base tree: 38 cases | 35 passed | 3 skipped | 620 assertions, exit 0
  • RED, [validation_order] against the unfixed tree: 2 cases | 0 passed | 2 failed, exit 3
  • GREEN after the fix: All tests passed (27 assertions in 2 test cases), exit 0
  • patched tree, full suite: 40 cases | 37 passed | 3 skipped | 647 assertions, exit 0
  • clean rebuild in a fresh build directory: identical to patched

The three skips are pre-existing and identical on both sides: KernelCache revision()/size() require cuDNN >= 9.27.

Cost

Omitted versus explicit spelling of the same graph in one process, AB/BA interleaved: first validate() 947.5 us versus 943.3 us (+0.45%, inside the inter-quartile spread), warm validate() 3.4 us on both, full Graph::build 398.6 ms versus 399.6 ms with the sign flipping between runs. This is a correctness fix, not a speedup, and the measurements are reported that way. The base "omitted" path is not comparable -- it fails in pre-validation and never builds a graph.

Not in scope

The backward has the identical shape: CompositeSDPABackwardNode::pre_validate_node() (L1251-1253) validates the factory-created virtual dQ/dK/dV the same way and reproduces it (ATTRIBUTE_NOT_SET "The dim for output_names::dQ is invalid"). It is left untouched here and reported on #703 for whoever owns that surface, together with a second static instance in sdpa_fp8_bwd.h:136-138 that is unreachable on Ada because the same function requires prop_major >= 9 first.

Related to #703.

Summary by CodeRabbit

  • Bug Fixes

    • Improved scaled dot-product attention output layout handling when dimensions or strides are omitted.
    • Automatically infers packed output layouts and validates them for supported rank and stride requirements.
    • Added support for partial layout specifications while continuing to reject invalid, repeated, or incomplete configurations.
  • Documentation

    • Clarified how supplied and inferred output properties are validated.
  • Tests

    • Expanded coverage for inferred, explicit, partial, invalid, and optional output layouts.

…validate_node (NVIDIA#703)

The SDPA forward graph factory creates O with output_tensor(), so it carries no
dim/stride unless the caller declares them, and infer_properties_node()
materializes a packed BHSD layout for an undeclared one -- the same contract the
Stats, Max and Sum_exp outputs of this node already follow. pre_validate_node()
checked that layout before inference ran, so a legal omission was rejected with
ATTRIBUTE_NOT_SET "The dim for output_names::O is invalid".

Only a caller-declared O layout is judged in pre now (dim and stride must be
declared together); the materialized layout is checked in post_validate_node(),
whose rank/last-stride check also replaces the old get_stride().back() -- UB on
an empty stride -- and keeps the original error codes and messages. A node-author
rule next to INode::pre_validate_node() records the phase contract.

Related to NVIDIA#703.
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview 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: deda636c-fe2d-4c4d-92d6-365799c7cbe9

📥 Commits

Reviewing files that changed from the base of the PR and between ebe3bba and 33353d1.

📒 Files selected for processing (3)
  • include/cudnn_frontend/node/scaled_dot_product_flash_attention.h
  • include/cudnn_frontend/node_interface.h
  • test/cpp/validate.cpp

Included review availability: Your plan provides up to 12 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The SDPA node now defers validation for undeclared output properties, infers packed BHSD dimensions and strides, and validates the materialized output. The node interface documents this validation order. Tests cover declared, inferred, partial, invalid, repeated, and expanded layouts.

Changes

SDPA output validation

Layer / File(s) Summary
Output validation contract
include/cudnn_frontend/node/scaled_dot_product_flash_attention.h, include/cudnn_frontend/node_interface.h
Pre-validation accepts undeclared O properties and validates supplied dimensions and strides together. The interface documents that checks dependent on inferred properties belong in post-validation.
Output property inference and post-validation
include/cudnn_frontend/node/scaled_dot_product_flash_attention.h
Undeclared O properties are inferred as packed BHSD. Post-validation checks rank 4 and last stride 1 for inferred or declared output properties.
SDPA validation coverage
test/cpp/validate.cpp
Tests cover output layout variants, invalid K strides, missing required optional input data, repeated validation, and preservation of inferred properties through graph expansion.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: anerudhan

Merge Risk: ⚪ Minimal · up to 33353

The deferred output-validation and inference paths have no identified merge-blocking risk.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. 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 and concisely describes the main change: validating the SDPA derived O layout after inference instead of during pre-validation.
Description check ✅ Passed The description is detailed and relevant. It explains the problem, implementation, scope, regression coverage, verification results, compatibility behavior, performance impact, and related issue. It d…
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR

Warning

Some tools did not complete. Review the errors below.

🔧 Cppcheck (2.21.0)
test/cpp/validate.cpp

include/cudnn_frontend_utils.h:193:9:performance:useInitializationList:Variable 'error_status' is assigned in constructor body. Consider performing initialization in initialization list.
include/cudnn_frontend_ExecutionPlanCache.h:97:48:performance:useInitializationList:Variable 'name' is assigned in constructor body. Consider performing initialization in initialization list.
include/cudnn_frontend/graph_properties.h:162:9:performance:useInitializationList:Variable 'pass_by_value' is assigned in constructor body. Consider performing initialization in initialization list.
include/cudnn_frontend/graph_properties.h:164:15:performance:useInitializationList:Variable 'stride' is assigned in constructor body. Consider performing initialization in initialization list.
include/cudnn_frontend/graph_properties.h:169:9:performance:useInitializationList:Variable 'pass_by_value' is assigned in constructor body. Consider performing initialization in initialization list.
include/cudnn_frontend/graph_p

... [truncated 8653 characters] ...

nction parameter 'x' should be passed by const reference.
include/cudnn_frontend/graph_interface.h:2079:56:performance:passedByValue:Function parameter 'attributes' should be passed by const reference.
include/cudnn_frontend/graph_interface.h:2089:74:performance:passedByValue:Function parameter 'attributes' should be passed by const reference.
include/cudnn_frontend/graph_interface.h:3820:68:performance:passedByValue:Function parameter 'x' should be passed by const reference.
include/cudnn_frontend_shim.h:161:64:performance:stlFindInsert:Searching before insertion is not necessary. Instead of 'dl_handles[library]=(library==CudaLibrary::CUDART)?load_cudart_so():load_cuda_so()' consider using 'dl_handles.try_emplace(library, (library==CudaLibrary::CUDART)?load_cudart_so():load_cuda_so());'.


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

@YangXu1990uiuc YangXu1990uiuc left a comment

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.

Codex bot review · model: gpt-6-astra

Reviewed 33353d1f1584 against base ebe3bba998b8.

Thank you for your contribution! The validation/inference ordering change makes sense: undeclared O gets a complete layout before dependent checks, explicit layouts remain intact, and partial or unsupported declarations still fail. I found no actionable correctness or compatibility issue in this change. The shared implementation also passed independent composite/unified GQA checks with different Q and V embedding sizes.

Validation: Compiled the exact-head C++ suite with C++17 and warning-as-error flags against CUDA 13.2/cuDNN 9.25.1. Repository suite: 37 passed, 3 expected skips requiring cuDNN >=9.27; the additional independent numerical probe passed too (combined 38 passed,3 skipped,100039 assertions). Built the two new validation_order tests against exact-base headers: both fail before the fix, including the omitted/repeated/expand paths. Both pass on the reviewed head; explicit layouts and invalid declaration controls retain their behavior. On SM100, independent analytic O and Stats checks passed all16 configurations across cuDNN 9.19.1/9.25.1, COMPOSITE/UNIFIED implementations, inferred BHSD/explicit BSHD output, and Dqk=64 with Dv=64/128. B=2,Hq=4,Hkv=2,Sq=16,Skv=32; output buffers were initialized to a nonzero sentinel. Measured max O/Stats error was zero in these uniform-attention cases. Rechecked the exact head/base, mergeability and current reviews. The fork Style workflow is pending maintainer authorization, not a failed code check.

Limitations: Numerical probes cover dense GQA on SM100, not a full paged/ragged/FP8 matrix. No separate GPU performance sweep: the change is in graph validation/layout inference and does not modify execution or kernels. Warm validation measurements were noisy and are not used as a performance claim.

Approved: no P0 or high-risk P1 found. Remaining findings stay with the owner; merge timing stays with the owner.

@YangXu1990uiuc

Copy link
Copy Markdown
Collaborator

I am requesting test CI for 33353d1f1584 with the following standalone command: @cudnn-ci-bot run python_tests. For future updates, request the appropriate test target before review; a maintainer may need to trigger it.

Codex bot review — model gpt-6-astra.

@YangXu1990uiuc

Copy link
Copy Markdown
Collaborator

@cudnn-ci-bot run python_tests

@cudnn-ci-bot

cudnn-ci-bot commented Sep 18, 2026

Copy link
Copy Markdown

🏁 Pipeline finished

SHA: 33353d1
Targets: python_tests
Branch: cudnn-gh/pr-1139-33353d1
Pipeline: 68662637
Last updated: 2026-09-18 20:58 UTC

18 passed, 7 failed, 6 manual

manual

  • manual:python_samples - Manual
  • manual:python_tests - ✅ Passed
  • manual:frost - Manual
  • manual:oss - Manual
  • manual:pycudnn - Manual
  • manual:multi_gpu - Manual
  • manual:backend - Manual

analysis

  • analysis:cudnn_clang_disable_exception - ✅ Passed
  • analysis:cudnn_v9_no_half_conversion - ✅ Passed
  • analysis:cudnn_clang - ✅ Passed
  • analysis:check-relative-includes - ✅ Passed
  • analysis:check-CUDNN_FRONTEND_SKIP_JSON_LIB - ✅ Passed
  • analysis:guardwords_scan - ❌ Old Failure (nightly failed)
  • analysis:jax-import-guard - ✅ Passed
  • san:build - ✅ Passed

build

  • analysis:api_index - ✅ Passed
  • build:dev:linux:amd64 - ✅ Passed
  • build:rel:linux:amd64 - ✅ Passed
  • build:dev:linux:arm64 - ✅ Passed
  • build:rel:linux:arm64 - ✅ Passed
  • build:rel:win:amd64 - ✅ Passed

python_tests

  • py_test:dev:sm80 - ❌ Old Failure (nightly failed)
  • py_test:dev:sm90 - ❌ Old Failure (nightly failed)
  • py_test:dev:sm100 - ❌ Old Failure (nightly failed)
  • py_test:rel:sm80 - ❌ Old Failure (nightly failed)
  • py_test:rel:sm90 - ❌ Old Failure (nightly failed)
  • py_test:rel:sm100 - ❌ Old Failure (nightly failed)

sanitizer_tests

  • san:cpp_test:sm80 - ✅ Passed
  • san:cpp_test:sm90 - ✅ Passed
  • san:cpp_test:sm100 - ✅ Passed

triage

  • triage:ai - ✅ 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.

3 participants