attn_utils.py 15 KB

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