Skip to content

CSA: add fused Compressor forward+backward CuTe-DSL kernels (ported from Megatron-LM) - #427

Open
zkyue wants to merge 10 commits into
NVIDIA:developfrom
zkyue:feat/csa-fused-compressor
Open

CSA: add fused Compressor forward+backward CuTe-DSL kernels (ported from Megatron-LM)#427
zkyue wants to merge 10 commits into
NVIDIA:developfrom
zkyue:feat/csa-fused-compressor

Conversation

@zkyue

@zkyue zkyue commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Ports the fused CSA/HCA Compressor gated-pooling kernels from Megatron-LM into the
FE-OSS Python API, following the maintainer guidance on Megatron-LM PR
#5984 that the kernels belong in
cudnn-frontend like the other CSA/HCA fused kernels
(comment).
Measurements and numerics analysis for the original kernels are in Megatron-LM issue
#5968.

The Compressor used by the CSA/HCA experimental attention variants implements its
gated-softmax pooling in eager PyTorch. For the THD packed layout, the region between
the two projection GEMMs and the RMSNorm — gather-index build, gather, + APE,
overlap-window transform (coff == 2), fp32 softmax over the window, gated weighted
sum — decomposes into ~39 forward and ~51 backward kernel launches per call (at
compress_ratio = 4) and materializes (total_comp, 2*ratio, 1, head_dim) window
intermediates. This PR delivers that region as one forward and one backward CuTe-DSL
kernel
behind the repo's APIBase pattern.

On top of the port (first commit, kernels byte-for-byte output-identical to the
Megatron original), two optimization commits — both identified by an ncu
hardware-ceiling audit of the ported kernels and both leaving forward/dKV/dScore
bitwise unchanged — are stacked separately for reviewability:

  1. Forward 32-bit vectorized access (cute.autovec_copy over register fragments,
    2 head dims per thread): forward kernel 1.20–1.36x across the benchmark shapes.
  2. Backward kernel-side zero-writes: the backward kernel now writes exact zeros to
    every never-consumed dKV/dScore slot itself, so the wrapper allocates those
    buffers with empty_like and the two whole-tensor bf16 zero-fills disappear:
    backward region 1.21–1.50x.

Once this lands, Megatron-LM PR #5984 will be reworked into a thin dispatch that calls
these wrappers when cudnn (with the cutedsl extra) is available, keeping its eager
fallback — the autograd wiring stays on the framework side; the APIs here are pure
kernels-plus-validation.

Files

  • python/cudnn/csa/compressor/compressor_sm100.py — the two kernels plus launch
    machinery (JIT cache per (ratio, head_dim, coff, device), capture-safe JIT guard,
    cached-launch fast path). Kernel math is unchanged from the Megatron original
    (bitwise-verified per commit); the optimization commits change the memory access
    pattern (forward) and add the zero-write ownership scheme (backward).
  • python/cudnn/csa/compressor/api.pyCSACompressorForward / CSACompressorBackward
    (APIBase: check_support / compile / execute) and the high-level
    csa_compressor_forward_wrapper / csa_compressor_backward_wrapper (allocate
    outputs, return TupleDict, LRU-cache compiled instances).
  • python/cudnn/csa/ — new package (CSA lazy namespace, README), registered in
    python/cudnn/__init__.py lazy imports next to DSA/NSA. Happy to move/rename the
    package (e.g. under deepseek_sparse_attention/) if you prefer a different home for
    CSA-side kernels.
  • docs/fe-oss-apis/csa.md (+ overview.md link) — semantics, numerics contract,
    support surface, usage, perf tables.
  • test/python/fe_api/csa/test_CSA_compressor.py — 40 tests, see below.

Support surface (check_support)

Compute capability 10.0 (the only validated architecture; the kernels use no
arch-specific features, so wider enablement is likely straightforward but stays opt-in
until validated), ratio == 4 / coff == 2 (the production CSA/HCA configuration; the
kernels are generic over (ratio, head_dim, coff in {1, 2}) and the gate can be lifted
once validated), BF16 kv/score/out, FP32 ape, int32 cu_seqlens/
cu_seqlens_comp, int32 flat offsets (total_tokens * coff * head_dim < 2**31),
head_dim <= 8388480 (the forward launch gridDim.y bound; the pre-vectorization
schedule had the same envelope, just unchecked),
contiguous tensors on one CUDA device with 16-byte-aligned base pointers (4-byte for the
int32 cu_seqlens; contiguity does not imply base alignment for storage-offset views,
so this is checked per call). Launches are anchored in the tensors' device context
(tensors on a non-current device work correctly), and explicit-stream calls
record_stream their operands so caller-released storages are not recycled while the
kernel is pending.

Numerics

All arithmetic is fp32 with a single final bf16 rounding; mul.rn.f32/fma.rn.f32 are
pinned in PTX so results do not depend on compiler FMA contraction.

  • Port fidelity: the kernels are bitwise identical to the Megatron original —
    verified on-device on 5 THD shape mixes (including ragged packs, segments shorter than
    ratio, and static-capacity padding): forward, dKV, dScore all torch.equal
    against the original module on identical inputs (dAPE agrees within the fp32-atomic
    envelope, max |diff| 2.5e-5; bitwise is not defined for either implementation there).
    The gate was re-run after each optimization commit.
  • vs an fp32-intermediate eager reference (same op order, fp32 throughout): dKV/
    dScore bit-identical on every tested shape; forward differs on <0.006% of
    elements within one bf16 rounding step.
  • vs an fp64 oracle: fused max abs error <= the eager path's error on every tested
    output (the eager path itself rounds softmax weights to bf16 and multiplies in bf16).
  • Backward is atomic-free for dKV/dScore (disjoint stores). The kernel writes those
    buffers in full: consumed positions get their gradients, and every never-consumed
    position (segment-tail tokens; the last block's first-half columns; segments shorter
    than ratio; token-capacity padding beyond cu_seqlens[-1]) gets an exact zero from
    a unique owning CTA — matching autograd without zero-initialized buffers (when
    total_comp == 0 nothing launches and the wrapper falls back to zeroed allocation).
    Forward, dKV, dScore replay bitwise identically run to run. dAPE is
    accumulated with fp32 atomics and is not bitwise run-to-run deterministic
    ; the
    backward APIs therefore raise under strict torch.use_deterministic_algorithms(True)
    and warn-and-run in warn-only mode (a deterministic per-CTA partials reduction is
    possible follow-up work).

