Skip to content

Fix DSA backward boundaries and relax SM100 BSA FP8 shape limits - #926

Merged
Anerudhan merged 4 commits into
NVIDIA:developfrom
jiayus-nvidia:fix/dsa-sm100-specialized-boundaries
Sep 7, 2026
Merged

Anerudhan merged 4 commits into
NVIDIA:developfrom
jiayus-nvidia:fix/dsa-sm100-specialized-boundaries

Conversation

@jiayus-nvidia

@jiayus-nvidia jiayus-nvidia commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Before submitting

  • I agree to license this contribution under the terms of LICENSE.txt.
  • I ran pre-commit run and committed any formatting changes. (pre-commit is unavailable in the test environment; Black, py_compile, and git diff --check results are listed below.)
  • I reviewed the Hard Rules in the AGENTS.md for each directory this PR touches and the changes comply.
  • I added GitHub labels: one cat-*, one or more area:* / op:*, and one orig-*.

Affected area

FE OSS kernels or CuTeDSL

Summary

  • Extend robust sparse-row boundary handling to the dedicated SM100 H16 and H32 DSA backward kernels.
  • Extend the SM90 kernel to reject negative and positive out-of-range indices in both compact and non-compact layouts, and clamp topk_length to the physical top-k width.
  • Carry packed per-row validity masks from the KV loader into probability recomputation so invalid rows contribute zero probability and zero gradient.
  • Use predicated cp.async in the SM90 loader to suppress invalid global reads while zero-filling their shared-memory rows.
  • Recheck sparse indices before every dKV scatter and define empty-row / saturating-sink limits without NaNs.
  • Add regression coverage for active invalid indices, mixed invalid holes across tiles, oversized lengths, empty rows, and saturating sinks.
  • Expand SM100/SM103 Sage FP8 BSA from the conservative B=1, H in {4, 8} support boundary to any positive batch and head counts; retain D=128, MHA, and 64-token sequence alignment requirements.
  • Update the package-wide CuTe DSL floor documentation and the DSA/CSA acknowledgements.

Why

#877 fixed invalid sparse-row handling in the generic SM100 kernel, but the dedicated H16 and H32 implementations still trusted active indices and retained the unsafe sink-limit behavior. #785 fixed the original SM90 empty-row, padded-tail, and sink failures, but active negative indices in compact mode, positive out-of-range indices in either mode, and topk_length values wider than the physical index row could still reach KV gathers, probability recomputation, or dKV scatter.

The updated kernels use one rule throughout the pipeline: a row participates only when it is inside the bounded active prefix and its KV index is in [0, S_kv). The validity decision is packed once by the loader and reused by the probability path; the scatter path independently rechecks the destination before issuing atomics.

The Sage FP8 BSA quantizer and SM100 launcher already derive grids, scales, and scheduler coordinates from runtime batch/head dimensions. The old B/H checks were conservative API gates rather than kernel requirements; B200 validation confirmed non-4/8 head counts and multi-batch execution in both ordinary and split-KV paths.

Related issues

Related to #676.

Builds on #785 and #877.

API and compatibility impact

There are no public function-signature changes. The accepted input domain of the public Sage FP8 BSA wrapper is expanded.

Behavior changes:

  • SM100/SM103 Sage FP8 BSA now accepts any positive batch and head counts.
  • Invalid sparse indices are ignored consistently in compact and non-compact layouts.
  • topk_length is clamped to [0, topk_idxs.shape[-1]] in-kernel.
  • Empty sparse rows and infinite or rescale-saturating sinks produce their limiting zero gradients instead of invalid memory accesses, hangs, or NaNs.

No newer CuTeDSL API is introduced and the supported architecture set is unchanged.

H100 full-wrapper performance (BF16, S_q = S_kv = 4096; negative change is faster):

