| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- # SPDX-License-Identifier: Apache-2.0
- # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
- """DeepSeek-V4.1 FlashMLA sparse backend, metadata, and metadata builders."""
- from dataclasses import dataclass
- from typing import Any, ClassVar
- import torch
- from vllm.config import VllmConfig
- from vllm.config.cache import CacheDType
- from vllm.platforms import current_platform
- from vllm.platforms.interface import DeviceCapability
- from vllm.v1.attention.backend import (
- AttentionBackend,
- AttentionCGSupport,
- AttentionMetadata,
- AttentionMetadataBuilder,
- CommonAttentionMetadata,
- MultipleOf,
- )
- from vllm.v1.attention.backends.mla.compressor_utils import get_compressed_slot_mapping
- from vllm.v1.attention.backends.utils import get_dcp_local_seq_lens
- from vllm.v1.attention.backends.mla.sparse_swa import (
- _LAYER_TYPE_C1A,
- _LAYER_TYPE_C2A,
- _LAYER_TYPE_SWAONLY,
- DeepseekSparseSWAMetadataBuilder,
- )
- from vllm.logger import init_logger
- from vllm.v1.kv_cache_interface import AttentionSpec
- logger = init_logger(__name__)
- # v4.1 per-layer compress ratios: 0 = pure sliding window, 1 = full-length
- # compressed cache, 2 = ratio-2 compressed cache. Ratio-1 and ratio-2 layers
- # both attend over indexer topk indices into a shared compressed cache but
- # differ in compressed page block size (block_size // ratio), so each needs its
- # own FlashMLA tile-scheduler plan.
- _V41_LAYER_TYPES: dict[int, str] = {
- 0: _LAYER_TYPE_SWAONLY,
- 1: _LAYER_TYPE_C1A,
- 2: _LAYER_TYPE_C2A,
- }
- def deepseek_v41_layer_type(compress_ratio: int) -> str:
- layer_type = _V41_LAYER_TYPES.get(compress_ratio)
- if layer_type is None:
- raise ValueError(
- f"Unsupported DeepSeek V4.1 compress_ratio={compress_ratio}; "
- "expected 0, 1, or 2."
- )
- return layer_type
- class DeepseekV41SparseSWAMetadataBuilder(DeepseekSparseSWAMetadataBuilder):
- """SWA metadata builder base for v4.1.
- The shared builder classifies decode layer types by the v4.0 ratios
- (1 = SWA-only, 4, 128). v4.1 uses 0 / 1 / 2, so recompute the set of
- tile-scheduler plans from the v4.1 topology.
- """
- supports_non_causal_multi_token_dcp: ClassVar[bool] = True
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- compress_ratios = getattr(
- self.vllm_config.model_config.hf_config, "compress_ratios", None
- ) or [0]
- self._layer_types = {
- deepseek_v41_layer_type(int(ratio)) for ratio in compress_ratios
- }
- class DeepseekV4SparseMLABackend(AttentionBackend):
- """DeepSeek-V4.1 sparse-MLA backend base.
- Subclasses ``AttentionBackend`` directly (not the V3.2
- ``FlashMLASparseBackend``): DeepSeek-V4.1 runs its own attention layer
- (``DeepseekV4Attention``), so it does not reuse the V3.2 builder or impl, and
- only needs to declare its own metadata builder, KV-cache layout, and the
- sparse-MLA capability flags.
- """
- supported_dtypes: ClassVar[list[torch.dtype]] = [torch.bfloat16]
- supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [
- "auto",
- "fp8_ds_mla",
- "fp8", # alias for fp8_ds_mla
- ]
- @staticmethod
- def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
- return [64 if current_platform.is_device_capability_family(90) else 128]
- @staticmethod
- def get_builder_cls() -> type["DeepseekV4SparseMLAMetadataBuilder"]:
- return DeepseekV4SparseMLAMetadataBuilder
- @staticmethod
- def get_impl_cls() -> type[Any]:
- # DeepSeek-V4.1 runs its attention through ``DeepseekV4Attention.forward``,
- # not the generic ``Attention``/``MLAAttention`` layer, so the backend's
- # impl class is never instantiated.
- raise NotImplementedError(
- "DeepseekV4SparseMLABackend has no separate impl class; DeepSeek-V4.1 "
- "attention runs through DeepseekV4Attention."
- )
- @classmethod
- def get_supported_head_sizes(cls) -> list[int]:
- # DeepSeek V4 layout: 448 NoPE + 64 RoPE = 512.
- return [512]
- @classmethod
- def is_mla(cls) -> bool:
- return True
- @classmethod
- def is_sparse(cls) -> bool:
- return True
- @classmethod
- def supports_sink(cls) -> bool:
- return True
- @classmethod
- def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
- return capability.major in [9, 10]
- @dataclass
- class DeepseekV4FlashMLAMetadata(AttentionMetadata):
- num_reqs: int
- max_query_len: int
- max_seq_len: int
- num_actual_tokens: int # Number of tokens excluding padding.
- query_start_loc: torch.Tensor
- slot_mapping: torch.Tensor
- block_table: torch.Tensor
- req_id_per_token: torch.Tensor
- block_size: int
- topk_tokens: int
- # DCP: the compressed cache is sharded over STATES with this interleave;
- # block_size stays the per-rank kernel page (tokens). seq_lens are the
- # global lengths (tokens); dcp_local_seq_lens this rank's share of the
- # compressed length (states), None at DCP1.
- cp_kv_cache_interleave_size: int = 1
- seq_lens: torch.Tensor | None = None
- dcp_local_seq_lens: torch.Tensor | None = None
- class DeepseekV4SparseMLAMetadataBuilder(
- AttentionMetadataBuilder[DeepseekV4FlashMLAMetadata]
- ):
- _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.UNIFORM_BATCH
- # The SM120 backend gathers queries / combines per token, so DSpark's
- # non-causal multi-token decode is supported under DCP (the draft layers
- # themselves are SWA-only and bypass the DCP path).
- supports_non_causal_multi_token_dcp: ClassVar[bool] = True
- def __init__(
- self,
- kv_cache_spec: AttentionSpec,
- layer_names: list[str],
- vllm_config: VllmConfig,
- device: torch.device,
- ) -> None:
- super().__init__(kv_cache_spec, layer_names, vllm_config, device)
- self.model_config = vllm_config.model_config
- # DCP geometry (self.dcp_world_size / dcp_rank come from the base
- # builder: process DCP for this sharded group). The V4.1 SM120 backend
- # gathers queries and combines LSEs per token, so multi-token (DSpark)
- # decodes stay decodes under DCP.
- self.cp_kv_cache_interleave_size = (
- vllm_config.parallel_config.cp_kv_cache_interleave_size
- )
- if self.dcp_world_size > 1 and self.cp_kv_cache_interleave_size != 1:
- raise NotImplementedError(
- "DeepSeek-V4.1 DCP supports cp_kv_cache_interleave_size=1 only"
- )
- # Classify single-token queries (plus num_speculative_tokens via
- # supports_spec_as_decode=True) as decodes; longer queries go to prefill.
- self._init_reorder_batch_threshold(
- 1, supports_spec_as_decode=True, supports_dcp_with_varlen=True
- )
- self.topk_tokens = self.model_config.hf_config.index_topk
- max_num_batched_tokens = vllm_config.scheduler_config.max_num_batched_tokens
- self.req_id_per_token_buffer = torch.empty(
- (max_num_batched_tokens,), dtype=torch.int32, device=device
- )
- assert isinstance(self.kv_cache_spec.tokens_per_state, int)
- self.compress_ratio = self.kv_cache_spec.tokens_per_state
- # Only kv-source layers own a compressed cache, and v4.1 compresses at
- # ratio 1 or 2.
- if self.compress_ratio not in (1, 2):
- raise ValueError(
- "DeepSeek V4.1 compressed-KV caches use compress_ratio 1 or 2; "
- f"got {self.compress_ratio}."
- )
- if self.dcp_world_size > 1:
- # Per-rank page geometry the state-space sharding relies on: the
- # builder must see the kernel page (block_size tokens = num_states
- # states per rank), never the manager block (x dcp).
- assert self.kv_cache_spec.block_size % self.compress_ratio == 0
- logger.info_once(
- "DeepSeek-V4.1 compressed KV under DCP%d: ratio %d, %d tokens "
- "(%d states) per page per rank, interleave %d",
- self.dcp_world_size,
- self.compress_ratio,
- self.kv_cache_spec.block_size,
- int(self.kv_cache_spec.num_states),
- self.cp_kv_cache_interleave_size,
- )
- # Pre-allocate compressed slot mapping buffer for CUDA graph address
- # stability when compress_ratio > 1.
- if self.compress_ratio > 1:
- self.compressed_slot_mapping_buffer = torch.empty(
- max_num_batched_tokens, dtype=torch.int64, device=device
- )
- def build(
- self,
- common_prefix_len: int,
- common_attn_metadata: CommonAttentionMetadata,
- fast_build: bool = False,
- ) -> DeepseekV4FlashMLAMetadata:
- cm = common_attn_metadata
- req_id_per_token = cm.token_to_req_indices(self.req_id_per_token_buffer)
- slot_mapping = cm.slot_mapping
- if self.compress_ratio > 1:
- slot_mapping = get_compressed_slot_mapping(
- cm.num_actual_tokens,
- cm.query_start_loc,
- cm.seq_lens,
- cm.block_table_tensor.clamp_(min=0),
- int(self.kv_cache_spec.num_states),
- self.compress_ratio,
- out=self.compressed_slot_mapping_buffer,
- dcp_world_size=self.dcp_world_size,
- dcp_rank=self.dcp_rank,
- cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
- )
- dcp_local_seq_lens = None
- if self.dcp_world_size > 1:
- dcp_local_seq_lens = get_dcp_local_seq_lens(
- cm.seq_lens // self.compress_ratio,
- self.dcp_world_size,
- self.dcp_rank,
- self.cp_kv_cache_interleave_size,
- )
- return DeepseekV4FlashMLAMetadata(
- num_reqs=cm.num_reqs,
- max_query_len=cm.max_query_len,
- max_seq_len=cm.max_seq_len,
- num_actual_tokens=cm.num_actual_tokens,
- query_start_loc=cm.query_start_loc,
- slot_mapping=slot_mapping,
- block_table=cm.block_table_tensor,
- req_id_per_token=req_id_per_token,
- block_size=self.kv_cache_spec.block_size,
- topk_tokens=self.topk_tokens,
- cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
- seq_lens=cm.seq_lens,
- dcp_local_seq_lens=dcp_local_seq_lens,
- )
- class DeepseekV4FlashMLAMetadataBuilder(DeepseekV4SparseMLAMetadataBuilder):
- _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
- class DeepseekV4FlashMLABackend(DeepseekV4SparseMLABackend):
- @staticmethod
- def get_name() -> str:
- return "FLASHMLA_SPARSE_DSV41"
- @staticmethod
- def get_builder_cls() -> type[DeepseekV4FlashMLAMetadataBuilder]:
- return DeepseekV4FlashMLAMetadataBuilder
|