CUDA graphs & static-capacity padding

  • The launch path is capture-compatible after a one-call warmup (or explicit
    compile()) per (ratio, head_dim, coff) configuration; a call that would JIT under
    capture raises a clear RuntimeError instead of corrupting the capture. Tested:
    capture fwd+bwd in one graph, replay with new data and a smaller device-side true
    row count
    (static capacity, changed cu_seqlens contents), bitwise dKV/dScore
    against the eager reference.
  • Forward computes rows beyond cu_seqlens_comp[-1] exactly like the eager code (window
    from token 0 with first-in-segment semantics); backward ignores incoming gradients on
    such padding rows — tested, including nonzero padding-row gradients and a pack whose
    leading segment is shorter than ratio.

Performance

Measured on 1x B200 (CC 10.0, driver 590.48.01); PyTorch 2.13.0 (CUDA 13.3),
nvidia-cutlass-dsl 4.6.1; BF16 kv/score, FP32 ape; ratio = 4, coff = 2; THD
packs of 8192-token sequences. Both implementations run on identical inputs over exactly
the replaced region (projection GEMMs, RMSNorm, RoPE excluded — unchanged); the eager
baseline is the verbatim replaced region of Megatron-LM Compressor._forward_thd.

Isolated GPU kernel time (nsys, sum of kernel durations per iteration, 50 iterations
after 20 warmup; no launch/host overhead; backward includes its dAPE zero-fill):

THD pack head_dim eager fwd fused fwd fwd eager bwd fused bwd bwd
1x8192 128 117.8 us 4.5 us 26.5x 187.2 us 12.8 us 14.6x
3x8192 128 229.8 us 10.0 us 23.0x 352.7 us 22.2 us 15.9x
1x8192 512 263.3 us 12.4 us 21.2x 425.0 us 22.8 us 18.6x
3x8192 512 664.3 us 35.0 us 19.0x 1155.8 us 66.0 us 17.5x

End-to-end wall clock of the same region (CUDA events, median of 100; includes launch
overhead; eager backward goes through torch autograd on the recorded eager graph, fused
backward is the explicit backward wrapper call — no autograd engine; not comparable to
the kernel-time rows):

THD pack head_dim eager fwd fused fwd fwd eager bwd fused bwd bwd
1x8192 128 333.7 us 37.8 us 8.8x 506.7 us 47.2 us 10.7x
3x8192 128 373.9 us 35.1 us 10.7x 561.9 us 54.6 us 10.3x
1x8192 512 409.2 us 39.7 us 10.3x 646.3 us 57.3 us 11.3x
3x8192 512 823.7 us 61.4 us 13.4x 1475.5 us 101.8 us 14.5x

How close to the hardware ceiling: an ncu audit of the ported kernels, prior to
the two optimization commits
(cache-flushed, --set full, all four benchmark shapes,
forward and backward) showed measured DRAM read volume matching the algorithmically
necessary bytes within 1% — the THD gather adds no over-read; stores fully coalesced
(32/32 bytes per sector) and loads nearly so (29–30/32). Neither L2 (<27% of peak) nor
DRAM (<33%) was close to saturation at these microsecond-scale sizes: the gap to a pure
DRAM-floor time was a mix of sub-wave grid width / occupancy, memory latency and (at
the largest shape) issue pressure — not wasted traffic. The two audit follow-ups are
the two optimization commits in this PR (forward 32-bit vectorization; backward
zero-fill elimination); they do not change the bytes the kernels must read, so the
traffic-optimality conclusion carries over, while the utilization percentages quoted
predate them. Registers stay spill-free throughout (forward 48, backward 72 per
thread); wider forward vectors (64/128/256-bit) were measured and rejected —
instruction count drops but register pressure and occupancy losses dominate.

The wall-clock numbers rely on the cached-launch fast path
(CUDNNFE_CSA_COMPRESSOR_FAST_LAUNCH=0 disables it): a per-config snapshot of the
CuTe-DSL launch state replayed with in-place argument mutation, which removes tens of
microseconds of per-call host overhead for these microsecond-scale kernels. The snapshot
construction introspects private-but-stable DSL internals; any structural mismatch on a
future nvidia-cutlass-dsl upgrade is caught at build time and the wrapper permanently
falls back to the regular launch path (it never launches from a partially built
snapshot). The isolated kernel times are launch-path independent.

Tests

test/python/fe_api/csa/test_CSA_compressor.py (40 tests, all L0; skip cleanly
without cudnn[cutedsl] or on non-CC-10.0 GPUs — the repo's pytest conftest itself
requires CUDA). The eager reference is a self-contained
mirror of the replaced Megatron-LM region (gather -> +APE -> overlap transform ->
softmax -> weighted sum) with selectable numerics (verbatim upstream / fp32
intermediates / fp64 oracle):

  • numerics vs fp32-eager (bitwise dKV/dScore), vs upstream eager (tolerance), vs
    fp64 oracle (fused error <= eager error), over ragged multi-segment packs including
    segments shorter than ratio, head_dim 128 and 512 plus an odd head_dim (65)
    covering the scalar-layout forward instantiation;
  • static-capacity padding (fwd semantics + ignored padding grads, including a leading
    segment shorter than ratio); empty-output edge case;
  • backward zero-write coverage: NaN-canary uninitialized dKV/dScore buffers stay
    bitwise-equal to zero-initialized runs and to the eager reference across ragged /
    degenerate / all-tiny / padded shapes — covering every never-consumed slot class
    directly, including grad buffers with token-capacity padding beyond cu_seqlens[-1]
    — and packs where no segment reaches ratio tokens (total_comp == 0) fall back to
    exact-zero allocation;
  • run-to-run determinism of forward/dKV/dScore; deterministic-mode rejection of the
    backward (strict) and warn-and-run semantics (warn-only);
  • CUDA-graph capture/replay (same data, then new data with a smaller true row count —
    which also exercises the token-capacity zero sweep) + the loud JIT-under-capture
    error;
  • check_support acceptance on meta samples and 13 rejection boundaries (ratio/coff
    envelope, dtypes, shape mismatches, int32 flat-offset and head_dim launch bounds, total_comp > 0 with
    total_tokens < ratio, contiguity, CPU tensors);
  • runtime hazards: misaligned base pointers rejected, explicit-stream input lifetime
    (record_stream), multi-device launch correctness (tensors on a non-current device);
  • class-API vs wrapper bitwise equivalence.

black --line-length 160 (26.3.1, per .pre-commit-config.yaml) clean.

Summary by CodeRabbit

  • New Features
    • Added an experimental CSA Fused Compressor API for gated-pooling forward/backward acceleration on supported NVIDIA GPUs.
    • Provides both class-based and wrapper-style interfaces, with stream-aware execution and CUDA-Graph capture-safe behavior (including optimized fast-launch when available).
  • Documentation
    • Added CSA Compressor API documentation and linked it from the FE-OSS operations overview.
  • Tests
    • Added end-to-end tests covering correctness, determinism rules, padding/empty/zero-gradient semantics, support checks, stream safety, and CUDA-Graph capture/replay.
  • Benchmarks
    • Added benchmarking tools for fused execution and graph replay performance.

