sparse_mla.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. """DeepSeek-V4.1 FlashMLA sparse backend, metadata, and metadata builders."""
  4. from dataclasses import dataclass
  5. from typing import Any, ClassVar
  6. import torch
  7. from vllm.config import VllmConfig
  8. from vllm.config.cache import CacheDType
  9. from vllm.platforms import current_platform
  10. from vllm.platforms.interface import DeviceCapability
  11. from vllm.v1.attention.backend import (
  12. AttentionBackend,
  13. AttentionCGSupport,
  14. AttentionMetadata,
  15. AttentionMetadataBuilder,
  16. CommonAttentionMetadata,
  17. MultipleOf,
  18. )
  19. from vllm.v1.attention.backends.mla.compressor_utils import get_compressed_slot_mapping
  20. from vllm.v1.attention.backends.mla.sparse_swa import (
  21. _LAYER_TYPE_C1A,
  22. _LAYER_TYPE_C2A,
  23. _LAYER_TYPE_SWAONLY,
  24. DeepseekSparseSWAMetadataBuilder,
  25. )
  26. from vllm.v1.kv_cache_interface import AttentionSpec
  27. # v4.1 per-layer compress ratios: 0 = pure sliding window, 1 = full-length
  28. # compressed cache, 2 = ratio-2 compressed cache. Ratio-1 and ratio-2 layers
  29. # both attend over indexer topk indices into a shared compressed cache but
  30. # differ in compressed page block size (block_size // ratio), so each needs its
  31. # own FlashMLA tile-scheduler plan.
  32. _V41_LAYER_TYPES: dict[int, str] = {
  33. 0: _LAYER_TYPE_SWAONLY,
  34. 1: _LAYER_TYPE_C1A,
  35. 2: _LAYER_TYPE_C2A,
  36. }
  37. def deepseek_v41_layer_type(compress_ratio: int) -> str:
  38. layer_type = _V41_LAYER_TYPES.get(compress_ratio)
  39. if layer_type is None:
  40. raise ValueError(
  41. f"Unsupported DeepSeek V4.1 compress_ratio={compress_ratio}; "
  42. "expected 0, 1, or 2."
  43. )
  44. return layer_type
  45. class DeepseekV41SparseSWAMetadataBuilder(DeepseekSparseSWAMetadataBuilder):
  46. """SWA metadata builder base for v4.1.
  47. The shared builder classifies decode layer types by the v4.0 ratios
  48. (1 = SWA-only, 4, 128). v4.1 uses 0 / 1 / 2, so recompute the set of
  49. tile-scheduler plans from the v4.1 topology.
  50. """
  51. def __init__(self, *args, **kwargs):
  52. super().__init__(*args, **kwargs)
  53. compress_ratios = getattr(
  54. self.vllm_config.model_config.hf_config, "compress_ratios", None
  55. ) or [0]
  56. self._layer_types = {
  57. deepseek_v41_layer_type(int(ratio)) for ratio in compress_ratios
  58. }
  59. class DeepseekV4SparseMLABackend(AttentionBackend):
  60. """DeepSeek-V4.1 sparse-MLA backend base.
  61. Subclasses ``AttentionBackend`` directly (not the V3.2
  62. ``FlashMLASparseBackend``): DeepSeek-V4.1 runs its own attention layer
  63. (``DeepseekV4Attention``), so it does not reuse the V3.2 builder or impl, and
  64. only needs to declare its own metadata builder, KV-cache layout, and the
  65. sparse-MLA capability flags.
  66. """
  67. supported_dtypes: ClassVar[list[torch.dtype]] = [torch.bfloat16]
  68. supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [
  69. "auto",
  70. "fp8_ds_mla",
  71. "fp8", # alias for fp8_ds_mla
  72. ]
  73. @staticmethod
  74. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  75. return [64 if current_platform.is_device_capability_family(90) else 128]
  76. @staticmethod
  77. def get_builder_cls() -> type["DeepseekV4SparseMLAMetadataBuilder"]:
  78. return DeepseekV4SparseMLAMetadataBuilder
  79. @staticmethod
  80. def get_impl_cls() -> type[Any]:
  81. # DeepSeek-V4.1 runs its attention through ``DeepseekV4Attention.forward``,
  82. # not the generic ``Attention``/``MLAAttention`` layer, so the backend's
  83. # impl class is never instantiated.
  84. raise NotImplementedError(
  85. "DeepseekV4SparseMLABackend has no separate impl class; DeepSeek-V4.1 "
  86. "attention runs through DeepseekV4Attention."
  87. )
  88. @classmethod
  89. def get_supported_head_sizes(cls) -> list[int]:
  90. # DeepSeek V4 layout: 448 NoPE + 64 RoPE = 512.
  91. return [512]
  92. @classmethod
  93. def is_mla(cls) -> bool:
  94. return True
  95. @classmethod
  96. def is_sparse(cls) -> bool:
  97. return True
  98. @classmethod
  99. def supports_sink(cls) -> bool:
  100. return True
  101. @classmethod
  102. def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
  103. return capability.major in [9, 10]
  104. @dataclass
  105. class DeepseekV4FlashMLAMetadata(AttentionMetadata):
  106. num_reqs: int
  107. max_query_len: int
  108. max_seq_len: int
  109. num_actual_tokens: int # Number of tokens excluding padding.
  110. query_start_loc: torch.Tensor
  111. slot_mapping: torch.Tensor
  112. block_table: torch.Tensor
  113. req_id_per_token: torch.Tensor
  114. block_size: int
  115. topk_tokens: int
  116. class DeepseekV4SparseMLAMetadataBuilder(
  117. AttentionMetadataBuilder[DeepseekV4FlashMLAMetadata]
  118. ):
  119. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.UNIFORM_BATCH
  120. def __init__(
  121. self,
  122. kv_cache_spec: AttentionSpec,
  123. layer_names: list[str],
  124. vllm_config: VllmConfig,
  125. device: torch.device,
  126. ) -> None:
  127. super().__init__(kv_cache_spec, layer_names, vllm_config, device)
  128. self.model_config = vllm_config.model_config
  129. # Classify single-token queries (plus num_speculative_tokens via
  130. # supports_spec_as_decode=True) as decodes; longer queries go to prefill.
  131. self._init_reorder_batch_threshold(1, supports_spec_as_decode=True)
  132. self.topk_tokens = self.model_config.hf_config.index_topk
  133. max_num_batched_tokens = vllm_config.scheduler_config.max_num_batched_tokens
  134. self.req_id_per_token_buffer = torch.empty(
  135. (max_num_batched_tokens,), dtype=torch.int32, device=device
  136. )
  137. assert isinstance(self.kv_cache_spec.tokens_per_state, int)
  138. self.compress_ratio = self.kv_cache_spec.tokens_per_state
  139. # Only kv-source layers own a compressed cache, and v4.1 compresses at
  140. # ratio 1 or 2.
  141. if self.compress_ratio not in (1, 2):
  142. raise ValueError(
  143. "DeepSeek V4.1 compressed-KV caches use compress_ratio 1 or 2; "
  144. f"got {self.compress_ratio}."
  145. )
  146. # Pre-allocate compressed slot mapping buffer for CUDA graph address
  147. # stability when compress_ratio > 1.
  148. if self.compress_ratio > 1:
  149. self.compressed_slot_mapping_buffer = torch.empty(
  150. max_num_batched_tokens, dtype=torch.int64, device=device
  151. )
  152. def build(
  153. self,
  154. common_prefix_len: int,
  155. common_attn_metadata: CommonAttentionMetadata,
  156. fast_build: bool = False,
  157. ) -> DeepseekV4FlashMLAMetadata:
  158. cm = common_attn_metadata
  159. req_id_per_token = cm.token_to_req_indices(self.req_id_per_token_buffer)
  160. slot_mapping = cm.slot_mapping
  161. if self.compress_ratio > 1:
  162. slot_mapping = get_compressed_slot_mapping(
  163. cm.num_actual_tokens,
  164. cm.query_start_loc,
  165. cm.seq_lens,
  166. cm.block_table_tensor.clamp_(min=0),
  167. int(self.kv_cache_spec.num_states),
  168. self.compress_ratio,
  169. out=self.compressed_slot_mapping_buffer,
  170. )
  171. return DeepseekV4FlashMLAMetadata(
  172. num_reqs=cm.num_reqs,
  173. max_query_len=cm.max_query_len,
  174. max_seq_len=cm.max_seq_len,
  175. num_actual_tokens=cm.num_actual_tokens,
  176. query_start_loc=cm.query_start_loc,
  177. slot_mapping=slot_mapping,
  178. block_table=cm.block_table_tensor,
  179. req_id_per_token=req_id_per_token,
  180. block_size=self.kv_cache_spec.block_size,
  181. topk_tokens=self.topk_tokens,
  182. )
  183. class DeepseekV4FlashMLAMetadataBuilder(DeepseekV4SparseMLAMetadataBuilder):
  184. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
  185. class DeepseekV4FlashMLABackend(DeepseekV4SparseMLABackend):
  186. @staticmethod
  187. def get_name() -> str:
  188. return "FLASHMLA_SPARSE_DSV41"
  189. @staticmethod
  190. def get_builder_cls() -> type[DeepseekV4FlashMLAMetadataBuilder]:
  191. return DeepseekV4FlashMLAMetadataBuilder