main
py 436 lines 14.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from __future__ import annotations
4
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from functools import cache
8 from typing import TYPE_CHECKING, Any
9
10 import torch
11
12 from vllm.distributed import get_tensor_model_parallel_world_size
13 from vllm.model_executor.warmup.jit_warmup import (
14 VllmJitKernel,
15 WarmupIntRange,
16 zip_inputs,
17 )
18 from vllm.platforms import current_platform
19 from vllm.utils.torch_utils import kv_cache_dtype_str_to_dtype
20
21 if TYPE_CHECKING:
22 from vllm.config import VllmConfig
23
24
25 def bucket_max_seqlen_q(max_seqlen_q: int) -> int:
26 """Round the FA4 scheduling bound up to a power of two."""
27 return 1 << max(0, max_seqlen_q - 1).bit_length()
28
29
30 @cache
31 def _use_sheared_bias() -> bool:
32 capability = current_platform.get_device_capability()
33 return capability is not None and capability.major in (10, 11)
34
35
36 @cache
37 def _get_score_mod(rel_extent: int) -> Callable:
38 """Return the score modification that adds Inkling relative bias."""
39 import cutlass.cute as cute
40 from cutlass.cute import Float32
41
42 from vllm.vllm_flash_attn.cute.seqlen_info import SeqlenInfoQK
43
44 @cute.jit
45 def score_mod_rel_bias(
46 scores: cute.TensorSSA,
47 b_idx: cute.TensorSSA,
48 h_idx: cute.TensorSSA,
49 q_idx: cute.TensorSSA,
50 kv_idx: cute.TensorSSA,
51 seqlen_info: SeqlenInfoQK,
52 aux_tensors: list[cute.Tensor],
53 ) -> cute.TensorSSA:
54 rel_logits = aux_tensors[0]
55
56 seqlen_local_offset = seqlen_info.seqlen_k - seqlen_info.seqlen_q
57 rel_dist = (q_idx + seqlen_local_offset) - kv_idx
58 global_q_idx = seqlen_info.offset_q + q_idx
59
60 rel_dist_0 = rel_dist[0]
61 rel_idx = rel_dist_0 if rel_dist_0 >= 0 else 0
62 rel_idx = rel_idx if rel_idx < rel_extent else (rel_extent - 1)
63
64 rel_bias = rel_logits[global_q_idx[0], h_idx[0], rel_idx]
65 rel_bias = Float32(rel_bias) if rel_dist_0 == rel_idx else Float32(0.0)
66 return scores + rel_bias
67
68 return score_mod_rel_bias
69
70
71 def inkling_fa4_num_splits(
72 *,
73 is_local: bool,
74 batch_size: int,
75 max_query_len: int,
76 num_heads: int,
77 num_kv_heads: int,
78 max_kv_len: int,
79 ) -> int:
80 """Return the split-KV cap for Inkling relative attention."""
81 capability = current_platform.get_device_capability()
82 if capability is not None and capability.major == 9:
83 return 1
84 if is_local:
85 return 1
86
87 q_rows = max_query_len * (num_heads // num_kv_heads)
88 q_tiles = (q_rows + 255) // 256
89 base_ctas = batch_size * num_kv_heads * q_tiles
90 # Shearing makes split/combine overhead more visible. Multi-tile causal
91 # prefill saturates around 64 CTAs. Batch-1 decode at very long context is
92 # memory-bound and uses a TP-specific cap measured through 1M KV tokens.
93 target_ctas = (
94 256 if q_tiles == 1 and batch_size == 1 else (128 if q_tiles == 1 else 64)
95 )
96 max_splits = 128
97 if q_tiles == 1 and batch_size == 1:
98 if num_kv_heads == 8:
99 max_splits = 16
100 elif num_kv_heads == 4 or max_kv_len <= 8192:
101 max_splits = 32
102 elif max_kv_len <= 65536:
103 max_splits = 64
104 else:
105 max_splits = 128
106 return max(
107 1,
108 min(target_ctas // base_ctas, max_splits, (max_kv_len + 127) // 128),
109 )
110
111
112 def _num_warps_bucket(num_reqs: int) -> int:
113 num_warps = min((num_reqs + 30) // 31, 32)
114 return 1 << (num_warps - 1).bit_length()
115
116
117 class InklingFA4RelAttentionKernel(
118 VllmJitKernel["InklingFA4RelAttentionKernel.CompileKey"]
119 ):
120 @dataclass(frozen=True)
121 class CompileKey:
122 is_local: bool
123 num_heads: int
124 num_kv_heads: int
125 head_dim: int
126 rel_extent: int
127 dtype: torch.dtype
128 kv_dtype: torch.dtype
129 block_size: int
130 window_size: tuple[int, int]
131 max_seqlen_q: int
132 num_splits: int
133 num_warps_bucket: int | None
134 large_num_reqs: bool
135
136 @staticmethod
137 def kernel(
138 q: torch.Tensor,
139 key_cache: torch.Tensor,
140 value_cache: torch.Tensor,
141 *,
142 block_table: torch.Tensor,
143 cache_seqlens: torch.Tensor,
144 cu_seqlens_q: torch.Tensor,
145 max_seqlen_q: int,
146 softmax_scale: float,
147 causal: bool,
148 window_size: tuple[int, int],
149 rel_extent: int,
150 rel_logits: torch.Tensor,
151 num_splits: int = 32,
152 out: torch.Tensor | None = None,
153 ) -> torch.Tensor:
154 """Paged varlen FA4 over the bound K/V cache with the Inkling relative bias.
155
156 ``q`` is ``(num_tokens, num_heads, head_dim)``; ``key_cache`` / ``value_cache``
157 are the paged caches ``(num_blocks, block_size, num_kv_heads, head_dim)``;
158 ``block_table`` is the per-request page table and ``cache_seqlens`` the
159 per-request KV lengths (``seqused_k``). ``rel_logits`` is
160 ``(num_tokens, num_heads, rel_extent)``.
161
162 Hopper uses standard FA4's score-mod gather. Blackwell uses tml-fa4's
163 sheared relative-bias layout.
164 """
165 # cute uses (None, None) to mean "no window".
166 cute_window = (None, None) if window_size == (-1, -1) else window_size
167
168 rel_logits = rel_logits.contiguous()
169 flash_attn_varlen_func: Callable[..., Any]
170 if _use_sheared_bias():
171 from vllm.third_party.tml_fa4 import (
172 flash_attn_varlen_func as tml_flash_attn_varlen_func,
173 )
174
175 flash_attn_varlen_func = tml_flash_attn_varlen_func
176 bias_kwargs: dict[str, Any] = {"rel_bias": rel_logits}
177 else:
178 from vllm.vllm_flash_attn.cute import (
179 flash_attn_varlen_func as cute_flash_attn_varlen_func,
180 )
181
182 flash_attn_varlen_func = cute_flash_attn_varlen_func
183 bias_kwargs = {
184 "score_mod": _get_score_mod(rel_extent),
185 "aux_tensors": [rel_logits],
186 }
187
188 ret = flash_attn_varlen_func(
189 q=q,
190 k=key_cache,
191 v=value_cache,
192 cu_seqlens_q=cu_seqlens_q,
193 seqused_k=cache_seqlens,
194 max_seqlen_q=max_seqlen_q,
195 page_table=block_table,
196 softmax_scale=softmax_scale,
197 causal=causal,
198 window_size=cute_window,
199 num_splits=num_splits,
200 return_lse=False,
201 out=out,
202 **bias_kwargs,
203 )
204 if isinstance(ret, tuple):
205 return ret[0]
206 return ret
207
208 def dispatch( # type: ignore[override]
209 self,
210 *,
211 is_local: bool,
212 num_heads: int,
213 num_kv_heads: int,
214 head_dim: int,
215 rel_extent: int,
216 dtype: torch.dtype,
217 kv_dtype: torch.dtype,
218 block_size: int,
219 window_size: tuple[int, int],
220 max_kv_len: int,
221 query_len: int,
222 num_reqs: int,
223 ) -> CompileKey:
224 max_seqlen_q = bucket_max_seqlen_q(query_len)
225 num_splits = inkling_fa4_num_splits(
226 is_local=is_local,
227 batch_size=num_reqs,
228 max_query_len=max_seqlen_q,
229 num_heads=num_heads,
230 num_kv_heads=num_kv_heads,
231 max_kv_len=max_kv_len,
232 )
233 return self.CompileKey(
234 is_local=is_local,
235 num_heads=num_heads,
236 num_kv_heads=num_kv_heads,
237 head_dim=head_dim,
238 rel_extent=rel_extent,
239 dtype=dtype,
240 kv_dtype=kv_dtype,
241 block_size=block_size,
242 window_size=window_size,
243 max_seqlen_q=max_seqlen_q,
244 num_splits=num_splits,
245 num_warps_bucket=(_num_warps_bucket(num_reqs) if num_splits > 1 else None),
246 large_num_reqs=num_reqs > 1024,
247 )
248
249 def _is_valid_warmup_dispatch(
250 self,
251 *,
252 query_len: int,
253 num_reqs: int,
254 max_num_batched_tokens: int,
255 ) -> bool:
256 return query_len + num_reqs <= max_num_batched_tokens + 1
257
258 def get_warmup_keys(self, vllm_config: VllmConfig) -> list[CompileKey]:
259 max_num_reqs = vllm_config.scheduler_config.max_num_seqs
260 max_num_batched_tokens = vllm_config.scheduler_config.max_num_batched_tokens
261 if max_num_reqs <= 0 or max_num_batched_tokens <= 0:
262 return []
263
264 hf_config = vllm_config.model_config.hf_config
265 get_text_config = getattr(hf_config, "get_text_config", None)
266 config = get_text_config() if callable(get_text_config) else hf_config
267
268 tp_size = get_tensor_model_parallel_world_size()
269 dtype = vllm_config.model_config.dtype
270 kv_dtype = kv_cache_dtype_str_to_dtype(
271 vllm_config.cache_config.cache_dtype,
272 vllm_config.model_config,
273 )
274 block_size = vllm_config.cache_config.block_size
275 local_extent = config.sliding_window_size
276
277 global_num_kv_heads = config.num_key_value_heads
278 local_num_kv_heads = config.swa_num_key_value_heads
279 assert config.num_attention_heads % tp_size == 0
280 assert config.swa_num_attention_heads % tp_size == 0
281 if global_num_kv_heads >= tp_size:
282 assert global_num_kv_heads % tp_size == 0
283 else:
284 assert tp_size % global_num_kv_heads == 0
285 if local_num_kv_heads >= tp_size:
286 assert local_num_kv_heads % tp_size == 0
287 else:
288 assert tp_size % local_num_kv_heads == 0
289
290 return self._trace_dispatch(self.dispatch)(
291 zip_inputs(
292 dict(
293 is_local=False,
294 num_heads=config.num_attention_heads // tp_size,
295 num_kv_heads=max(1, global_num_kv_heads // tp_size),
296 head_dim=config.head_dim,
297 rel_extent=config.rel_extent,
298 dtype=dtype,
299 kv_dtype=kv_dtype,
300 block_size=block_size,
301 window_size=(-1, -1),
302 max_kv_len=vllm_config.model_config.max_model_len,
303 ),
304 dict(
305 is_local=True,
306 num_heads=config.swa_num_attention_heads // tp_size,
307 num_kv_heads=max(1, local_num_kv_heads // tp_size),
308 head_dim=config.swa_head_dim,
309 rel_extent=local_extent,
310 dtype=dtype,
311 kv_dtype=kv_dtype,
312 block_size=block_size,
313 window_size=(local_extent - 1, 0),
314 max_kv_len=local_extent,
315 ),
316 ),
317 query_len=WarmupIntRange(
318 1,
319 max_num_batched_tokens + 1,
320 advance=lambda value: bucket_max_seqlen_q(value) + 1,
321 ),
322 num_reqs=WarmupIntRange(1, max_num_reqs + 1),
323 max_num_batched_tokens=max_num_batched_tokens,
324 _when=self._is_valid_warmup_dispatch,
325 )
326
327 def compile(self, compile_key: CompileKey) -> None:
328 from torch._subclasses.fake_tensor import FakeTensorMode
329
330 if compile_key.num_splits == 1 or compile_key.num_warps_bucket is None:
331 num_reqs = 1025 if compile_key.large_num_reqs else 1
332 else:
333 min_num_warps = (
334 1
335 if compile_key.num_warps_bucket == 1
336 else compile_key.num_warps_bucket // 2 + 1
337 )
338 min_num_reqs = (min_num_warps - 1) * 31 + 1
339 num_reqs = max(
340 min_num_reqs,
341 1025 if compile_key.large_num_reqs else 1,
342 )
343
344 with FakeTensorMode():
345 device = torch.accelerator.current_accelerator()
346 total_q = compile_key.max_seqlen_q + num_reqs - 1
347 q = torch.empty(
348 total_q,
349 compile_key.num_heads,
350 compile_key.head_dim,
351 dtype=compile_key.dtype,
352 device=device,
353 )
354 kv = torch.empty(
355 1,
356 2,
357 compile_key.block_size,
358 compile_key.num_kv_heads,
359 compile_key.head_dim,
360 dtype=compile_key.kv_dtype,
361 device=device,
362 )
363 key_cache, value_cache = kv.unbind(1)
364 self.kernel(
365 q,
366 key_cache,
367 value_cache,
368 block_table=torch.empty(
369 num_reqs,
370 1,
371 dtype=torch.int32,
372 device=device,
373 ),
374 cache_seqlens=torch.empty(
375 num_reqs,
376 dtype=torch.int32,
377 device=device,
378 ),
379 cu_seqlens_q=torch.empty(
380 num_reqs + 1,
381 dtype=torch.int32,
382 device=device,
383 ),
384 max_seqlen_q=compile_key.max_seqlen_q,
385 softmax_scale=compile_key.head_dim**-1,
386 causal=True,
387 window_size=compile_key.window_size,
388 rel_extent=compile_key.rel_extent,
389 rel_logits=torch.empty(
390 total_q,
391 compile_key.num_heads,
392 compile_key.rel_extent,
393 dtype=compile_key.dtype,
394 device=device,
395 ),
396 num_splits=compile_key.num_splits,
397 out=torch.empty_like(q),
398 )
399
400 def __call__(
401 self,
402 q: torch.Tensor,
403 key_cache: torch.Tensor,
404 value_cache: torch.Tensor,
405 *,
406 block_table: torch.Tensor,
407 cache_seqlens: torch.Tensor,
408 cu_seqlens_q: torch.Tensor,
409 max_seqlen_q: int,
410 softmax_scale: float,
411 causal: bool,
412 window_size: tuple[int, int],
413 rel_extent: int,
414 rel_logits: torch.Tensor,
415 num_splits: int = 32,
416 out: torch.Tensor | None = None,
417 ) -> torch.Tensor:
418 return self.kernel(
419 q,
420 key_cache,
421 value_cache,
422 block_table=block_table,
423 cache_seqlens=cache_seqlens,
424 cu_seqlens_q=cu_seqlens_q,
425 max_seqlen_q=max_seqlen_q,
426 softmax_scale=softmax_scale,
427 causal=causal,
428 window_size=window_size,
429 rel_extent=rel_extent,
430 rel_logits=rel_logits,
431 num_splits=num_splits,
432 out=out,
433 )
434
435
436 _INKLING_FA4_REL_ATTENTION_KERNEL = InklingFA4RelAttentionKernel()