main
py 369 lines 12.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from dataclasses import dataclass
5 from typing import ClassVar
6
7 import torch
8
9 import vllm.envs as envs
10 from vllm.config import VllmConfig
11 from vllm.config.cache import CacheDType
12 from vllm.logger import init_logger
13 from vllm.model_executor.layers.attention.mla_attention import (
14 MLACommonBackend,
15 MLACommonDecodeMetadata,
16 MLACommonImpl,
17 MLACommonMetadata,
18 MLACommonMetadataBuilder,
19 QueryLenSupport,
20 )
21 from vllm.platforms.interface import DeviceCapability
22 from vllm.utils.math_utils import round_up
23 from vllm.utils.torch_utils import is_quantized_kv_cache
24 from vllm.v1.attention.backend import (
25 AttentionCGSupport,
26 AttentionLayer,
27 AttentionType,
28 MultipleOf,
29 )
30 from vllm.v1.attention.backends.fa_utils import (
31 flash_attn_supports_mla,
32 get_flash_attn_version,
33 )
34 from vllm.v1.kv_cache_interface import AttentionSpec
35 from vllm.vllm_flash_attn import ( # type: ignore[attr-defined]
36 flash_attn_varlen_func,
37 get_scheduler_metadata,
38 )
39
40 logger = init_logger(__name__)
41
42
43 class FlashAttnMLABackend(MLACommonBackend):
44 supported_dtypes: ClassVar[list[torch.dtype]] = [torch.float16, torch.bfloat16]
45 supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [
46 "auto",
47 "float16",
48 "bfloat16",
49 ]
50
51 @staticmethod
52 def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]:
53 return [MultipleOf(16)]
54
55 @staticmethod
56 def get_name() -> str:
57 return "FLASH_ATTN_MLA"
58
59 @classmethod
60 def supports_batch_invariance(cls) -> bool:
61 return True
62
63 @staticmethod
64 def get_builder_cls() -> type["FlashAttnMLAMetadataBuilder"]:
65 return FlashAttnMLAMetadataBuilder
66
67 @staticmethod
68 def get_impl_cls() -> type["FlashAttnMLAImpl"]:
69 return FlashAttnMLAImpl
70
71 @classmethod
72 def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
73 return capability.major == 9
74
75 @classmethod
76 def supports_combination(
77 cls,
78 head_size: int,
79 dtype: torch.dtype,
80 kv_cache_dtype: CacheDType | None,
81 block_size: int | None,
82 use_mla: bool,
83 has_sink: bool,
84 use_sparse: bool,
85 use_mm_prefix: bool,
86 device_capability: DeviceCapability,
87 ) -> str | None:
88 if not flash_attn_supports_mla():
89 return "FlashAttention MLA not supported on this device"
90 return None
91
92
93 @dataclass
94 class FlashAttnMLADecodeMetadata(MLACommonDecodeMetadata):
95 query_start_loc: torch.Tensor
96 max_query_len: int
97 max_seq_len: int
98 scheduler_metadata: torch.Tensor | None = None
99 max_num_splits: int = 0
100
101
102 @dataclass
103 class FlashAttnMLAMetadata(MLACommonMetadata[FlashAttnMLADecodeMetadata]):
104 pass
105
106
107 class FlashAttnMLAMetadataBuilder(MLACommonMetadataBuilder[FlashAttnMLAMetadata]):
108 _cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.UNIFORM_BATCH
109 query_len_support: ClassVar[QueryLenSupport] = QueryLenSupport.VARLEN
110 reorder_batch_threshold: int = 512 # process small prefills with decode pathway
111
112 def __init__(
113 self,
114 kv_cache_spec: AttentionSpec,
115 layer_names: list[str],
116 vllm_config: VllmConfig,
117 device: torch.device,
118 ):
119 interleave_size = vllm_config.parallel_config.cp_kv_cache_interleave_size
120 super().__init__(
121 kv_cache_spec,
122 layer_names,
123 vllm_config,
124 device,
125 FlashAttnMLAMetadata,
126 supports_dcp_with_varlen=(interleave_size == 1),
127 )
128 self.max_num_splits = 0 # No upper bound on the number of splits.
129 self.fa_aot_schedule = get_flash_attn_version() == 3
130
131 self.use_full_cuda_graph = (
132 self.compilation_config.cudagraph_mode.has_full_cudagraphs()
133 )
134 self.max_cudagraph_size = self.compilation_config.max_cudagraph_capture_size
135
136 if self.use_full_cuda_graph and self.fa_aot_schedule:
137 # FA3 scheduler_metadata size: 1 + round_up(batch_size, 4) * 4
138 # The +1 is for the tile_count_semaphore (synchronization).
139 # The 4 slots per batch element (num_prepare_batch_vectors) are:
140 # prepare_varlen + dynamic_split + sort_batches + head_swizzle
141 # See: https://github.com/vllm-project/flash-attention/blob/5824e6e/hopper/flash_api.cpp#L664-L671 # noqa: E501
142 max_batch_size = max(
143 vllm_config.scheduler_config.max_num_seqs,
144 self.max_cudagraph_size or 0,
145 )
146 self.scheduler_metadata = torch.zeros(
147 1 + round_up(max_batch_size, 4) * 4,
148 dtype=torch.int32,
149 device=self.device,
150 )
151 # When using cuda graph, we need to set the upper bound of the
152 # number of splits so that large enough intermediate buffers are
153 # pre-allocated during capture.
154 self.max_num_splits = (
155 vllm_config.attention_config.flash_attn_max_num_splits_for_cuda_graph
156 )
157
158 if envs.VLLM_BATCH_INVARIANT:
159 self.max_num_splits = 1
160
161 def _schedule_decode(
162 self,
163 num_reqs,
164 cu_query_lens,
165 max_query_len,
166 seqlens,
167 max_seq_len,
168 causal,
169 max_num_splits,
170 ):
171 if self.fa_aot_schedule:
172 return get_scheduler_metadata(
173 batch_size=num_reqs,
174 max_seqlen_q=max_query_len,
175 max_seqlen_k=max_seq_len,
176 num_heads_q=self.num_heads * self.dcp_world_size,
177 num_heads_kv=1,
178 headdim=self.mla_dims.qk_rope_head_dim,
179 cache_seqlens=seqlens,
180 qkv_dtype=self.kv_cache_spec.dtype,
181 headdim_v=self.mla_dims.kv_lora_rank,
182 page_size=self.page_size,
183 cu_seqlens_q=cu_query_lens,
184 causal=causal,
185 num_splits=max_num_splits,
186 )
187 return None
188
189 def _build_decode(
190 self,
191 block_table_tensor: torch.Tensor,
192 seq_lens_device: torch.Tensor,
193 max_seq_len: int,
194 query_start_loc_cpu: torch.Tensor,
195 query_start_loc_device: torch.Tensor,
196 num_decode_tokens: int,
197 max_query_len: int,
198 dcp_tot_seq_lens_device: torch.Tensor | None,
199 ) -> FlashAttnMLADecodeMetadata:
200 # Measured, not the promised bound: _schedule_decode asserts metadata size
201 # against a persistent buffer.
202 query_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]
203 max_query_len = query_lens_cpu.max().item()
204
205 # For Flash Attention MLA + full cudagraph
206 max_num_splits = 0
207 if (
208 self.use_full_cuda_graph
209 and self.max_cudagraph_size is not None
210 and num_decode_tokens <= self.max_cudagraph_size
211 ):
212 # NOTE(woosuk): Setting num_splits > 1 may increase the memory
213 # usage, because the intermediate buffers of size [num_splits,
214 # num_heads, num_tokens, head_size] are allocated. Therefore,
215 # we only set num_splits when using cuda graphs.
216 max_num_splits = self.max_num_splits
217
218 if envs.VLLM_BATCH_INVARIANT:
219 max_num_splits = 1
220
221 scheduler_metadata = self._schedule_decode(
222 num_reqs=seq_lens_device.shape[0],
223 cu_query_lens=query_start_loc_device,
224 max_query_len=max_query_len,
225 seqlens=seq_lens_device,
226 max_seq_len=max_seq_len,
227 causal=True,
228 max_num_splits=max_num_splits,
229 )
230
231 if self.use_full_cuda_graph and scheduler_metadata is not None:
232 n = scheduler_metadata.shape[0]
233 # Ensure the persistent buffer is large enough
234 assert n <= self.scheduler_metadata.shape[0], (
235 f"Scheduler metadata size {n} exceeds buffer size "
236 f"{self.scheduler_metadata.shape[0]}"
237 )
238 self.scheduler_metadata[:n] = scheduler_metadata
239 # NOTE(woosuk): We should zero out the rest of the scheduler
240 # metadata to guarantee the correctness. Otherwise, some thread
241 # blocks may use the invalid scheduler metadata and overwrite the
242 # output buffer.
243 self.scheduler_metadata[n:] = 0
244 scheduler_metadata = self.scheduler_metadata[:n]
245
246 metadata = FlashAttnMLADecodeMetadata(
247 block_table=block_table_tensor,
248 seq_lens=seq_lens_device,
249 query_start_loc=query_start_loc_device,
250 max_query_len=max_query_len,
251 max_seq_len=max_seq_len,
252 scheduler_metadata=scheduler_metadata,
253 max_num_splits=max_num_splits,
254 dcp_tot_seq_lens=dcp_tot_seq_lens_device,
255 )
256 return metadata
257
258
259 class FlashAttnMLAImpl(MLACommonImpl[FlashAttnMLAMetadata]):
260 can_return_lse_for_decode: bool = True
261 supports_dcp: bool = True
262
263 def __init__(
264 self,
265 num_heads: int,
266 head_size: int,
267 scale: float,
268 num_kv_heads: int,
269 alibi_slopes: list[float] | None,
270 sliding_window: int | None,
271 kv_cache_dtype: str,
272 logits_soft_cap: float | None,
273 attn_type: str,
274 kv_sharing_target_layer_name: str | None,
275 # MLA Specific Arguments
276 **mla_args,
277 ) -> None:
278 super().__init__(
279 num_heads,
280 head_size,
281 scale,
282 num_kv_heads,
283 alibi_slopes,
284 sliding_window,
285 kv_cache_dtype,
286 logits_soft_cap,
287 attn_type,
288 kv_sharing_target_layer_name,
289 **mla_args,
290 )
291
292 assert flash_attn_supports_mla(), "FlashAttnMLA is not supported on this device"
293
294 unsupported_features = [alibi_slopes, sliding_window, logits_soft_cap]
295 if any(unsupported_features):
296 raise NotImplementedError(
297 "FlashAttnMLAImpl does not support one of the following: "
298 "alibi_slopes, sliding_window, logits_soft_cap"
299 )
300
301 if attn_type != AttentionType.DECODER:
302 raise NotImplementedError(
303 "Encoder self-attention and "
304 "encoder/decoder cross-attention "
305 "are not implemented for "
306 "FlashAttnMLAImpl"
307 )
308
309 if is_quantized_kv_cache(self.kv_cache_dtype):
310 raise NotImplementedError(
311 "FlashAttnMLA V1 with FP8 KV cache not yet supported"
312 )
313
314 def forward_mqa(
315 self,
316 q: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
317 kv_c_and_k_pe_cache: torch.Tensor,
318 attn_metadata: FlashAttnMLAMetadata,
319 layer: AttentionLayer,
320 ) -> tuple[torch.Tensor, torch.Tensor | None]:
321 assert kv_c_and_k_pe_cache.numel() > 0
322 assert attn_metadata.decode is not None
323
324 if type(q) is tuple:
325 q_nope, q_pe = q
326 else:
327 q_nope, q_pe = torch.split(
328 q, [self.kv_lora_rank, self.qk_rope_head_dim], dim=-1
329 )
330
331 if is_quantized_kv_cache(self.kv_cache_dtype):
332 raise NotImplementedError("FP8 FlashAttention MLA not yet supported")
333
334 kv_c_cache = kv_c_and_k_pe_cache[..., : self.kv_lora_rank]
335 k_pe_cache = kv_c_and_k_pe_cache[..., self.kv_lora_rank :]
336
337 # NOTE(matt): During CUDA graph capture, max_query_len can be 0, but the
338 # kernel uses this to calculate grid dimensions. Ensure it's at least 1
339 # to prevent invalid grid configuration during graph capture.
340 max_seqlen_q = max(attn_metadata.decode.max_query_len, 1)
341
342 attn_out = flash_attn_varlen_func(
343 q=q_pe,
344 k=k_pe_cache.unsqueeze(-2), # Add head dim of 1
345 v=kv_c_cache.unsqueeze(-2), # Add head dim of 1
346 q_v=q_nope,
347 max_seqlen_q=max_seqlen_q,
348 cu_seqlens_q=attn_metadata.decode.query_start_loc,
349 max_seqlen_k=attn_metadata.decode.max_seq_len,
350 seqused_k=attn_metadata.decode.seq_lens,
351 block_table=attn_metadata.decode.block_table,
352 softmax_scale=self.scale,
353 causal=True,
354 return_softmax_lse=self.need_to_return_lse_for_decode,
355 fa_version=3, # only version 3 is supported
356 scheduler_metadata=attn_metadata.decode.scheduler_metadata,
357 num_splits=attn_metadata.decode.max_num_splits,
358 cp_world_size=self.dcp_world_size,
359 cp_rank=self.dcp_rank,
360 cp_tot_seqused_k=attn_metadata.decode.dcp_tot_seq_lens,
361 )
362
363 if self.need_to_return_lse_for_decode:
364 o, lse = attn_out
365 # FA returns LSE in shape [ H, B ] but DCP wants [ B, H ]
366 return o, lse.transpose(0, 1) # [ H, B ] -> [ B, H ]
367 else:
368 o = attn_out
369 return o, None