zkyue added 3 commits July 23, 2026 07:05
Port the fused CSA/HCA Compressor gated-pooling kernels from Megatron-LM
(NVIDIA/Megatron-LM#5984) into the FE-OSS Python API,
per the maintainer request in
NVIDIA/Megatron-LM#5984 (comment).

One forward and one backward CuTe-DSL kernel fuse the THD gated-softmax
pooling region (gather -> +APE -> overlap-window transform -> fp32 softmax ->
gated weighted sum -> bf16 cast). Kernel math is unchanged from the Megatron
original (verified bitwise on forward/dKV/dScore); the interface is reshaped
to the APIBase pattern: CSACompressorForward / CSACompressorBackward classes,
csa_compressor_forward_wrapper / csa_compressor_backward_wrapper, a new
python/cudnn/csa package, docs, and fe_api tests (numerics vs fp32/upstream
eager references and an fp64 oracle, ragged packs, static-capacity padding,
run-to-run determinism, CUDA-graph capture/replay, check_support boundaries).

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
…opy)

Widen every bf16 load/store in the fused Compressor forward kernel from
scalar (16-bit) to paired 32-bit accesses: each thread now owns 2 adjacent
head dims and moves its window slices through 32-bit universal copies
(cute.autovec_copy over register fragments; cute.assume makes the
alignment provable). The per-lane fp32 math is unchanged and in the same
order, so the forward output stays bitwise identical to the previous
kernel (re-verified against the Megatron-LM original on the same 5 THD
shape-mix gate as the original port). Odd head_dims keep a scalar
(vec == 1) instantiation of the same kernel; the head_dim 128/512
production shapes take vec == 2.

The launch schedule moves from (rows_per_cta=4, threads=128) to one row
per CTA with 64-thread column groups, which widens the sub-wave grids
that limit the small shapes.

Measured (nsys pure kernel time, B200, ratio=4 coff=2, THD packs of
8192-token sequences, forward kernel only):

  shape        before    after    speedup
  1x8192 d128   6.0 us   4.5 us   1.36x
  3x8192 d128  12.6 us  10.0 us   1.26x
  1x8192 d512  16.0 us  12.4 us   1.29x
  3x8192 d512  42.1 us  35.0 us   1.20x

Alternatives measured and rejected on the same matrix (all bitwise-equal):
a PTX-unpack vec2 prototype (slower than the pure-DSL version:
4.7/10.5/12.9/35.7 us), and 64/128/256-bit per-thread vectors, which cut
executed instructions but lose to register pressure and occupancy
(80/147/255 regs vs 48; the 256-bit variant spills). Registers stay
spill-free at 48 (previous kernel: 40).

Tests: numerics shape matrix extended with an odd head_dim case to cover
the scalar-layout instantiation, and a check_support rejection for
head_dims beyond the launch bound.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
The backward previously required zero-initialized dKV/dScore buffers: the
kernel only wrote consumed positions, and two tensor-wide bf16 fill
kernels (torch.zeros_like) supplied the zeros everywhere else. Those
fills cost as much DRAM traffic as the kernel's own stores.

The kernel now writes every position itself: consumed positions get their
gradients as before (disjoint, atomic-free stores), and each
never-consumed slot class gets an exact zero from a unique natural owner,
keeping all stores disjoint and dKV/dScore bitwise run-to-run
deterministic:

  - first-half columns of each segment's last block's own tokens
    (coff == 2; no next block consumes them) -> that last block;
  - per-segment tail tokens (seqlen % ratio, both halves) -> the
    segment's last block;
  - all tokens of segments with zero output blocks (seqlen < ratio) ->
    CTA column bidx == 0;
  - tokens beyond cu_seqlens[-1] (static token-capacity padding of the
    gradient buffers, the CUDA-graph case) -> grid-strided across CTA
    columns.

The backward wrapper allocates grad_kv/grad_score with torch.empty_like
instead of torch.zeros_like (the fp32 dAPE fill stays: it is the atomic
accumulator). When total_comp == 0 the kernel cannot launch, so the
wrapper falls back to zeroed allocations to preserve autograd's
exact-zero semantics; the class API documents the same caveat.

Measured on the backward region (kernel + remaining fills vs kernel +
three fills before), B200, ratio=4 coff=2, THD packs of 8192-token
sequences; nsys = sum of kernel durations per iteration, wall = CUDA
events around the wrapper call:

  shape        nsys before -> after      wall before -> after
  1x8192 d128  15.5 -> 12.8 us (1.21x)   56.2 -> 47.2 us (1.19x)
  3x8192 d128  29.9 -> 22.2 us (1.35x)   61.8 -> 54.6 us (1.13x)
  1x8192 d512  34.2 -> 22.8 us (1.50x)   67.9 -> 57.3 us (1.19x)
  3x8192 d512  90.9 -> 66.0 us (1.38x)  111.6 -> 101.8 us (1.10x)

The zero-writes cost the kernel 8 registers (64 -> 72, occupancy 44% ->
41% at 3x8192 d512) and a small amount of extra store traffic, repaid
several times over by dropping the two whole-tensor fills.

dKV/dScore remain bitwise identical to the Megatron-LM original on the
5-shape port gate and to the fp32 eager reference on every test shape.

Tests: NaN-canary suite proving the kernel fully overwrites uninitialized
buffers (ragged, degenerate short segments, all-tiny packs, head_dim 512,
static-capacity padded rows) bitwise against zero-initialized runs and
the eager reference; a zeros-fallback test for packs where no segment
reaches ratio tokens. The existing CUDA-graph replay test covers the
token-capacity padding class (it fails without the grid-strided sweep).

Docs: csa.md updated (buffer contract, fill count, perf tables refreshed
for both this commit and the forward vectorization).

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an experimental CSA fused Compressor API with lazy exports, Python wrappers, SM100 CuTe-DSL forward/backward kernels, ratio-specific dispatch, cached launches, CUDA-graph support, benchmarking, documentation, and extensive correctness and runtime tests.

Changes

CSA Fused Compressor

