-
Notifications
You must be signed in to change notification settings - Fork 224
DSA: fix 576-wide d_sink, test/document top-k index semantics, B300 benchmarks #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -119,6 +119,15 @@ Backward pass for DeepSeek Sparse Attention. Expects the forward outputs | |
| - `topk_length` (optional): `(total_S_q,)` INT32 — per-query valid count | ||
| - **Outputs** — tuple `(dq, dkv, d_sink)` | ||
| - **Constraints** — SM90 or SM100; SM90 supports the FlashMLA DSA shape with `head_dim ∈ {512, 576}` | ||
| - **Top-k index semantics** — without `topk_length`, invalid `topk_idxs` | ||
| entries must be `-1` sentinels (the FlashMLA convention). With | ||
| `topk_length`, the first `topk_length[i]` entries of each row must all be | ||
| valid indices — this compact-prefix fast path skips per-row sentinel | ||
| checks, so `-1` entries inside the prefix are not allowed. `topk_length` | ||
| values must be `>= 1`; to make a query row inert (e.g. padding rows for | ||
| fixed-shape capture), use `topk_length = 1` with any valid index and a | ||
| zeroed `dout` row, which yields an exactly-zero `dq` row and no `dkv` / | ||
| `d_sink` contribution. | ||
|
Comment on lines
+122
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Map the relevant files first
git ls-files docs/fe-oss-apis/dsa.md test/python/fe_api/dsa/dsa_reference.py
# Show the relevant section of the docs with line numbers
sed -n '100,150p' docs/fe-oss-apis/dsa.md | cat -n
# Inspect the reference helper for top-k index handling
rg -n "topk|total_S_kv|invalid|sentinel|topk_length" test/python/fe_api/dsa/dsa_reference.py -n -A 4 -B 4
# If needed, show the surrounding implementation
python3 - <<'PY'
from pathlib import Path
p = Path('test/python/fe_api/dsa/dsa_reference.py')
text = p.read_text()
for needle in ['topk_length', 'total_S_kv', 'topk_idxs']:
idx = text.find(needle)
print(f"\n== {needle} @ {idx} ==")
if idx != -1:
start = max(0, idx - 500)
end = min(len(text), idx + 1800)
print(text[start:end])
PYRepository: NVIDIA/cudnn-frontend Length of output: 18739 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search the docs for any other definition of top-k bounds or topk_length semantics
rg -n "topk_length|topk_max|total_S_kv|valid indices|sentinel|FlashMLA" docs/fe-oss-apis/dsa.md -n -A 3 -B 3
# Search the reference helper for any explicit assertion on topk_length bounds
rg -n "assert|clamp|min=1|max=|topk_length" test/python/fe_api/dsa/dsa_reference.py -n -A 3 -B 3Repository: NVIDIA/cudnn-frontend Length of output: 13212 Document the top-k index range. “Valid index” should explicitly mean 🤖 Prompt for AI Agents |
||
|
|
||
| ```python | ||
| result = DSA.sparse_attention_backward_wrapper( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -216,3 +216,47 @@ def fail_on_pad(*args, **kwargs): | |
| atol=5e-2, | ||
| rtol=5e-2, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.L0 | ||
| @torch_fork_set_rng(seed=0) | ||
| def test_DSA_sparse_attention_backward_sentinel_matches_topk_length(): | ||
| """-1-sentinel indices (FlashMLA convention) match the compact-prefix path. | ||
|
|
||
| Without ``topk_length``, invalid ``topk_idxs`` entries are ``-1`` | ||
| sentinels and the kernel zero-fills those rows; with ``topk_length``, | ||
| valid indices must be a compact prefix and per-row sentinel checks are | ||
| skipped. The same valid index set must produce the same gradients either | ||
| way. The two modes are different compiled variants, so this also guards | ||
| the variant selection. | ||
| """ | ||
| if not torch.cuda.is_available() or torch.cuda.get_device_capability()[0] < 10: | ||
| pytest.skip("SM100+ GPU required") | ||
|
|
||
| s_q, s_kv, h, d, topk = 512, 2048, 64, 512, 256 | ||
| device = "cuda" | ||
| q = torch.randn(s_q, h, d, dtype=torch.bfloat16, device=device) | ||
| kv = torch.randn(s_kv, d, dtype=torch.bfloat16, device=device) | ||
| attn_sink = torch.randn(h, dtype=torch.float32, device=device) | ||
| topk_idxs = torch.stack([torch.randperm(s_kv, device=device)[:topk] for _ in range(s_q)]).to(torch.int32) | ||
| topk_length = torch.randint(1, topk + 1, (s_q,), dtype=torch.int32, device=device) | ||
|
|
||
| # Sentinel variant: same valid prefix, -1 beyond each row's length. | ||
| slot = torch.arange(topk, device=device).unsqueeze(0).expand(s_q, topk) | ||
| idx_sentinel = torch.where(slot < topk_length.unsqueeze(1), topk_idxs, torch.full_like(topk_idxs, -1)) | ||
|
|
||
| out, lse = ref_sparse_attention_forward(q, kv, attn_sink, topk_idxs, topk_length=topk_length) | ||
| dout = torch.randn_like(out) | ||
|
|
||
| try: | ||
| from cudnn import DSA | ||
|
|
||
| r_len = DSA.sparse_attention_backward_wrapper(q, kv, out, dout, lse, attn_sink, topk_idxs, topk_length=topk_length) | ||
| r_sen = DSA.sparse_attention_backward_wrapper(q, kv, out, dout, lse, attn_sink, idx_sentinel, topk_length=None) | ||
| except (ImportError, ValueError, NotImplementedError, RuntimeError) as e: | ||
| pytest.skip(f"Unsupported testcase: {e}") | ||
|
Comment on lines
+251
to
+257
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Do not skip unexpected This catches kernel compilation, launch, validation, and implementation regressions as “unsupported testcase” skips. Restrict the skip to a known unsupported condition and let unexpected runtime failures fail the test; otherwise this parity test cannot reliably guard the compiled variants. 🤖 Prompt for AI Agents |
||
| torch.cuda.synchronize() | ||
|
|
||
| torch.testing.assert_close(r_len["dq"], r_sen["dq"], atol=1e-2, rtol=1e-2) | ||
| torch.testing.assert_close(r_len["dkv"], r_sen["dkv"], atol=1e-2, rtol=1e-2) | ||
| torch.testing.assert_close(r_len["d_sink"], r_sen["d_sink"], atol=1e-2, rtol=1e-2) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
Qualify the “~7 SOL points” comparison.
The 32k row is 44.3% for
d_qk=512versus 34.2% ford_qk=576, a 10.1-point gap. State that the gap is ~7 points through 16k and widens to ~10 points at 32k.🤖 Prompt for AI Agents