| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """SM120 implementation variant for ``FLASHINFER_MLA_SPARSE_SM120``.""" |
| 4 | |
| 5 | from typing import TYPE_CHECKING, cast |
| 6 | |
| 7 | import torch |
| 8 | |
| 9 | from vllm.model_executor.layers.attention.sparse_mla_attention import ( |
| 10 | SparseMLACommonImpl, |
| 11 | ) |
| 12 | from vllm.v1.attention.backend import AttentionLayer, AttentionType |
| 13 | from vllm.v1.attention.backends.mla.flashinfer_mla_sparse import ( |
| 14 | FlashInferMLASparseMetadata, |
| 15 | _get_workspace_buffer, |
| 16 | ) |
| 17 | from vllm.v1.attention.backends.mla.index_group import HiSparseMLAIndexGroup |
| 18 | from vllm.v1.attention.backends.mla.sparse_utils import ( |
| 19 | triton_convert_req_index_to_global_index, |
| 20 | ) |
| 21 | |
| 22 | if TYPE_CHECKING: |
| 23 | from vllm.model_executor.models.deepseek_v2 import Indexer |
| 24 | |
| 25 | |
| 26 | def _kv_scale_format_for_model(model_type: str | None) -> str: |
| 27 | if model_type is not None and model_type.startswith("glm"): |
| 28 | return "arbitrary_fp32" |
| 29 | return "pow2_fp32" |
| 30 | |
| 31 | |
| 32 | class FlashInferMLASparseSM120Impl(SparseMLACommonImpl[FlashInferMLASparseMetadata]): |
| 33 | """SM120 FlashInfer sparse-MLA implementation.""" |
| 34 | |
| 35 | is_sparse = True |
| 36 | supports_dense_mha_prefill = False |
| 37 | |
| 38 | def __init__( |
| 39 | self, |
| 40 | num_heads: int, |
| 41 | head_size: int, |
| 42 | scale: float, |
| 43 | num_kv_heads: int, |
| 44 | alibi_slopes: list[float] | None, |
| 45 | sliding_window: int | None, |
| 46 | kv_cache_dtype: str, |
| 47 | logits_soft_cap: float | None, |
| 48 | attn_type: str, |
| 49 | kv_sharing_target_layer_name: str | None, |
| 50 | indexer: "Indexer | None" = None, |
| 51 | **mla_args, |
| 52 | ) -> None: |
| 53 | if any([alibi_slopes, sliding_window, logits_soft_cap]): |
| 54 | raise NotImplementedError( |
| 55 | "FLASHINFER_MLA_SPARSE_SM120 does not support alibi_slopes / " |
| 56 | "sliding_window / logits_soft_cap" |
| 57 | ) |
| 58 | if attn_type != AttentionType.DECODER: |
| 59 | raise NotImplementedError( |
| 60 | "FLASHINFER_MLA_SPARSE_SM120 only supports decoder self-attention" |
| 61 | ) |
| 62 | |
| 63 | if kv_cache_dtype != "fp8_ds_mla": |
| 64 | raise NotImplementedError( |
| 65 | "FLASHINFER_MLA_SPARSE_SM120 requires the packed fp8_ds_mla " |
| 66 | f"KV cache layout; got kv_cache_dtype={kv_cache_dtype!r}." |
| 67 | ) |
| 68 | |
| 69 | topk_indices_buffer = mla_args.pop("topk_indices_buffer", None) |
| 70 | super().__init__( |
| 71 | num_heads, |
| 72 | head_size, |
| 73 | scale, |
| 74 | num_kv_heads, |
| 75 | alibi_slopes, |
| 76 | sliding_window, |
| 77 | kv_cache_dtype, |
| 78 | logits_soft_cap, |
| 79 | attn_type, |
| 80 | kv_sharing_target_layer_name, |
| 81 | indexer=indexer, |
| 82 | topk_indices_buffer=topk_indices_buffer, |
| 83 | **mla_args, |
| 84 | ) |
| 85 | from vllm.config import get_current_vllm_config |
| 86 | |
| 87 | vllm_config = get_current_vllm_config() |
| 88 | model_type = None |
| 89 | if vllm_config.model_config is not None: |
| 90 | model_type = getattr( |
| 91 | vllm_config.model_config.hf_text_config, "model_type", None |
| 92 | ) |
| 93 | self.kv_scale_format = _kv_scale_format_for_model(model_type) |
| 94 | |
| 95 | from vllm.utils.flashinfer import has_flashinfer_sparse_mla_sm120 |
| 96 | |
| 97 | if not has_flashinfer_sparse_mla_sm120(): |
| 98 | raise RuntimeError( |
| 99 | "FLASHINFER_MLA_SPARSE_SM120 requires FlashInfer's " |
| 100 | "sparse MLA decode API." |
| 101 | ) |
| 102 | assert self.topk_indices_buffer is not None |
| 103 | |
| 104 | self.supports_quant_query_input = False |
| 105 | self._workspace_buffer: torch.Tensor | None = None |
| 106 | |
| 107 | def forward_mqa( |
| 108 | self, |
| 109 | q: torch.Tensor | tuple[torch.Tensor, torch.Tensor], |
| 110 | kv_c_and_k_pe_cache: torch.Tensor, |
| 111 | attn_metadata: FlashInferMLASparseMetadata, |
| 112 | layer: AttentionLayer, |
| 113 | ) -> tuple[torch.Tensor, torch.Tensor | None]: |
| 114 | if isinstance(q, tuple): |
| 115 | q = torch.cat(q, dim=-1) |
| 116 | |
| 117 | num_actual_toks = q.shape[0] |
| 118 | |
| 119 | assert self.topk_indices_buffer is not None |
| 120 | topk_indices = self.topk_indices_buffer[:num_actual_toks] |
| 121 | index_group = self.index_group |
| 122 | if isinstance(index_group, HiSparseMLAIndexGroup): |
| 123 | num_decode_tokens = attn_metadata.num_decode_tokens |
| 124 | outputs = [] |
| 125 | if num_decode_tokens: |
| 126 | topk_indices_physical = cast( |
| 127 | torch.Tensor, |
| 128 | index_group.convert_logical_to_physical_topk( |
| 129 | self.index_group_index, |
| 130 | topk_indices[:num_decode_tokens], |
| 131 | attn_metadata, |
| 132 | block_stride_rows=None, |
| 133 | return_valid_counts=False, |
| 134 | ), |
| 135 | ) |
| 136 | outputs.append( |
| 137 | self._run_mqa_kernel( |
| 138 | q[:num_decode_tokens], |
| 139 | index_group.physical_kv_cache(self.index_group_index), |
| 140 | topk_indices_physical, |
| 141 | ) |
| 142 | ) |
| 143 | if num_decode_tokens < num_actual_toks: |
| 144 | cache = index_group.cache(self.index_group_index) |
| 145 | if num_decode_tokens == 0 and cache.all_context_pages_resident: |
| 146 | topk_indices_physical = cast( |
| 147 | torch.Tensor, |
| 148 | index_group.convert_logical_to_physical_topk( |
| 149 | self.index_group_index, |
| 150 | topk_indices, |
| 151 | attn_metadata, |
| 152 | block_stride_rows=None, |
| 153 | return_valid_counts=False, |
| 154 | ), |
| 155 | ) |
| 156 | prefill_cache = index_group.physical_kv_cache( |
| 157 | self.index_group_index |
| 158 | ) |
| 159 | else: |
| 160 | prefill_cache, block_table, req_ids = ( |
| 161 | index_group.stage_prefill_rows( |
| 162 | self.index_group_index, |
| 163 | kv_c_and_k_pe_cache, |
| 164 | attn_metadata, |
| 165 | ) |
| 166 | ) |
| 167 | topk_indices_physical = cast( |
| 168 | torch.Tensor, |
| 169 | triton_convert_req_index_to_global_index( |
| 170 | req_ids, |
| 171 | block_table, |
| 172 | topk_indices[num_decode_tokens:], |
| 173 | BLOCK_SIZE=attn_metadata.block_size, |
| 174 | NUM_TOPK_TOKENS=topk_indices.shape[1], |
| 175 | ), |
| 176 | ) |
| 177 | outputs.append( |
| 178 | self._run_mqa_kernel( |
| 179 | q[num_decode_tokens:], |
| 180 | prefill_cache, |
| 181 | topk_indices_physical, |
| 182 | ) |
| 183 | ) |
| 184 | output = torch.cat(outputs) if len(outputs) > 1 else outputs[0] |
| 185 | return output, None |
| 186 | |
| 187 | topk_indices_physical = cast( |
| 188 | torch.Tensor, |
| 189 | triton_convert_req_index_to_global_index( |
| 190 | attn_metadata.req_id_per_token[:num_actual_toks], |
| 191 | attn_metadata.block_table, |
| 192 | topk_indices, |
| 193 | BLOCK_SIZE=attn_metadata.block_size, |
| 194 | NUM_TOPK_TOKENS=topk_indices.shape[1], |
| 195 | ), |
| 196 | ) |
| 197 | return ( |
| 198 | self._run_mqa_kernel( |
| 199 | q, |
| 200 | kv_c_and_k_pe_cache, |
| 201 | topk_indices_physical, |
| 202 | ), |
| 203 | None, |
| 204 | ) |
| 205 | |
| 206 | def _run_mqa_kernel( |
| 207 | self, |
| 208 | q: torch.Tensor, |
| 209 | kv_cache: torch.Tensor, |
| 210 | topk_indices_physical: torch.Tensor, |
| 211 | ) -> torch.Tensor: |
| 212 | num_actual_toks = q.shape[0] |
| 213 | |
| 214 | output = q.new_empty( |
| 215 | (num_actual_toks, self.num_heads, self.kv_lora_rank), |
| 216 | dtype=q.dtype, |
| 217 | ) |
| 218 | |
| 219 | if self._workspace_buffer is None: |
| 220 | self._workspace_buffer = _get_workspace_buffer(q.device) |
| 221 | |
| 222 | from vllm.utils.flashinfer import ( |
| 223 | flashinfer_trtllm_batch_decode_with_kv_cache_mla, |
| 224 | ) |
| 225 | |
| 226 | sparse_capacity = topk_indices_physical.shape[1] |
| 227 | out = flashinfer_trtllm_batch_decode_with_kv_cache_mla( |
| 228 | query=q.unsqueeze(1), |
| 229 | kv_cache=kv_cache.view(torch.uint8).unsqueeze(1), |
| 230 | workspace_buffer=self._workspace_buffer, |
| 231 | qk_nope_head_dim=self.qk_nope_head_dim, |
| 232 | kv_lora_rank=self.kv_lora_rank, |
| 233 | qk_rope_head_dim=self.qk_rope_head_dim, |
| 234 | block_tables=topk_indices_physical.unsqueeze(1), |
| 235 | seq_lens=None, |
| 236 | max_seq_len=sparse_capacity, |
| 237 | out=output.unsqueeze(1), |
| 238 | bmm1_scale=self.scale, |
| 239 | bmm2_scale=1.0, |
| 240 | sparse_mla_top_k=sparse_capacity, |
| 241 | kv_scale_format=self.kv_scale_format, |
| 242 | ) |
| 243 | return out.squeeze(1) |