Layer / File(s) Summary
Public package surface and documentation
docs/fe-oss-apis/*, python/cudnn/__init__.py, python/cudnn/csa/*
Adds lazy CSA exports, compressor re-exports, package documentation, and API documentation covering semantics, constraints, performance, and testing.
Validated Python API and wrapper execution
python/cudnn/csa/compressor/api.py
Adds support checks, runtime validation, compiled-API caching, stream handling, class APIs, and forward/backward wrappers with gradient-buffer contracts.
SM100 kernels and cached launch execution
python/cudnn/csa/compressor/compressor_sm100.py
Adds fused ratio=4 forward/backward kernels, pinned fp32 arithmetic, deterministic zero writes, capture-guarded compilation, and cached launch replay.
Ratio=128 kernels and schedule dispatch
python/cudnn/csa/compressor/compressor_sm100_r128.py
Adds dedicated chunked kernels, schedule buckets, occupancy selection, deterministic merges, and cached ratio=128 dispatch.
Correctness gates and performance tooling
test/python/fe_api/csa/*, benchmark/csa/*
Adds eager-reference tests, determinism and padding checks, CUDA-graph coverage, ratio=128 numerical gates, timing benchmarks, and PTX register/spill probes.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ForwardWrapper
  participant API
  participant Scheduler
  participant Kernel
  Caller->>ForwardWrapper: submit packed CSA tensors
  ForwardWrapper->>API: infer shape and fetch compiled configuration
  API->>Scheduler: select ratio-specific kernel and schedule
  Scheduler->>Kernel: launch or replay cached arguments
  Kernel-->>Caller: write compressed bf16 output
Loading

Suggested labels: mod-backend

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding fused CSA Compressor CuTe-DSL kernels from Megatron-LM.
Description check ✅ Passed The description is detailed and covers summary, motivation, support, testing, performance, and compatibility, though it omits some template headings.
Docstring Coverage ✅ Passed Docstring coverage is 97.06% which is sufficient. The required threshold is 80.00%.
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.

@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: 4

🧹 Nitpick comments (1)
python/cudnn/csa/compressor/__init__.py (1)

8-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sort the __all__ entries.

Ruff RUF022 reports this list as unsorted. Place the backward symbols before the forward symbols in both class and wrapper groups.

🤖 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 `@python/cudnn/csa/compressor/__init__.py` around lines 8 - 13, Sort the
__all__ entries in the compressor module by placing CSACompressorBackward before
CSACompressorForward, and csa_compressor_backward_wrapper before
csa_compressor_forward_wrapper.

Source: Linters/SAST tools

🤖 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 `@docs/fe-oss-apis/csa.md`:
- Around line 61-62: Update the support-bounds list in the CSA documentation to
include the enforced constraints from the compressor API: total_comp * head_dim
must be less than 2**31, and total_comp must be zero when total_tokens is less
than ratio. Preserve the existing bounds.
- Around line 190-192: Update the documented pytest command near the CSA
compressor test instructions to run from the test/python working directory,
using the fe_api/csa/test_CSA_compressor.py path so pytest.ini and conftest.py
are applied consistently.

In `@python/cudnn/csa/__init__.py`:
- Line 36: Update the module-level __all__ declaration to a literal list of
explicit string names, including CSA and every public symbol currently provided
by _SYMBOLS.keys(); remove the dynamic starred mapping-key expansion while
preserving the same export set.

In `@python/cudnn/csa/compressor/api.py`:
- Around line 109-118: Update the warnings.warn call in the
deterministic-algorithm handling block to pass stacklevel=2, so the
RuntimeWarning points to the immediate caller rather than this internal helper.
Leave the existing message, warning category, and exception path unchanged.

---

Nitpick comments:
In `@python/cudnn/csa/compressor/__init__.py`:
- Around line 8-13: Sort the __all__ entries in the compressor module by placing
CSACompressorBackward before CSACompressorForward, and
csa_compressor_backward_wrapper before csa_compressor_forward_wrapper.
🪄 Autofix (Beta)

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: 64d2d78a-480d-43f2-bb95-7cfce924c524

📥 Commits

Reviewing files that changed from the base of the PR and between 3a9ed3f and be2e367.

📒 Files selected for processing (9)
  • docs/fe-oss-apis/csa.md
  • docs/fe-oss-apis/overview.md
  • python/cudnn/__init__.py
  • python/cudnn/csa/README.md
  • python/cudnn/csa/__init__.py
  • python/cudnn/csa/compressor/__init__.py
  • python/cudnn/csa/compressor/api.py
  • python/cudnn/csa/compressor/compressor_sm100.py
  • test/python/fe_api/csa/test_CSA_compressor.py

Comment thread docs/fe-oss-apis/csa.md Outdated
Comment thread docs/fe-oss-apis/csa.md
Comment thread python/cudnn/csa/__init__.py Outdated
Comment thread python/cudnn/csa/compressor/api.py
- docs: list the total_comp * head_dim < 2**31 and total_comp > 0 with
  total_tokens < ratio support boundaries already enforced in check_support
- docs: run the compressor tests from test/python per repo convention
- csa/__init__.py: spell out __all__ as an explicit literal (Ruff PLE0604)
- compressor/__init__.py: sort __all__ (Ruff RUF022)
- compressor/api.py: add stacklevel=2 to the deterministic-mode RuntimeWarning

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
@Anerudhan

Copy link
Copy Markdown
Collaborator

Thanks @zkyue for the contribution.
Have asked the relevant folks to review your contribution.

@Anerudhan Anerudhan added orig-external Reported or requested by an external user, customer, or community contributor. mod-cutedsl CuTeDSL kernels, generated kernels, examples, or related integration work. cat-enhancements labels Jul 24, 2026
@Anerudhan Anerudhan added this to the Frontend 1.27.0 milestone Jul 24, 2026
@hxbai

hxbai commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Hi @zkyue , thanks for your contribution!

I have some comments:

  1. Can these kernels be extended to more general cases, including ratio==128, coff=1 and CP support?
  2. For your benchmark, did you enable CUDA Graphs for the eager implementation? If not, could you provide one?

zkyue added 2 commits July 27, 2026 10:55
…raphs

Address review feedback on the PR NVIDIA#427 fairness benchmark (benchmark/csa/bench_csa_compressor.py).

- Drop the subtraction-derived backward-graph estimate. Graph variants are now two directly measured, symmetric columns for both eager and fused: a forward-only graph (eager under no_grad) and a forward+backward total graph.

- Capture the eager fwd+bwd graph with stable, pre-allocated zero .grad buffers (the supported torch CUDA-graph pattern): the captured region zeros them in place, then runs forward + autograd backward, so every replay accumulates into a zeroed buffer -- numerically identical to a single fresh backward. A per-shape graph-vs-fresh-backward cross-check is printed (all shapes bitwise-equal).

- The fused total graph captures the forward wrapper immediately followed by the backward wrapper.

- Emit both the per-call and graph tables from a single run; graph speedups are eager/fused of the displayed us, truncated to one decimal (never rounded up). The backward replay is reported only as ~total-fwd (a reference), never as a column.

Not collected by pytest; black -l160 clean.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
Address review feedback on the CSA compressor performance tables (docs/fe-oss-apis/csa.md).

- Replace the CUDA-graph table (which published a subtraction-derived backward column) with two directly measured columns: eager/fused forward-only graph and eager/fused forward+backward total graph. No backward-graph column; backward is noted only as ~total-fwd, never as a measured value.

- Refresh both the per-call and graph tables from a single run of the benchmark harness; graph speedups are eager/fused of the displayed us, truncated to one decimal.

- State that capture collapses each side's per-op launches into a single replay rather than removes launch/host overhead; say less launch-bound rather than compute-bound.

- Note how the re-run reproduces the previously published per-call wall clock, and document the eager-backward capture basis (stable zero .grad buffers).

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
@zkyue

zkyue commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for both questions — answers below with measurements.

Q1 — extending to ratio=128, coff=1, and CP.

  • coff=1: already a complete kernel path (win = ratio, own-block window, no
    overlap — compressor_sm100.py:358/425-426/501/582); it's blocked only by the
    ratio!=4 or coff!=2 gate in check_support (api.py:163). I can widen the gate to
    coff in {1,2} and add numerics tests — a small change, not new kernel work.
  • ratio=128: this needs a new kernel, not a parameter bump. The kernel holds the
    whole window in per-thread registers, so register demand scales with the window. I
    compiled the forward at a few ratios and read the real numbers (ptxas --gpu-name sm_100a, head_dim 128): ratio=4 → 48 registers, 0 spill; ratio=32 already hits
    the 255-reg cap; ratio=128, coff=2 spills ~33 KB of local memory per thread (14 KB
    stores + 19 KB loads) and JIT time goes 0.6 s → 9.5 s. The configuration you named,
    ratio=128, coff=1, still spills — 255-reg cap, 2.5 KB stores + 2.5 KB loads, 1.9 KB
    stack
    — so coff=1 does not rescue it. That's a register wall (no smem window is
    allocated), so ratio=128 needs a cooperative/online-softmax rewrite of fwd+bwd
    regardless of coff. We plan to submit that dedicated ratio=128 kernel as a
    follow-up PR
    rather than scope-creep this one.
  • CP: expected to be integration-only. The kernel itself is CP-agnostic — the pooling
    is per-segment-local (a block only touches its own tokens and, for coff=2, the
    previous block of the same segment); the only cross-rank dependency is the one-block
    overlap, which Megatron's csa_cp_utils already exchanges at the rank boundary
    (_LeftBoundaryExchange ring P2P + prepare_cp_compressor_input). So the work is wiring
    the fused path into the CP pre_grouped layout (today the fused branch is taken on the
    non-CP path, csa.py:1063), pending CP numerics/replay validation — not a kernel change.

Q2 — eager baseline and CUDA Graphs.

You're right — the eager baseline in the harness was not CUDA-graphed; it timed each
call with CUDA events, so its ~39 fwd / ~51 bwd launches carried full launch/host
overhead. I added graph-wrapped variants for both eager and fused in
benchmark/csa/bench_csa_compressor.py (commit 62790d6), same shapes (ratio=4, coff=2;
1×/3× 8192, head_dim 128/512), median of 100 after 30 warmup, replay timing.

The graph variants are captured symmetrically as two directly measured columns for each
side — a forward-only graph and a forward+backward total graph (no subtraction).
For the eager total graph, the autograd backward of the captured forward is captured
against stable, pre-allocated zero .grad buffers (the supported torch pattern), so every
replay is numerically identical to a single fresh backward — I verified that per shape
(dKV/dScore/dAPE bitwise-equal to a fresh non-graph backward).

Headline numbers (µs, B200, 1×8192/d128): per-call forward 343.7 (eager) vs 37.4 (fused);
graph forward 119.2 vs 10.8 (11.0×); graph forward+backward total 364.6 vs 22.8
(15.9×). Full tables are in the commit / docs/fe-oss-apis/csa.md.

Graphing both sides is the fair comparison: capturing each side into a graph collapses its
per-op launches into a single replay, which narrows the absolute forward gap most on the
smallest, launch-bound shape (~2.8× at 1×8192/d128) and barely on the largest,
less launch-bound shape (~1.2×). The fused advantage is not a launch-overhead artifact —
the fused/eager ratio is similar to or larger under graphs, and on the largest shape the
gap barely moves. No change to the kernels.

@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: 1

🧹 Nitpick comments (2)
benchmark/csa/bench_csa_compressor.py (2)

271-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated leaf-tensor construction across three sites.

The kv/score/ape.clone().requires_grad_(True) triple is repeated verbatim in _capture_eager_total (twice) and in measure's _fwd_for_bwd. Extracting a small helper reduces the chance of the three copies drifting (e.g. if a dtype/device nuance needs to change later).

♻️ Suggested refactor
+def _make_leaves(kv, score, ape):
+    return kv.clone().requires_grad_(True), score.clone().requires_grad_(True), ape.clone().requires_grad_(True)
+
+
 def _capture_eager_total(kv, score, ape, cu, cuc, total_comp, go, ratio, d, coff, warmup):
     ...
-    kvl = kv.clone().requires_grad_(True)
-    scl = score.clone().requires_grad_(True)
-    apl = ape.clone().requires_grad_(True)
+    kvl, scl, apl = _make_leaves(kv, score, ape)
     ...
-    ref_k = kv.clone().requires_grad_(True)
-    ref_s = score.clone().requires_grad_(True)
-    ref_a = ape.clone().requires_grad_(True)
+    ref_k, ref_s, ref_a = _make_leaves(kv, score, ape)

Also applies to: 304-306, 340-346

🤖 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 `@benchmark/csa/bench_csa_compressor.py` around lines 271 - 277, Extract the
repeated kv/score/ape leaf-tensor construction into a small helper and reuse it
at the three sites in _capture_eager_total and measure’s _fwd_for_bwd. The
helper should clone each input and enable gradients, while preserving the
existing stable zero grad buffer initialization where required.

117-117: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor lint cleanups flagged by Ruff.

[0] + list(...) can be [0, *list(...)] (RUF005) at Lines 117 and 119, and the loop variable d is unused in the loop bodies at Lines 424, 432, 445, 458 (B007, rename to _).

Also applies to: 119-119, 424-424, 432-432, 445-445, 458-458

🤖 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 `@benchmark/csa/bench_csa_compressor.py` at line 117, Apply Ruff’s minor lint
fixes in the benchmark code: replace the list concatenations used to build `cu`
at both affected locations with iterable unpacking, and rename the unused loop
variable `d` to `_` in the loops around the four reported locations. Preserve
the existing computation and loop behavior.

Source: Linters/SAST tools

🤖 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 `@benchmark/csa/bench_csa_compressor.py`:
- Around line 384-392: Guard all displayed-timing divisions against zero in _x
and _xtrunc1, and in the inline ef / ff and eb / fb calculations near the
reported rows. Preserve the existing speedup output for nonzero denominators,
while returning a safe fallback or otherwise skipping only the affected speedup
value when a rounded denominator is 0.0 so collected rows still print.

---

Nitpick comments:
In `@benchmark/csa/bench_csa_compressor.py`:
- Around line 271-277: Extract the repeated kv/score/ape leaf-tensor
construction into a small helper and reuse it at the three sites in
_capture_eager_total and measure’s _fwd_for_bwd. The helper should clone each
input and enable gradients, while preserving the existing stable zero grad
buffer initialization where required.
- Line 117: Apply Ruff’s minor lint fixes in the benchmark code: replace the
list concatenations used to build `cu` at both affected locations with iterable
unpacking, and rename the unused loop variable `d` to `_` in the loops around
the four reported locations. Preserve the existing computation and loop
behavior.
🪄 Autofix (Beta)

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: 5c82cdfa-7f83-42fd-97cf-ef9d92022166

📥 Commits

Reviewing files that changed from the base of the PR and between 148fd06 and 891057b.

📒 Files selected for processing (2)
  • benchmark/csa/bench_csa_compressor.py
  • docs/fe-oss-apis/csa.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/fe-oss-apis/csa.md

Comment thread benchmark/csa/bench_csa_compressor.py
zkyue added 3 commits July 27, 2026 11:20
Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
The kernels are already generic over coff — for coff=1 the window is the
block's own ratio tokens (no overlap transform, always valid); only the
check_support gate restricted the APIs to the production coff=2 form.
Widen the gate to coff in {1, 2} (ratio stays gated at 4), update the
error message and the envelope prose in api.py / docs / README, and
parametrize the tests over both coff forms: numerics vs the
fp32/upstream/fp64 eager references (the eager reference already
implements coff=1 faithfully — it skips the overlap transform), static-
capacity padding, replay determinism, empty outputs, NaN-canary
zero-writes, deterministic-mode error paths, CUDA-graph capture,
class-vs-wrapper equivalence, and check_support accept/reject
boundaries (new coff=0 / coff=3 rejects; a new empty-segment shape
covers both coff forms). No kernel changes.

Addresses hxbai's coff=1 question on the PR.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
Bring the PR's Python docstring coverage over CodeRabbit's 80% pre-merge
gate: module docstrings for the csa package/compressor __init__ files
and one-line docstrings for the remaining undocumented helpers in
api.py (cache/ctor/compile plumbing), compressor_sm100.py (launcher
ctors), the test helpers, and the benchmark helpers. interrogate 1.7.0
over the PR's six CSA Python files: 69.4% -> 100%. Docstrings only — no
logic, kernel, or launch-path changes.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>
@jiayus-nvidia

Copy link
Copy Markdown
Contributor

LGTM too

…th ratio dispatch

The generic CSA compressor kernels keep the whole coff * ratio pooling
window in per-thread registers; past ratio ~32 that hits the
255-register wall and spills kilobytes of local memory per thread, so
ratio=128 needs dedicated kernels rather than a lifted gate. This adds
them behind the same public API: check_support now admits
ratio in {4, 128} x coff in {1, 2} (ratio=128 additionally gated to
head_dim in {128, 512}, the hardware-validated set) and the wrappers
and class APIs route to the matching kernel family by ratio
transparently.

Design: the forward streams the window with a chunked online softmax
(one CTA per output row, per-column (max, denom, acc) triples merged in
one fixed smem order); its launch schedule is bucketed by output-row
count (small/default/large, all precompiled for CUDA-graph safety) and
per bucket selects two-phase accumulation and an ex2.approx fast exp
where measured faster. The backward stages each row's window through
shared memory chunk-parallel, fuses the per-chunk den/S partial sums
into the same pass, merges partials in a fixed chunk order, and stores
gradients with a hoisted 1/den multiply — keeping kernel-side
zero-writes to never-consumed dKV/dScore slots and fp32-atomic dAPE
exactly as at ratio=4. ptxas (sm_100a): forward 32-51 registers,
backward 48-128, 0 spill / 0 stack across all 16 shipped
(config, schedule) kernels (reproducer:
benchmark/csa/reg_probe_csa_compressor_r128.py).

Numerics contract, per ratio family (docs/fe-oss-apis/csa.md): ratio=4
keeps its bitwise contract UNCHANGED — dKV/dScore bit-identical to the
fp32-intermediate eager reference, at coff 1 and 2. ratio=128 is
deterministic + faithful to that same fp32-intermediate eager
reference: bitwise run-to-run determinism of out/dKV/dScore
(NaN-prefill replay tested); the same values within final-bf16 rounding
at the gate tolerances (differing elements <= max(1, 0.1%), max_abs <=
1.6e-2, calibrated on the gate's documented input distribution — bf16
deviations scale 2^k with 2^k inputs while the fp32 intermediates stay
finite); the eager reference's non-finite propagation (finite inputs
that overflow its fp32 intermediates poison both sides alike, with the
stated caveat that the fused order saturates earlier near fp32 max —
un-normalized chunk partials); and, on inputs whose fp32 intermediates
stay finite in both evaluation orders, fp64-oracle parity (at least as
close to an fp64 oracle as the eager reference). The reduction reorders
and fast-exp buckets were each adopted on a measured win and are
covered by that contract.

Performance (nsys isolated GPU kernel time, 1x B200, vs the
fp32-intermediate eager reference region on identical inputs): forward
13.2-44.4x, backward 7.4-21.9x across coff {1, 2} x head_dim {128, 512}
x 8k-131k tokens; e.g. coff=2, d=128, 65k tokens: fwd 726.3 -> 16.4 us
(44.4x), bwd 960.0 -> 61.2 us (15.7x). Full tables with methodology in
docs/fe-oss-apis/csa.md.

Validation (B200, CC 10.0): the committed 21-case contract gate
(benchmark/csa/gate_csa_compressor_r128.py) passes 21/21 with shipped
schedule coverage asserted (forward 10/10, backward 6/6 buckets);
pytest fe_api/csa/test_CSA_compressor.py: 88 passed, 1 skipped at the
default L0 level and 98 passed, 1 skipped with -m "L0 or L1" (both
ratio families; the ratio=4 rows, including the coff=1 envelope, stay
on their bitwise assertions). New tests cover the ratio=128 dispatch
envelope (schedule selection at every nb_total bucket boundary; every
shipped (config, schedule) kernel executed against the full contract at
L1), exact-zero assertions on every never-consumed dKV/dScore slot
class in the NaN-canary, CUDA-graph capture/replay for the new family,
and grad_ape zeroing-ownership regressions.

Signed-off-by: zky <51477259+zkyue@users.noreply.github.com>

@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: 1

🧹 Nitpick comments (2)
python/cudnn/csa/compressor/compressor_sm100_r128.py (1)

857-916: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: the grad_out vector is loaded twice per row.

fr_go2 (phase 2) and fr_go (phase 4) load the same mGO slice for the same (bb, cvec). Registers survive cute.arch.barrier(), so phase 4 could reuse the phase-2 value and drop one global load per row. Only worth it if it doesn't push the register budget past the ptxas 0-spill target you documented.

🤖 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 `@python/cudnn/csa/compressor/compressor_sm100_r128.py` around lines 857 - 916,
Optionally reuse the phase-2 grad_out register tensor `fr_go2` in phase 4
instead of reloading the same `mGO` slice into `fr_go`, preserving correctness
across the barrier. Verify ptxas remains within the documented zero-spill
register budget; retain the existing phase-4 load if reuse causes spills or
exceeds that budget.
benchmark/csa/gate_csa_compressor_r128.py (1)

130-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared eager reference instead of duplicating it.

_batch_of_row, _overlap_transform_thd, _eager_pool, run_eager_bwd, _make_inputs and _make_go are verbatim copies of test/python/fe_api/csa/test_CSA_compressor.py (lines ~73-163). The module docstring stakes the gate's authority on them being "identical", but nothing enforces that — a fix to one copy silently invalidates the other. A single importable helper (e.g. benchmark/csa/_csa_eager_ref.py consumed by both) would keep the guarantee mechanical.

🤖 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 `@benchmark/csa/gate_csa_compressor_r128.py` around lines 130 - 213, The eager
reference helpers are duplicated between the benchmark gate and the CSA
compressor test, so the claimed identical behavior is not enforced. Extract
_batch_of_row, _overlap_transform_thd, _eager_pool, run_eager_bwd, _make_inputs,
and _make_go into one importable shared helper module, then update both
consumers to import and use those definitions while preserving their current
behavior and interfaces.
🤖 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 `@benchmark/csa/reg_probe_csa_compressor_r128.py`:
- Around line 75-85: Update the kernel probing loop over M._COMPILED in the
relevant probe function to use the documented compiled-handle PTX accessor
fn.ptx instead of fn.artifacts.PTX. If PTX retention is disabled and fn.ptx is
unavailable, guard that case and skip the kernel without aborting the probe,
while preserving the existing all_clean and count behavior for processed
kernels.

---

Nitpick comments:
In `@benchmark/csa/gate_csa_compressor_r128.py`:
- Around line 130-213: The eager reference helpers are duplicated between the
benchmark gate and the CSA compressor test, so the claimed identical behavior is
not enforced. Extract _batch_of_row, _overlap_transform_thd, _eager_pool,
run_eager_bwd, _make_inputs, and _make_go into one importable shared helper
module, then update both consumers to import and use those definitions while
preserving their current behavior and interfaces.

In `@python/cudnn/csa/compressor/compressor_sm100_r128.py`:
- Around line 857-916: Optionally reuse the phase-2 grad_out register tensor
`fr_go2` in phase 4 instead of reloading the same `mGO` slice into `fr_go`,
preserving correctness across the barrier. Verify ptxas remains within the
documented zero-spill register budget; retain the existing phase-4 load if reuse
causes spills or exceeds that budget.
🪄 Autofix (Beta)

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: c0694b0f-5805-4238-8542-7555a2d89388

📥 Commits

Reviewing files that changed from the base of the PR and between 251f048 and d77e48e.

📒 Files selected for processing (11)
  • benchmark/csa/bench_csa_compressor.py
  • benchmark/csa/gate_csa_compressor_r128.py
  • benchmark/csa/reg_probe_csa_compressor_r128.py
  • docs/fe-oss-apis/csa.md
  • python/cudnn/csa/README.md
  • python/cudnn/csa/__init__.py
  • python/cudnn/csa/compressor/__init__.py
  • python/cudnn/csa/compressor/api.py
  • python/cudnn/csa/compressor/compressor_sm100.py
  • python/cudnn/csa/compressor/compressor_sm100_r128.py
  • test/python/fe_api/csa/test_CSA_compressor.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • python/cudnn/csa/README.md
  • python/cudnn/csa/compressor/init.py
  • python/cudnn/csa/init.py
  • docs/fe-oss-apis/csa.md
  • benchmark/csa/bench_csa_compressor.py
  • python/cudnn/csa/compressor/compressor_sm100.py

Comment on lines +75 to +85
for key, fn in sorted(M._COMPILED.items(), key=str):
kind, _ratio, d, coff, sched, _dev = key
if kind == "r128fwd":
vec, tchunks, threads_x, twophase, fastexp = sched
tag = f"fwd_c{coff}d{d}_v{vec}t{tchunks}x{threads_x}" + ("_2ph" if twophase else "") + ("_fexp" if fastexp else "")
else:
vec, tchunks, threads_x, fastexp = sched
tag = f"bwd_c{coff}d{d}_v{vec}t{tchunks}x{threads_x}" + ("_fexp" if fastexp else "")
all_clean = ptxas_one(tag, fn.artifacts.PTX, args.arch, out_dir) and all_clean
n += 1
print(f"{n} kernels probed ({args.arch}); {'ALL 0 spill / 0 stack' if all_clean else 'SPILL/STACK OR PTXAS FAILURE DETECTED'}", flush=True)

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

🧩 Analysis chain

🌐 Web query:

nvidia-cutlass-dsl cute.compile compiled artifacts PTX attribute CUTE_DSL_KEEP

💡 Result:

In NVIDIA CUTLASS CuTe DSL, CUTE_DSL_KEEP is an environment variable used to control the dumping of generated artifacts (such as IR, PTX, CUBIN, and SASS) to files for offline inspection [1][2]. Key details regarding artifacts and compilation options: Environment Variable CUTE_DSL_KEEP You can set CUTE_DSL_KEEP to a comma-separated list of tokens to save specific artifacts [1][3]. For example: - export CUTE_DSL_KEEP=ptx (Saves PTX assembly) [1][2] - export CUTE_DSL_KEEP=cubin (Saves CUBIN binary) [1][2] - export CUTE_DSL_KEEP=all (Saves all supported artifacts) [1][2] Programmatic Access to Compiled Artifacts When a kernel is compiled using cute.compile, the resulting object allows programmatic access to the generated artifacts via specific attributes [1][2]. Importantly, these attributes are only populated if the corresponding environment variable (e.g., CUTE_DSL_KEEP_PTX=1 or the relevant CUTE_DSL_KEEP token) is set [4]: - compiled_func.ptx: Accesses the generated PTX code [1][2]. - compiled_func.cubin: Accesses the generated CUBIN data [1][2]. - compiled_func.mlir: Accesses the generated IR code [1][2]. Compilation Options in cute.compile Alternatively, you can control the generation of these files directly within your Python code when calling cute.compile by using specific compilation options [5]: - KeepPTX: Equivalent to setting a flag to keep the generated PTX file [5]. - KeepCUBIN: Equivalent to setting a flag to keep the generated CUBIN file [5]. These options are typically imported from cutlass.cute and passed into cute.compile to manage artifact retention without relying solely on environment variables [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the benchmark file and inspect the relevant section.
ast-grep outline benchmark/csa/reg_probe_csa_compressor_r128.py --view expanded || true
echo '---'
cat -n benchmark/csa/reg_probe_csa_compressor_r128.py | sed -n '1,140p'
echo '---'
# Search for artifact access patterns in the repo.
rg -n "artifacts\.PTX|\.ptx\b|KeepPTX|CUTE_DSL_KEEP|artifacts\." benchmark -S || true

Repository: NVIDIA/cudnn-frontend

Length of output: 5956


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('benchmark/csa/reg_probe_csa_compressor_r128.py')
print(p.exists(), p.stat().st_size if p.exists() else None)
PY

Repository: NVIDIA/cudnn-frontend

Length of output: 169


🌐 Web query:

"fn.artifacts.PTX" "cutlass" "cute.compile" OR "compiled_func.ptx"

💡 Result:

In NVIDIA CUTLASS, when using the CuTe Python DSL, cute.compile generates a compiled JIT executor object [1][2]. To access the generated PTX artifacts from this compiled function (e.g., compiled_func.ptx), you must explicitly enable the retention of these artifacts during the compilation process [1][3]. By default, these attributes are not populated [3]. You can enable them using one of the following methods: 1. Environment Variables: Set the CUTE_DSL_KEEP environment variable to include ptx (e.g., export CUTE_DSL_KEEP=ptx) [1][4]. This allows you to access the generated PTX code programmatically via the ptx attribute of the compiled function object [1][3]. 2. Compilation Options: Pass KeepPTX as a compilation option to cute.compile [5][6][7]. This can be done using the Pythonic composition interface: python from cutlass.cute import KeepPTX compiled_func = cute.compile[KeepPTX](my_kernel, ...) print(compiled_func.ptx) Alternatively, you can pass it as a string option if supported by your version of CUTLASS (e.g., options="--keep-ptx") [8][7]. Note that simply referring to compiled_func.ptx without having set these configuration flags will typically return None [3]. Accessing these artifacts is primarily intended for debugging, profiling, and offline inspection of generated assembly [1][4][9].

Citations:


Use the documented PTX handle fn.artifacts.PTX isn’t the public CuTe DSL surface; the compiled handle exposes fn.ptx when PTX retention is enabled. Switching to that accessor — or guarding it and skipping the kernel — avoids an AttributeError that aborts the whole probe.

🤖 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 `@benchmark/csa/reg_probe_csa_compressor_r128.py` around lines 75 - 85, Update
the kernel probing loop over M._COMPILED in the relevant probe function to use
the documented compiled-handle PTX accessor fn.ptx instead of fn.artifacts.PTX.
If PTX retention is disabled and fn.ptx is unavailable, guard that case and skip
the kernel without aborting the probe, while preserving the existing all_clean
and count behavior for processed kernels.

@zkyue

zkyue commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@hxbai two updates on this branch since your last pass (cc @jiayus-nvidia — thanks for the LGTM, and apologies for moving the target right after it):

coff=1: delivered as promised, same bitwise contract. ratio=4 now admits coff in {1, 2}; the kernels were already generic over coff, and the coff=1 path holds the identical contract — dKV/dScore bitwise-equal (torch.equal) to the fp32-intermediate eager reference across the fully parametrized suite and a standalone gate, forward within one bf16 rounding step.

ratio=128: delivered in this PR (one commit, d77e48e6) rather than the follow-up PR we'd sketched — the work converged while this one was in review.

  • Why dedicated kernels: the generic ones keep the whole window in registers and hit the 255-register wall past ratio≈32. Forward = chunked online softmax with bucketed launch schedules; backward = staged-smem with fused per-chunk reductions. ptxas: 0 spill / 0 stack on all 16 shipped (config, schedule) kernels.
  • Headline (nsys isolated kernel time, B200, vs the fp32-intermediate eager region): fwd 13.2–44.4x, bwd 7.4–21.9x across coff {1, 2} × head_dim {128, 512} × 8k–131k tokens.
  • The ratio=128 numerics contract differs from ratio=4 by design (docs → Numerics): ratio=4 keeps its bitwise dKV/dScore contract unchanged; ratio=128 is deterministic + tolerance — bitwise run-to-run deterministic on out/dKV/dScore, faithful to the fp32-intermediate eager reference within calibrated final-bf16-rounding tolerances, reproduces the reference's non-finite propagation when inputs overflow its fp32 intermediates, and carries fp64-oracle parity on finite-intermediate inputs. A committed 21-case gate + ptxas probe (benchmark/csa/) reproduce every published number.

Branch state validated end-to-end: pytest 88 passed / 1 skipped at L0 (98 / 1 at -m "L0 or L1"); ratio=128 contract gate 21/21; coff=1 bitwise gate all-pass.

@hxbai

hxbai commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

@zkyue thanks for your contribution!

What about putting these kernels into https://github.com/NVIDIA/cudnn-frontend/tree/develop/python/cudnn/deepseek_sparse_attention rather than creating a new csa dir? @Anerudhan do you have any suggestions?

@zkyue

zkyue commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Absolutely, happy to relocate. Two shapes, whichever you both prefer: (a) flat into python/cudnn/deepseek_sparse_attention/ alongside the DSA kernels, or (b) a compressor/ subpackage inside it. Docs/tests/lazy exports follow the same move either way. I'll push the relocation as soon as the layout is settled.

@Anerudhan

Copy link
Copy Markdown
Collaborator

Thanks @zkyue

I do prefer the current directory structure.

python/cudnn/ 
     native_sparse_attention/             # NSA paper: Selection + CompressionAttn + SWA + TopK 
     deepseek_sparse_attention/      # DSA: backward, indexer, score kernels 
     csa/                                                # CSA/HCA algortihm that can be resued later .

Also: We are planning to move to Apache-2 License from MIT license. Pinging here for your approval for the files you authored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cat-enhancements mod-cutedsl CuTeDSL kernels, generated kernels, examples, or related integration work. orig-external Reported or requested by an external user, customer, or community contributor.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants