Skip to content

[WIP][TRITON][GLUON][GFX950][DSV4] Sparse MLA training backward - #4766

Draft
wangye805 wants to merge 1 commit into
mainfrom
yewang12/dsv4_train_bwd_gluon
Draft

[WIP][TRITON][GLUON][GFX950][DSV4] Sparse MLA training backward#4766
wangye805 wants to merge 1 commit into
mainfrom
yewang12/dsv4_train_bwd_gluon

Conversation

@wangye805

Copy link
Copy Markdown

Adds the training backward for the DeepSeek-V4 sparse MLA attention on gfx950 (CDNA4). This is the counterpart to the DSv4 sparse prefill forward added in #3833 — aiter had no sparse-MLA backward before this.

Contract

Same op as the forward's has_pe=False path: shared-KV GQA with K == V == kv as one dense 512-wide tensor, RoPE applied in place caller-side, scale 1/sqrt(512), attn_sink folded into the softmax denominator only, topk_indices == -1 masked out.

from aiter.ops.triton.gluon.mla_gluon import mla_gluon
from aiter.ops.triton.attention.sparse_attention_dsv4_bwd import sparse_mla_bwd_dsv4

o, lse = mla_gluon(..., has_pe=False, attn_sink=sink, return_lse=True)
dq, dkv, d_sink = sparse_mla_bwd_dsv4(q, kv, do, o, lse, topk, attn_sink=sink)

mla_gluon's lse is already sink-inclusive (the sink is folded into e_max/e_sum before lse = e_max + log(e_sum)), which is the convention this backward expects, so the two compose directly with no adaptation.

num_kv >= T is supported, so a compressed pool (kv rows T..num_kv-1) works.

Structure

Five kernels plus one torch reduction, split across the gluon / triton / public-API trees as usual:

phase impl notes
delta = rowsum(O*dO) triton streams bf16, accumulates fp32
dQ gluon one LDS read of the gathered KV feeds both the S and dP MFMAs; also emits this chunk's dS/P
dKV-interm gluon contracts over all heads inside one MFMA pair, Q/dO transposed once into registers, D split across grid.y
CSR build torch sort + searchsorted on int16-narrowed keys
dKV gather triton atomic-free — the scatter is inverted so each KV row gathers its own contributors
d_sink torch 26 us

R_CHUNK splits the rank dimension and defaults to unchunked. It exists only to bound the interm intermediate (T*topk*512 bf16 = 2.0 GiB at T=4096 topk=512) and costs a dQ read-modify-write between chunks plus one CSR build per chunk.

Performance

MI355X, T=4096 H=128 topk=512, realistic SWA(128)+pool top-k, against the Primus-Turbo FlyDSL backward on identical tensors in one process:

ours flydsl
per-kernel sum 3.380 ms / 407 TFLOPS 3.223 ms / 426 TFLOPS
end-to-end 3.238 ms / 424 TFLOPS 3.471 ms / 396 TFLOPS

Read that as parity — the two differ mainly in how much each allocates per call, which is why the ordering flips between the two rows.

Correctness

cos > 0.999 on dq / dkv / d_sink against torch autograd through an independent fp32 reference forward, across H in {64,128}, pool and no-pool, chunked and unchunked, with and without attn_sink.

op_tests/triton_tests/attention/test_sparse_attention_dsv4_bwd.py  ......  6 passed

Please note

The test skips on non-gfx950, so it will not exercise anything unless CI has a CDNA4 runner. It has been run on MI355X. The kernels use CDNA4 MFMA layouts and buffer_load_to_shared and are not portable to gfx942 as written; the public wrapper asserts the arch so other targets get a clear error rather than a Gluon compile failure.

Adds the training backward for the DeepSeek-V4 sparse MLA attention on gfx950
(CDNA4). This is the counterpart to the DSv4 sparse prefill forward added in
#3833; aiter had no sparse-MLA backward before this.

Same op contract as the forward's has_pe=False path: shared-KV GQA with
K == V == kv as one dense 512-wide tensor, RoPE applied in place caller-side,
scale 1/sqrt(512), attn_sink folded into the softmax denominator only, and
topk_indices == -1 masked out.

    from aiter.ops.triton.gluon.mla_gluon import mla_gluon
    from aiter.ops.triton.attention.sparse_attention_dsv4_bwd import (
        sparse_mla_bwd_dsv4,
    )

    o, lse = mla_gluon(..., has_pe=False, attn_sink=sink, return_lse=True)
    dq, dkv, d_sink = sparse_mla_bwd_dsv4(q, kv, do, o, lse, topk, attn_sink=sink)

mla_gluon's lse is already sink-inclusive (the sink is folded into e_max/e_sum
before lse = e_max + log(e_sum)), which is the convention this backward expects,
so the two compose directly with no adaptation.

Five kernels plus one torch reduction:

  delta        triton   rowsum(O*dO), streams bf16 and accumulates fp32
  dQ           gluon    one LDS read of the gathered KV feeds both the S and dP
                        MFMAs; also emits this chunk's dS / P
  dKV-interm   gluon    contracts over all heads inside one MFMA pair, Q/dO
                        transposed once into registers, D split across grid.y
  CSR build    torch    sort + searchsorted on int16-narrowed keys
  dKV gather   triton   atomic-free -- the scatter is inverted so each KV row
                        gathers its own contributors
  d_sink       torch    26 us

num_kv >= T is supported, so a compressed pool (kv rows T..num_kv-1) works.
R_CHUNK splits the rank dimension and defaults to unchunked; it exists only to
bound the interm intermediate (T*topk*512 bf16, 2.0 GiB at T=4096 topk=512) and
costs a dQ read-modify-write between chunks plus one CSR build per chunk.

Performance (MI355X, T=4096 H=128 topk=512, realistic SWA(128)+pool top-k),
against the Primus-Turbo FlyDSL backward on identical tensors in one process:

                        ours      flydsl
  per-kernel sum      3.380 ms   3.223 ms     407 vs 426 TFLOPS
  end-to-end          3.238 ms   3.471 ms     424 vs 396 TFLOPS

Read that as parity: the two differ mainly in how much each allocates per call.

Correctness: cos > 0.999 on dq / dkv / d_sink against torch autograd through an
independent fp32 reference forward, across H in {64,128}, pool and no-pool,
chunked and unchunked, with and without attn_sink.

Note the test skips on non-gfx950, so it will not exercise anything unless CI
has a CDNA4 runner. It has been run on MI355X.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wangye805
wangye805 requested review from a team and a lite review from Copilot August 14, 2026 19:44
@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs; main branch always runs both MI35X and MI300X
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 4766 --add-label <label>

Copilot AI 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.

Pull request overview

Adds the DeepSeek‑V4 sparse MLA training backward path for gfx950/CDNA4, complementing the existing sparse prefill forward by introducing the full backward pipeline (delta precompute, dQ, dKV interm, inverted top‑k CSR, dKV gather, and optional sink gradient) with Gluon + Triton kernels, plus correctness tests and Gluon docs.

Changes:

  • Introduce public wrapper sparse_mla_bwd_dsv4(...) under aiter/ops/triton/attention/ that orchestrates the backward pipeline and chunking via R_CHUNK.
  • Add new Gluon kernels for dQ and dKV-intermediate computation, and Triton kernels for delta and CSR gather accumulation.
  • Add gfx950-only correctness tests and document the new Gluon module in aiter/ops/triton/gluon/README.md.

Reviewed changes

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

Show a summary per file
File Description
op_tests/triton_tests/attention/test_sparse_attention_dsv4_bwd.py New gfx950-gated correctness tests vs autograd reference (including chunking + sink cases).
aiter/ops/triton/gluon/sparse_attention_dsv4_bwd_gluon.py New Gluon kernels for dQ and dKV-intermediate for DSv4 sparse MLA backward.
aiter/ops/triton/gluon/README.md Document the new DSv4 sparse MLA backward entry and performance notes.
aiter/ops/triton/attention/sparse_attention_dsv4_bwd.py New public API wrapper implementing the DSv4 sparse MLA backward pipeline (chunked/unchunked).
aiter/ops/triton/_triton_kernels/attention/sparse_attention_dsv4_bwd.py New Triton kernels/utilities for delta, inverted-topk CSR build, and atomic-free dKV gather accumulation.
Suppressed comments (3)

aiter/ops/triton/attention/sparse_attention_dsv4_bwd.py:110

  • R_CHUNK can be set to a value that does not evenly divide TOPK (or even exceeds it). In that case, the Gluon kernels still iterate over R_CHUNK entries starting at R_START=r, which can read past the end of each topk_indices row and produce incorrect results / OOB reads. Please enforce that R_CHUNK is a positive divisor of TOPK (or pad topk_indices to a multiple).
    if scale is None:
        scale = 1.0 / (D**0.5)
    if R_CHUNK is None:
        R_CHUNK = TOPK
    lse = lse.float().contiguous()

aiter/ops/triton/attention/sparse_attention_dsv4_bwd.py:104

  • The wrapper documents strict dtypes/shapes (bf16 Q/KV/dO/O, fp32 lse/sink, int32 indices) but currently only checks contiguity/shapes partially. Adding explicit dtype + shape validation (including lse.shape == (T, H) and attn_sink shape/dtype when provided) will prevent hard-to-debug miscompiles or silent correctness issues.
    T, H, D = q.shape
    TOPK = topk_indices.shape[1]
    num_kv = kv.shape[0]
    assert D == 512, f"DSv4 sparse-MLA backward is fixed to head_dim 512, got {D}"
    assert kv.shape[-1] == D and do.shape == q.shape and o.shape == q.shape
    assert num_kv >= T, f"num_kv ({num_kv}) must be >= T ({T})"
    assert q.is_contiguous() and kv.is_contiguous() and do.is_contiguous()
    assert o.is_contiguous() and topk_indices.is_contiguous()

aiter/ops/triton/_triton_kernels/attention/sparse_attention_dsv4_bwd.py:70

  • This new Triton kernel is also missing a config-aware repr=make_kernel_repr(...) (rule: "Kernel conventions"). Adding a repr here improves profiling/trace readability and aligns with other attention kernels that define repr objects.
@triton.jit
def _bwd_dkv_gather_acc_v4_be(
    Interm_ptr,  # [T, R_CHUNK, D] bf16, flat [T*R_CHUNK, D]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +35 to +37
Measured on MI355X at ``T=4096 H=128 topk=512`` with a realistic SWA(128)+pool top-k:
delta 0.178 / dQ 1.391 / interm 1.152 / CSR build 0.130 / gather 0.503 / d_sink 0.026 ms,
3.380 ms total = 407 TFLOPS.
Comment on lines +627 to +628
Defaults measured at T=4096 H=128 topk=512 (MI355X): 1.170 ms vs 1.631 for
``dkv_interm_v4`` = 1.39x. Sweep notes:
Comment on lines +275 to +277
`R_CHUNK` splits the rank dimension. Leave it `None` (unchunked) unless memory forces otherwise: it exists only to bound the `interm` intermediate (`T*topk*512` bf16, 2.0 GiB at T=4096 topk=512) and costs a dQ read-modify-write between chunks plus one CSR build per chunk.

**Measured** (MI355X, T=4096 H=128 topk=512, SWA(128)+pool top-k): 3.38 ms / 407 TFLOPS as a per-kernel sum, 3.24 ms / 424 TFLOPS end-to-end.
Comment on lines +19 to +25
import torch
import triton
import triton.language as tl


@triton.jit
def _delta_v4_kernel(
@wangye805
wangye805 marked this pull request as draft August 14, 2026 19:52
@wangye805 wangye805 changed the title [TRITON][GLUON][GFX950][DSV4] Sparse MLA training backward [WIP][TRITON][GLUON][GFX950][DSV4] Sparse MLA training backward Aug 14, 2026
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