Shape top-k topk_length Before After Change
H32, Dqk576, Dv512 64 yes 0.7952 ms 0.7402 ms -6.9%
H32, Dqk576, Dv512 512 yes 2.3438 ms 2.3456 ms +0.08%
H32, Dqk576, Dv512 2048 yes 8.0299 ms 8.0733 ms +0.54%
H32, Dqk576, Dv512 64 no 0.7942 ms 0.7597 ms -4.35%
H32, Dqk576, Dv512 512 no 2.5141 ms 2.3088 ms -8.17%
H32, Dqk576, Dv512 2048 no 9.1505 ms 8.0165 ms -12.39%
H64, Dqk512, Dv512 64 yes 1.2472 ms 1.0958 ms -12.14%
H64, Dqk512, Dv512 512 yes 2.8867 ms 2.8823 ms -0.15%
H64, Dqk512, Dv512 2048 yes 9.0662 ms 9.1544 ms +0.97%
H64, Dqk512, Dv512 512 no 2.8430 ms 2.4628 ms -13.37%

Each entry is the median of nine CUDA-event samples after 30 warm-up calls; each sample contains 40-200 complete wrapper calls depending on top-k. The compact topk=2048 regression was reproduced in reverse run order. SM100 DSA performance was not measured in this H100 run; the BSA correctness validation below was run separately on B200.

Testing

Environment:

  • NVIDIA H100 80GB HBM3 (SM90)
  • CUDA 12.8 / PyTorch 2.9.1
  • cuDNN backend 9.22.0
  • nvidia-cutlass-dsl 4.5.2

The new SM90 positive logical-OOB regression was first run against the parent revision and failed RED with an all-NaN dQ.

Complete DSA backward L0 file after rebasing onto current develop:

cd test/python
PYTHONPATH=/code/github/cudnn-frontend/python:/code/dsl_env/lib/python3.12/site-packages \
  python -m pytest -q -m L0 fe_api/dsa/test_DSA_sparse_attention_backward.py

Result: 32 passed, 30 skipped, 26 deselected in 113.28 s. SM100-only DSA cases skipped on the H100 host and are left to SM100 CI.

Sage FP8 BSA validation environment:

  • NVIDIA B200 (SM100)
  • CUDA 13.2 / PyTorch 2.12.0a0+5aff3928d8.nv26.05
  • cuDNN backend 9.22.0
  • nvidia-cutlass-dsl 4.6.2

The new arbitrary-B/H public API cases were first run against the old guards: H=3 and B=2, H=3 failed RED. Targeted quantizer and internal split-workspace cases also failed at their respective old guards.

cd test/python
PYTHONPATH=/code/github/cudnn-frontend/python \\
  python -m pytest -q fe_api/bsa/test_BSA_attention_fp8.py

Result: 26 passed in 6.90 s on B200/SM100.

Static and formatting checks:

python -m py_compile <changed Python files>
python -m black --check --line-length 160 <changed Python files>
git diff --check upstream/develop...HEAD

Result: all passed; Black reported all five files unchanged.

Summary by CodeRabbit

  • New Features

    • Expanded SM100/SM103 Sage FP8 support to accommodate positive batch sizes and head counts.
    • Added broader FP8 coverage across batch sizes and attention-head configurations.
  • Bug Fixes

    • Improved sparse attention backward handling for invalid or out-of-range top-k indices and lengths.
    • Invalid rows are safely ignored, with appropriate zeroed outputs and gradients.
    • Improved handling of empty rows, oversized lengths, and extreme attention-sink values.
    • Added key/value bounds validation to prevent invalid memory access and accumulation.
  • Documentation

    • Clarified handling of invalid top-k entries and lengths.

@jiayus-nvidia jiayus-nvidia added cat-bug Reports of incorrect behavior, crashes, regressions, or unexpected results. orig-nv-eng Reported or requested by NVIDIA engineering. mod-cutedsl CuTeDSL kernels, generated kernels, examples, or related integration work. op: DSA DSA related area:sparse_attention labels Sep 7, 2026
@coderabbitai

coderabbitai Bot commented Sep 7, 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: 278a8a9a-a4ed-426e-bed9-477b61300b61

📥 Commits

Reviewing files that changed from the base of the PR and between 95fca86 and a166eba.

📒 Files selected for processing (6)
  • docs/fe-oss-apis/bsa.md
  • python/cudnn/block_sparse_attention/_fp8_quant.py
  • python/cudnn/block_sparse_attention/_interface.py
  • python/cudnn/block_sparse_attention/api.py
  • python/cudnn/deepseek_sparse_attention/README.md
  • test/python/fe_api/bsa/test_BSA_attention_fp8.py
💤 Files with no reviewable changes (3)
  • python/cudnn/block_sparse_attention/api.py
  • python/cudnn/block_sparse_attention/_interface.py
  • python/cudnn/block_sparse_attention/_fp8_quant.py

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


📝 Walkthrough

Walkthrough

Changes

The change hardens SM90 and SM100 sparse-attention backward kernels against invalid sparse rows and edge-case sink values. It also expands SM100/SM103 FP8 block-sparse support to positive batch and head counts beyond the former restrictions.

Sparse backward validity handling

Layer / File(s) Summary
SM100 validity metadata and loading
python/cudnn/deepseek_sparse_attention/sparse_attention_backward/dsa_bwd_sm100_h16.py, python/cudnn/deepseek_sparse_attention/sparse_attention_backward/dsa_bwd_sm100_h32.py
SM100 kernels add packed validity metadata, clamp top-k lengths, validate sparse rows, and propagate validity through KV loading.
SM100 probability and gradient validation
python/cudnn/deepseek_sparse_attention/sparse_attention_backward/dsa_bwd_sm100_h16.py, python/cudnn/deepseek_sparse_attention/sparse_attention_backward/dsa_bwd_sm100_h32.py
Invalid rows are excluded from probabilities and dKV accumulation. Sink normalization handles infinite and saturating values explicitly.
SM90 validity pipeline and scatter bounds
python/cudnn/deepseek_sparse_attention/sparse_attention_backward/dsa_bwd_sm90.py
SM90 stores packed row-validity flags, masks invalid rows, clamps lengths, and bounds dKV scatter operations.
Input contract and regression coverage
python/cudnn/deepseek_sparse_attention/sparse_attention_backward/_interface_sm90.py, test/python/fe_api/dsa/test_DSA_sparse_attention_backward.py, python/cudnn/deepseek_sparse_attention/README.md
Documentation defines ignored out-of-range entries and clamped lengths. Tests cover invalid rows, empty rows, sink limits, layouts, finite gradients, and the updated acknowledgements.

FP8 block-sparse shape support

Layer / File(s) Summary
FP8 validation and shape coverage
python/cudnn/block_sparse_attention/*, test/python/fe_api/bsa/test_BSA_attention_fp8.py
FP8 validation removes former batch and head restrictions. Tests cover multiple batch and head combinations.
FP8 support documentation
docs/fe-oss-apis/bsa.md
Documentation updates the CuTe DSL floor and the supported batch and head counts.

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

Merge Risk: ⚪ Minimal · up to a166e

This change hardens sparse backward handling for invalid and empty rows and expands FP8 block-sparse shape support, with regression coverage for the stated cases. No current merge-blocking risk is identified.

Suggested reviewers: anerudhan

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.28% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 6 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes both primary changes: DSA backward boundary fixes and relaxed SM100 BSA FP8 shape limits.
Description check ✅ Passed The description covers all required sections, explains the changes and motivation, documents compatibility impact, related issues, testing environments, exact results, and validation checks.
Full details: Docstring Coverage

Explanation

Docstring coverage is 51.28% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 39 functions across 6 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@jiayus-nvidia jiayus-nvidia added this to the Frontend 1.29.0 milestone Sep 7, 2026
@jiayus-nvidia jiayus-nvidia changed the title Fix DSA backward boundaries across SM90 and specialized SM100 kernels Fix DSA backward boundaries and relax SM100 BSA FP8 shape limits Sep 7, 2026
@jiayus-nvidia jiayus-nvidia added the cat-feature Requests for new functionality, APIs, examples, or behavior improvements. label Sep 7, 2026
@jiayus-nvidia

Copy link
Copy Markdown
Contributor Author

@cudnn-ci-bot run oss

@cudnn-ci-bot

cudnn-ci-bot commented Sep 7, 2026

Copy link
Copy Markdown

🏁 Pipeline finished

SHA: a166eba
Targets: oss
Branch: cudnn-gh/pr-926-a166eba
Pipeline: 66590594
Last updated: 2026-09-07 08:00 UTC

@Anerudhan
Anerudhan merged commit 346c500 into NVIDIA:develop Sep 7, 2026
7 of 8 checks passed
@Anerudhan Anerudhan mentioned this pull request Sep 12, 2026
thynics pushed a commit to thynics/cudnn-frontend that referenced this pull request Sep 14, 2026
The sink-folded LSE of the two-CTA D512 kernel only special-cased
lse == +inf, so an infinite sink evaluated inf - inf, a finite sink whose
log2(e) rescale overflows (for example 2.4e38) did the same, and both
produced NaN dq and dkv. An empty row with the sink disabled (lse and
sink both -inf) produced NaN d_sink through the dSink reduction, which
also had no saturating-sink handling.

Test the rescaled LSE and sink values as the H16/H32 kernels do since
NVIDIA#926, and give the dSink reduction the same +inf / -inf sink handling as
the generic kernel. Index validity was already aligned with the generic
kernel. Related to NVIDIA#676.
jiayus-nvidia pushed a commit that referenced this pull request Sep 15, 2026
* feat(dsa): add H128/D576 two-CTA SM100 sparse attention backward

Add a (2,1,1)-cluster CuTe DSL backward kernel for BF16 DeepSeek Sparse
Attention with H=128, D=576 and D_v=512 on SM100-class devices, and route
the public backward API to it from tensor metadata.

- Score, dP, dQ, dV and dK run on cta_group::2 tensor cores with the two
  CTAs of a cluster splitting the 128 heads; the D64 QK/dQ tails are
  separate CG2 rounds and the dK tail is a per-CTA warp MMA with FP32
  atomics. P and dS are exchanged between the CTAs with shared::cluster
  bulk copies. Slot validity is applied before every score and gradient
  operation, including the K rows retained for dQ, so ignored top-k slots
  cannot contaminate gradients.
- Short query batches spread each query over several clusters (one dQ
  owner plus dV/dK workers); long sequences use one cluster per query.
- Dispatch requires an SM100-class device (compute capability 10.0 or
  10.3, validated on B200 and B300), contiguous BF16 inputs and topk_max in
  {128, 512, 1024, 1152, 2048}; FP16, other head counts and top-k sizes,
  noncontiguous inputs, SM107 and SM90 keep their existing paths.
- The plan compiles from tensor metadata without a priming launch and
  executes without allocation: the wrapper allocates dq/dkv/d_sink and a
  reusable uint8 workspace whose size is exposed through
  SparseAttentionBackward.scratch_workspace_bytes(). Zero-initialization
  of the FP32 dKV/dSink accumulators happens in-kernel on the launch
  stream, and the wrapper cache key now includes device and strides.
- Compatible with nvidia-cutlass-dsl 4.5 (the declared floor): warp-MMA
  fragments are created from the partitioned tensors and loop temporaries
  are declared ahead of dynamic branches.

Document the dispatch rule, the workspace contract and the benchmark
invocation.

* fix(dsa): guard the H128/D512 two-CTA sink fold and dSink reduction

The sink-folded LSE of the two-CTA D512 kernel only special-cased
lse == +inf, so an infinite sink evaluated inf - inf, a finite sink whose
log2(e) rescale overflows (for example 2.4e38) did the same, and both
produced NaN dq and dkv. An empty row with the sink disabled (lse and
sink both -inf) produced NaN d_sink through the dSink reduction, which
also had no saturating-sink handling.

Test the rescaled LSE and sink values as the H16/H32 kernels do since
#926, and give the dSink reduction the same +inf / -inf sink handling as
the generic kernel. Index validity was already aligned with the generic
kernel. Related to #676.

* test(dsa): cover the H128/D576 two-CTA backward route

- Dispatch: H128/D576/BF16 with each supported topk_max selects the
  two-CTA backend on compute capability 10.0 and 10.3; FP16, D_v=576,
  unsupported top-k sizes, SM107, SM90 and noncontiguous inputs fall back
  to the generic M64 path.
- Numerics against the PyTorch reference for topk 128..2048 with and
  without topk_length, covering both the split-cluster regime used by
  short query batches and the single-cluster regime that long sequences
  take (s_q=129 exceeds the two-SM cluster count of SM100 parts).
- Ignored slots pointing at NaN KV rows leave dq/dkv/d_sink finite, the
  reused workspace is untouched outside the declared size, and
  execute() neither compiles nor allocates on the hot path.
- Sink normalization, zero and out-of-range topk_length boundaries, and
  non-default-stream ordering are extended to the H128 configuration.

* test(dsa): assert the H128/D576 two-CTA route is executed

The dispatch tests only exercised the selector. Spy on the two-CTA launch
entry so the wrapper-path numerics test and the plan-path ignored-slot
test fail if the generic kernel served the request instead. Requested in
review of #1048.

* test(dsa): verify the D576 route in the out-of-range index test

The D576 parametrization asserted only the selector result, which the
dispatch tests already cover; spy on the two-CTA launch entry instead so
a generic fallback cannot pass it. Also state in the docs that both H128
two-CTA backends clamp topk_length, since the D512 kernel does as well.

* fix(dsa): keep padded slots finite in the H128/D512 two-CTA probability path

The two-CTA D512 kernel zero-fills K and V for padded, negative,
out-of-range, or past-length slots, so their score is exactly zero and
the exp2 argument equals the folded negative LSE. Once that exceeds 128
(every valid logit and the sink far below zero) exp2 overflows, the
infinite dS reaches the dQ UMMA, and inf * 0 turns the whole dQ row into
NaN, while the generic, H16 and D576 kernels stay finite.

Clamp the exp2 argument at 64. A valid slot's argument is the log2 of a
probability and never approaches that bound, so valid values are
unchanged; invalid slots keep P and dS finite, and the zero-filled K and
V rows then contribute exact zeros to dQ and are never scattered to dKV.
Masking P per slot instead cost 5-8% on B200 because the math warps sit
on the critical path. Adds a regression test for both H128 two-CTA
routes. Related to #676.

* test(dsa): scope the H128 sink-normalization case to the two-CTA route

On devices that do not select the H128/D576 two-CTA backend (SM107) the
H128 parametrization exercised the generic M64 kernel, which this change
does not touch and which upstream does not otherwise run for H128/D576.
Skip it there, like the other H128/D576 cases; H32 keeps the existing
coverage of the generic path.

* fix(dsa): propagate NaN through the D512 clamp and assert the two-CTA routes in tests

The exp2-argument clamp used the DSL default fmin (PTX min.f32), which
returns the non-NaN operand and would have turned a NaN LSE or sink into
finite gradients on the D512 two-CTA route while the generic and D576
kernels propagate NaN; use the NaN-preserving form. The sink-limit,
padded-slot and sink-normalization tests now assert that the two-CTA
route actually executed rather than only checking the selector. The
benchmark README comment for the D512 route now lists SM100/SM103 like
its dispatch.

* refactor(dsa): key the backward wrapper cache on contiguity, not strides

Routing is the only stride-dependent decision: check_support() selects
the two-CTA route from all(desc.is_contiguous()), the two-CTA plan is
compiled for the contiguous layout, the fallback consumes the runtime
tensors, and the workspace size does not depend on strides. A single
all-inputs-contiguous flag therefore separates the plans that must not
be shared, without recording every stride tuple in the key. Requested in
review of #1048.

---------

Co-authored-by: Long Cheng <longcheng@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:sparse_attention cat-bug Reports of incorrect behavior, crashes, regressions, or unexpected results. cat-feature Requests for new functionality, APIs, examples, or behavior improvements. mod-cutedsl CuTeDSL kernels, generated kernels, examples, or related integration work. op: DSA DSA related orig-nv-eng Reported or requested by NVIDIA engineering.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants