Skip to content

feat(kernels): Triton FlashAttention LSE export + varlen packing#233

Open
Billy1900 wants to merge 2 commits into
RL-Align:mainfrom
Billy1900:feat/triton-attention-lse-varlen
Open

feat(kernels): Triton FlashAttention LSE export + varlen packing#233
Billy1900 wants to merge 2 commits into
RL-Align:mainfrom
Billy1900:feat/triton-attention-lse-varlen

Conversation

@Billy1900

@Billy1900 Billy1900 commented Jul 19, 2026

Copy link
Copy Markdown

Summary

First step of a stack toward "Fused FlashAttention with causal mask, varlen
packing, and exported attention LSE" (see discussion in the repo). Rather than
attempting SM90 WGMMA+TMA + SM80 + ROCm MFMA + Triton all in one PR, this
lands the Triton piece first: it's the only backend where a real kernel
already existed to extend (rl_engine/kernels/ops/triton/triton_attn.py), and
it becomes the cross-platform semantic baseline the SM90/SM80/ROCm kernels
get checked against later, rather than against each other.

What this adds

  • Attention-domain LSE export: return_lse=True on the existing dense
    triton_flash_attention. No new kernel math — M (running max) and L
    (running sum-exp) were already computed online for the backward pass, just
    never returned. LSE is marked non-differentiable (ctx.mark_non_differentiable),
    matching flash_attn's external softmax_lse contract. Default calls
    (return_lse=False) are unchanged — no breaking change to the (currently
    uncalled) public API.
  • triton_flash_attention_varlen: a new packed variable-length path over
    cu_seqlens-style [total_tokens, H, D] tensors (same convention as
    flash_attn_varlen_func and this repo's pack op, feat(kernels): add fused masking + variable-length pack-and-pad op  #182), with forward +
    backward kernels. Causal masking uses the Skv - Sq anchor from
    NativeAttentionOp (docs/operators/attention.md) so prefill (Sq == Skv)
    and decode (Sq < Skv) share one formula.

Explicitly out of scope for this PR: SM90 WGMMA+TMA, SM80 mma.sync, and
ROCm MFMA kernels, and GQA support (the pre-existing dense Triton kernel
didn't support GQA either — not a regression). Not wired into
KernelRegistry yet.

Correctness

Both paths are checked against an independent fp32 masked-softmax +
logsumexp reference (not against each other, so a shared bug in the
online-softmax loop can't hide). Run end-to-end on real H100 SXM5 hardware:

  • Dense: out max-abs-diff ≈ 1.4e-3, lse1e-6 vs. reference (fp16
    inputs, fp32 accumulation).
  • Varlen: forward + backward (dq/dk/dv) checked across causal/non-causal,
    decode-style (Sq < Skv, varying independently per sequence in the batch),
    sequence lengths deliberately not aligned to the kernel's 64-token block
    size, head_dim 64 and 128, and a zero-length sequence in the batch (a real
    case for fully-masked/empty RL responses — packed tensors have the next
    sequence's data immediately after a short one, so this is a genuine
    correctness boundary, not just an edge case to tolerate).

8/8 tests in tests/test_triton_attention_varlen.py pass; the pre-existing
26/26 in tests/test_attention.py are untouched and still pass.

Docs

Added docs/operators/attention-varlen.md (template-following operator page,
wired into docs/.nav.yml and docs/operators/README.md), including an
explicit note that this LSE is attention-domain, not the vocab-domain LSE the
fused_logp/linear_logp kernels already use internally — same name,
different tensor, worth flagging to avoid confusion.

Test plan

  • pytest tests/test_triton_attention_varlen.py -v — 8 passed (H100 SXM5)
  • pytest tests/test_attention.py -v — 26 passed, no regressions
  • ruff check, black --check, isort --check-only all clean

Summary by CodeRabbit

  • New Features

    • Added optional per-query attention log-sum-exp (LSE) return for dense Triton FlashAttention.
    • Introduced packed variable-length FlashAttention that uses cumulative sequence lengths for unpadded inputs.
    • Supports causal/non-causal and decode-style scenarios, including uneven and zero-length batches.
  • Documentation

    • Added a new Operators page for the variable-length FlashAttention baseline and linked it from the operator index and navigation.
  • Tests

    • Added Triton-gated validation for dense LSE correctness, gradient behavior, packed/varlen cases, and non-contiguous input layouts.

Extends the Triton FlashAttention fallback with an attention-domain LSE
output (M + log(L), already computed online for the backward pass but
previously discarded) and a packed variable-length path operating on
cu_seqlens-style [total_tokens, H, D] tensors instead of a padded dense
batch. This is the cross-platform semantic baseline the planned SM90
WGMMA+TMA, SM80 mma.sync, and ROCm MFMA fused-attention kernels will be
checked against.

Both paths are validated against an independent per-sequence fp32
masked-softmax + logsumexp reference (not against the dense kernel
itself), covering causal/non-causal, decode-style (Sq < Skv), sequence
lengths not aligned to the kernel's block size, and empty sequences in
a packed batch -- run end-to-end on H100 hardware.
@Billy1900
Billy1900 requested a review from Flink-ddd as a code owner July 19, 2026 09:53
Copilot AI review requested due to automatic review settings July 19, 2026 09:53
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7968b47e-9fbb-4ee4-9e16-e06dca7fc7a1

📥 Commits

Reviewing files that changed from the base of the PR and between 3d5364b and 8276596.

📒 Files selected for processing (2)
  • rl_engine/kernels/ops/triton/triton_attn.py
  • tests/test_triton_attention_varlen.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_triton_attention_varlen.py

📝 Walkthrough

Walkthrough

Adds optional attention-domain LSE export to dense Triton FlashAttention, introduces packed variable-length attention, adds CUDA/Triton correctness tests, and documents the APIs, tensor contracts, backend scope, validation, and limitations.

Changes

Triton attention enhancements

Layer / File(s) Summary
Dense attention LSE export
rl_engine/kernels/ops/triton/triton_attn.py, tests/test_triton_attention_varlen.py
Dense FlashAttention optionally returns non-differentiable float32 LSE while preserving the default tensor-only return and backward behavior.
Packed variable-length attention
rl_engine/kernels/ops/triton/triton_attn.py, tests/test_triton_attention_varlen.py
Adds packed cu_seqlens attention with optional LSE output and validates causal, non-causal, uneven-length, decode-style, head-dimension, zero-length, non-contiguous, output, LSE, and gradient cases.
Operator documentation and navigation
docs/operators/attention-varlen.md, docs/operators/README.md, docs/.nav.yml
Documents the dense and packed APIs, tensor contracts, reference behavior, test coverage, backend scope, performance notes, and known limitations, and adds the page to navigation.

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

Sequence Diagram(s)

sequenceDiagram
  participant TestVarlenAttention
  participant triton_flash_attention_varlen
  participant TritonAttentionVarlenKernel
  participant ref_attn
  TestVarlenAttention->>triton_flash_attention_varlen: submit packed q, k, v, and cu_seqlens
  triton_flash_attention_varlen->>TritonAttentionVarlenKernel: execute attention with return_lse
  TritonAttentionVarlenKernel-->>triton_flash_attention_varlen: return packed output and LSE
  TestVarlenAttention->>ref_attn: compute fp32 per-sequence reference
  ref_attn-->>TestVarlenAttention: return reference output and LSE
  TestVarlenAttention->>TestVarlenAttention: compare outputs, LSE, and gradients
Loading

Suggested reviewers: kjldefeated, inaniloquentee, flink-ddd, ethanzero2hero

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: Triton FlashAttention LSE export and variable-length packing support.
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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds functionality to the Triton FlashAttention fallback kernels to (1) optionally export attention-domain LSE and (2) support packed variable-length (cu_seqlens) attention, positioning Triton as the cross-platform semantic baseline for upcoming SM90/SM80/ROCm fused implementations.

Changes:

  • Extend dense triton_flash_attention with return_lse=True to return attention-domain log-sum-exp (non-differentiable).
  • Add triton_flash_attention_varlen with forward/backward kernels for packed [total_tokens, H, D] + cu_seqlens inputs, including causal decode-style masking.
  • Add dedicated tests and operator documentation for the new LSE export and varlen path.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
rl_engine/kernels/ops/triton/triton_attn.py Adds dense LSE export and introduces packed varlen FlashAttention (fwd+bwd) kernels/APIs.
tests/test_triton_attention_varlen.py New CUDA+Triton-gated tests validating dense LSE + varlen fwd/bwd against an independent fp32 reference.
docs/operators/attention-varlen.md New operator page documenting LSE export + packed varlen attention contract and limitations.
docs/operators/README.md Adds the new operator doc page to the operators index.
docs/.nav.yml Wires the new operator doc page into site navigation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +891 to +892
do = do.contiguous()
dq = torch.zeros_like(q)
Comment on lines +826 to +831
batch = cu_seqlens_q.numel() - 1
assert cu_seqlens_k.numel() - 1 == batch

cu_seqlens_q = cu_seqlens_q.to(device=q.device, dtype=torch.int32)
cu_seqlens_k = cu_seqlens_k.to(device=q.device, dtype=torch.int32)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
tests/test_triton_attention_varlen.py (1)

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

Prefix unused unpacked vars. H and D from H, Sq, D = q.shape are unused (Ruff RUF059). Rename to _H/_D or use _, Sq, _.

🤖 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 `@tests/test_triton_attention_varlen.py` at line 54, Update the shape unpacking
near the attention test to mark the unused H and D dimensions as intentionally
unused, using underscore-prefixed names or anonymous underscores while retaining
Sq for subsequent logic.

Source: Linters/SAST tools

rl_engine/kernels/ops/triton/triton_attn.py (1)

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

Consider fp32 accumulation for dq atomics.

dq is accumulated via tl.atomic_add in the input dtype (fp16), summing contributions from every K-block. fp16 atomic accumulation compounds rounding error; a fp32 dq scratch buffer (cast to q.dtype at the end) would be more robust. Current tests pass within atol=0.05, so this is not blocking.

🤖 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 `@rl_engine/kernels/ops/triton/triton_attn.py` at line 792, Update the dq
accumulation flow around the tl.atomic_add call to use an fp32 scratch buffer
for atomic accumulation, then cast the completed result back to q.dtype when
writing the final dq output. Ensure all K-block contributions use the fp32
buffer and preserve the existing masking behavior.
🤖 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 `@rl_engine/kernels/ops/triton/triton_attn.py`:
- Around line 903-948: Fix varlen backward stride handling in the wrapper
invoking _bwd_preprocess_varlen and _bwd_kernel_varlen: do is contiguous, but
these kernels currently reuse out/q strides while q, k, and v may be
non-contiguous. Either make q, k, and v contiguous before launching both
kernels, or add and pass dedicated do strides through both launches, preserving
correct delta and gradient results for packed non-contiguous inputs.

---

Nitpick comments:
In `@rl_engine/kernels/ops/triton/triton_attn.py`:
- Line 792: Update the dq accumulation flow around the tl.atomic_add call to use
an fp32 scratch buffer for atomic accumulation, then cast the completed result
back to q.dtype when writing the final dq output. Ensure all K-block
contributions use the fp32 buffer and preserve the existing masking behavior.

In `@tests/test_triton_attention_varlen.py`:
- Line 54: Update the shape unpacking near the attention test to mark the unused
H and D dimensions as intentionally unused, using underscore-prefixed names or
anonymous underscores while retaining Sq for subsequent logic.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4bd91fa-2f38-473f-ac41-d98a69fcfb0b

📥 Commits

Reviewing files that changed from the base of the PR and between 6df029a and 3d5364b.

📒 Files selected for processing (5)
  • docs/.nav.yml
  • docs/operators/README.md
  • docs/operators/attention-varlen.md
  • rl_engine/kernels/ops/triton/triton_attn.py
  • tests/test_triton_attention_varlen.py

Comment thread rl_engine/kernels/ops/triton/triton_attn.py
_bwd_preprocess_varlen indexed DO with Out's strides, and _bwd_kernel_varlen
indexed DO with Q's strides. do.contiguous() only guarantees DO's own
contiguous layout -- it does not make DO's strides equal to Out's or Q's,
which may be non-contiguous (e.g. a transposed view). The mismatch produced
wrong gradients (or out-of-bounds reads) whenever Q/Out were non-contiguous.

Fixes by plumbing do.stride(*) through both kernels, matching how the
pre-existing dense _bwd_preprocess/_bwd_kernel already keep DO's strides
separate from Out's.

Added a regression test building q/k/v as transposed (non-contiguous) views;
confirmed it fails against the prior code (85% of dq elements wrong, max
abs diff 2.3) and passes with this fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants