main
py 248 lines 8.53 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """FlashInfer backend for MLA prefill."""
4
5 from typing import TYPE_CHECKING, ClassVar
6
7 import torch
8
9 import vllm.envs as envs
10 from vllm.utils.gpu_sync_debug import gpu_sync_allowed
11 from vllm.v1.attention.backends.mla.prefill.base import (
12 MLADimensions,
13 MLAPrefillBackend,
14 )
15 from vllm.v1.attention.backends.utils import (
16 PerLayerParameters,
17 get_per_layer_parameters,
18 infer_global_hyperparameters,
19 log2_lse_to_ln,
20 )
21 from vllm.v1.worker.workspace import current_workspace_manager
22
23 if TYPE_CHECKING:
24 from vllm.config import VllmConfig
25 from vllm.model_executor.layers.attention.mla_attention import (
26 MLACommonPrefillMetadata,
27 )
28 from vllm.platforms.interface import DeviceCapability
29
30 try:
31 from flashinfer import BatchPrefillWithRaggedKVCacheWrapper
32 except ImportError:
33 BatchPrefillWithRaggedKVCacheWrapper = object # type: ignore[misc,assignment]
34
35 _DEFAULT_NUM_CHUNKS = 32
36
37
38 class FlashInferPrefillBackend(MLAPrefillBackend):
39 """FlashInfer backend for MLA prefill."""
40
41 supported_mla_dimensions: ClassVar[list[MLADimensions]] = [
42 MLADimensions(
43 qk_nope_head_dim=128,
44 qk_rope_head_dim=64,
45 v_head_dim=128,
46 ),
47 ]
48
49 @staticmethod
50 def get_name() -> str:
51 return "FLASHINFER"
52
53 @classmethod
54 def supports_compute_capability(cls, device_capability: "DeviceCapability") -> bool:
55 return device_capability.major == 10
56
57 @classmethod
58 def is_available(cls) -> bool:
59 try:
60 from flashinfer import (
61 BatchPrefillWithRaggedKVCacheWrapper, # noqa: F401
62 )
63
64 return True
65 except ImportError:
66 return False
67
68 def __init__(
69 self,
70 num_heads: int,
71 scale: float,
72 kv_lora_rank: int,
73 qk_nope_head_dim: int,
74 qk_rope_head_dim: int,
75 v_head_dim: int,
76 vllm_config: "VllmConfig",
77 ) -> None:
78 super().__init__(
79 num_heads=num_heads,
80 scale=scale,
81 kv_lora_rank=kv_lora_rank,
82 qk_nope_head_dim=qk_nope_head_dim,
83 qk_rope_head_dim=qk_rope_head_dim,
84 v_head_dim=v_head_dim,
85 vllm_config=vllm_config,
86 )
87
88 self._prefill_main: BatchPrefillWithRaggedKVCacheWrapper | None = None
89 self._prefill_chunks: list[BatchPrefillWithRaggedKVCacheWrapper] = []
90 self._global_hyperparameters: PerLayerParameters | None = None
91 (self._workspace_buffer,) = current_workspace_manager().get_simultaneous(
92 ((envs.VLLM_FLASHINFER_WORKSPACE_BUFFER_SIZE,), torch.uint8),
93 )
94
95 def _ensure_chunks(
96 self,
97 num_chunks: int,
98 workspace_buffer: torch.Tensor,
99 ) -> None:
100 if len(self._prefill_chunks) < num_chunks:
101 for _ in range(len(self._prefill_chunks), num_chunks):
102 self._prefill_chunks.append(
103 BatchPrefillWithRaggedKVCacheWrapper(
104 workspace_buffer, "NHD", backend="cutlass"
105 )
106 )
107
108 def _resolve_global_hyperparameters(self) -> PerLayerParameters:
109 if self._global_hyperparameters is not None:
110 return self._global_hyperparameters
111
112 from vllm.model_executor.layers.attention.mla_attention import (
113 MLACommonImpl,
114 )
115 from vllm.model_executor.layers.attention_layer_base import (
116 AttentionLayerBase,
117 )
118
119 # Match any layer with an MLA impl, not just the MLAAttention wrapper:
120 # fused MLA modules (Kimi-K3's MultiHeadLatentAttention) register a
121 # different layer type. Keying on impl also excludes linear/KDA layers.
122 forward_context = self.vllm_config.compilation_config.static_forward_context
123 layer_names = [
124 name
125 for name, layer in forward_context.items()
126 if isinstance(layer, AttentionLayerBase)
127 and isinstance(getattr(layer, "impl", None), MLACommonImpl)
128 ]
129
130 self._global_hyperparameters = infer_global_hyperparameters(
131 get_per_layer_parameters(
132 self.vllm_config,
133 layer_names,
134 MLACommonImpl, # type: ignore[type-abstract]
135 )
136 )
137 return self._global_hyperparameters
138
139 def prepare_metadata(
140 self,
141 prefill_metadata: "MLACommonPrefillMetadata",
142 ) -> None:
143 global_hyperparameters = self._resolve_global_hyperparameters()
144 qo_indptr = prefill_metadata.query_start_loc
145 has_context = prefill_metadata.chunked_context is not None
146 if self._prefill_main is None:
147 self._prefill_main = BatchPrefillWithRaggedKVCacheWrapper(
148 self._workspace_buffer, "NHD", backend="cutlass"
149 )
150 self._ensure_chunks(_DEFAULT_NUM_CHUNKS, self._workspace_buffer)
151
152 if has_context:
153 chunked_context = prefill_metadata.chunked_context
154 assert chunked_context is not None
155 self._ensure_chunks(len(chunked_context.chunks), self._workspace_buffer)
156
157 num_qo_heads = self.num_heads
158 num_kv_heads = num_qo_heads
159
160 head_dim_qk = self.qk_nope_head_dim + self.qk_rope_head_dim
161 head_dim_vo = self.v_head_dim
162 kv_indptr = qo_indptr.clone()
163
164 assert self._prefill_main is not None
165 # FlashInfer's plan() currently copies GPU indptrs to the CPU.
166 # This will be fixed by https://github.com/vllm-project/vllm/pull/52657.
167 with gpu_sync_allowed():
168 self._prefill_main.plan(
169 qo_indptr=qo_indptr,
170 kv_indptr=kv_indptr,
171 num_qo_heads=num_qo_heads,
172 num_kv_heads=num_kv_heads,
173 head_dim_qk=head_dim_qk,
174 head_dim_vo=head_dim_vo,
175 causal=True,
176 sm_scale=global_hyperparameters.sm_scale,
177 window_left=global_hyperparameters.window_left,
178 logits_soft_cap=global_hyperparameters.logits_soft_cap,
179 q_data_type=prefill_metadata.q_data_type,
180 o_data_type=prefill_metadata.output_dtype,
181 )
182
183 if has_context:
184 chunked_context = prefill_metadata.chunked_context
185 assert chunked_context is not None
186 for chunk in chunked_context.chunks:
187 with gpu_sync_allowed():
188 self._prefill_chunks[chunk.index].plan(
189 qo_indptr=chunk.query_start_loc,
190 kv_indptr=chunk.cu_seq_lens,
191 num_qo_heads=num_qo_heads,
192 num_kv_heads=num_kv_heads,
193 head_dim_qk=head_dim_qk,
194 head_dim_vo=head_dim_vo,
195 causal=False,
196 sm_scale=global_hyperparameters.sm_scale,
197 window_left=global_hyperparameters.window_left,
198 logits_soft_cap=global_hyperparameters.logits_soft_cap,
199 q_data_type=prefill_metadata.q_data_type,
200 o_data_type=prefill_metadata.output_dtype,
201 )
202
203 def supports_out(self) -> bool:
204 # Planned with head_dim_vo == v_head_dim, so the output is unpadded.
205 return True
206
207 def run_prefill_new_tokens(
208 self,
209 q: torch.Tensor,
210 k: torch.Tensor,
211 v: torch.Tensor,
212 return_softmax_lse: bool,
213 out: torch.Tensor | None = None,
214 output_scale: torch.Tensor | None = None,
215 ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:
216 assert self._prefill_main is not None
217
218 ret = self._prefill_main.run(
219 q=q,
220 k=k,
221 v=v,
222 out=out,
223 return_lse=return_softmax_lse,
224 )
225
226 if isinstance(ret, tuple):
227 # Convert from (q_len, num_heads) to (num_heads, q_len)
228 return ret[0], log2_lse_to_ln(ret[1].transpose(0, 1))
229 return ret
230
231 def run_prefill_context_chunk(
232 self,
233 chunk: "MLACommonPrefillMetadata.ContextChunk",
234 q: torch.Tensor,
235 k: torch.Tensor,
236 v: torch.Tensor,
237 out: torch.Tensor | None = None,
238 ) -> tuple[torch.Tensor, torch.Tensor]:
239 attn_out, lse = self._prefill_chunks[chunk.index].run(
240 q=q,
241 k=k,
242 v=v,
243 out=out,
244 return_lse=True,
245 )
246
247 # Convert from (q_len, num_heads) to (num_heads, q_len)
248 return attn_out, log2_lse_to_ln(lse.transpose(0, 1))