[Diffusion][MiniMax-H3] Add SM120 Sage compute for SubBlock sparse attention (#40116)
This commit is contained in:
@@ -267,7 +267,7 @@ class MiniMaxH3PipelineConfig(PipelineConfig):
|
||||
)
|
||||
if compute_mode == "sage_fp8":
|
||||
capability = current_platform.get_device_capability()
|
||||
if capability is None or capability.to_int() != 90:
|
||||
if capability is None or capability.to_int() not in (90, 120):
|
||||
found = (
|
||||
capability.as_version_str()
|
||||
if capability is not None
|
||||
@@ -275,14 +275,21 @@ class MiniMaxH3PipelineConfig(PipelineConfig):
|
||||
)
|
||||
raise ValueError(
|
||||
"MiniMax-H3 SubBlock compute_mode='sage_fp8' currently "
|
||||
"requires SM90 (compute capability 9.0); "
|
||||
"requires SM90 or SM120 (compute capability 9.0 or 12.0); "
|
||||
f"found {found}."
|
||||
)
|
||||
from sglang.kernels.ops.attention.subblock_sage_fp8_sm90 import (
|
||||
_load_sparge_attention_sm90_ops,
|
||||
)
|
||||
if capability.to_int() == 90:
|
||||
from sglang.kernels.ops.attention.subblock_sage_fp8_sm90 import (
|
||||
_load_sparge_attention_sm90_ops,
|
||||
)
|
||||
|
||||
_load_sparge_attention_sm90_ops()
|
||||
_load_sparge_attention_sm90_ops()
|
||||
else:
|
||||
from sglang.multimodal_gen.runtime.layers.attention.backends.subblock_sparse_attn import (
|
||||
_load_sm120_sage_ops,
|
||||
)
|
||||
|
||||
_load_sm120_sage_ops()
|
||||
if selected_backend is AttentionBackendEnum.VIDEO_SPARSE_ATTN_H3:
|
||||
if server_args.ring_degree > 1:
|
||||
raise ValueError(
|
||||
|
||||
+20
-4
@@ -4,8 +4,8 @@ Routes a SubBlock plan to SGLang's CuTe-DSL block-sparse FlashAttention kernel
|
||||
on SM90 or FlashInfer's architecture-specific blk64 kernels on SM100 and SM120.
|
||||
The architecture-neutral `"compute_mode": "sage_fp8"` selects online Sage-style
|
||||
FP8 compute; its current SM90 implementation uses the native SageAttention2
|
||||
INT8-QK/FP8-PV Hopper kernel. Future SM100 and SM120 Sage FP8 implementations
|
||||
can register under the same configuration value.
|
||||
INT8-QK/FP8-PV Hopper kernel. SM120 uses FlashInfer's CuTe-DSL Sage kernel
|
||||
with Q64 x K64 blocks. SM100 supports BF16 only.
|
||||
Nothing is trained and no weights change: a cheap estimator runs before
|
||||
attention and hands the selected kernel a `q2k_block_index`.
|
||||
|
||||
@@ -46,7 +46,7 @@ are listed below.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| GPU | **compute capability 9.0, 10.0, or 12.0** — H100 / H200 use SGLang's CuTe-DSL SM90 block-sparse FlashAttention kernel or the current `sage_fp8` implementation; B200 / GB200 use FlashInfer's architecture-specific `sm_100a` BF16 kernel; SM120 devices use FlashInfer's `bsa_attn_sm120_blk64_fwd` CuTe-DSL BF16 kernel. Other capabilities, including 10.3 (B300 / GB300), are rejected. |
|
||||
| GPU | **compute capability 9.0, 10.0, or 12.0** — H100 / H200 use SGLang's CuTe-DSL SM90 block-sparse FlashAttention kernel or the current `sage_fp8` implementation; B200 / GB200 use FlashInfer's architecture-specific `sm_100a` BF16 kernel; SM120 devices use FlashInfer's CuTe-DSL BF16 or Sage FP8 kernel. Other capabilities, including 10.3 (B300 / GB300), are rejected. |
|
||||
| dtype | bfloat16 |
|
||||
| head_dim | 128 |
|
||||
| attention | non-causal, one contiguous sequence per call |
|
||||
@@ -106,7 +106,17 @@ the normal router's 16-token key pooling cells. Install the optional dependency:
|
||||
pip install git+https://github.com/thu-ml/SpargeAttn.git --no-build-isolation
|
||||
```
|
||||
|
||||
Enable it on SM90 with:
|
||||
SM120 requires FlashInfer's CuTe-DSL SM120 Sage backend and
|
||||
`quantize_sage_qkv_sm120`, added in
|
||||
[FlashInfer #4691](https://github.com/flashinfer-ai/flashinfer/pull/4691).
|
||||
The default FlashInfer `0.6.18` pin does not include these APIs. After installing
|
||||
SGLang, install the FlashInfer source revision used for SM120 validation:
|
||||
|
||||
```bash
|
||||
pip install "git+https://github.com/flashinfer-ai/flashinfer.git@6a84331eb6013e5e61018dc2be532ae90520d30f"
|
||||
```
|
||||
|
||||
Enable it on SM90 or SM120 with:
|
||||
|
||||
```bash
|
||||
--attention-backend-config '{"compute_mode":"sage_fp8"}'
|
||||
@@ -195,3 +205,9 @@ Tests: `test/unit/test_subblock_sparse_attention.py` and
|
||||
test covers the native production dispatch. Running at a full block budget must
|
||||
reproduce dense attention up to the expected quantization error, pinning routing
|
||||
indices, ragged tails, scale domains and the softmax scale in one check.
|
||||
|
||||
Run the SM120 Sage numerical test with:
|
||||
|
||||
```bash
|
||||
python test/manual/attention/test_subblock_sage_fp8_sm120.py
|
||||
```
|
||||
|
||||
+63
-15
@@ -21,10 +21,9 @@ individual keys of the defaults below::
|
||||
Requirements inherited from the kernels: compute capability 9.0 (Hopper) or
|
||||
10.0/12.0 (Blackwell), bf16, head_dim 128. Hopper uses SGLang's CuTe-DSL SM90
|
||||
block-sparse FlashAttention kernel by default; ``compute_mode="sage_fp8"``
|
||||
selects its native SageAttention2 INT8-QK/FP8-PV kernel. The mode name is stable
|
||||
across architectures, so future SM100/SM120 implementations can use their own
|
||||
Sage FP8 arithmetic without changing server configuration. B200 and SM120
|
||||
devices currently use FlashInfer's architecture-specific blk64 BF16 kernels.
|
||||
selects native SageAttention2 on SM90 and FlashInfer's CuTe-DSL Sage kernel
|
||||
on SM120, both using INT8 QK and FP8 PV. SM100 uses the BF16 kernel because
|
||||
its Sage path does not support SubBlock's variable counts and partial blocks.
|
||||
Inside the DiT, unsupported calls run dense instead; unsupported GPU
|
||||
architectures are rejected.
|
||||
|
||||
@@ -110,6 +109,13 @@ DEFAULT_COMPUTE_MODE = "bf16"
|
||||
_DIT_LAYER_PREFIX = re.compile(r"^blocks\.(\d+)\.")
|
||||
|
||||
|
||||
def _sage_key_block_size() -> int:
|
||||
# Resolve on the worker's current device, after device initialization.
|
||||
if torch.cuda.is_available() and torch.cuda.get_device_capability() == (12, 0):
|
||||
return SUBBLOCK_SPARSE_BLOCK_SIZE
|
||||
return SAGE_FP8_SM90_KEY_BLOCK_SIZE
|
||||
|
||||
|
||||
def _dit_layer_index(prefix: str) -> int | None:
|
||||
match = _DIT_LAYER_PREFIX.match(prefix)
|
||||
return int(match.group(1)) if match else None
|
||||
@@ -233,6 +239,50 @@ def _sm100_sparse_attention(
|
||||
return out[0] if isinstance(out, tuple) else out
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _load_sm120_sage_ops():
|
||||
try:
|
||||
from flashinfer.cute_dsl.sparse.bsa_attn_sm120 import (
|
||||
bsa_attn_sm120_blk64_sage_fwd,
|
||||
)
|
||||
from flashinfer.cute_dsl.sparse.bsa_utils.sage_quant_sm120 import (
|
||||
quantize_sage_qkv_sm120,
|
||||
)
|
||||
except (ImportError, OSError) as exc:
|
||||
raise ImportError(
|
||||
"SM120 SubBlock sage_fp8 requires FlashInfer with the CuTe-DSL "
|
||||
"SM120 Sage backend and quantize_sage_qkv_sm120."
|
||||
) from exc
|
||||
return quantize_sage_qkv_sm120, bsa_attn_sm120_blk64_sage_fwd
|
||||
|
||||
|
||||
def _sm120_sage_fp8_sparse_attention(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
q2k_block_index: torch.Tensor,
|
||||
topk: int,
|
||||
softmax_scale: float,
|
||||
block_counts: torch.Tensor | None = None,
|
||||
) -> torch.Tensor:
|
||||
"""Quantize BSHD activations online and execute a Q64 x K64 Sage plan."""
|
||||
quantize, attention = _load_sm120_sage_ops()
|
||||
q_hnd, k_hnd, v_hnd = (x.transpose(1, 2).contiguous() for x in (q, k, v))
|
||||
quantized = quantize(q_hnd, k_hnd, v_hnd)
|
||||
out = attention(
|
||||
*quantized,
|
||||
q2k_block_index.contiguous(),
|
||||
topk,
|
||||
block_sizes=_cached_block_sizes(k.shape[1], k.device),
|
||||
q2k_block_nums=(
|
||||
block_counts.contiguous() if block_counts is not None else None
|
||||
),
|
||||
softmax_scale=softmax_scale,
|
||||
backend="cute_dsl",
|
||||
)
|
||||
return out.transpose(1, 2).contiguous()
|
||||
|
||||
|
||||
def _sm120_sparse_attention(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
@@ -273,16 +323,13 @@ def _get_subblock_sparse_attention_runner(
|
||||
if capability == (10, 0):
|
||||
if compute_mode != "bf16":
|
||||
raise RuntimeError(
|
||||
f"SubBlock compute_mode={compute_mode!r} currently targets SM90; "
|
||||
"the SM100 FlashInfer Sage adapter is not wired in SGLang yet."
|
||||
"SM100 FlashInfer Sage does not support SubBlock variable block "
|
||||
"counts and partial blocks; use compute_mode='bf16'."
|
||||
)
|
||||
return _sm100_sparse_attention
|
||||
if capability == (12, 0):
|
||||
if compute_mode != "bf16":
|
||||
raise RuntimeError(
|
||||
f"SubBlock compute_mode={compute_mode!r} currently targets SM90; "
|
||||
"the SM120 FlashInfer Sage adapter is not wired in SGLang yet."
|
||||
)
|
||||
if compute_mode == "sage_fp8":
|
||||
return _sm120_sage_fp8_sparse_attention
|
||||
return _sm120_sparse_attention
|
||||
raise RuntimeError(
|
||||
"SubBlock sparse attention supports compute capability 9.0, 10.0, or 12.0; "
|
||||
@@ -378,9 +425,10 @@ class SubBlockSparseSchedule(msgspec.Struct, frozen=True):
|
||||
|
||||
config = get_global_server_args().attention_backend_config or {}
|
||||
compute_mode = str(config.get("compute_mode", DEFAULT_COMPUTE_MODE))
|
||||
# Hopper's native kernel consumes 128-token K blocks. Eight sub-blocks
|
||||
# preserve the default router's 16-token key pooling cells.
|
||||
default_n_k = 8 if compute_mode == "sage_fp8" else DEFAULT_N_K
|
||||
# Use 16-token pooling cells for both K128 (SM90) and K64 (SM120).
|
||||
default_n_k = (
|
||||
_sage_key_block_size() // 16 if compute_mode == "sage_fp8" else DEFAULT_N_K
|
||||
)
|
||||
schedule = SubBlockSparseSchedule(
|
||||
sparsity=float(config.get("sparsity", DEFAULT_SPARSITY)),
|
||||
skip_first_steps=int(
|
||||
@@ -453,7 +501,7 @@ class SubBlockSparseAttentionImpl(AttentionImpl):
|
||||
n_k=self.schedule.n_k,
|
||||
n_q=self.schedule.n_q,
|
||||
block_size_k=(
|
||||
SAGE_FP8_SM90_KEY_BLOCK_SIZE
|
||||
_sage_key_block_size()
|
||||
if self.schedule.compute_mode == "sage_fp8"
|
||||
else SUBBLOCK_SPARSE_BLOCK_SIZE
|
||||
),
|
||||
|
||||
@@ -167,9 +167,15 @@ class TestSubBlockSparseSchedule(unittest.TestCase):
|
||||
self.assertEqual(schedule.compute_mode, "bf16")
|
||||
|
||||
def test_sage_fp8_uses_16_token_key_subblocks_by_default(self):
|
||||
with _patch_schedule({"compute_mode": "sage_fp8"}):
|
||||
schedule = SubBlockSparseSchedule.from_server_args()
|
||||
self.assertEqual(schedule.n_k, 8)
|
||||
for capability, expected_n_k in (((9, 0), 8), ((12, 0), 4)):
|
||||
with (
|
||||
self.subTest(capability=capability),
|
||||
patch("torch.cuda.is_available", return_value=True),
|
||||
patch("torch.cuda.get_device_capability", return_value=capability),
|
||||
_patch_schedule({"compute_mode": "sage_fp8"}),
|
||||
):
|
||||
schedule = SubBlockSparseSchedule.from_server_args()
|
||||
self.assertEqual(schedule.n_k, expected_n_k)
|
||||
|
||||
def test_explicit_sage_fp8_n_k_is_respected(self):
|
||||
with _patch_schedule({"compute_mode": "sage_fp8", "n_k": 4}):
|
||||
@@ -320,11 +326,18 @@ class TestSubBlockGating(unittest.TestCase):
|
||||
with _patch_step(20):
|
||||
self.assertFalse(impl._sparse_ready(q, q))
|
||||
|
||||
def test_sage_fp8_builds_the_sm90_64x128_router(self):
|
||||
impl = self._impl("blocks.9.attn", compute_mode="sage_fp8")
|
||||
self.assertEqual(impl.router.block_size_k, 128)
|
||||
self.assertEqual(impl.router.n_k, 8)
|
||||
self.assertEqual(impl.router.budget_granularity, 1)
|
||||
def test_sage_fp8_builds_architecture_specific_router(self):
|
||||
for capability, key_block_size, n_k in (((9, 0), 128, 8), ((12, 0), 64, 4)):
|
||||
with (
|
||||
self.subTest(capability=capability),
|
||||
patch("torch.cuda.is_available", return_value=True),
|
||||
patch("torch.cuda.get_device_capability", return_value=capability),
|
||||
):
|
||||
impl = self._impl("blocks.9.attn", compute_mode="sage_fp8")
|
||||
self.assertEqual(impl.router.block_size_k, key_block_size)
|
||||
self.assertEqual(impl.router.n_k, n_k)
|
||||
self.assertEqual(impl.router.block_size_k // impl.router.n_k, 16)
|
||||
self.assertEqual(impl.router.budget_granularity, 1)
|
||||
|
||||
|
||||
@requires_subblock_kernel
|
||||
|
||||
Reference in New Issue
Block a user