test(sdpa): fuzz per-tensor ragged token-stride gaps in test_mhas_v2 - #516
Conversation
📝 WalkthroughWalkthroughRagged SDPA configurations now generate independent Q/K/V/O token-stride gaps with defined fallback cases. Ragged allocations, gradients, and offsets use the configured strides. Regression tests cover explicit stride overrides and packed-stride fallbacks. ChangesRagged SDPA stride handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@test/python/sdpa/random_config.py`:
- Line 77: Update the shape unpacking in the affected function to avoid binding
the unused b value, while preserving the existing h, s, and d bindings used by
the function.
🪄 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: 825b6793-276e-417f-a1b4-ed1a66b4b956
📒 Files selected for processing (5)
python/cudnn/sdpa/fwd/engines.pypython/cudnn/sdpa/graph_analyzer.pytest/python/sdpa/fp16.pytest/python/sdpa/random_config.pytest/python/test_mhas_v2.py
1552036 to
63ad9a6
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
test/python/sdpa/random_config.py (1)
211-233: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep gap assignment stable for partially specified strides.
_gap_rngis consumed only whengap_fnruns. Therefore, settingstride_qexplicitly shifts the generated gaps for K/V/O, even whenrng_geom_seedis unchanged. This makes per-tensor layouts depend on which earlier strides were overridden.Draw all four gap values before the
Nonechecks, then assign fixed values to Q/K/V/O. Add a regression test for a configuration with onlystride_qspecified.🤖 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/random_config.py` around lines 211 - 233, The gap RNG in the stride initialization flow must not depend on which strides are explicitly provided. In the _gapped/gap_fn logic, draw four gap values for Q, K, V, and O before the stride_q/stride_k/stride_v/stride_o None checks, then use those fixed values only for missing strides while preserving explicit values. Add a regression test covering a configuration with only stride_q specified and verify the generated K/V/O layouts remain stable.
🤖 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.
Outside diff comments:
In `@test/python/sdpa/random_config.py`:
- Around line 211-233: The gap RNG in the stride initialization flow must not
depend on which strides are explicitly provided. In the _gapped/gap_fn logic,
draw four gap values for Q, K, V, and O before the
stride_q/stride_k/stride_v/stride_o None checks, then use those fixed values
only for missing strides while preserving explicit values. Add a regression test
covering a configuration with only stride_q specified and verify the generated
K/V/O layouts remain stable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 8536b67d-a680-4967-b103-b71bcbc208ac
📒 Files selected for processing (2)
test/python/sdpa/random_config.pytest/python/test_mhas_v2.py
🚧 Files skipped from review as they are similar to previous changes (1)
- test/python/test_mhas_v2.py
The FROST THD forward lowerings rebuilt packed (1, T, H, D) views with hardcoded (t*h*d, h*d, d, 1) strides, so a THD tensor declaring a wider token stride — e.g. a K/V view of a kv-interleaved [T, 2, H, D] buffer (token stride 2*h*d), the layout torch.nn.attention.varlen users get by slicing a fused KV projection — was claimed and silently mis-addressed (100% of O wrong on both sdpa_fwd_prefill_sm120 and sdpa_fwd_prefill_sm100_d128; backend engines serve the same graphs correctly). Fix: adapter-level normalization, the THD analogue of the dense path's compact-BSHD normalization. New SdpaFwdDsl helpers read each tensor's DECLARED (token, head, elem) strides from its descriptor; a packed declaration stays exactly the zero-copy view it was, and a non-packed one gathers into packed scratch carved from the workspace (Q/K/V) or scatters back from it after the launch (O). Worst-case scratch (b * s_max * h * d per non-packed tensor) is added to scratch_workspace_bytes, so per-execute allocations stay zero on the FROST dispatch path. The t_kv == 0 O-zeroing paths now also zero through the declared view. No kernel changes. Verified (torch nightly cu132, FE develop + PR NVIDIA#516's fuzz tests): - previously-failing gapped repros now pass WITH the frost engines serving, on cc 10.0 (sm100 engine) and RTX 5080 (sm120 engine) - 128-test slice of the fwd ragged L0 sweep passes under CUDNN_FRONTEND_ENABLE_FROST_ENGINES=1 - packed configs unchanged (zero-copy path untouched) Follow-ups (tracked, not in this change): - native kernel-side stride support (TMA descriptors / address math over declared strides) to remove the gather/scatter copies - fp8/mxfp8 THD: inherit the normalization when that path is wired; their test harness also does not honor configured strides yet - cu_seq_len (cu_ragged) form derives offsets internally assuming packed - future FROST THD backward engines must adopt the declared-stride contract from day one - head-axis stride gaps are honored by the gather view but not fuzzed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second stage on top of the adapter normalization: the kernels now express declared strides NATIVELY, so gapped THD views run zero-copy. - compile() on all five f16 kernels (sm120, sm100 d128/d192_d128/d256/ d512) takes optional caller-declared (batch, seq, head, elem) strides per tensor (lru cache-key); None keeps the compact specialization bit-for-bit. Strided fakes are built with make_fake_tensor, validated against the TMA 16-byte global-stride rule. - SM120: the K/V TMA layouts read the tensor's strides instead of recomputing packed ones (Q/O offset math was already layout-driven); the entry validator accepts padded, 16-byte-granular BSHD storage (compact = the equality special case). - SM100: the Q/K/V/O TMA descriptors are built from the tensor views, so declared strides flow in unchanged; the THD O-descriptor builder steps per-batch bases by O's declared seq-axis stride (o_tensor.stride[1]) instead of the packed n_qh*d_v. - Adapters bind declared-stride views natively when every stride is a 16-byte multiple (x8 elements at 2 bytes/elem) and fall back to the packed gather/scatter normalization otherwise — the normalization stays as the safety net for sub-granularity strides. Verified (torch nightly cu132, ToT develop + PR NVIDIA#516's fuzz tests): - gapped seeded repros pass with the frost engines serving natively on cc 10.0 (sm100 d128) and RTX 5080 (sm120) - 128-test fwd ragged L0 sweep slice: 128/128 on cc 10.0 (all four sm100 flavors reached), 84/84 slice on sm120 - ex-ops suite (incl. kv-interleaved views) 11/11 on both boxes - dense fwd L0 sweep slice: 182 passed / 8 skipped (dense compile paths pass no strides -> unchanged compact specialization) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
60b9a43 to
1cc22af
Compare
…nels; decline what TMA cannot express A THD tensor may declare a wider token stride than the packed h*d — e.g. a K/V view of a kv-interleaved [T, 2, H, D] buffer (token stride 2*h*d), the layout torch.nn.attention.varlen users produce by slicing a fused KV projection. The THD lowerings rebuilt packed (1, T, H, D) views with hardcoded strides, so such graphs were claimed and silently mis-addressed (100% of O wrong on both sdpa_fwd_prefill_sm120 and the sm100 flavors; caught by PR NVIDIA#516's fuzz coverage and PyTorch's own varlen suite). Native support, no fallback (AGENTS Hard Rule 2): - compile() on all five f16 kernels (sm120, sm100 d128/d192_d128/d256/ d512) takes optional caller-declared (batch, seq, head, elem) strides per tensor (lru cache-key); None keeps the compact specialization bit-for-bit. Strided fakes via make_fake_tensor, validated against the TMA 16-byte global-stride rule. - SM120: kv_tma_desc reads the tensor's strides instead of recomputing packed ones (Q/O offset math was already layout-driven); the entry validator accepts padded 16-byte-granular BSHD storage (compact = the equality special case). - SM100: the Q/K/V/O TMA descriptors are built from the tensor views, so declared strides flow in unchanged; the THD O-descriptor builder steps per-batch bases by O's declared seq-axis stride (o_tensor.stride[1]). - Adapters bind declared-stride (1, T, H, D) views directly. What TMA cannot express is REJECTED in check_support (NotImplementedError naming the offending strides), so the Router falls back to an engine that honors the declaration: non-innermost-contiguous head dim, or token/head strides that are not multiples of 8 elements (sub- granularity strides also violate the graph API's pointer-alignment contract for the backend, so declining is correct, not conservative). - The SM120 FP8 THD path (NVIDIA#509) keeps the packed contract for now: non-packed declarations are declined (_thd_check_strides_packed); extending native strides there is tracked as a follow-up. Verified (torch nightly cu132, ToT develop + PR NVIDIA#516's fuzz tests): gapped seeded repros pass with the frost engines serving natively on cc 10.0 (sm100) and RTX 5080 (sm120); 128-test fwd ragged L0 sweep slice green on cc 10.0 (all four sm100 flavors) and 84-test slice on sm120; ex-ops suite incl. kv-interleaved views 11/11 on both; dense fwd slice 182 passed (dense compile paths pass no strides -> unchanged); packed THD configs bit-for-bit unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1cc22af to
9b57eab
Compare
…6 fwd kernels (#526) * frost(sdpa): native THD stride support in the SM100/SM120 f16 fwd kernels; decline what TMA cannot express A THD tensor may declare a wider token stride than the packed h*d — e.g. a K/V view of a kv-interleaved [T, 2, H, D] buffer (token stride 2*h*d), the layout torch.nn.attention.varlen users produce by slicing a fused KV projection. The THD lowerings rebuilt packed (1, T, H, D) views with hardcoded strides, so such graphs were claimed and silently mis-addressed (100% of O wrong on both sdpa_fwd_prefill_sm120 and the sm100 flavors; caught by PR #516's fuzz coverage and PyTorch's own varlen suite). Native support, no fallback (AGENTS Hard Rule 2): - compile() on all five f16 kernels (sm120, sm100 d128/d192_d128/d256/ d512) takes optional caller-declared (batch, seq, head, elem) strides per tensor (lru cache-key); None keeps the compact specialization bit-for-bit. Strided fakes via make_fake_tensor, validated against the TMA 16-byte global-stride rule. - SM120: kv_tma_desc reads the tensor's strides instead of recomputing packed ones (Q/O offset math was already layout-driven); the entry validator accepts padded 16-byte-granular BSHD storage (compact = the equality special case). - SM100: the Q/K/V/O TMA descriptors are built from the tensor views, so declared strides flow in unchanged; the THD O-descriptor builder steps per-batch bases by O's declared seq-axis stride (o_tensor.stride[1]). - Adapters bind declared-stride (1, T, H, D) views directly. What TMA cannot express is REJECTED in check_support (NotImplementedError naming the offending strides), so the Router falls back to an engine that honors the declaration: non-innermost-contiguous head dim, or token/head strides that are not multiples of 8 elements (sub- granularity strides also violate the graph API's pointer-alignment contract for the backend, so declining is correct, not conservative). - The SM120 FP8 THD path (#509) keeps the packed contract for now: non-packed declarations are declined (_thd_check_strides_packed); extending native strides there is tracked as a follow-up. Verified (torch nightly cu132, ToT develop + PR #516's fuzz tests): gapped seeded repros pass with the frost engines serving natively on cc 10.0 (sm100) and RTX 5080 (sm120); 128-test fwd ragged L0 sweep slice green on cc 10.0 (all four sm100 flavors) and 84-test slice on sm120; ex-ops suite incl. kv-interleaved views 11/11 on both; dense fwd slice 182 passed (dense compile paths pass no strides -> unchanged); packed THD configs bit-for-bit unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(agents): Hard Rule 2 — serve the declared layout natively or decline, never adapt Closes the loophole Rule 1's letter leaves open: adapter-side normalization copies that make an unsupported layout runnable. Workspace carving does not legitimize a data-tensor copy (the carve exemption is for metadata and dead-slot dummies), the dense path's grandfathered normalization is not a license for new ones, and whatever check_support accepts the kernel must address natively. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * frost(sdpa): review hardening — validate runtime THD buffers, decline overlapping strides - _thd_view validates the runtime buffer against its declaration before reinterpreting storage: dtype/device must match and the base address must be 16-byte aligned (TMA global-address rule / assumed_align=16); as_strided already rejects views past the underlying allocation. - _thd_check_strides_native additionally requires covering (non-overlapping) strides — head >= d, token >= heads*head — matching the SM120 kernel's is_layout_supported, so sub-dense declarations are declined at check_support instead of failing at the per-execute compile (or racing on O writes on SM100). - Kernel _fake_bshd guards: the head dim must be innermost-contiguous; d256/d512 validate the O stride at BPE_O (the O storage dtype byte size). - Clearer SM120 layout-rejection message (the entry validator accepts padded storage now; the text still demanded compact). The THD host-prep stream binding flagged in the same review round is a pre-existing issue (#476) and is split into a separate PR. Addresses CodeRabbit review feedback on #526. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * frost(sdpa): make the THD native-stride gate dtype-aware (16 // itemsize) The gate hardcoded the TMA 16-byte global-stride rule as 8 elements, the f16/bf16 case. It lives in the shared base class, so express the quantum in the tensor's own element units — 8 at 2 B/elem, 16 at 1 B/elem (fp8), 4 at 4 B/elem — per descriptor, so mixed-precision declarations check each tensor at its own dtype. No behavior change for the f16 paths this PR enables; the fp8 native-stride follow-up (#537) inherits the correct quantum for free. Suggested by @Aneureka in review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Ragged Q/K/V/O were always generated with canonical packed strides (token stride == h*d): the gaps stride machinery was dense-only, and PR NVIDIA#462 varies only the STATS layout. So no sweep could ever produce a tensor like a K/V view of a kv-interleaved [T, 2, H, D] buffer (token stride 2*h*d) — the layout torch.nn.attention.varlen users get by slicing a fused KV projection. New with_ragged_token_gap knob in both ragged random sweeps: each of Q/K/V/O independently draws token stride h*d + gap, gap in {0, 8, 64, roundup8(h*d)} seeded from rng_geom_seed (deterministic through serialize/deserialize repro; explicit strides still win). Gaps stay multiples of 8 elements so ragged base addresses keep the packed layout's alignment class (the graph API requires 16-byte-aligned pointers). Harness: ragged buffers (incl. gradients) are allocated with the configured strides, and ragged offsets scale by each tensor's actual stride[2] — the same generalization NVIDIA#462 made for stats offsets. KNOWN FAILURES this exposes (intentionally not masked): the FROST THD forward engines (sdpa_fwd_prefill_sm120, sdpa_fwd_prefill_sm100_d128) claim non-packed-stride THD graphs and silently mis-address them (100% of O wrong; the stride ORDER is still BSHD, so the order-only layout gate passes). Backend engines serve every gapped combination correctly, fwd and bwd. The failing configs under CUDNN_FRONTEND_ENABLE_FROST_ENGINES=1 are the repro set for fixing the THD lowerings to honor declared strides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the fp16 ragged sweeps Per review: drop the True/False dice — the fp16 ragged sweeps enable the knob unconditionally, and the packed case comes from the gap draw itself. Each of Q/K/V/O independently draws gap = n*h*d tokens, n in 0..3 (n=0 packed, n=1 a kv-interleaved [T, 2, H, D] view, n=2 a [T, 3, H, D] QKV-interleave). Whole-token gaps keep every ragged base address in the packed layout's alignment class by construction, replacing the previous multiples-of-8-elements rule. The config field stays default-False: fixed configs and the fp8/mxfp8 harnesses still assume packed allocations, and the cu_ragged form derives offsets internally. Verified: backend engines pass 5/5 seeded repros, a bf16 training config, and a 128-test slice of the fwd ragged L0 sweep; FROST THD fwd engines keep failing gapped draws (the intended standing repro set). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ding The gap RNG was consumed lazily, one draw per stride left unspecified — so explicitly pinning e.g. stride_q shifted the gaps K/V/O derive from the same rng_geom_seed. Draw all four values up front in fixed Q/K/V/O order and apply each only where the stride is missing: per-tensor layouts are now a function of the seed alone. The all-defaults path (the sweeps) consumes draws in the same order as before, so existing seeded repros reproduce identical strides. Adds a GPU-free regression test pinning stride_q and asserting the K/V/O strides match the all-defaults derivation (seed chosen so the old lazy behavior visibly shifts two of the three gaps). Also renames the unused batch binding in compute_packed_strides (RUF059). Addresses CodeRabbit review feedback on NVIDIA#516. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t expressible Drop the per-sweep with_ragged_token_gap opt-in: the field now defaults True, so EVERY ragged config fuzzes its per-tensor token strides. fill_derived_fields falls back to packed automatically where a gap is not yet expressible or handled — cu / offset-multiplier forms bind offsets as cu (x multiplier) and cannot declare a token gap (NVIDIA#538), and the fp8/mxfp8 harnesses (1-byte data types) allocate assuming packed (NVIDIA#537) — so mixed sweeps (ragged + cu_ragged in one RandomChoice) gap exactly the draws that support it. Explicit strides are never touched (the gap only fills strides left None), so recorded repro dicts reproduce exactly. The regression test also locks in the new semantics: default-on for plain ragged, packed for the three fallback forms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9b57eab to
746ef17
Compare
|
Rebased onto the merged develop (now includes #526's native declared-stride support and #533's SM120 backward) and addressed the review findings: Gap assignment stable under partial stride overrides (outside-diff finding) — fixed in 17be556. The gap RNG was consumed lazily, one draw per stride left unspecified, so pinning e.g. Unused Follow-up simplification (746ef17, per review discussion): the per-sweep Verification on SM100 (removing the sweep knob shifts the RNG draw sequence, so all sweep configs are a fresh sample): regression test green, fwd ragged slice 84/84, fp8 ragged slice green (auto-packed fallback confirmed); bwd ragged slice 125 passed / 87 skipped / 0 failed. |
|
@cudnn-ci-bot run frost |
|
🚀 Running mirror pipeline Branch: cudnn-gh/pr-516-746ef17 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@test/python/sdpa/random_config.py`:
- Around line 125-139: Prevent affected FROST forward engines from being
selected for configurations with non-packed ragged strides before enabling
with_ragged_token_gap by default. Add the eligibility decline in the
engine-selection or validation path used by fill_derived_fields, and preserve
packed-layout eligibility; if no such guard exists, change with_ragged_token_gap
back to False until the FROST fix is available.
🪄 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: ef9eaf0f-4613-4510-8826-715549a16668
📒 Files selected for processing (2)
test/python/sdpa/random_config.pytest/python/test_mhas_v2.py
| # Each ragged tensor (Q/K/V/O and gradients) independently draws a token | ||
| # stride of 1-4 whole tokens (gap = n*h*d, n in 0..3, seeded from | ||
| # rng_geom_seed): n=0 is the plain packed case, n=1 is exactly a view of | ||
| # an interleaved [T, 2, H, D] buffer (the layout | ||
| # torch.nn.attention.varlen users produce by slicing a fused KV | ||
| # projection), n=2 a [T, 3, H, D] QKV-interleave, and so on. Whole-token | ||
| # gaps keep every ragged base address in the packed layout's alignment | ||
| # class by construction (sub-token gaps can violate the graph API's | ||
| # 16-byte pointer-alignment contract — an odd-element gap is illegal for | ||
| # every engine). Default True: every ragged config fuzzes its layouts. | ||
| # fill_derived_fields auto-falls-back to packed where a gap is not yet | ||
| # expressible or handled: cu / offset-multiplier forms (#538) and the | ||
| # fp8/mxfp8 harnesses (#537). Configs with explicit strides are | ||
| # unaffected (the gap only fills strides left None). | ||
| with_ragged_token_gap: bool = True |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Do not default-enable a layout that FROST executes incorrectly.
The PR objective states that FROST THD forward engines address non-packed strides incorrectly and return wrong O values. Line 139 enables these layouts for all applicable ragged configurations, but no engine-side decline prevents FROST selection.
Add an eligibility decline for affected FROST engines before this default is enabled. Otherwise, keep with_ragged_token_gap disabled by default until the engine fix is available.
🤖 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/random_config.py` around lines 125 - 139, Prevent affected
FROST forward engines from being selected for configurations with non-packed
ragged strides before enabling with_ragged_token_gap by default. Add the
eligibility decline in the engine-selection or validation path used by
fill_derived_fields, and preserve packed-layout eligibility; if no such guard
exists, change with_ragged_token_gap back to False until the FROST fix is
available.
… 9.26+) The stats (LSE) tensor stride was previously pinned to BHSD in the random test generator with a TODO comment, because the SM80 flash-bprop codegen used packed seq-stride addressing in the stats loads and silently produced wrong results for any other layout (NVBug 6057616). The backend fix landed in cuDNN dev as MR !4147 and will ship in 9.26. The stats layout is DRAWN unconditionally — so one seed derives identical shapes and Q/K/V/O strides on every backend version, preserving the layouts-are-a-function-of-the-seed-alone property NVIDIA#516 established — and APPLIED only when backend_version() >= 92600; older backends fall back to the packed BHSD default (old behavior), and full layout coverage engages automatically once CI deploys 9.26. Also wires the --implementation CLI option through the random fwd/bwd/bias L0 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 9.26+) The stats (LSE) tensor stride was previously pinned to BHSD in the random test generator with a TODO comment, because the SM80 flash-bprop codegen used packed seq-stride addressing in the stats loads and silently produced wrong results for any other layout (NVBug 6057616). The backend fix landed in cuDNN dev as MR !4147 and will ship in 9.26. The stats layout is DRAWN unconditionally — so one seed derives identical shapes and Q/K/V/O strides on every backend version, preserving the layouts-are-a-function-of-the-seed-alone property NVIDIA#516 established — and APPLIED only when backend_version() >= 92600; older backends fall back to the packed BHSD default (old behavior), and full layout coverage engages automatically once CI deploys 9.26. Also wires the --implementation CLI option through the random fwd/bwd/bias L0 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… in tests (#304) * test: randomize softmax-stats strides in bwd SDPA random tests (cuDNN 9.26+) The stats (LSE) tensor stride was previously pinned to BHSD in the random test generator with a TODO comment, because the SM80 flash-bprop codegen used packed seq-stride addressing in the stats loads and silently produced wrong results for any other layout (NVBug 6057616). The backend fix landed in cuDNN dev as MR !4147 and will ship in 9.26. The stats layout is DRAWN unconditionally — so one seed derives identical shapes and Q/K/V/O strides on every backend version, preserving the layouts-are-a-function-of-the-seed-alone property #516 established — and APPLIED only when backend_version() >= 92600; older backends fall back to the packed BHSD default (old behavior), and full layout coverage engages automatically once CI deploys 9.26. Also wires the --implementation CLI option through the random fwd/bwd/bias L0 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: reject non-BHSD softmax-stats strides for cuDNN < 9.26 (NVBug 6057616) The SM80 (and SM100 dBias) backward kernels in cuDNN < 9.26 ignore the declared strides of the Stats tensor and address it as packed BHSD, silently producing wrong gradients for any other layout. Guard both the forward and backward paths in the C++ cuDNN backend: - sdpa_support_surface.h: reject a non-ragged Stats *output* with non-BHSD strides when building a forward graph on cuDNN < 9.26, so the error surfaces at graph construction time rather than at bprop. - CompositeSDPABackwardNode::pre_validate_node(): reject a non-ragged Stats *input* with non-BHSD strides on cuDNN < 9.26 (the authoritative check; the forward check above is early-warning only). Also fixes a pre-existing formatting issue in the same region of scaled_dot_product_flash_attention.h: missing space in if(, mismatched continuation indent, and a tab in the closing brace. test: randomize softmax-stats strides unconditionally in random_config.py (the FE now rejects the broken configs on < 9.26 rather than silently corrupting; tests skip via GRAPH_NOT_SUPPORTED instead of producing wrong gradients). Verified 378/378 bwd L0 pass on cuDNN 9.26 (H100). --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
What
Ragged Q/K/V/O were always generated with canonical packed strides (token stride ==
h*d): thegapsstride machinery inrandom_config.pywas dense-only, and #462 varies only the STATS layout. So no sweep could ever produce a tensor like a K/V view of a kv-interleaved[T, 2, H, D]buffer (token stride2*h*d) — the exact layouttorch.nn.attention.varlenusers get by slicing a fused KV projection (PyTorch's own varlen suite uses it inAttentionBlock).with_ragged_token_gap, enabled unconditionally in the fp16 ragged sweeps (no True/False dice — the packed case comes from the gap draw itself): each of Q/K/V/O independently draws a token stride of 1–4 whole tokens (gap =n*h*d, n ∈ 0..3, seeded fromrng_geom_seed; deterministic through serialize/deserialize repro, explicit strides still win). n=0 is plain packed, n=1 exactly a kv-interleaved[T, 2, H, D]view, n=2 a[T, 3, H, D]QKV-interleave. Whole-token gaps keep every ragged base address in the packed layout's alignment class by construction (sub-token gaps can violate the graph API's 16-byte pointer-alignment contract — we verified an odd-element gap is illegal for every engine). The config field defaults False: fixed configs and the fp8/mxfp8 harnesses still assume packed allocations, and the cu_ragged form derives offsets internally. Harness: ragged buffers (incl. gradients) are allocated with the configured strides, and ragged offsets scale by each tensor's actualstride[2]— the same generalization #462 made for stats offsets.Known failures this exposes (intentionally NOT masked)
The FROST THD forward engines (
sdpa_fwd_prefill_sm120,sdpa_fwd_prefill_sm100_d128) claim non-packed-stride THD graphs and silently mis-address them — 100 % of O wrong (the stride ORDER is still BSHD, so the order-only layout gate passes; the THD lowerings rebuild packed views). Verified on cc 10.0 and RTX 5080 (sm120), FE develop 3f17f5b, torch nightly. Backend engines serve every gapped combination correctly, fwd and bwd (5/5 seeded repros, a bf16 training config, and a 128-test slice of the fwd ragged L0 sweep).Per review direction, this PR ships the coverage only — no engine-side decline — so the failing configs under
CUDNN_FRONTEND_ENABLE_FROST_ENGINES=1are the standing repro set for fixing the THD lowerings to honor declared strides.Deterministic repro (fails on FROST, passes on backend; no-gap configs unchanged on both routes):
Found while integrating PyTorch's
varlen_attnonto the cuDNN python API (caught by PyTorch's upstream varlen test suite).🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests