Delete cutlass_mla, non-Marlin GPTQ, AWQ AOT kernel, and Dual Chunk Flash Attention (#32114)

Co-authored-by: Brayden Zhong <brayden@radixark.ai>
This commit is contained in:
Brayden Zhong
2026-09-10 15:12:01 +08:00
committed by GitHub
co-authored by Brayden Zhong
parent 7152c14384
commit c0b790cf7f
71 changed files with 48 additions and 12654 deletions
@@ -34,29 +34,6 @@ hardware/version X).
# A. Container re-image required
## A.1. `flash_attn` SM10.x wheel missing
**Affected**: `dual_chunk/test_dual_chunk_flash_attn.py` (entire class — 5
test methods, ~18 subtests)
**Symptom on GB300**:
```
ImportError: cannot import name 'flash_attn_varlen_func' from 'flash_attn'
```
**Root cause**: `DualChunkFlashAttentionBackend` calls `flash_attn_varlen_func`
via `sglang.kernels.ops.attention.flash_attention`. On SM 8.x / 9.x that resolves to
sgl-kernel's FA3 build (works on H200). On other SMs, the JIT kernel falls
back to the upstream `flash_attn` (FA2) wheel — but the
`lmsysorg/sglang:nightly-dev-cu13` container's `flash_attn` package on
SM10.x is missing `flash_attn_varlen_func`.
**Gate**: `_dual_chunk_fa_supported()` in
`dual_chunk/test_dual_chunk_flash_attn.py` skips the whole class on the
fallback-broken path. Hopper passes through unchanged.
**Fix**: Re-image with an SM10.x-compiled `flash_attn` wheel.
## A.2. tilelang `wait_wgmma` template missing on SM10.x
**Affected**:
@@ -89,7 +66,6 @@ so that "skipped: ..." results have a quick lookup.
| Backend | Required SM | Gate location | Error if unguarded |
|---|---|---|---|
| `cutlass_mla` | exactly SM 10.0 (B200) | `mla/test_cutlass_mla.py::_supported` | `cutlass_mla_decode is only supported on compute capability 10.0, but found sm version 103` |
| `flashmla` decode/verify | SM 9.0 (Hopper) only | `mla/test_flashmla.py:_DECODE_REQUIRES_SM90A` | `Dense decode MLA is only supported on SM90a architecture` |
| `trtllm_mla` | SM 12.0a / 12.1a | `mla/test_trtllm_mla.py::_supported` | FlashInfer XQA MLA dispatch reject |
| `tokenspeed_mla` | SM ≥ 10.0 + FP8 KV + pkg | `mla/test_tokenspeed_mla.py::_supported` | `tokenspeed_mla` import or kernel dispatch |
@@ -99,7 +75,7 @@ so that "skipped: ..." results have a quick lookup.
| `fa3` (non-MLA) | SM 80 or SM 90 | `_is_fa3_supported` in `flash_attention_v3.py` | `attention_registry.py:177-180` reject |
**SM10.3 vs SM10.0**: GB300 is SM10.3. Gates that require exactly SM10.0
(cutlass_mla, dsa trtllm) intentionally skip on GB300 because the kernel
(dsa trtllm) intentionally skip on GB300 because the kernel
binaries in the container aren't compiled for sm_103. Flip the gates to
`major == 10` (drop the `minor == 0`) once GB300-compiled binaries land.
@@ -133,13 +109,7 @@ methods that record each backend's failure mode inline as
| `mla/test_flashmla.py::test_layout_robustness_cases` (extend) | `non_monotonic_extend` | FlashMLA extend raises CUDA illegal memory access. |
| `mla/test_flashmla.py::test_layout_robustness_cases` (decode) | `interleaved_pages` | FlashMLA decode raises `shape '[-1, 64, 1, 32]' is invalid for input of size N`. |
### Dual-chunk
| Test | Layout | Root cause |
|---|---|---|
| `dual_chunk/test_dual_chunk_flash_attn.py::test_layout_robustness_cases` (extend) | `non_monotonic_extend` | `_dual_chunk_flash_attn_prefill_func` uses `cu_seqlens_*` indexing into contiguous K slots (`dual_chunk_flashattention_backend.py:834+`); scattered extend-token slots break that contiguity. |
**Total**: 9 layout-handling production bugs documented.
**Total**: 8 layout-handling production bugs documented.
## C.2. Speculative-mode rejects
@@ -205,7 +175,6 @@ fail at backend init.
| Backend | Required page size(s) | Citation |
|---|---|---|
| FlashMLA | `64` only | `server_args.py:2767-2770` |
| Cutlass MLA | `128` only | `server_args.py:2776-2779`, `cutlass_mla_backend.py:31` |
| TRT-LLM MLA | `{32, 64}` | `server_args.py:2790-2794` |
| Tokenspeed MLA | `{32, 64}` | `server_args.py:2809-2813`, `tokenspeed_mla_backend.py:111-113` |
| TRT-LLM MHA | `{16, 32, 64}` | `server_args.py:2849-2853` |
@@ -241,12 +210,9 @@ fail at backend init.
| Test file | Failure type | Section |
|---|---|---|
| `dual_chunk/test_dual_chunk_flash_attn.py` | Container: `flash_attn` SM10.x wheel | §A.1 |
| `dual_chunk/test_dual_chunk_flash_attn.py::test_layout_robustness_cases` (non_monotonic_extend) | Layout-handling bug | §C.1 |
| `dsa/test_dsa.py::test_sparse_tilelang_*` | Container: tilelang `wait_wgmma` | §A.2 |
| `dsa/test_dsa.py::test_sparse_*_impl_variants` (tilelang row) | Container: tilelang `wait_wgmma` | §A.2 |
| `dsa/test_dsa.py::test_sparse_*_impl_variants` (fa3 / trtllm rows) | Hardware gate | §B |
| `mla/test_cutlass_mla.py` (all) | Hardware gate (SM 10.0 exactly) | §B |
| `mla/test_flashmla.py` (DECODE/verify subtests) | Hardware gate (SM 9.0 Hopper) | §B |
| `mla/test_flashinfer.py::test_runner_mode_eagle_draft_cuda_graph_runner_cases` | Backend bug gated on SM≥10 | §C.3 |
| `mla/test_flashinfer.py::test_layout_robustness_cases` | Layout-handling bug | §C.1 |
@@ -1,90 +0,0 @@
# Dual-Chunk Attention Capability Matrix
This folder covers dual-chunk attention tests. `dual_chunk_flash_attn` is not
a dense backend swap: it expects a packed five-way query projection (`query`,
`succ`, `inter`, and critical variants), so the dense Q/K/V harness is
structurally wrong for this method. The single attention backend here is
`dual_chunk_flash_attn`; the rows below distinguish kernel-path modes.
## Coverage Matrix
Columns are runner modes; rows are kernel-path modes of the single
`dual_chunk_flash_attn` backend. Cells use:
- **✓ \<variants\>** — exercised, with the config variants listed in the cell
- **—** — not applicable / not exercised
- **blocked: \<reason\>** — production-unsupported, not a follow-up
- **deferred: \<reason\>** — could land later, currently disabled
| Kernel path | Eager Phase 2 | CG decode | PCG extend | BCG extend | Verify eager | Verify CG | DE eager | DE CG | DE-V2 CG | EAGLE-draft runner | EAGLE-DE runner | FKVMTP runner |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Non-sparse | ✓ first-window, successor-chunk, inter-chunk extend/decode layouts + GQA decode | deferred: graph metadata for dual-chunk not scoped | deferred | deferred | blocked: `init_forward_metadata` asserts `is_prefill() or is_decode()` (`dual_chunk_flashattention_backend.py:179`); `TARGET_VERIFY` falls under `is_prefill()` but the wrapper hasn't been wired through | deferred | deferred | deferred | blocked: `DRAFT_EXTEND_V2` excluded from `is_prefill()` alias (see Production-Unsupported below) | deferred | deferred | — |
| Sparse all-column (`vertical_size`/`slash_size` chosen so every key in the first chunk is selected) | ✓ single-request first-chunk, multi-request first-chunk, page-boundary first-chunk | — | — | — | blocked: same `is_prefill` assertion | — | — | — | blocked: same | — | — | — |
| Sparse sub-window (`vertical_size=4`, `slash_size=4`, `seq_len=128`) | ✓ independent DCA top-k/split/fallback reference + torch sparse-kernel reference | — | — | — | — | — | — | — | — | — | — | — |
| Threshold-gated sparse (`sparse_attention_threshold=100`, seq_len=16 → gate disables sparse, falls back to dense) | ✓ verifies `current_orig_seq_len > threshold` gate semantics | — | — | — | — | — | — | — | — | — | — | — |
## Input And Config Coverage
- Page size 1 extend, exact-page extend, page-boundary crossing extend, and
ragged extend batches.
- Decode page-boundary coverage and GQA decode coverage.
- Successor-chunk and inter-chunk extend/decode layouts where `query_succ`
and `query_inter` are active and use independent projection weights.
- Sparse all-column prefill uses `head_dim=128` to match the local sparse
FlashAttention build and selects every column in the first chunk
(≤16 tokens) so the dense reference remains valid.
- Multi-request sparse and page-boundary sparse variants exercise per-request
`cu_seqlens_*` slicing inside `_dual_chunk_flash_attn_prefill_func`.
- Sub-window sparse prefill uses `vertical_size=4`, `slash_size=4`, and
`seq_len=128` to verify the DCA-specific content-aware top-k split and
empty-stage fallback against an independent reference, then verifies the
sparse output against a torch sparse-kernel reference that consumes the
production block/column metadata.
- Threshold-gated sparse uses `sparse_attention_threshold=100` so a 16-token
prompt bypasses the sparse kernel and falls through to the dense chunk
flash path, exercising the gate semantics in the wrapper.
## Container Gate (SM10.x)
`DualChunkFlashAttentionBackend` calls `flash_attn_varlen_func` via
`sglang.kernels.ops.attention.flash_attention`. On SM8.x / SM9.x that resolves to sgl-kernel's
FA3 build; on SM != {8, 9} (notably SM10.x / GB300) the JIT kernel falls back
to the upstream `flash_attn` (FA2) wheel, which the
`lmsysorg/sglang:nightly-dev-cu13` container ships without an SM10.x-compiled
`flash_attn_varlen_func`. `test_dual_chunk_flash_attn.py` probes both paths at
module import: if FA3 is supported (`major in {8, 9}`) it runs unconditionally;
otherwise it tries `from flash_attn import flash_attn_varlen_func` and skips the
whole class with the documented reason if the symbol is missing. Re-image with
an SM10.x-compiled flash_attn wheel to clear; no test-code change needed.
See `KNOWN_FAILURES.md` §1 for the full root cause + fix.
## Production-Unsupported
- **Non-prefill / non-decode forward modes** —
`dual_chunk_flashattention_backend.py:179` asserts
`forward_mode.is_prefill() or forward_mode.is_decode()`. `is_prefill()`
aliases to `is_extend()` (`forward_batch_info.py:103-104`) and covers
`EXTEND` / `MIXED` / `DRAFT_EXTEND` / `TARGET_VERIFY` / `SPLIT_PREFILL` /
`DLLM_EXTEND`, but `DRAFT_EXTEND_V2` is excluded by default. So
`DRAFT_EXTEND_V2` is structurally unreachable for `dual_chunk_flash_attn`.
- **Non-causal / windowed-attention requests** — `forward_extend` raises
`ValueError("Dual Chunk Attention does not support causal=False")`
(`dual_chunk_flashattention_backend.py:698`) and
`ValueError("Dual Chunk Attention does not support window_size")`
(`dual_chunk_flashattention_backend.py:700`).
- **Sparse mode `chunk_len % block_size != 0`** — raises
`ValueError("chunk_len must be divisible by block_size.")`
(`dual_chunk_flashattention_backend.py:860, 1491`). The current fixture
picks divisible values.
- **Unsupported `head_dim`** — only `head_dim in {16, 32, 64, 128, 256, 512}`
is accepted (`dual_chunk_flashattention_backend.py:1611`).
## Next Work
- Populate CUDA graph and PCG/BCG runner metadata after eager non-sparse
coverage is stable across more chunk layouts.
- **Broaden sub-window sparse coverage** — the current regression case covers
`prefix_lens=(0,)`, `extend_lens=(128,)`, and no GQA. Add
multi-request batches, nonzero prefixes, GQA, and more sparse config variants
once those paths need explicit sparse pruning coverage. The 64x64
vertical/slash converter remains covered at the sgl-kernel layer.
@@ -1 +0,0 @@
"""Dual-chunk attention unit-test package."""
@@ -1,177 +0,0 @@
import unittest
import torch
from sglang.srt.model_executor.forward_batch_info import ForwardMode
from sglang.test.kits.attention_unittest.attention_methods.dual_chunk_attention import (
DualChunkAttentionCase,
make_dual_chunk_cases,
make_dual_chunk_sparse_cases,
make_dual_chunk_sparse_sub_window_cases,
make_dual_chunk_sparse_threshold_gated_cases,
run_dual_chunk_attention_case,
run_dual_chunk_sparse_attention_case,
run_dual_chunk_sparse_sub_window_case,
run_dual_chunk_sparse_threshold_gated_case,
)
from sglang.test.kits.attention_unittest.runner_modes.cuda_graph_decode_runner import (
run_dual_chunk_cuda_graph_decode_case,
)
from sglang.test.test_utils import CustomTestCase
# Container gate (KNOWN_FAILURES.md §1): `DualChunkFlashAttentionBackend` calls
# `flash_attn_varlen_func` on every forward via
# `sglang.kernels.ops.attention.flash_attention`. On SM8x/SM9x, that resolves to sgl-kernel's
# FA3 build (which works). On SM != {8, 9} (notably SM10.3 / GB300), the JIT
# kernel falls back to the upstream `flash_attn` (FA2) wheel — but the
# `lmsysorg/sglang:nightly-dev-cu13` container's `flash_attn` package ships
# without `flash_attn_varlen_func` on SM10.x, so every dual-chunk forward
# fails at import time inside the fallback. Skip the whole suite only when
# that fallback path is actually broken (not on Hopper, where we never enter it).
# Re-image the container with an SM10.3-compiled flash_attn wheel to clear.
def _dual_chunk_fa_supported() -> tuple[bool, str]:
if not torch.cuda.is_available():
return False, "CUDA is required"
major, _minor = torch.cuda.get_device_capability()
# FA3 path is taken when sm major is 8 or 9 (see
# `sglang.kernels.ops.attention.flash_attention_v3._is_fa3_supported`). On that path
# the upstream `flash_attn` fallback is never invoked.
if major in (8, 9):
return True, ""
# Otherwise (sm 7.x or sm >= 10.x) the JIT kernel falls back to upstream
# `flash_attn.flash_attn_varlen_func`. Probe it; if missing, skip.
try:
from flash_attn import ( # noqa: F401
flash_attn_varlen_func as _flash_attn_varlen_func,
)
return True, ""
except ImportError as exc:
return False, (
f"flash_attn_varlen_func is not available in upstream `flash_attn` "
f"(SM{major}.x JIT-kernel fallback): {exc}. "
f"Re-image the container with an SM{major}.x-compiled flash_attn wheel."
)
_DUAL_CHUNK_FLASH_ATTN_AVAILABLE, _DUAL_CHUNK_SKIP_REASON = _dual_chunk_fa_supported()
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=10, stage="base-b", runner_config="4-gpu-b200")
register_cuda_ci(est_time=10, stage="base-b", runner_config="1-gpu-large")
@unittest.skipIf(not torch.cuda.is_available(), "CUDA is required")
@unittest.skipIf(not _DUAL_CHUNK_FLASH_ATTN_AVAILABLE, _DUAL_CHUNK_SKIP_REASON)
class TestDualChunkFlashAttentionBackendCorrectness(CustomTestCase):
CASES = make_dual_chunk_cases("dual_chunk_flash_attn")
SPARSE_CASES = make_dual_chunk_sparse_cases("dual_chunk_flash_attn")
SPARSE_THRESHOLD_GATED_CASES = make_dual_chunk_sparse_threshold_gated_cases(
"dual_chunk_flash_attn"
)
SPARSE_SUB_WINDOW_CASES = make_dual_chunk_sparse_sub_window_cases(
"dual_chunk_flash_attn"
)
# Replay prefix_lens must each be >= capture_prefix_len (= fill-value - 1).
# Dual-chunk's `get_cuda_graph_seq_len_fill_value()` returns 1, so capture
# uses prefix=0. We pick a 3-request batch with varied lengths to exercise
# both the page-boundary and within-page slots.
CUDA_GRAPH_DECODE_CASES = (
DualChunkAttentionCase(
name="runner_cuda_graph_dual_chunk_decode_page_boundary",
backend="dual_chunk_flash_attn",
forward_mode=ForwardMode.DECODE,
num_heads=4,
num_kv_heads=4,
page_size=16,
prefix_lens=(14, 15, 16),
),
)
def test_projected_dual_chunk_attention_cases(self):
for case in self.CASES:
with self.subTest(case=case.name, backend=case.backend):
run_dual_chunk_attention_case(self, case)
def test_sparse_dual_chunk_attention_cases(self):
for case in self.SPARSE_CASES:
with self.subTest(case=case.name, backend=case.backend):
run_dual_chunk_sparse_attention_case(self, case)
def test_sparse_dual_chunk_threshold_gated_cases(self):
for case in self.SPARSE_THRESHOLD_GATED_CASES:
with self.subTest(case=case.name, backend=case.backend):
run_dual_chunk_sparse_threshold_gated_case(self, case)
def test_sparse_dual_chunk_sub_window_cases(self):
for case in self.SPARSE_SUB_WINDOW_CASES:
with self.subTest(case=case.name, backend=case.backend):
run_dual_chunk_sparse_sub_window_case(self, case)
def test_runner_mode_cuda_graph_decode_cases(self):
for case in self.CUDA_GRAPH_DECODE_CASES:
with self.subTest(case=case.name, backend=case.backend):
run_dual_chunk_cuda_graph_decode_case(self, case)
# Layout-robustness. See dense/test_triton.py for the rationale.
# dual_chunk_flash_attn EXTEND fails on non_monotonic_extend with
# ~67% mismatch and max abs diff ~1.1. The dual-chunk prefill path
# uses `cu_seqlens_*` indexing into a contiguous K layout
# (see `_dual_chunk_flash_attn_prefill_func` in
# dual_chunk_flashattention_backend.py:834+), which assumes K for
# the new extend tokens is laid out contiguously in
# `[begin, end)` slot order. Scattering extend-token slots within a
# request breaks that contiguity. Documented as a known production
# limitation.
LAYOUT_ROBUSTNESS_CASES = (
DualChunkAttentionCase(
name="layout_dual_chunk_extend_two_request",
backend="dual_chunk_flash_attn",
forward_mode=ForwardMode.EXTEND,
num_heads=4,
num_kv_heads=4,
page_size=16,
prefix_lens=(0, 0),
extend_lens=(16, 32),
),
DualChunkAttentionCase(
name="layout_dual_chunk_decode_page_boundary",
backend="dual_chunk_flash_attn",
forward_mode=ForwardMode.DECODE,
num_heads=4,
num_kv_heads=4,
page_size=16,
prefix_lens=(14, 15, 16),
),
)
LAYOUT_KNOWN_FAILURES = {
("layout_dual_chunk_extend_two_request", "non_monotonic_extend"): (
"dual_chunk_flash_attn prefill uses cu_seqlens_* indexing "
"into contiguous K slots within an extend "
"(`_dual_chunk_flash_attn_prefill_func` in "
"dual_chunk_flashattention_backend.py:834+); scattered "
"extend-token slots break that contiguity."
),
}
def test_layout_robustness_cases(self):
for case in self.LAYOUT_ROBUSTNESS_CASES:
for layout in ("interleaved_pages", "non_monotonic_extend"):
if layout == "non_monotonic_extend" and case.forward_mode.is_decode():
continue
reason = self.LAYOUT_KNOWN_FAILURES.get((case.name, layout))
if reason is not None:
print(
f"[layout-known-failure] {case.name} x {layout}: {reason}",
flush=True,
)
continue
with self.subTest(case=case.name, layout=layout):
run_dual_chunk_attention_case(self, case, loc_layout=layout)
if __name__ == "__main__":
unittest.main()
@@ -20,7 +20,6 @@ Columns are runner modes; rows are attention backends. Cells use:
| `triton` | ✓ 10 input layouts (page 1/16/32, prefix/decode edges) | ✓ MLA decode page-boundary | ✓ ragged page-boundary extend | ✓ ragged page-boundary extend | ✓ EAGLE chain (topk=1) | ✓ EAGLE tree (topk=2) | — (V1 DE not enabled for Triton MLA; Triton uses V2 path) | — | ✓ fixed-tokens-per-req | ✓ chain (topk=1) + tree (topk=2) | ✓ via `DRAFT_EXTEND_V2` graph runner | — (no FKVMTP wiring for MLA) |
| `flashinfer` | ✓ 10 input layouts with DeepSeek-like `kv_lora_rank=512`, `qk_rope_head_dim=64` | ✓ MLA decode page-boundary | ✓ ragged page-boundary extend | ✓ ragged page-boundary extend | ✓ EAGLE chain (topk=1) | ✓ EAGLE chain (topk=1) | ✓ EAGLE ragged-accept | ✓ EAGLE ragged-accept | blocked: `is_draft_extend()` default `include_v2=False` (`flashinfer_mla_backend.py:432,501,454-455,512`) | ✓ chain (topk=1) only — tree blocked by `topk=1` reject (`flashinfer_mla_backend.py:910-913`) | ✓ EAGLE ragged-accept (V1) | — (no FKVMTP wiring for MLA) |
| `flashmla` | ✓ FlashMLA-compatible page-size-64 cases (zero-prefix exact page, input page edges 63/64/65, prefix exact page, total exact page, cross page, ragged, decode page-boundary, decode bsz=1 nonzero prefix) | ✓ page-size-64 decode page-boundary | ✓ ragged page-boundary extend | ✓ ragged page-boundary extend | ✓ EAGLE chain (topk=1) | ✓ EAGLE chain (topk=1) | ✓ EAGLE ragged-accept | deferred: parent FlashInfer-MLA capture path expects 1D `cuda_graph_kv_indices`, FlashMLA allocates 2D `[max_bs, (max_context+PAGE_SIZE)//PAGE_SIZE]` (`flashmla_backend.py:347-348` + parent `init_forward_metadata_capture_cuda_graph`) | — (FlashMLA does not implement V2) | ✓ chain (topk=1) only — tree blocked by `topk=1` reject (`flashmla_backend.py:555-558`) | — (DE CG deferred above) | — |
| `cutlass_mla` | skip:hw — needs SM 10.0+ (Blackwell); current 1 case uses `ForwardMode.EXTEND` but `CutlassMLABackend` only overrides `forward_decode` (`cutlass_mla_backend.py:226`) and falls through to FlashInfer MLA for other modes → **case should be DECODE**; PAGE_SIZE fixed at 128 (`cutlass_mla_backend.py:31`) | — (decode-only backend; no extend/CG) | — | — | blocked: tree via `topk=1` reject inherited from FlashInfer MLA parent | — | — | — | — | — | — | — |
| `trtllm_mla` | skip:hw — needs SM 12.0a / 12.1a (`is_sm120_supported`) | — | — | — | blocked: `topk=1` only (`trtllm_mla_backend.py:1223-1229` inherits from FlashInfer MLA) | — | — | — | — | — | — | — |
| `tokenspeed_mla` | skip:hw — needs `find_spec("tokenspeed_mla")`, SM 10.0+, and `kv_cache_dtype=fp8_e4m3` (`server_args.py:2814-2818`); current MLA fixture does not emit FP8 KV cache | — | — | — | blocked: `topk=1` only (`tokenspeed_mla_backend.py:341-347` inherits from TRT-LLM MLA) | — | — | — | — | — | — | — |
@@ -60,17 +59,12 @@ multi-step draft backends and cannot ever appear at runtime.
- **Tokenspeed MLA tree verify / draft-extend with `topk > 1`** —
`TokenspeedMLAMultiStepDraftBackend` inherits from
`TRTLLMMLAMultiStepDraftBackend` (`tokenspeed_mla_backend.py:341-347`).
- **Cutlass MLA extend / verify / draft-extend** — `CutlassMLABackend` only
overrides `forward_decode` (`cutlass_mla_backend.py:226`) and only handles
`is_decode_or_idle` in `init_forward_metadata*` (`cutlass_mla_backend.py:86,
156, 197`). Anything else falls through to FlashInfer MLA.
- **FlashInfer-MLA `DRAFT_EXTEND_V2` graph capture/replay** —
`flashinfer_mla_backend.py:432,501` only route through `is_draft_extend()`
(default `include_v2=False`); `else: raise ValueError("Invalid mode")` at
`flashinfer_mla_backend.py:454-455,512`.
- **All MLA backends fixed page size** — FlashMLA forces `page_size=64`,
Cutlass MLA forces `page_size=128`, TRT-LLM MLA and Tokenspeed MLA force
`page_size in {32, 64}`.
TRT-LLM MLA and Tokenspeed MLA force `page_size in {32, 64}`.
## Backend Container Gate (SM10.x)
@@ -91,9 +85,6 @@ See `KNOWN_FAILURES.md` §3 for the full root cause + fix.
override capture/replay in `FlashMLABackend` to use its 2D layout, or
allocate both parent-style 1D and FlashMLA-style 2D buffers and route
`DRAFT_EXTEND` to the parent path).
- Switch `mla/test_cutlass_mla.py` to `ForwardMode.DECODE` so it actually
exercises `CutlassMLABackend.forward_decode` instead of falling through to
FlashInfer MLA when SM 10.0+ is available.
- Add hardware-gated tests for `cutlass_mla`, `trtllm_mla`, and `tokenspeed_mla`
- Add hardware-gated tests for `trtllm_mla` and `tokenspeed_mla`
decode (chain spec only) when the appropriate hardware/KV dtype fixtures are
available.
@@ -1,98 +0,0 @@
import unittest
import torch
from sglang.srt.model_executor.forward_batch_info import ForwardMode
from sglang.test.kits.attention_unittest.attention_methods.mla_attention import (
MLAAttentionCase,
run_mla_attention_case,
)
from sglang.test.test_utils import CustomTestCase
# Cutlass MLA requires exactly Blackwell SM 10.0. The sgl-kernel
# `cutlass_mla_decode` checks `sm_version == 100` (major*10+minor), so
# SM 10.3 (GB300) reports sm_version=103 and is rejected by the kernel.
# PAGE_SIZE is fixed to 128 in the backend.
_REQUIRED_SM_MAJOR = 10
_REQUIRED_SM_MINOR = 0
MLA_SHAPE_KWARGS = dict(
kv_lora_rank=512,
qk_rope_head_dim=64,
hidden_size=1024,
max_context_len=256,
)
def _supported() -> tuple[bool, str]:
if not torch.cuda.is_available():
return False, "CUDA is required"
major, minor = torch.cuda.get_device_capability()
if major != _REQUIRED_SM_MAJOR or minor != _REQUIRED_SM_MINOR:
return (
False,
f"cutlass_mla requires exactly SM {_REQUIRED_SM_MAJOR}.{_REQUIRED_SM_MINOR} "
f"(B200 Blackwell); got SM {major}.{minor}",
)
return True, ""
_SUPPORTED, _SKIP_REASON = _supported()
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=12, stage="base-b", runner_config="4-gpu-b200")
register_cuda_ci(est_time=10, stage="base-b", runner_config="1-gpu-large")
@unittest.skipIf(not _SUPPORTED, _SKIP_REASON)
class TestCutlassMLAAttentionBackendCorrectness(CustomTestCase):
# CutlassMLABackend only overrides `forward_decode`; EXTEND falls through
# to the FlashInferMLAAttnBackend parent and bypasses cutlass code
# entirely. Use DECODE so the test actually exercises the cutlass kernel
# on Blackwell. Page size is fixed to PAGE_SIZE=128 (server_args.py
# forces this for cutlass_mla).
CASES = (
MLAAttentionCase(
name="mla_decode_cutlass_page_boundary",
backend="cutlass_mla",
forward_mode=ForwardMode.DECODE,
num_heads=4,
page_size=128,
prefix_lens=(126, 127, 128),
),
MLAAttentionCase(
name="mla_decode_cutlass_bsz1_nonzero_prefix",
backend="cutlass_mla",
forward_mode=ForwardMode.DECODE,
num_heads=4,
page_size=128,
prefix_lens=(63,),
),
MLAAttentionCase(
name="mla_decode_cutlass_above_page",
backend="cutlass_mla",
forward_mode=ForwardMode.DECODE,
num_heads=4,
page_size=128,
prefix_lens=(128, 129, 130),
),
MLAAttentionCase(
name="mla_decode_cutlass_multi_page",
backend="cutlass_mla",
forward_mode=ForwardMode.DECODE,
num_heads=4,
page_size=128,
prefix_lens=(127, 200, 255),
),
)
def test_projected_mla_attention_cases(self):
for case in self.CASES:
with self.subTest(case=case.name, backend=case.backend):
run_mla_attention_case(self, case, **MLA_SHAPE_KWARGS)
if __name__ == "__main__":
unittest.main()
@@ -15,13 +15,6 @@ register_cuda_ci(
est_time=5, stage="base-b-kernel-benchmark", runner_config="1-gpu-large"
)
try:
from sgl_kernel import awq_dequantize as aot_awq_dequantize
AOT_AVAILABLE = True
except ImportError:
AOT_AVAILABLE = False
IS_CI = is_in_ci()
if IS_CI:
@@ -34,59 +27,16 @@ else:
configs = list(itertools.product(qweight_row_range, qweight_cols_range))
def check_correctness():
if not AOT_AVAILABLE:
print("sgl_kernel AOT not available, skipping correctness check")
return
qweight_row, qweight_col = 128, 16
device = torch.device("cuda")
qweight = torch.randint(
0,
torch.iinfo(torch.int32).max,
(qweight_row, qweight_col),
dtype=torch.int32,
device=device,
)
group_size = qweight_row
scales_row = qweight_row // group_size
scales_col = qweight_col * 8
scales = torch.rand(scales_row, scales_col, dtype=torch.float16, device=device)
qzeros = torch.randint(
0,
torch.iinfo(torch.int32).max,
(scales_row, qweight_col),
dtype=torch.int32,
device=device,
)
jit_out = jit_awq_dequantize(qweight, scales, qzeros)
aot_out = aot_awq_dequantize(qweight, scales, qzeros)
torch.cuda.synchronize()
torch.testing.assert_close(jit_out, aot_out, rtol=0, atol=0)
print("Correctness check passed (JIT vs AOT)")
if AOT_AVAILABLE:
line_vals = ["jit", "aot"]
line_names = ["JIT Kernel", "AOT Kernel"]
styles = [("blue", "-"), ("green", "-")]
else:
line_vals = ["jit"]
line_names = ["JIT Kernel"]
styles = [("blue", "-")]
@triton.testing.perf_report(
triton.testing.Benchmark(
x_names=["qweight_row", "qweight_col"],
x_vals=configs,
line_arg="provider",
line_vals=line_vals,
line_names=line_names,
styles=styles,
line_vals=["jit"],
line_names=["JIT Kernel"],
styles=[("blue", "-")],
ylabel="us",
plot_name="awq-dequantize-jit-vs-aot",
plot_name="awq-dequantize-jit",
args={},
)
)
@@ -111,16 +61,12 @@ def benchmark(qweight_row, qweight_col, provider):
device=device,
)
if provider == "jit":
fn = lambda: jit_awq_dequantize(qweight, scales, qzeros)
elif provider == "aot":
fn = lambda: aot_awq_dequantize(qweight, scales, qzeros)
else:
if provider != "jit":
raise ValueError(f"Unknown provider: {provider}")
fn = lambda: jit_awq_dequantize(qweight, scales, qzeros)
return run_benchmark(fn)
if __name__ == "__main__":
check_correctness()
benchmark.run(print_data=True)
@@ -11,13 +11,6 @@ from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=9, stage="base-b-kernel-unit", runner_config="1-gpu-large")
try:
from sgl_kernel import awq_dequantize as aot_awq_dequantize
AOT_AVAILABLE = True
except ImportError:
AOT_AVAILABLE = False
def reverse_awq_order(t: torch.Tensor):
bits = 4
@@ -117,54 +110,5 @@ def test_awq_dequantize_jit_vs_torch(
)
@pytest.mark.parametrize(
"qweight_row,qweight_col,is_bf16_act",
list(
itertools.product(
[128, 256, 512, 1024, 3584],
[16, 32, 64, 128, 448],
[True, False],
)
),
)
def test_awq_dequantize_jit_vs_aot(
qweight_row: int, qweight_col: int, is_bf16_act: bool
):
if not AOT_AVAILABLE:
pytest.skip("sgl_kernel AOT not available")
device = torch.device("cuda")
qweight = torch.randint(
0,
torch.iinfo(torch.int32).max,
(qweight_row, qweight_col),
dtype=torch.int32,
device=device,
)
group_size = qweight_row
scales_row = qweight_row // group_size
scales_col = qweight_col * 8
if is_bf16_act:
scales = torch.rand(scales_row, scales_col, dtype=torch.bfloat16, device=device)
else:
scales = torch.rand(scales_row, scales_col, dtype=torch.float16, device=device)
qzeros = torch.randint(
0,
torch.iinfo(torch.int32).max,
(scales_row, qweight_col),
dtype=torch.int32,
device=device,
)
# Run both implementations
aot_out = aot_awq_dequantize(qweight, scales, qzeros)
jit_out = jit_awq_dequantize(qweight, scales, qzeros)
# Bitwise equality
torch.testing.assert_close(jit_out, aot_out, rtol=0, atol=0)
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v", "-s"]))
@@ -1,60 +0,0 @@
"""GPTQ builds its per-layer scheme lazily in `create_weights`, so the layer has
to declare `scheme = None` for the `is None` probe to see it.
Regression: the probe used to be `hasattr(layer, "scheme")`, which degraded to
always-true once `LinearBase` grew that class default -- the scheme was never
built and every GPTQ model died with ``'NoneType' object has no attribute
'create_weights'``.
"""
import unittest
import torch
from sglang.srt.layers.linear import LinearBase, ReplicatedLinear
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
from sglang.srt.layers.quantization.gptq.gptq import GPTQConfig
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=11, suite="base-a-test-cpu")
_GPTQ_CHECKPOINT_CONFIG = {
"bits": 4,
"group_size": 128,
"desc_act": False,
"lm_head": False,
"dynamic": {},
"checkpoint_format": "gptq",
"true_sequential": True,
"static_groups": False,
}
class TestGPTQSchemeAttach(CustomTestCase):
def test_linear_layer_gets_a_scheme(self):
layer = ReplicatedLinear(
input_size=256,
output_size=128,
bias=False,
params_dtype=torch.float16,
quant_config=GPTQConfig.from_config(_GPTQ_CHECKPOINT_CONFIG),
prefix="model.layers.0.mlp.down_proj",
)
self.assertIsNotNone(layer.scheme)
self.assertTrue(hasattr(layer, "qweight"))
def test_scheme_default_is_declared_on_every_quantizable_layer_base(self):
"""`get_linear_quant_method` hands a linear method a `LinearBase` or a
quantized `ParallelLMHead`; `GPTQMarlinConfig` hands
`GPTQMarlinMoEMethod` a bare `FusedMoE`. The MoE attach has no e2e
coverage, so this is its only guard.
"""
self.assertIsNone(LinearBase.scheme)
self.assertIsNone(VocabParallelEmbedding.scheme)
self.assertIsNone(FusedMoE.scheme)
if __name__ == "__main__":
unittest.main()
@@ -101,7 +101,7 @@ class TestPageMajorBackendAllowlist(unittest.TestCase):
# MLA-family kernels that must never leak into the MHA arm.
MLA_ONLY_BACKENDS = ("trtllm_mla", "cutedsl_mla", "tokenspeed_mla", "flashmla")
# No kernel-facing-id wiring anywhere: must stay rejected until they get one.
UNWIRED_BACKENDS = ("cutlass_mla", "aiter")
UNWIRED_BACKENDS = ("aiter",)
def test_triton_allowed_on_every_arm(self):
"""Triton reads both view families, so it is the one backend neither
@@ -1423,7 +1423,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
from sglang.srt.arg_groups.overrides import (
ResolvedView,
_attention_backend_default,
_attention_backend_dual_chunk,
_attention_backend_fa3_fp8_fallback,
_attention_backend_platform_fallbacks,
)
@@ -1459,19 +1458,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
with override_platform(has_amx=True):
self.assertEqual(_attention_backend_platform_fallbacks(view), {})
# dual-chunk config: mismatched explicit backend raises verbatim
def _mc(dual):
return SimpleNamespace(
_model_config=SimpleNamespace(
hf_config=SimpleNamespace(dual_chunk_attention_config=dual)
),
attention_backend="fa3",
)
with self.assertRaises(ValueError):
_attention_backend_dual_chunk(ResolvedView(_mc({"a": 1})))
self.assertEqual(_attention_backend_dual_chunk(ResolvedView(_mc(None))), {})
def test_dllm_platform_paths_at_callable_level(self):
from sglang.srt.arg_groups.overrides import (
ResolvedView,
@@ -2668,16 +2654,6 @@ class TestGoldenModelOverrides(_IsolatedPublish):
),
{"page_size": 64},
)
# chained: cutlass_mla decode -> 128, then trtllm_mha prefill keeps 128
self.assertEqual(
_mla_backend_page_constraints(
_view(
decode_attention_backend="cutlass_mla",
prefill_attention_backend="trtllm_mha",
)
),
{"page_size": 128},
)
# no matching backend: nothing declared
self.assertEqual(_mla_backend_page_constraints(_view()), {})