attn_utils.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from collections.abc import Mapping, Sequence
  4. from contextlib import AbstractContextManager, nullcontext
  5. from dataclasses import dataclass
  6. from typing import Any, cast
  7. import torch
  8. from vllm.config import VllmConfig, get_layers_from_vllm_config
  9. from vllm.model_executor.layers.attention import Attention
  10. from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
  11. from vllm.multimodal.inputs import MultiModalFeatureSpec
  12. from vllm.v1.attention.backend import (
  13. AttentionCGSupport,
  14. CommonAttentionMetadata,
  15. )
  16. from vllm.v1.kv_cache_interface import (
  17. AttentionSpec,
  18. KVCacheConfig,
  19. KVCacheSpec,
  20. UniformTypeKVCacheSpecs,
  21. is_dcp_replicated_spec,
  22. )
  23. from vllm.v1.worker.gpu.model_states.interface import ModelSpecificAttnMetadata
  24. from vllm.v1.worker.utils import (
  25. AttentionGroup,
  26. add_kv_sharing_layers_to_kv_cache_groups,
  27. allocate_kv_cache,
  28. bind_kv_cache,
  29. prepare_kernel_block_sizes,
  30. )
  31. @dataclass(frozen=True)
  32. class AttentionCGSupportInfo:
  33. min_cg_support: AttentionCGSupport = AttentionCGSupport.ALWAYS
  34. min_cg_attn_backend: str | None = None
  35. def narrow(
  36. self, support: AttentionCGSupport, backend: str | None
  37. ) -> "AttentionCGSupportInfo":
  38. """Return an info tightened by ``support`` if it is more restrictive.
  39. Lets attention groups built outside ``init_attn_backend`` (e.g.
  40. encoder-only layers) contribute to the runner's cudagraph decision.
  41. """
  42. if support.value < self.min_cg_support.value:
  43. return AttentionCGSupportInfo(support, backend)
  44. return self
  45. def get_kv_cache_spec(vllm_config: VllmConfig) -> dict[str, KVCacheSpec]:
  46. kv_cache_spec: dict[str, KVCacheSpec] = {}
  47. layer_type = cast(type[Any], AttentionLayerBase)
  48. attn_layers = get_layers_from_vllm_config(vllm_config, layer_type)
  49. for layer_name, attn_module in attn_layers.items():
  50. if getattr(attn_module, "kv_sharing_target_layer_name", None):
  51. # This layer will use KV cache of the sharing target layer.
  52. continue
  53. # Skip modules that don't need KV cache (eg encoder-only attention)
  54. if spec := attn_module.get_kv_cache_spec(vllm_config):
  55. if isinstance(spec, AttentionSpec):
  56. spec = attn_module.get_attn_backend().customize_spec(spec)
  57. kv_cache_spec[layer_name] = spec
  58. return kv_cache_spec
  59. def get_shared_kv_cache_layers(vllm_config: VllmConfig):
  60. attn_layers = get_layers_from_vllm_config(vllm_config, Attention)
  61. return {
  62. layer_name: kv_tgt_layer
  63. for layer_name, attn_module in attn_layers.items()
  64. if (kv_tgt_layer := attn_module.kv_sharing_target_layer_name)
  65. }
  66. def init_attn_backend(
  67. kv_cache_config: KVCacheConfig,
  68. vllm_config: VllmConfig,
  69. device: torch.device,
  70. active_layer_names: set[str] | None = None,
  71. ) -> tuple[list[list[AttentionGroup]], AttentionCGSupportInfo, list[int]]:
  72. # Phase 1: discover attention groups for each kv cache group.
  73. attn_groups: list[list[AttentionGroup]] = []
  74. # Add KV-sharing layers to their target's kv cache group so they are
  75. # discovered alongside the target layer in Phase 1 below.
  76. add_kv_sharing_layers_to_kv_cache_groups(
  77. get_shared_kv_cache_layers(vllm_config), kv_cache_config.kv_cache_groups
  78. )
  79. # Phase 1: discover attention groups for each kv cache group.
  80. for kv_cache_group_id, kv_cache_group_spec in enumerate(
  81. kv_cache_config.kv_cache_groups
  82. ):
  83. layer_names = kv_cache_group_spec.layer_names
  84. if active_layer_names is not None:
  85. layer_names = list(active_layer_names.intersection(layer_names))
  86. layer_type = cast(type[Any], AttentionLayerBase)
  87. attn_layers = get_layers_from_vllm_config(vllm_config, layer_type, layer_names)
  88. group_map: dict[tuple[tuple[str, str], KVCacheSpec, int], AttentionGroup] = {}
  89. group_order: list[tuple[tuple[str, str], KVCacheSpec, int]] = []
  90. for layer_name in layer_names:
  91. attn_backend = attn_layers[layer_name].get_attn_backend()
  92. layer_kv_cache_spec: KVCacheSpec = kv_cache_group_spec.kv_cache_spec
  93. if isinstance(layer_kv_cache_spec, UniformTypeKVCacheSpecs):
  94. layer_kv_cache_spec = layer_kv_cache_spec.kv_cache_specs[layer_name]
  95. # Split on per-rank num_heads_q so layers with different Q-head
  96. # counts (e.g. a spec-decode draft head and its target) get separate
  97. # metadata builders.
  98. num_heads_q = getattr(attn_layers[layer_name], "num_heads", 0)
  99. key = (attn_backend.full_cls_name(), layer_kv_cache_spec, num_heads_q)
  100. if key not in group_map:
  101. group_map[key] = AttentionGroup(
  102. attn_backend, [layer_name], layer_kv_cache_spec, kv_cache_group_id
  103. )
  104. group_order.append(key)
  105. else:
  106. group_map[key].layer_names.append(layer_name)
  107. attn_groups.append([group_map[key] for key in group_order])
  108. # Phase 2: pick a kernel block size per kv cache group that is supported
  109. # by all backends within that group.
  110. kernel_block_sizes = prepare_kernel_block_sizes(kv_cache_config, attn_groups)
  111. # Phase 3: create metadata builders and determine cudagraph support.
  112. attn_backend_workspace: torch.Tensor | None = None
  113. for kv_cache_group_id, groups in enumerate(attn_groups):
  114. kernel_block_size = None
  115. if kv_cache_group_id < len(kernel_block_sizes):
  116. kernel_block_size = kernel_block_sizes[kv_cache_group_id]
  117. for group in groups:
  118. group.create_metadata_builders(
  119. vllm_config=vllm_config,
  120. device=device,
  121. kernel_block_size=kernel_block_size,
  122. num_metadata_builders=1,
  123. )
  124. builder = group.get_metadata_builder(0)
  125. if attn_backend_workspace is None:
  126. if hasattr(builder, "_get_workspace_buffer"):
  127. attn_backend_workspace = builder._get_workspace_buffer()
  128. else:
  129. if hasattr(builder, "set_workspace_buffer"):
  130. builder.set_workspace_buffer(attn_backend_workspace)
  131. attn_cg_support_info = get_attn_cg_support(attn_groups, vllm_config)
  132. return attn_groups, attn_cg_support_info, kernel_block_sizes
  133. def get_attn_cg_support(
  134. attn_groups: list[list[AttentionGroup]],
  135. vllm_config: VllmConfig,
  136. checked_layer_names: set[str] | None = None,
  137. ) -> AttentionCGSupportInfo:
  138. """Return the weakest CUDA graph support among the checked layers."""
  139. min_cg_support = AttentionCGSupport.ALWAYS
  140. min_cg_attn_backend = None
  141. for groups in attn_groups:
  142. for group in groups:
  143. if checked_layer_names is not None and checked_layer_names.isdisjoint(
  144. group.layer_names
  145. ):
  146. continue
  147. builder = group.get_metadata_builder(0)
  148. cg_support = builder.get_cudagraph_support(
  149. vllm_config,
  150. group.kv_cache_spec,
  151. )
  152. if cg_support.value < min_cg_support.value:
  153. min_cg_support = cg_support
  154. min_cg_attn_backend = group.backend.__name__
  155. return AttentionCGSupportInfo(
  156. min_cg_support=min_cg_support,
  157. min_cg_attn_backend=min_cg_attn_backend,
  158. )
  159. def get_query_lens_mismatch_unsupported_backend(
  160. attn_groups: list[list[AttentionGroup]],
  161. checked_layer_names: set[str] | None = None,
  162. ) -> str | None:
  163. """Name the first backend needing the CPU query lengths to be exact, if any.
  164. The attention selector already excludes these when adaptive verification is
  165. enabled, but models that hard-wire their backend never consult it. See
  166. AttentionBackend.supports_device_cpu_query_lens_mismatch().
  167. """
  168. for groups in attn_groups:
  169. for group in groups:
  170. if checked_layer_names is not None and checked_layer_names.isdisjoint(
  171. group.layer_names
  172. ):
  173. continue
  174. if not group.backend.supports_device_cpu_query_lens_mismatch():
  175. return group.backend.__name__
  176. return None
  177. def init_kv_cache(
  178. runner_kv_caches: list[torch.Tensor | list[torch.Tensor]],
  179. forward_context: dict[str, Any],
  180. kv_cache_config: KVCacheConfig,
  181. device: torch.device,
  182. kernel_block_sizes: list[int],
  183. vllm_config: VllmConfig,
  184. kv_cache_allocation_context: AbstractContextManager | None = None,
  185. ) -> dict[str, Any]:
  186. allocation_context = kv_cache_allocation_context or nullcontext()
  187. with allocation_context:
  188. kv_caches = allocate_kv_cache(
  189. kv_cache_config,
  190. device,
  191. vllm_config.cache_config.get_resolved_kv_cache_layout(),
  192. kernel_block_sizes,
  193. )
  194. for layer_name, target in get_shared_kv_cache_layers(vllm_config).items():
  195. kv_caches[layer_name] = kv_caches[target]
  196. # Dual-attention models (e.g. LongCat-Flash) put two Attention modules per
  197. # decoder layer, so a layer name carries two integers (layer + module index).
  198. num_attn_module = (
  199. 2
  200. if vllm_config.model_config.hf_config.model_type
  201. in ("longcat_flash", "longcat_flash_ngram")
  202. else 1
  203. )
  204. bind_kv_cache(
  205. kv_caches,
  206. forward_context,
  207. runner_kv_caches,
  208. num_attn_module,
  209. kv_cache_groups=kv_cache_config.kv_cache_groups,
  210. )
  211. return kv_caches
  212. def build_slot_mappings_by_layer(
  213. slot_mappings: torch.Tensor, kv_cache_config: KVCacheConfig
  214. ) -> dict[str, torch.Tensor]:
  215. slot_mappings_by_layer: dict[str, torch.Tensor] = {}
  216. kv_cache_groups = kv_cache_config.kv_cache_groups
  217. for slot_mapping, kv_cache_group in zip(slot_mappings, kv_cache_groups):
  218. for layer_name in kv_cache_group.layer_names:
  219. slot_mappings_by_layer[layer_name] = slot_mapping
  220. return slot_mappings_by_layer
  221. def build_attn_metadata(
  222. attn_groups: list[list[AttentionGroup]],
  223. num_reqs: int,
  224. num_tokens: int,
  225. query_start_loc_gpu: torch.Tensor,
  226. query_start_loc_cpu: torch.Tensor,
  227. max_query_len: int,
  228. seq_lens: torch.Tensor,
  229. max_seq_len: int,
  230. block_tables: Sequence[torch.Tensor],
  231. slot_mappings: torch.Tensor,
  232. kv_cache_config: KVCacheConfig,
  233. seq_lens_cpu_upper_bound: torch.Tensor | None = None,
  234. dcp_local_seq_lens: torch.Tensor | None = None,
  235. positions: torch.Tensor | None = None,
  236. is_prefilling: torch.Tensor | None = None,
  237. mm_req_doc_ranges: dict[int, list[tuple[int, int]]] | None = None,
  238. model_specific_attn_metadata: ModelSpecificAttnMetadata | None = None,
  239. for_cudagraph_capture: bool = False,
  240. causal: bool | torch.Tensor | Mapping[int, bool] = True,
  241. rswa_prefix_lens: torch.Tensor | None = None,
  242. ) -> dict[str, Any]:
  243. seq_lens = seq_lens[:num_reqs]
  244. if dcp_local_seq_lens is not None:
  245. dcp_local_seq_lens = dcp_local_seq_lens[:num_reqs]
  246. if seq_lens_cpu_upper_bound is not None:
  247. seq_lens_cpu_upper_bound = seq_lens_cpu_upper_bound[:num_reqs]
  248. attn_metadata: dict[str, Any] = {}
  249. num_kv_cache_groups = len(kv_cache_config.kv_cache_groups)
  250. for i in range(num_kv_cache_groups):
  251. block_table = block_tables[i]
  252. slot_mapping = slot_mappings[i]
  253. # DCP-replicated groups hold every position on every rank: their
  254. # builders must see the full seq_lens, not this rank's shard.
  255. group_dcp_local_seq_lens = (
  256. None
  257. if is_dcp_replicated_spec(kv_cache_config.kv_cache_groups[i].kv_cache_spec)
  258. else dcp_local_seq_lens
  259. )
  260. # Per-group causal for hybrid drafters (mixed SWA/full attention).
  261. group_causal = (
  262. causal if isinstance(causal, (bool, torch.Tensor)) else causal.get(i, True)
  263. )
  264. common_attn_metadata_extra_kwargs = (
  265. model_specific_attn_metadata.get_extra_common_attn_kwargs(i, num_reqs)
  266. if model_specific_attn_metadata is not None
  267. else {}
  268. )
  269. # Model-specific metadata (e.g. Mamba hybrid) may supply its own
  270. # padding-aware is_prefilling, which takes precedence over the default.
  271. group_is_prefilling = common_attn_metadata_extra_kwargs.pop(
  272. "is_prefilling", is_prefilling
  273. )
  274. common_attn_metadata = CommonAttentionMetadata(
  275. query_start_loc=query_start_loc_gpu,
  276. query_start_loc_cpu=query_start_loc_cpu,
  277. seq_lens=seq_lens,
  278. seq_lens_cpu_upper_bound=seq_lens_cpu_upper_bound,
  279. max_seq_len=max_seq_len,
  280. num_reqs=num_reqs,
  281. num_actual_tokens=num_tokens,
  282. max_query_len=max_query_len,
  283. block_table_tensor=block_table,
  284. slot_mapping=slot_mapping,
  285. causal=group_causal,
  286. dcp_local_seq_lens=group_dcp_local_seq_lens,
  287. positions=positions,
  288. is_prefilling=group_is_prefilling,
  289. mm_req_doc_ranges=mm_req_doc_ranges,
  290. rswa_prefix_lens=rswa_prefix_lens,
  291. **common_attn_metadata_extra_kwargs,
  292. )
  293. for attn_group in attn_groups[i]:
  294. attn_metadata_builder = attn_group.get_metadata_builder(0)
  295. if for_cudagraph_capture:
  296. metadata = attn_metadata_builder.build_for_cudagraph_capture(
  297. common_attn_metadata
  298. )
  299. else:
  300. attn_metadata_extra_kwargs = (
  301. model_specific_attn_metadata.get_extra_attn_kwargs(
  302. attn_metadata_builder,
  303. num_reqs,
  304. )
  305. if model_specific_attn_metadata is not None
  306. else {}
  307. )
  308. metadata = attn_metadata_builder.build(
  309. common_prefix_len=0,
  310. common_attn_metadata=common_attn_metadata,
  311. **attn_metadata_extra_kwargs,
  312. )
  313. for layer_name in attn_group.layer_names:
  314. attn_metadata[layer_name] = metadata
  315. return attn_metadata
  316. def compute_mm_prefix_ranges(
  317. req_ids: list[str],
  318. mm_features: dict[str, list[MultiModalFeatureSpec]],
  319. sliding_window: int | None = None,
  320. ) -> dict[int, list[tuple[int, int]]]:
  321. """Compute PrefixLM bidirectional ranges for multimodal tokens.
  322. Ranges exceeding sliding_window are skipped to prevent early tokens
  323. from attending across the entire image span.
  324. """
  325. req_doc_ranges: dict[int, list[tuple[int, int]]] = {}
  326. for req_idx, req_id in enumerate(req_ids):
  327. image_doc_ranges = []
  328. for mm_feature in mm_features.get(req_id, ()):
  329. if mm_feature.modality not in ("image", "video"):
  330. continue
  331. for r in mm_feature.mm_position.extract_embeds_range():
  332. if sliding_window is not None and (r[1] - r[0] + 1) > sliding_window:
  333. continue
  334. image_doc_ranges.append(r)
  335. req_doc_ranges[req_idx] = image_doc_ranges
  336. return req_doc_ranges