backend.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. from abc import ABC, abstractmethod
  4. from dataclasses import dataclass, replace
  5. from enum import Enum
  6. from typing import TYPE_CHECKING, Any, ClassVar, Generic, Protocol, TypeVar
  7. import numpy as np
  8. import torch
  9. from typing_extensions import deprecated
  10. from vllm.model_executor.layers.quantization.utils.quant_utils import (
  11. kFp8Dynamic64Sym,
  12. kFp8Dynamic128Sym,
  13. kFp8StaticTensorSym,
  14. kNvfp4Dynamic,
  15. )
  16. if TYPE_CHECKING:
  17. from vllm.config import VllmConfig
  18. from vllm.config.cache import CacheDType
  19. from vllm.model_executor.layers.linear import ColumnParallelLinear
  20. from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey
  21. from vllm.platforms.interface import DeviceCapability
  22. from vllm.v1.kv_cache_interface import (
  23. AttentionSpec,
  24. KVCacheLayout,
  25. KVCacheSpec,
  26. KVQuantMode,
  27. )
  28. from vllm.v1.kv_cache_interface import KVCacheLayout, get_kv_quant_mode
  29. class AttentionType(str, Enum):
  30. """
  31. Attention type.
  32. Use string to be compatible with `torch.compile`.
  33. """
  34. DECODER = "decoder"
  35. """Decoder attention between previous layer Q/K/V."""
  36. ENCODER = "encoder"
  37. """Encoder attention between previous layer Q/K/V for encoder-decoder."""
  38. ENCODER_ONLY = "encoder_only"
  39. """Encoder attention between previous layer Q/K/V."""
  40. ENCODER_DECODER = "encoder_decoder"
  41. """Attention between dec. Q and enc. K/V for encoder-decoder."""
  42. class MultipleOf:
  43. base: int
  44. def __init__(self, base: int):
  45. self.base = base
  46. class AttentionBackend(ABC):
  47. """Abstract class for attention backends."""
  48. supported_dtypes: ClassVar[list[torch.dtype]] = [torch.float16, torch.bfloat16]
  49. supported_kv_cache_dtypes: ClassVar[list["CacheDType"]] = [
  50. "auto",
  51. "float16",
  52. "bfloat16",
  53. ]
  54. # Does attention's forward() include kv cache update?
  55. forward_includes_kv_cache_update: bool = True
  56. @staticmethod
  57. def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
  58. return [MultipleOf(1)]
  59. @staticmethod
  60. @abstractmethod
  61. def get_name() -> str:
  62. raise NotImplementedError
  63. @staticmethod
  64. @abstractmethod
  65. def get_impl_cls() -> type["AttentionImplBase"]:
  66. raise NotImplementedError
  67. @staticmethod
  68. @abstractmethod
  69. def get_builder_cls(): # -> Type["AttentionMetadataBuilder"]:
  70. raise NotImplementedError
  71. @classmethod
  72. def full_cls_name(cls) -> tuple[str, str]:
  73. return (cls.__module__, cls.__qualname__)
  74. @classmethod
  75. def get_supported_head_sizes(cls) -> list[int]:
  76. return []
  77. @classmethod
  78. def supports_head_size(cls, head_size: int) -> bool:
  79. supported_head_sizes = cls.get_supported_head_sizes()
  80. return (not supported_head_sizes) or head_size in supported_head_sizes
  81. @classmethod
  82. def supports_dtype(cls, dtype: torch.dtype) -> bool:
  83. return dtype in cls.supported_dtypes
  84. @classmethod
  85. def supports_kv_cache_dtype(cls, kv_cache_dtype: "CacheDType | None") -> bool:
  86. if kv_cache_dtype is None:
  87. return True
  88. return (not cls.supported_kv_cache_dtypes) or (
  89. kv_cache_dtype in cls.supported_kv_cache_dtypes
  90. )
  91. @classmethod
  92. def supports_block_size(cls, block_size: int | None) -> bool:
  93. if block_size is None:
  94. return True
  95. supported_kernel_block_sizes = cls.get_supported_kernel_block_sizes()
  96. if not supported_kernel_block_sizes:
  97. return True
  98. for supported_size in supported_kernel_block_sizes:
  99. if isinstance(supported_size, MultipleOf):
  100. supported_size = supported_size.base
  101. # With hybrid_blocks feature, the framework-level block size
  102. # only needs to be a multiple of the kernel's requirement,
  103. # even if the kernel requires a fixed block_size.
  104. if block_size % supported_size == 0:
  105. return True
  106. return False
  107. @classmethod
  108. def customize_spec(cls, spec: "AttentionSpec") -> "AttentionSpec":
  109. """Adjust the layer's KV cache spec for this backend's kernels. Used when the
  110. kernels want KV packed in a specific way.
  111. NOTE: temporary compatibility API. Today the Attention layer builds the spec
  112. from the model config and the backend only gets to adjust it post-hoc; the end
  113. state is for the backend to build and return the spec directly, at which point
  114. this hook goes away.
  115. (see: https://github.com/vllm-project/vllm/issues/42449)
  116. """
  117. return spec
  118. @classmethod
  119. def get_preferred_block_size(cls, default_block_size: int) -> int:
  120. supported_sizes = cls.get_supported_kernel_block_sizes()
  121. if not supported_sizes:
  122. return default_block_size
  123. if cls.supports_block_size(default_block_size):
  124. return default_block_size
  125. return min(s.base if isinstance(s, MultipleOf) else s for s in supported_sizes)
  126. @classmethod
  127. def is_mla(cls) -> bool:
  128. return False
  129. @classmethod
  130. def supports_sink(cls) -> bool:
  131. return False
  132. @classmethod
  133. def supports_alibi_sqrt(cls) -> bool:
  134. return False
  135. @classmethod
  136. def supports_mm_prefix(cls) -> bool:
  137. return False
  138. @classmethod
  139. def is_sparse(cls) -> bool:
  140. return False
  141. @classmethod
  142. def supports_per_head_quant_scales(cls) -> bool:
  143. return False
  144. @classmethod
  145. def supports_sliding_window(cls) -> bool:
  146. return False
  147. @classmethod
  148. def supports_non_causal(cls) -> bool:
  149. """Check if backend supports non-causal (bidirectional) attention
  150. for decoder models.
  151. Unlike ENCODER_ONLY attention type which implies a different
  152. execution model, this refers to non-causal attention within the
  153. standard paged-KV-cache decoder path.
  154. """
  155. return False
  156. @classmethod
  157. def supports_batch_invariance(cls) -> bool:
  158. return False
  159. @classmethod
  160. def supports_kv_connector(cls) -> bool:
  161. return True
  162. @classmethod
  163. def supports_device_cpu_query_lens_mismatch(cls) -> bool:
  164. """Whether this backend can run a batch whose device query_start_loc disagrees
  165. with the CPU one; backends that plan off the CPU query lengths must opt out.
  166. Currently only verification requests are affected: adaptive verification trims
  167. their drafts on device. On the CPU the draft budget is evenly distributed across
  168. requests, so the total draft budget, the decode/prefill split point and the CPU
  169. prefill query lengths all stay correct.
  170. SSM backends opt out: their recurrent-state planning is built from the CPU
  171. per-request boundaries, which the trimmed batch no longer matches.
  172. """
  173. return not cls.is_ssm()
  174. @classmethod
  175. def supports_pcp(cls) -> bool:
  176. try:
  177. return cls.get_impl_cls().supports_pcp
  178. except NotImplementedError:
  179. return False
  180. @classmethod
  181. def supports_non_causal_dcp(cls) -> bool:
  182. builder_cls = cls.get_builder_cls()
  183. return bool(getattr(builder_cls, "supports_non_causal_multi_token_dcp", False))
  184. @classmethod
  185. def supports_attn_type(cls, attn_type: str) -> bool:
  186. """Check if backend supports a given attention type.
  187. By default, only supports decoder attention.
  188. Backends should override this to support other attention types.
  189. """
  190. return attn_type == AttentionType.DECODER
  191. @classmethod
  192. def supports_compute_capability(cls, capability: "DeviceCapability") -> bool:
  193. return True
  194. @classmethod
  195. def supports_combination(
  196. cls,
  197. head_size: int,
  198. dtype: torch.dtype,
  199. kv_cache_dtype: "CacheDType | None",
  200. block_size: int | None,
  201. use_mla: bool,
  202. has_sink: bool,
  203. use_sparse: bool,
  204. use_mm_prefix: bool,
  205. device_capability: "DeviceCapability",
  206. ) -> str | None:
  207. return None
  208. @classmethod
  209. def validate_configuration(
  210. cls,
  211. head_size: int,
  212. dtype: torch.dtype,
  213. kv_cache_dtype: "CacheDType | None",
  214. block_size: int | None,
  215. use_mla: bool,
  216. has_sink: bool,
  217. use_sparse: bool,
  218. use_mm_prefix: bool,
  219. use_per_head_quant_scales: bool,
  220. device_capability: "DeviceCapability",
  221. attn_type: str,
  222. has_sliding_window: bool = False,
  223. use_non_causal: bool = False,
  224. use_batch_invariant: bool = False,
  225. use_kv_connector: bool = False,
  226. use_pcp: bool = False,
  227. use_adaptive_verification: bool = False,
  228. use_dcp: bool = False,
  229. ) -> list[str]:
  230. invalid_reasons = []
  231. if not cls.supports_head_size(head_size):
  232. invalid_reasons.append("head_size not supported")
  233. if not cls.supports_dtype(dtype):
  234. invalid_reasons.append("dtype not supported")
  235. if not cls.supports_kv_cache_dtype(kv_cache_dtype):
  236. invalid_reasons.append("kv_cache_dtype not supported")
  237. if not cls.supports_block_size(block_size):
  238. invalid_reasons.append("block_size not supported")
  239. if use_mm_prefix and not cls.supports_mm_prefix():
  240. invalid_reasons.append(
  241. "partial multimodal token full attention not supported"
  242. )
  243. if use_mla != cls.is_mla():
  244. if use_mla:
  245. invalid_reasons.append("MLA not supported")
  246. else:
  247. invalid_reasons.append("non-MLA not supported")
  248. if has_sink and not cls.supports_sink():
  249. invalid_reasons.append("attention sinks not supported")
  250. if use_sparse != cls.is_sparse():
  251. if use_sparse:
  252. invalid_reasons.append("sparse not supported")
  253. else:
  254. invalid_reasons.append("non-sparse not supported")
  255. if use_per_head_quant_scales and not cls.supports_per_head_quant_scales():
  256. invalid_reasons.append("per-head quant scales not supported")
  257. if not cls.supports_compute_capability(device_capability):
  258. invalid_reasons.append("compute capability not supported")
  259. if not cls.supports_attn_type(attn_type):
  260. invalid_reasons.append(f"attention type {attn_type} not supported")
  261. if has_sliding_window and not cls.supports_sliding_window():
  262. invalid_reasons.append("sliding window not supported")
  263. if use_non_causal and not cls.supports_non_causal():
  264. invalid_reasons.append("non-causal attention not supported")
  265. if use_mla and use_non_causal and use_dcp and not cls.supports_non_causal_dcp():
  266. invalid_reasons.append("non-causal MLA attention with DCP not supported")
  267. if use_batch_invariant and not cls.supports_batch_invariance():
  268. invalid_reasons.append("batch invariance not supported")
  269. if use_kv_connector and not cls.supports_kv_connector():
  270. invalid_reasons.append("KV connector not supported")
  271. if use_pcp and not cls.supports_pcp():
  272. invalid_reasons.append("PCP not supported")
  273. if (
  274. use_adaptive_verification
  275. and not cls.supports_device_cpu_query_lens_mismatch()
  276. ):
  277. invalid_reasons.append(
  278. "device-cpu query lens mismatch not supported, "
  279. "this is needed for adaptive verification"
  280. )
  281. combination_reason = cls.supports_combination(
  282. head_size,
  283. dtype,
  284. kv_cache_dtype,
  285. block_size,
  286. use_mla,
  287. has_sink,
  288. use_sparse,
  289. use_mm_prefix,
  290. device_capability,
  291. )
  292. if combination_reason is not None:
  293. invalid_reasons.append(combination_reason)
  294. return invalid_reasons
  295. @classmethod
  296. def supported_kv_cache_layouts(cls) -> tuple[KVCacheLayout, ...] | None:
  297. """Layouts this backend's kernels can consume, most preferred first, or
  298. None when the kernels consume any layout and express no preference."""
  299. return None
  300. @classmethod
  301. def is_ssm(cls) -> bool:
  302. return False
  303. class AttentionMetadata:
  304. pass
  305. T = TypeVar("T", bound=AttentionMetadata)
  306. @dataclass
  307. class CommonAttentionMetadata:
  308. """
  309. Per-batch attention metadata, shared across layers and backends.
  310. AttentionMetadataBuilder instances use it to construct per-layer metadata.
  311. For many of the tensors we keep both GPU and CPU versions.
  312. """
  313. query_start_loc: torch.Tensor
  314. query_start_loc_cpu: torch.Tensor
  315. """(batch_size + 1,), the start location of each request in query Tensor"""
  316. seq_lens: torch.Tensor
  317. """(batch_size,), the number of computed tokens for each request"""
  318. num_reqs: int
  319. """Number of requests"""
  320. # TODO(lucas): rename to num_tokens since it may be padded and this is misleading
  321. num_actual_tokens: int
  322. """Total number of tokens in batch"""
  323. max_query_len: int
  324. """Longest query in batch"""
  325. max_seq_len: int
  326. """Longest context length (may be an upper bound)"""
  327. block_table_tensor: torch.Tensor
  328. slot_mapping: torch.Tensor
  329. causal: bool | torch.Tensor = True
  330. # Needed by FastPrefillAttentionBuilder
  331. logits_indices_padded: torch.Tensor | None = None
  332. num_logits_indices: int | None = None
  333. max_logits_per_req: int | None = None
  334. # Needed by CrossAttentionBuilder
  335. encoder_seq_lens: torch.Tensor | None = None
  336. encoder_seq_lens_cpu: np.ndarray | None = None
  337. dcp_local_seq_lens: torch.Tensor | None = None
  338. dcp_local_seq_lens_cpu: torch.Tensor | None = None
  339. """Sequence lengths of the local rank in decode context parallelism world"""
  340. positions: torch.Tensor | None = None
  341. """(num_actual_tokens,) token positions. Optional; set when the caller
  342. has positions available so that builders can pre-compute position-dependent
  343. sparse metadata for DeepSeek V4 C128A layers."""
  344. is_prefilling: torch.Tensor | None = None
  345. """(batch_size,) bool tensor: True if request is still in prefill phase
  346. (num_computed_tokens < num_prompt_tokens). Used by some backends to
  347. distinguish actual decodes from short extends."""
  348. seq_lens_cpu_upper_bound: torch.Tensor | None = None
  349. """(batch_size,) CPU upper bound on seq_lens. Precise for prefill rows
  350. and for all rows outside async spec decode; optimistic for async-spec
  351. decode rows (assumes every draft was accepted). Not safe for kernels
  352. that need exact per-row context lengths on decode rows."""
  353. mm_req_doc_ranges: dict[int, list[tuple[int, int]]] | None = None
  354. """PrefixLM bidirectional ranges for multimodal tokens. Maps
  355. request index to list of (start, end) token position ranges
  356. where bidirectional attention should apply. None for text-only
  357. batches or non-PrefixLM models. A request's ranges must not overlap."""
  358. rswa_prefix_lens: torch.Tensor | None = None
  359. """(batch_size,) per-request prefix length (prompt/image token count) for
  360. Reference Sliding Window Attention (R-SWA). Tokens with logical index below
  361. this stay globally visible; later (generated) tokens additionally see a
  362. fixed sliding window. None disables R-SWA. The attention backend copies this
  363. into its own persistent buffer and reads ``rswa_window`` from model config."""
  364. replayssm_decode_base_cpu: torch.Tensor | None = None
  365. """(batch_size,) CPU ring origin for Mamba2 ReplaySSM decode: num_computed
  366. at the current decode run's last full-state write. write_pos counts from
  367. here, so a preemption-resumed request re-anchors past the prompt boundary."""
  368. # WARNING: Deprecated fields. Will be removed in a future release (v0.15.0)
  369. _seq_lens_cpu: torch.Tensor | None = None
  370. _num_computed_tokens_cpu: torch.Tensor | None = None
  371. _num_computed_tokens_cache: torch.Tensor | None = None
  372. _token_to_req_indices_cache: torch.Tensor | None = None
  373. def batch_size(self) -> int:
  374. return self.seq_lens.shape[0]
  375. def naive_query_lens(self) -> torch.Tensor:
  376. """Naive because it assumes that query ends where the next query starts."""
  377. return self.query_start_loc[1:] - self.query_start_loc[:-1]
  378. def replace(self, **kwargs) -> "CommonAttentionMetadata":
  379. return replace(self, **kwargs)
  380. @property
  381. @deprecated(
  382. """
  383. Prefer using device seq_lens directly to avoid implicit H<>D sync.
  384. If a CPU copy is needed, use `seq_lens.cpu()` instead.
  385. Will be removed in a future release, please migrate as soon as possible.
  386. """
  387. )
  388. def seq_lens_cpu(self) -> torch.Tensor:
  389. if self._seq_lens_cpu is None:
  390. self._seq_lens_cpu = self.seq_lens.to("cpu")
  391. return self._seq_lens_cpu
  392. @property
  393. @deprecated(
  394. """
  395. Prefer using device seq_lens directly to avoid implicit H<>D sync which breaks full
  396. async scheduling. If a CPU copy is needed, it can be derived from
  397. query_start_loc_cpu and seq_lens.
  398. Will be removed in a future release, please migrate as soon as possible.
  399. """
  400. )
  401. def num_computed_tokens_cpu(self) -> torch.Tensor:
  402. if self._num_computed_tokens_cpu is None:
  403. query_seq_lens = (
  404. self.query_start_loc_cpu[1:] - self.query_start_loc_cpu[:-1]
  405. )
  406. self._num_computed_tokens_cpu = self.seq_lens_cpu - query_seq_lens
  407. return self._num_computed_tokens_cpu
  408. def compute_num_computed_tokens(self) -> torch.Tensor:
  409. """Compute num_computed_tokens on device (seq_lens - query_lens)."""
  410. if self._num_computed_tokens_cache is None:
  411. query_lens = self.query_start_loc[1:] - self.query_start_loc[:-1]
  412. self._num_computed_tokens_cache = self.seq_lens - query_lens
  413. return self._num_computed_tokens_cache
  414. def token_to_req_indices(self, buffer: torch.Tensor) -> torch.Tensor:
  415. """Build or reuse the per-token request index mapping."""
  416. num_tokens = self.num_actual_tokens
  417. if self._token_to_req_indices_cache is not None:
  418. assert self._token_to_req_indices_cache.device == buffer.device
  419. assert self._token_to_req_indices_cache.dtype == torch.int32
  420. assert self._token_to_req_indices_cache.shape[0] >= num_tokens
  421. return self._token_to_req_indices_cache[:num_tokens]
  422. # Built from the device query_start_loc: adaptive verification decides the
  423. # per-request draft split on device, so the CPU copy carries the right total
  424. # but not the right per-request boundaries. Padding requests have a query
  425. # length of zero and drop out of the repeat.
  426. num_mapped_tokens = int(self.query_start_loc_cpu[-1])
  427. query_lens = self.query_start_loc[1:] - self.query_start_loc[:-1]
  428. assert buffer.shape[0] >= max(num_mapped_tokens, num_tokens)
  429. token_to_req_indices = torch.repeat_interleave(
  430. torch.arange(query_lens.shape[0], dtype=torch.int32, device=buffer.device),
  431. query_lens,
  432. output_size=num_mapped_tokens,
  433. )
  434. buffer[:num_mapped_tokens].copy_(token_to_req_indices)
  435. if num_mapped_tokens < num_tokens:
  436. buffer[num_mapped_tokens:num_tokens].zero_()
  437. self._token_to_req_indices_cache = buffer[: max(num_mapped_tokens, num_tokens)]
  438. return self._token_to_req_indices_cache[:num_tokens]
  439. # TODO(lucas): remove once we have FULL-CG spec-decode support
  440. def unpadded(
  441. self, num_actual_tokens: int, num_actual_reqs: int
  442. ) -> "CommonAttentionMetadata":
  443. maybe_slice_reqs = lambda x: x[:num_actual_reqs] if x is not None else None
  444. return CommonAttentionMetadata(
  445. query_start_loc=self.query_start_loc[: num_actual_reqs + 1],
  446. query_start_loc_cpu=self.query_start_loc_cpu[: num_actual_reqs + 1],
  447. seq_lens=self.seq_lens[:num_actual_reqs],
  448. _seq_lens_cpu=self._seq_lens_cpu[:num_actual_reqs]
  449. if self._seq_lens_cpu is not None
  450. else None,
  451. _num_computed_tokens_cpu=self._num_computed_tokens_cpu[:num_actual_reqs]
  452. if self._num_computed_tokens_cpu is not None
  453. else None,
  454. num_reqs=num_actual_reqs,
  455. num_actual_tokens=num_actual_tokens,
  456. max_query_len=self.max_query_len,
  457. max_seq_len=self.max_seq_len,
  458. block_table_tensor=self.block_table_tensor[:num_actual_reqs],
  459. slot_mapping=self.slot_mapping[:num_actual_tokens],
  460. causal=self.causal[:num_actual_reqs]
  461. if isinstance(self.causal, torch.Tensor)
  462. else self.causal,
  463. logits_indices_padded=self.logits_indices_padded,
  464. num_logits_indices=self.num_logits_indices,
  465. max_logits_per_req=self.max_logits_per_req,
  466. encoder_seq_lens=maybe_slice_reqs(self.encoder_seq_lens),
  467. encoder_seq_lens_cpu=maybe_slice_reqs(self.encoder_seq_lens_cpu),
  468. dcp_local_seq_lens=maybe_slice_reqs(self.dcp_local_seq_lens),
  469. dcp_local_seq_lens_cpu=maybe_slice_reqs(self.dcp_local_seq_lens_cpu),
  470. is_prefilling=maybe_slice_reqs(self.is_prefilling),
  471. rswa_prefix_lens=maybe_slice_reqs(self.rswa_prefix_lens),
  472. replayssm_decode_base_cpu=maybe_slice_reqs(self.replayssm_decode_base_cpu),
  473. )
  474. M = TypeVar("M")
  475. class AttentionCGSupport(Enum):
  476. """Constants for the cudagraph support of the attention backend
  477. Here we do not consider the cascade attention, as currently
  478. it is never cudagraph supported."""
  479. ALWAYS = 3
  480. """Cudagraph always supported; supports mixed-prefill-decode"""
  481. UNIFORM_BATCH = 2
  482. """Cudagraph supported for batches the only contain query lengths that are
  483. the same, this can be used for spec-decode
  484. i.e. "decodes" are 1 + num_speculative_tokens"""
  485. UNIFORM_SINGLE_TOKEN_DECODE = 1
  486. """Cudagraph supported for batches the only contain query_len==1 decodes"""
  487. NEVER = 0
  488. """NO cudagraph support"""
  489. class AttentionMetadataBuilder(ABC, Generic[M]):
  490. # Does this backend/builder support CUDA Graphs for attention (default: no).
  491. # Do not access directly. Call get_cudagraph_support() instead.
  492. _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.NEVER
  493. # Does this backend/builder reorder the batch?
  494. # If not, set this to None. Otherwise set it to the query
  495. # length that will be pulled into the front of the batch.
  496. reorder_batch_threshold: int | None = None
  497. # Does this backend/builder support updating the block table in existing
  498. # metadata
  499. supports_update_block_table: bool = False
  500. # Whether the builder constructor requires the block-table width.
  501. requires_block_table_width: ClassVar[bool] = False
  502. # Whether all step-dependent draft decode metadata can be updated in place,
  503. # allowing one metadata build to be reused across autoregressive draft steps.
  504. supports_draft_decode_metadata_update: bool = False
  505. @abstractmethod
  506. def __init__(
  507. self,
  508. kv_cache_spec: "KVCacheSpec",
  509. layer_names: list[str],
  510. vllm_config: "VllmConfig",
  511. device: torch.device,
  512. ):
  513. self.kv_cache_spec = kv_cache_spec
  514. self.layer_names = layer_names
  515. self.vllm_config = vllm_config
  516. self.device = device
  517. self.kernel_block_size: int | None = None
  518. def set_kernel_block_size(self, kernel_block_size: int) -> None:
  519. self.kernel_block_size = kernel_block_size
  520. @classmethod
  521. def get_cudagraph_support(
  522. cls: type["AttentionMetadataBuilder"],
  523. vllm_config: "VllmConfig",
  524. kv_cache_spec: "KVCacheSpec",
  525. ) -> AttentionCGSupport:
  526. """Get the cudagraph support level of this builder class."""
  527. return cls._cudagraph_support
  528. def _init_reorder_batch_threshold(
  529. self,
  530. reorder_batch_threshold: int | None = 1,
  531. supports_spec_as_decode: bool = False,
  532. supports_dcp_with_varlen: bool = False,
  533. ) -> None:
  534. self.reorder_batch_threshold = reorder_batch_threshold
  535. if self.reorder_batch_threshold is not None and supports_spec_as_decode:
  536. # If the backend supports spec-as-decode kernels, then we can set
  537. # the reorder_batch_threshold based on the number of speculative
  538. # tokens from the config.
  539. speculative_config = self.vllm_config.speculative_config
  540. if (
  541. speculative_config is not None
  542. and speculative_config.num_speculative_tokens is not None
  543. ):
  544. max_num_queries_for_spec = (
  545. 1
  546. + (2 if speculative_config.parallel_drafting else 1)
  547. * speculative_config.num_speculative_tokens
  548. )
  549. self.reorder_batch_threshold = max(
  550. self.reorder_batch_threshold,
  551. max_num_queries_for_spec,
  552. )
  553. if (
  554. self.vllm_config.parallel_config.decode_context_parallel_size > 1
  555. and not supports_dcp_with_varlen
  556. ):
  557. self.reorder_batch_threshold = 1
  558. @abstractmethod
  559. def build(
  560. self,
  561. common_prefix_len: int,
  562. common_attn_metadata: CommonAttentionMetadata,
  563. fast_build: bool = False,
  564. ) -> M:
  565. """
  566. Central method that builds attention metadata.
  567. Some builders (MLA) require reorder_batch to be called prior to build.
  568. Args:
  569. common_prefix_len: The length of the common prefix of the batch.
  570. common_attn_metadata: The common attention metadata.
  571. fast_build: The meta-data will prioritize speed of building over
  572. then speed at execution. Can be used for spec-decode where the
  573. result of a build call may only be used for few layers/iters.
  574. """
  575. raise NotImplementedError
  576. def update_block_table(
  577. self,
  578. metadata: M,
  579. blk_table: torch.Tensor,
  580. slot_mapping: torch.Tensor,
  581. ) -> M:
  582. """
  583. Update the block table for the attention metadata.
  584. Faster when theres multiple kv-cache groups that create virtually the
  585. same metadata but just with different block tables.
  586. Only needs to be implemented if supports_update_block_table is True.
  587. """
  588. raise NotImplementedError
  589. def build_for_cudagraph_capture(
  590. self, common_attn_metadata: CommonAttentionMetadata
  591. ) -> M:
  592. """
  593. Build attention metadata for CUDA graph capture. Uses build by default.
  594. Subclasses that override this method should call self.build or
  595. super().build_for_cudagraph_capture.
  596. """
  597. return self.build(
  598. common_prefix_len=0, common_attn_metadata=common_attn_metadata
  599. )
  600. def build_for_drafting(
  601. self,
  602. common_attn_metadata: CommonAttentionMetadata,
  603. draft_index: int,
  604. ) -> M:
  605. """
  606. Build attention metadata for draft model. Uses build by default.
  607. Args:
  608. common_attn_metadata: The common attention metadata.
  609. draft_index: The index of the current draft operation.
  610. When speculating a chain of tokens, this index refers to the
  611. draft attempt for the i-th token.
  612. For tree-based attention, this index instead refers to the
  613. draft attempt for the i-th level in the tree of tokens.
  614. """
  615. return self.build(
  616. common_prefix_len=0,
  617. common_attn_metadata=common_attn_metadata,
  618. fast_build=True,
  619. )
  620. def update_draft_decode_metadata(self, metadata: M) -> None:
  621. """Update step-dependent draft decode metadata in place.
  622. The fused draft loop may call this method during full CUDA graph
  623. capture. CUDA graph replay does not run this Python method, so
  624. implementations must emit capture-safe operations and keep replayed
  625. tensor state in persistent storage.
  626. """
  627. raise NotImplementedError
  628. def use_cascade_attention(
  629. self,
  630. common_prefix_len: int,
  631. query_lens: np.ndarray,
  632. num_query_heads: int,
  633. num_kv_heads: int,
  634. use_alibi: bool,
  635. use_sliding_window: bool,
  636. use_local_attention: bool,
  637. num_sms: int,
  638. dcp_world_size: int,
  639. ) -> bool:
  640. return False
  641. class AttentionLayer(Protocol):
  642. _q_scale: torch.Tensor
  643. _k_scale: torch.Tensor
  644. _k_scale_cpu: torch.Tensor
  645. _v_scale: torch.Tensor
  646. _v_scale_cpu: torch.Tensor
  647. _q_scale_float: float
  648. _k_scale_float: float
  649. _v_scale_float: float
  650. _prob_scale: torch.Tensor
  651. def forward(
  652. self,
  653. query: torch.Tensor,
  654. key: torch.Tensor,
  655. value: torch.Tensor,
  656. kv_cache: torch.Tensor,
  657. attn_metadata: AttentionMetadata,
  658. ) -> torch.Tensor: ...
  659. class AttentionImplBase(ABC, Generic[T]):
  660. """Base class for attention implementations.
  661. Contains common attributes and initialization logic shared by both
  662. standard AttentionImpl and MLAAttentionImpl. Does not define a forward
  663. method - subclasses define their own forward interfaces.
  664. """
  665. # Whether this impl uses a sparse (top-k) attention path. Used by MLA to
  666. # route between the dense-MHA prefill and sparse-MQA paths.
  667. is_sparse: ClassVar[bool] = False
  668. # Whether this impl provides a dense-MHA prefill path (forward_mha). Sparse
  669. # impls without one run the top-k MQA path for all requests.
  670. supports_dense_mha_prefill: ClassVar[bool] = True
  671. # Required attributes that all impls should have
  672. num_heads: int
  673. head_size: int
  674. scale: float
  675. # Whether the attention impl can return the softmax lse for decode.
  676. # Some features like decode context parallelism require the softmax lse.
  677. can_return_lse_for_decode: bool = False
  678. # Base of the logarithm used by this backend when returning softmax lse.
  679. # True => natural log (lse = ln(sum(exp(qk))))
  680. # -- e.g. Triton MLA, FlashAttention, FlashMLA, Cutlass MLA
  681. # False => base 2 (lse = log2(sum(exp(qk))))
  682. # -- e.g. FlashInfer trtllm-gen MLA
  683. # The DCP combine kernel (cp_lse_ag_out_rs / dcp_a2a_lse_reduce in
  684. # vllm/v1/attention/ops/dcp.py) branches on this via its IS_BASE_E
  685. # constexpr; getting it wrong silently corrupts the cross-shard
  686. # softmax denominator.
  687. lse_base_on_e: bool = True
  688. # Whether the attention impl supports Prefill Context Parallelism.
  689. supports_pcp: bool = False
  690. # Whether the attention impl supports Decode Context Parallelism.
  691. supports_dcp: bool = True
  692. # Whether the attention impl(or ops) supports MTP
  693. # when cp_kv_cache_interleave_size > 1
  694. supports_mtp_with_cp_non_trivial_interleave_size: bool = False
  695. # some attention backends might not always want to return lse
  696. # even if they can return lse (for efficiency reasons)
  697. need_to_return_lse_for_decode: bool = False
  698. # Whether this attention implementation supports pre-quantized query input.
  699. # When True, the attention layer will quantize queries before passing them
  700. # to this backend, allowing torch.compile to fuse the quantization with
  701. # previous operations. This is typically supported when using FP8 KV cache
  702. # with compatible attention kernels (e.g., TRT-LLM).
  703. # Subclasses should set this in __init__.
  704. # TODO add support to more backends:
  705. # https://github.com/vllm-project/vllm/issues/25584
  706. supports_quant_query_input: bool = False
  707. dcp_world_size: int
  708. dcp_rank: int
  709. pcp_world_size: int
  710. pcp_rank: int
  711. total_cp_world_size: int
  712. total_cp_rank: int
  713. def __new__(cls, *args, **kwargs):
  714. # use __new__ so that all subclasses will call this
  715. self = super().__new__(cls)
  716. try:
  717. from vllm.distributed.parallel_state import get_dcp_group
  718. self.dcp_world_size = get_dcp_group().world_size
  719. self.dcp_rank = get_dcp_group().rank_in_group
  720. except AssertionError:
  721. # DCP might not be initialized in testing
  722. self.dcp_world_size = 1
  723. self.dcp_rank = 0
  724. try:
  725. from vllm.distributed.parallel_state import get_pcp_group
  726. self.pcp_world_size = get_pcp_group().world_size
  727. self.pcp_rank = get_pcp_group().rank_in_group
  728. except AssertionError:
  729. self.pcp_world_size = 1
  730. self.pcp_rank = 0
  731. self.total_cp_world_size = self.dcp_world_size
  732. self.total_cp_rank = self.dcp_rank
  733. self.need_to_return_lse_for_decode = (
  734. self.dcp_world_size > 1 and self.can_return_lse_for_decode
  735. )
  736. return self
  737. def process_weights_after_loading(self, act_dtype: torch.dtype):
  738. pass
  739. class AttentionImpl(AttentionImplBase[T], Generic[T]):
  740. """Standard attention implementation with forward method."""
  741. kv_cache_dtype: str
  742. @property
  743. def kv_quant_mode(self) -> "KVQuantMode":
  744. """Return the KV cache quantization mode for this layer."""
  745. return get_kv_quant_mode(self.kv_cache_dtype)
  746. @abstractmethod
  747. def __init__(
  748. self,
  749. num_heads: int,
  750. head_size: int,
  751. scale: float,
  752. num_kv_heads: int | None = None,
  753. alibi_slopes: list[float] | None = None,
  754. sliding_window: int | None = None,
  755. kv_cache_dtype: str = "auto",
  756. logits_soft_cap: float | None = None,
  757. attn_type: str = AttentionType.DECODER,
  758. kv_sharing_target_layer_name: str | None = None,
  759. ) -> None:
  760. raise NotImplementedError
  761. @abstractmethod
  762. def forward(
  763. self,
  764. layer: AttentionLayer,
  765. query: torch.Tensor,
  766. key: torch.Tensor,
  767. value: torch.Tensor,
  768. kv_cache: torch.Tensor,
  769. attn_metadata: T,
  770. output: torch.Tensor,
  771. output_scale: torch.Tensor | None = None,
  772. output_block_scale: torch.Tensor | None = None,
  773. ) -> torch.Tensor:
  774. raise NotImplementedError
  775. def fused_output_quant_supported(self, quant_key: "QuantKey") -> bool:
  776. """
  777. Does this attention implementation support fused output quantization.
  778. This is used by the AttnFusionPass to only fuse output quantization
  779. onto implementations that support it.
  780. Args:
  781. quant_key: QuantKey object that describes the quantization op
  782. Returns:
  783. is fusion supported for this type of quantization
  784. """
  785. return False
  786. def fused_qk_norm_rope_kvcache_supported(self):
  787. """
  788. Does this attention implementation support fused QKNorm+RoPE+KVCache fusion.
  789. This is used by the QkNormRopeKvCachePattern to only fuse the QKNorm ops
  790. with the RoPE ops and the KV cache update for implementations that support it.
  791. """
  792. return False
  793. def fused_rope_kvcache_supported(self):
  794. """
  795. Does this attention implementation support RoPE+KVCache fusion.
  796. This is used by the RopeKVCacheFusionPass to only fuse the RoPE ops
  797. with the KV cache update for implementations that support it.
  798. """
  799. return False
  800. def do_qk_norm_rope_kvcache_update(
  801. self,
  802. layer: AttentionLayer,
  803. qkv: torch.Tensor,
  804. q_out: torch.Tensor,
  805. k_out: torch.Tensor,
  806. positions: torch.Tensor,
  807. q_weight: torch.Tensor,
  808. k_weight: torch.Tensor,
  809. rms_norm_eps: float,
  810. cos_sin_cache: torch.Tensor,
  811. is_neox: bool,
  812. kv_cache: torch.Tensor,
  813. layer_slot_mapping: torch.Tensor,
  814. ):
  815. """
  816. If `fused_qk_norm_rope_kvcache_supported` returns True, this method
  817. will be called by the fused custom op. Applies QK-norm + RoPE and
  818. writes K/V to the KV cache. Results are written to the pre-allocated
  819. q_out and k_out tensors; V is split from QKV at the graph level.
  820. """
  821. raise NotImplementedError
  822. def do_rope_and_kv_cache_update(
  823. self,
  824. layer: AttentionLayer,
  825. query: torch.Tensor,
  826. key: torch.Tensor,
  827. value: torch.Tensor,
  828. positions: torch.Tensor,
  829. cos_sin_cache: torch.Tensor,
  830. is_neox: bool,
  831. kv_cache: torch.Tensor,
  832. layer_slot_mapping: torch.Tensor,
  833. ):
  834. """
  835. If `fused_rope_kvcache_supported` returns True, this method will be called
  836. by torch.ops.vllm.fused_rope_and_unified_kv_cache_update
  837. to perform the inplace RoPE and KV cache update.
  838. """
  839. raise NotImplementedError
  840. class MLAAttentionImpl(AttentionImplBase[T], Generic[T]):
  841. """MLA attention implementation with forward_mqa and forward_mha methods."""
  842. supports_pcp: bool = True
  843. @abstractmethod
  844. def __init__(
  845. self,
  846. num_heads: int,
  847. head_size: int,
  848. scale: float,
  849. num_kv_heads: int,
  850. alibi_slopes: list[float] | None,
  851. sliding_window: int | None,
  852. kv_cache_dtype: str,
  853. logits_soft_cap: float | None,
  854. attn_type: str,
  855. kv_sharing_target_layer_name: str | None,
  856. # MLA Specific Arguments
  857. q_lora_rank: int | None,
  858. kv_lora_rank: int,
  859. qk_nope_head_dim: int,
  860. qk_rope_head_dim: int,
  861. qk_head_dim: int,
  862. v_head_dim: int,
  863. kv_b_proj: "ColumnParallelLinear",
  864. indexer: object | None = None,
  865. q_pad_num_heads: int | None = None,
  866. ) -> None:
  867. raise NotImplementedError
  868. def forward_mha(
  869. self,
  870. q: torch.Tensor,
  871. kv_c_normed: torch.Tensor,
  872. k_pe: torch.Tensor,
  873. kv_c_and_k_pe_cache: torch.Tensor,
  874. attn_metadata: T,
  875. k_scale: torch.Tensor,
  876. output: torch.Tensor,
  877. output_scale: torch.Tensor | None = None,
  878. ) -> None:
  879. """MHA-style prefill forward pass."""
  880. raise NotImplementedError
  881. @abstractmethod
  882. def forward_mqa(
  883. self,
  884. q: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
  885. kv_c_and_k_pe_cache: torch.Tensor,
  886. attn_metadata: T,
  887. layer: AttentionLayer,
  888. ) -> tuple[torch.Tensor, torch.Tensor | None]:
  889. """MQA-style decode forward pass."""
  890. raise NotImplementedError
  891. def fused_output_quant_supported(self, quant_key: "QuantKey"):
  892. """
  893. Does this attention implementation support fused output quantization.
  894. Since MLA quantization is done manually in forward_impl (common code),
  895. all MLA backends support it by default.
  896. """
  897. return quant_key in (
  898. kFp8StaticTensorSym,
  899. kNvfp4Dynamic,
  900. kFp8Dynamic128Sym,
  901. kFp8Dynamic64Sym,
  902. )
  903. def do_kv_cache_update(
  904. self,
  905. kv_c_normed: torch.Tensor,
  906. k_pe: torch.Tensor,
  907. kv_cache: torch.Tensor,
  908. slot_mapping: torch.Tensor,
  909. kv_cache_dtype: str,
  910. k_scale: torch.Tensor,
  911. ) -> None:
  912. if kv_cache.numel() == 0:
  913. return
  914. from vllm import _custom_ops as ops
  915. ops.concat_and_cache_mla(
  916. kv_c_normed,
  917. k_pe.squeeze(1),
  918. kv_cache,
  919. slot_mapping.flatten(),
  920. kv_cache_dtype=kv_cache_dtype,
  921. scale=k_scale,
  922. )
  923. def subclass_attention_backend(
  924. name_prefix: str,
  925. attention_backend_cls: type[AttentionBackend],
  926. builder_cls: type[AttentionMetadataBuilder[M]],
  927. ) -> type[AttentionBackend]:
  928. """
  929. Return a new subclass where `get_builder_cls` returns `builder_cls`.
  930. """
  931. name: str = name_prefix + attention_backend_cls.__name__ # type: ignore
  932. return type(
  933. name, (attention_backend_cls,), {"get_builder_cls": lambda: builder_cls}
  934. )
  935. def subclass_attention_backend_with_overrides(
  936. name_prefix: str,
  937. attention_backend_cls: type[AttentionBackend],
  938. overrides: dict[str, Any],
  939. ) -> type[AttentionBackend]:
  940. name: str = name_prefix + attention_backend_cls.__name__ # type: ignore
  941. return type(name, (attention_backend_cls,), overrides)