@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 3b736e1c38
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Attention][DCP] Support DCP with query length > 1 (MTP) with FA3 (#25049)

Signed-off-by: Ming Yang <minos.future@gmail.com>

Ming Yang committed Oct 9, 2025 at 08:06 UTC 3b736e1c3869a593fed5b2eb58abe0186081d7cd
8 files changed +45 -13
cmake/external_projects/vllm_flash_attn.cmake
+1 -1
@@ -38,7 +38,7 @@ else()
38 FetchContent_Declare(
39 vllm-flash-attn
40 GIT_REPOSITORY https://github.com/vllm-project/flash-attention.git
41 - GIT_TAG 4695e6bed5366c41e28c06cd86170166e4f43d00
41 + GIT_TAG 8f468e7da54a8e2f98abfa7c38636aac91c0cba1
42 GIT_PROGRESS TRUE
43 # Don't share the vllm-flash-attn build between build types
44 BINARY_DIR ${CMAKE_BINARY_DIR}/vllm-flash-attn
vllm/v1/attention/backends/mla/common.py
+14 -2
@@ -370,6 +370,7 @@ class CudnnPrefillMetadata(MLACommonPrefillMetadata):
370 class MLACommonDecodeMetadata:
371 block_table: torch.Tensor
372 seq_lens: torch.Tensor
373 + dcp_tot_seq_lens: Optional[torch.Tensor]
374
375
376 D = TypeVar("D", bound=MLACommonDecodeMetadata)
@@ -682,10 +683,12 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
683 query_start_loc_cpu: torch.Tensor,
684 query_start_loc_device: torch.Tensor,
685 num_decode_tokens: int,
686 + dcp_tot_seq_lens_device: Optional[torch.Tensor],
687 ) -> MLACommonDecodeMetadata:
688 return MLACommonDecodeMetadata(
689 block_table=block_table_tensor,
690 seq_lens=seq_lens_device,
691 + dcp_tot_seq_lens=dcp_tot_seq_lens_device,
692 )
693
694 def build_for_cudagraph_capture(
@@ -727,6 +730,7 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
730 query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu
731 seq_lens = common_attn_metadata.seq_lens
732 seq_lens_cpu = common_attn_metadata.seq_lens_cpu
733 + dcp_local_seq_lens = common_attn_metadata.dcp_local_seq_lens
734
735 query_seq_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]
736
@@ -742,7 +746,10 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
746
747 # Note(hc): update seq_lens of decode reqs under DCP.
748 if self.dcp_world_size > 1:
745 - seq_lens[:num_decodes] = seq_lens[:num_decodes] // self.dcp_world_size + (
749 + assert dcp_local_seq_lens is not None
750 + dcp_local_seq_lens[:num_decodes] = seq_lens[
751 + :num_decodes
752 + ] // self.dcp_world_size + (
753 self.dcp_rank <= (seq_lens[:num_decodes] - 1) % self.dcp_world_size
754 )
755
@@ -899,10 +906,15 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
906 decode_metadata = self._build_decode(
907 block_table_tensor=block_table_tensor[:num_decodes, ...],
908 seq_lens_cpu=seq_lens_cpu[:num_decodes],
902 - seq_lens_device=seq_lens[:num_decodes],
909 + seq_lens_device=dcp_local_seq_lens[:num_decodes]
910 + if self.dcp_world_size > 1 and dcp_local_seq_lens is not None
911 + else seq_lens[:num_decodes],
912 query_start_loc_cpu=query_start_loc_cpu[: num_decodes + 1],
913 query_start_loc_device=query_start_loc[: num_decodes + 1],
914 num_decode_tokens=num_decode_tokens,
915 + dcp_tot_seq_lens_device=seq_lens[:num_decodes]
916 + if self.dcp_world_size > 1
917 + else None,
918 )
919
920 attn_metadata = self.metadata_cls(
vllm/v1/attention/backends/mla/flashattn_mla.py
+7 -9
@@ -17,7 +17,6 @@ from vllm.attention.utils.fa_utils import (
17 get_flash_attn_version,
18 )
19 from vllm.config import VllmConfig
20 -from vllm.distributed.parallel_state import get_dcp_group
20 from vllm.logger import init_logger
21 from vllm.v1.attention.backends.mla.common import (
22 MLACommonBackend,
@@ -107,12 +106,6 @@ class FlashAttnMLAMetadataBuilder(MLACommonMetadataBuilder[FlashAttnMLAMetadata]
106 # pre-allocated during capture.
107 self.max_num_splits = envs.VLLM_FLASH_ATTN_MAX_NUM_SPLITS_FOR_CUDA_GRAPH
108
110 - # TODO(lucas): Until we add support for the DCP custom masking we need
111 - # to restrict decodes to q_len == 1 when DCP is enabled.
112 - self.reorder_batch_threshold = (
113 - 1 if get_dcp_group().world_size > 1 else self.reorder_batch_threshold
114 - )
115 -
109 def _schedule_decode(
110 self, num_reqs, cu_query_lens, max_query_len, seqlens, max_seq_len, causal
111 ):
@@ -121,7 +114,7 @@ class FlashAttnMLAMetadataBuilder(MLACommonMetadataBuilder[FlashAttnMLAMetadata]
114 batch_size=num_reqs,
115 max_seqlen_q=max_query_len,
116 max_seqlen_k=max_seq_len,
124 - num_heads_q=self.num_heads,
117 + num_heads_q=self.num_heads * self.dcp_world_size,
118 num_heads_kv=1,
119 headdim=self.mla_dims.qk_rope_head_dim,
120 cache_seqlens=seqlens,
@@ -142,10 +135,11 @@ class FlashAttnMLAMetadataBuilder(MLACommonMetadataBuilder[FlashAttnMLAMetadata]
135 query_start_loc_cpu: torch.Tensor,
136 query_start_loc_device: torch.Tensor,
137 num_decode_tokens: int,
138 + dcp_tot_seq_lens_device: Optional[torch.Tensor],
139 ) -> FlashAttnMLADecodeMetadata:
140 query_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]
141 max_query_len = query_lens_cpu.max().item()
148 - max_seq_len = seq_lens_cpu.max().item()
142 + max_seq_len = seq_lens_device.max().item()
143
144 scheduler_metadata = self._schedule_decode(
145 num_reqs=seq_lens_cpu.numel(),
@@ -188,6 +182,7 @@ class FlashAttnMLAMetadataBuilder(MLACommonMetadataBuilder[FlashAttnMLAMetadata]
182 max_seq_len=max_seq_len,
183 scheduler_metadata=scheduler_metadata,
184 max_num_splits=max_num_splits,
185 + dcp_tot_seq_lens=dcp_tot_seq_lens_device,
186 )
187
188
@@ -289,6 +284,9 @@ class FlashAttnMLAImpl(MLACommonImpl[FlashAttnMLAMetadata]):
284 fa_version=3, # only version 3 is supported
285 scheduler_metadata=attn_metadata.decode.scheduler_metadata,
286 num_splits=attn_metadata.decode.max_num_splits,
287 + cp_world_size=self.dcp_world_size,
288 + cp_rank=self.dcp_rank,
289 + cp_tot_seqused_k=attn_metadata.decode.dcp_tot_seq_lens,
290 )
291
292 if self.need_to_return_lse_for_decode:
vllm/v1/attention/backends/mla/flashmla.py
+2
@@ -106,6 +106,7 @@ class FlashMLAMetadataBuilder(MLACommonMetadataBuilder[FlashMLAMetadata]):
106 query_start_loc_cpu: torch.Tensor,
107 query_start_loc_device: torch.Tensor,
108 num_decode_tokens: int,
109 + dcp_tot_seq_lens_device: Optional[torch.Tensor],
110 ) -> FlashMLADecodeMetadata:
111 tile_scheduler_metadata, num_splits = get_mla_metadata(
112 seq_lens_device,
@@ -146,6 +147,7 @@ class FlashMLAMetadataBuilder(MLACommonMetadataBuilder[FlashMLAMetadata]):
147 seq_lens=seq_lens_device,
148 tile_scheduler_metadata=tile_scheduler_metadata,
149 num_splits=num_splits,
150 + dcp_tot_seq_lens=dcp_tot_seq_lens_device,
151 )
152
153
vllm/v1/attention/backends/mla/rocm_aiter_mla.py
+2
@@ -116,6 +116,7 @@ class AiterMLAMetadataBuilder(MLACommonMetadataBuilder[AiterMLAMetadata]):
116 query_start_loc_cpu: torch.Tensor,
117 query_start_loc_device: torch.Tensor,
118 num_decode_tokens: int,
119 + dcp_tot_seq_lens_device: Optional[torch.Tensor],
120 ) -> AiterMLADecodeMetadata:
121 page_size = self.kv_cache_spec.block_size
122 block_table_bounds = (seq_lens_device + page_size - 1) // page_size
@@ -174,6 +175,7 @@ class AiterMLAMetadataBuilder(MLACommonMetadataBuilder[AiterMLAMetadata]):
175 paged_kv_indices=paged_kv_indices,
176 paged_kv_last_page_len=paged_kv_last_page_len,
177 qo_indptr=qo_indptr,
178 + dcp_tot_seq_lens=dcp_tot_seq_lens_device,
179 )
180
181 return attn_metadata
vllm/v1/attention/backends/utils.py
+3
@@ -93,6 +93,9 @@ class CommonAttentionMetadata:
93 # Needed by CrossAttentionBuilder
94 encoder_seq_lens: Optional[np.ndarray] = None
95
96 + dcp_local_seq_lens: Optional[torch.Tensor] = None
97 + """Sequence lengths of the local rank in decode context parallelism world"""
98 +
99
100 def slice_query_start_locs(
101 query_start_loc: torch.Tensor,
vllm/v1/spec_decode/eagle.py
+2
@@ -597,6 +597,7 @@ class EagleProposer:
597 block_table_tensor=common_attn_metadata.block_table_tensor,
598 slot_mapping=common_attn_metadata.slot_mapping[token_indices],
599 causal=True,
600 + dcp_local_seq_lens=common_attn_metadata.dcp_local_seq_lens,
601 )
602
603 token_indices_to_sample = (
@@ -868,6 +869,7 @@ class EagleProposer:
869 block_table_tensor=common_attn_metadata.block_table_tensor,
870 slot_mapping=common_attn_metadata.slot_mapping[token_indices],
871 causal=True,
872 + dcp_local_seq_lens=common_attn_metadata.dcp_local_seq_lens,
873 )
874
875 return spec_common_attn_metadata, token_indices
vllm/v1/worker/gpu_model_runner.py
+14 -1
@@ -398,6 +398,10 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
398 self.max_num_reqs + 1, dtype=torch.int32
399 )
400 self.seq_lens = self._make_buffer(self.max_num_reqs, dtype=torch.int32)
401 + if self.dcp_world_size > 1:
402 + self.dcp_local_seq_lens = self._make_buffer(
403 + self.max_num_reqs, dtype=torch.int32
404 + )
405 # Because inputs_embeds may be bfloat16 and we don't need a numpy
406 # version of this tensor, avoid a RuntimeError by not creating a
407 # numpy buffer.
@@ -581,7 +585,10 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
585 # NOTE(lucas): currently no backend supports the custom masking
586 # required for DCP with q_len > 1, so we assert here. Remove this
587 # assert once the custom mask is support is added to FA3.
584 - if self.dcp_world_size > 1:
588 + if (
589 + self.dcp_world_size > 1
590 + and envs.VLLM_ATTENTION_BACKEND != "FLASH_ATTN_MLA"
591 + ):
592 assert self.reorder_batch_threshold == 1, (
593 "DCP not support reorder_batch_threshold > 1 now."
594 )
@@ -1335,6 +1342,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1342 num_logits_indices=logits_indices.size(0),
1343 causal=True,
1344 encoder_seq_lens=encoder_seq_lens,
1345 + dcp_local_seq_lens=self.dcp_local_seq_lens.gpu[:num_reqs]
1346 + if self.dcp_world_size > 1
1347 + else None,
1348 )
1349
1350 if self.speculative_config and spec_decode_common_attn_metadata is None:
@@ -3310,6 +3320,9 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3320 kv_cache_group_id
3321 ].slot_mapping.gpu[:num_tokens],
3322 causal=True,
3323 + dcp_local_seq_lens=self.dcp_local_seq_lens.gpu[:num_reqs]
3324 + if self.dcp_world_size > 1
3325 + else None,
3326 )
3327 for attn_group in self.attn_groups[kv_cache_group_id]:
3328 if ubatch_slices is not None: