cp_utils.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from types import SimpleNamespace
  4. from typing import TYPE_CHECKING, Any, cast
  5. import torch
  6. from vllm.config import VllmConfig, get_layers_from_vllm_config
  7. from vllm.distributed import get_dcp_group
  8. from vllm.logger import init_logger
  9. from vllm.v1.attention.backend import CommonAttentionMetadata
  10. from vllm.v1.attention.backends.utils import split_decodes_prefills_and_extends
  11. if TYPE_CHECKING:
  12. from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
  13. else:
  14. AttentionLayerBase = object
  15. logger = init_logger(__name__)
  16. def check_attention_cp_compatibility(vllm_config: VllmConfig) -> None:
  17. pcp_size = vllm_config.parallel_config.prefill_context_parallel_size
  18. dcp_size = vllm_config.parallel_config.decode_context_parallel_size
  19. interleave_size = vllm_config.parallel_config.cp_kv_cache_interleave_size
  20. if pcp_size * dcp_size > 1:
  21. layer_type = cast(type[Any], AttentionLayerBase)
  22. layers = get_layers_from_vllm_config(vllm_config, layer_type)
  23. for layer in layers.values():
  24. get_attn_backend = getattr(layer, "get_attn_backend", None)
  25. if pcp_size > 1 and get_attn_backend is not None:
  26. backend = get_attn_backend()
  27. assert backend.supports_pcp(), (
  28. "PCP requires attention backend support, "
  29. f"but {backend.get_name()} does not support PCP."
  30. )
  31. layer_impl = getattr(layer, "impl", None)
  32. if layer_impl is None and hasattr(layer, "need_to_return_lse_for_decode"):
  33. # DeepSeek-V4.1 attention runs its own forward (no
  34. # AttentionImpl) and exposes the DCP contract on the layer.
  35. layer_impl = layer
  36. if layer_impl is None:
  37. continue
  38. if vllm_config.speculative_config is not None and interleave_size > 1:
  39. assert layer_impl.supports_mtp_with_cp_non_trivial_interleave_size, (
  40. "MTP with cp_kv_cache_interleave_size > 1 is not "
  41. f"supported in {layer_impl.__class__.__name__}."
  42. )
  43. if dcp_size > 1:
  44. assert layer_impl.need_to_return_lse_for_decode, (
  45. "Decode Context Parallelism (DCP) requires attention "
  46. "implementations to return the softmax LSE during decode, "
  47. f"but {layer_impl.__class__.__name__} does not. "
  48. "Try a different backend by setting "
  49. "--attention-backend or disable DCP."
  50. )
  51. def get_kv_cache_shard_count() -> int:
  52. try:
  53. dcp_world_size = get_dcp_group().world_size
  54. except AssertionError:
  55. # DCP might not be initialized in testing
  56. dcp_world_size = 1
  57. return dcp_world_size
  58. def get_dcp_dummy_context_len(
  59. dcp_world_size: int,
  60. cp_kv_cache_interleave_size: int,
  61. has_kv_cache_config: bool,
  62. create_mixed_batch: bool,
  63. is_graph_capturing: bool,
  64. uniform_decode: bool,
  65. ) -> int:
  66. if (
  67. dcp_world_size <= 1
  68. or not has_kv_cache_config
  69. or not (create_mixed_batch or (is_graph_capturing and uniform_decode))
  70. ):
  71. return 0
  72. return dcp_world_size * cp_kv_cache_interleave_size
  73. def prepare_dcp_dummy_context_metadata(
  74. *,
  75. input_batch: Any,
  76. kv_cache_config: Any,
  77. query_pos: Any,
  78. positions: torch.Tensor,
  79. query_start_loc: Any,
  80. num_reqs: int,
  81. num_tokens_unpadded: int,
  82. dcp_dummy_context_len: int,
  83. ) -> None:
  84. """Populate valid fake KV metadata for DCP CUDA graph warmup/capture."""
  85. if dcp_dummy_context_len == 0:
  86. return
  87. # DCP graph warmup may exercise context attention, so block-table entries
  88. # must point at allocated KV blocks.
  89. assert kv_cache_config is not None
  90. max_valid_block_id = kv_cache_config.num_blocks - 1
  91. assert max_valid_block_id > 0
  92. for blk_table in input_batch.block_table.block_tables:
  93. max_row_blocks = (
  94. blk_table.max_num_blocks_per_req // blk_table.blocks_per_kv_block
  95. )
  96. block_ids = [
  97. (block_idx % max_valid_block_id) + 1 for block_idx in range(max_row_blocks)
  98. ]
  99. for req_idx in range(num_reqs):
  100. blk_table.add_row(block_ids, req_idx)
  101. blk_table.commit_block_table(num_reqs)
  102. query_pos.copy_to_gpu(num_tokens_unpadded)
  103. positions[:num_tokens_unpadded] = (
  104. query_pos.gpu[:num_tokens_unpadded] + dcp_dummy_context_len
  105. )
  106. input_batch.block_table.compute_slot_mapping(
  107. num_reqs,
  108. query_start_loc.gpu[: num_reqs + 1],
  109. positions[:num_tokens_unpadded],
  110. )
  111. def should_skip_dcp_context_attention(context_kv_lens_cpu: torch.Tensor) -> bool:
  112. """Whether DCP context attention can be skipped for this batch.
  113. Must be computed from rank-invariant inputs only (the global context
  114. lengths, NOT this rank's local share from get_dcp_local_seq_lens): the
  115. non-skip path in _forward_with_dcp issues DCP collectives (query
  116. all-gather + LSE combine), so every DCP rank must take the same branch.
  117. A rank can hold zero local context tokens while other ranks still hold
  118. context for the same batch.
  119. """
  120. return int(context_kv_lens_cpu.max().item()) == 0
  121. def split_dcp_context_queries(
  122. query_start_loc: torch.Tensor,
  123. seq_lens_cpu_upper_bound: torch.Tensor | None,
  124. max_query_len: int,
  125. num_actual_tokens: int,
  126. ) -> tuple[int, int, int, int]:
  127. """Split reordered DCP context queries into decode and extend regions."""
  128. num_reqs = query_start_loc.shape[0] - 1
  129. if max_query_len <= 1:
  130. return num_reqs, 0, num_actual_tokens, 0
  131. if seq_lens_cpu_upper_bound is None:
  132. return 0, num_reqs, 0, num_actual_tokens
  133. common_attn_metadata = cast(
  134. CommonAttentionMetadata,
  135. SimpleNamespace(
  136. max_query_len=max_query_len,
  137. num_reqs=num_reqs,
  138. num_actual_tokens=num_actual_tokens,
  139. query_start_loc_cpu=query_start_loc,
  140. seq_lens_cpu_upper_bound=seq_lens_cpu_upper_bound,
  141. is_prefilling=None,
  142. ),
  143. )
  144. (
  145. num_decodes,
  146. num_extends,
  147. _num_prefills,
  148. num_decode_tokens,
  149. num_extend_tokens,
  150. _num_prefill_tokens,
  151. ) = split_decodes_prefills_and_extends(common_attn_metadata)
  152. return num_decodes, num_extends, num_decode_tokens, num_extend_tokens
  153. def should_split_fa2_dcp_context_attention(
  154. fa_version: int | None,
  155. max_query_len: int,
  156. num_reqs: int,
  157. num_decode_reqs: int,
  158. num_context_prefill_reqs: int,
  159. ) -> bool:
  160. num_prefills = num_reqs - num_decode_reqs
  161. # TODO: Remove this FA2-only DCP compatibility path once FA4 supports
  162. # the Qwen3.5 head_size=256 shape on Blackwell and can be used here.
  163. # FA2 paged-varlen context attention can fail for DCP mixed batches when
  164. # decode rows, context-bearing extend rows, and zero-context pure prefill
  165. # rows are submitted together.
  166. return (
  167. fa_version == 2
  168. and max_query_len > 1
  169. and num_prefills > 0
  170. and (num_decode_reqs > 0 or num_context_prefill_reqs < num_prefills)
  171. )
  172. def run_split_fa2_dcp_context_attention(
  173. flash_attn_varlen_func: Any,
  174. query_across_dcp: torch.Tensor,
  175. key_cache: torch.Tensor,
  176. value_cache: torch.Tensor,
  177. dcp_context_out: torch.Tensor,
  178. cu_seqlens_q: torch.Tensor,
  179. max_seqlen_q: int,
  180. dcp_context_kv_lens: torch.Tensor,
  181. max_dcp_context_kv_len: int,
  182. softmax_scale: float,
  183. alibi_slopes: torch.Tensor | None,
  184. sliding_window_size: list[int] | None,
  185. block_table: torch.Tensor,
  186. softcap: float,
  187. fa_version: int,
  188. q_descale: torch.Tensor | None,
  189. k_descale: torch.Tensor | None,
  190. v_descale: torch.Tensor | None,
  191. max_num_splits: int,
  192. num_heads: int,
  193. dcp_world_size: int,
  194. num_decode_reqs: int,
  195. num_context_prefill_reqs: int,
  196. num_decode_tokens: int,
  197. num_context_prefill_tokens: int,
  198. ) -> tuple[torch.Tensor, torch.Tensor]:
  199. dcp_context_out.zero_()
  200. context_lse = torch.full(
  201. (num_heads * dcp_world_size, query_across_dcp.shape[0]),
  202. -torch.inf,
  203. dtype=torch.float32,
  204. device=query_across_dcp.device,
  205. )
  206. if num_decode_tokens > 0:
  207. _, decode_context_lse = flash_attn_varlen_func(
  208. q=query_across_dcp[:num_decode_tokens],
  209. k=key_cache,
  210. v=value_cache,
  211. out=dcp_context_out[:num_decode_tokens],
  212. cu_seqlens_q=cu_seqlens_q[: num_decode_reqs + 1],
  213. max_seqlen_q=1,
  214. seqused_k=dcp_context_kv_lens[:num_decode_reqs],
  215. max_seqlen_k=max_dcp_context_kv_len,
  216. softmax_scale=softmax_scale,
  217. causal=False,
  218. alibi_slopes=alibi_slopes,
  219. window_size=sliding_window_size,
  220. block_table=block_table[:num_decode_reqs],
  221. softcap=softcap,
  222. return_softmax_lse=True,
  223. scheduler_metadata=None,
  224. fa_version=fa_version,
  225. q_descale=q_descale[:num_decode_reqs] if q_descale is not None else None,
  226. k_descale=k_descale[:num_decode_reqs] if k_descale is not None else None,
  227. v_descale=v_descale[:num_decode_reqs] if v_descale is not None else None,
  228. num_splits=max_num_splits,
  229. )
  230. context_lse[:, :num_decode_tokens] = decode_context_lse
  231. if num_context_prefill_tokens > 0:
  232. prefill_start = num_decode_tokens
  233. prefill_end = prefill_start + num_context_prefill_tokens
  234. prefill_query_start_loc = (
  235. cu_seqlens_q[
  236. num_decode_reqs : num_decode_reqs + num_context_prefill_reqs + 1
  237. ]
  238. - num_decode_tokens
  239. )
  240. prefill_req_slice = slice(
  241. num_decode_reqs, num_decode_reqs + num_context_prefill_reqs
  242. )
  243. _, prefill_context_lse = flash_attn_varlen_func(
  244. q=query_across_dcp[prefill_start:prefill_end],
  245. k=key_cache,
  246. v=value_cache,
  247. out=dcp_context_out[prefill_start:prefill_end],
  248. cu_seqlens_q=prefill_query_start_loc,
  249. max_seqlen_q=max_seqlen_q,
  250. seqused_k=dcp_context_kv_lens[prefill_req_slice],
  251. max_seqlen_k=max_dcp_context_kv_len,
  252. softmax_scale=softmax_scale,
  253. causal=False,
  254. alibi_slopes=alibi_slopes,
  255. window_size=sliding_window_size,
  256. block_table=block_table[prefill_req_slice],
  257. softcap=softcap,
  258. return_softmax_lse=True,
  259. scheduler_metadata=None,
  260. fa_version=fa_version,
  261. q_descale=q_descale[prefill_req_slice] if q_descale is not None else None,
  262. k_descale=k_descale[prefill_req_slice] if k_descale is not None else None,
  263. v_descale=v_descale[prefill_req_slice] if v_descale is not None else None,
  264. num_splits=max_num_splits,
  265. )
  266. context_lse[:, prefill_start:prefill_end] = prefill_context_lse
  267. return dcp_context_out, context_lse