main
py 464 lines 13.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """This file contains ops for ViT attention to be compatible with torch.compile
4 as there are operations here not supported by torch.compile (for instance,
5 `.item()` in flash attention)
6
7 Using these ops and wrapping vision blocks with `torch.compile` can speed up
8 throughput in vision models by ~5% relative on H100, and improve token
9 latencies by ~7% (see qwen2_5_vl for example usage)
10
11 To use these ops, you must have a recent version of PyTorch installed (>= 2.4.0)
12 """
13
14 from typing import Any
15
16 import einops
17 import torch
18 import torch.nn.functional as F
19
20 from vllm._aiter_ops import rocm_aiter_ops
21 from vllm.platforms import current_platform
22 from vllm.utils.gpu_sync_debug import gpu_sync_allowed
23 from vllm.utils.torch_utils import direct_register_custom_op
24
25
26 def flash_attn_maxseqlen_wrapper(
27 q: torch.Tensor,
28 k: torch.Tensor,
29 v: torch.Tensor,
30 batch_size: int,
31 is_rocm_aiter: bool,
32 fa_version: int | None,
33 scale: float | None = None,
34 cu_seqlens: torch.Tensor | None = None,
35 max_seqlen: torch.Tensor | None = None,
36 ) -> torch.Tensor:
37 kwargs: dict[str, Any] = {}
38 if is_rocm_aiter:
39 from aiter import flash_attn_varlen_func
40
41 kwargs["window_size"] = (-1, -1)
42 else:
43 from vllm.v1.attention.backends.fa_utils import flash_attn_varlen_func
44
45 if not current_platform.is_rocm() and fa_version is not None:
46 kwargs["fa_version"] = fa_version
47
48 q_len = q.size(1)
49 if cu_seqlens is None:
50 cu_seqlens = torch.arange(
51 0, (batch_size + 1) * q_len, step=q_len, dtype=torch.int32, device=q.device
52 )
53 if max_seqlen is None:
54 max_seqlen = q_len
55 else:
56 # `flash_attn_varlen_func` needs a Python int for kernel launch bounds.
57 with gpu_sync_allowed():
58 max_seqlen = max_seqlen.item()
59
60 q, k, v = (einops.rearrange(x, "b s ... -> (b s) ...") for x in [q, k, v])
61 supports_out = not current_platform.is_rocm()
62 if supports_out:
63 if current_platform.is_cuda() and torch.cuda.is_current_stream_capturing():
64 # workaround for encoder CUDA-graph replay paddings with NaNs edge case
65 kwargs["out"] = torch.zeros(
66 q.shape[0], q.shape[1], v.shape[-1], dtype=q.dtype, device=q.device
67 )
68 else:
69 kwargs["out"] = torch.empty_like(q, memory_format=torch.contiguous_format)
70 output = flash_attn_varlen_func(
71 q,
72 k,
73 v,
74 cu_seqlens_q=cu_seqlens,
75 cu_seqlens_k=cu_seqlens,
76 max_seqlen_q=max_seqlen,
77 max_seqlen_k=max_seqlen,
78 dropout_p=0.0,
79 causal=False,
80 softmax_scale=scale,
81 **kwargs,
82 )
83 if not supports_out:
84 output = output.contiguous()
85 context_layer = einops.rearrange(output, "(b s) h d -> b s h d", b=batch_size)
86 return context_layer
87
88
89 def flash_attn_maxseqlen_wrapper_fake(
90 q: torch.Tensor,
91 k: torch.Tensor,
92 v: torch.Tensor,
93 batch_size: int,
94 is_rocm_aiter: bool,
95 fa_version: int | None,
96 scale: float | None = None,
97 cu_seqlens: torch.Tensor | None = None,
98 max_seqlen: torch.Tensor | None = None,
99 ) -> torch.Tensor:
100 return torch.empty_like(q, memory_format=torch.contiguous_format)
101
102
103 direct_register_custom_op(
104 op_name="flash_attn_maxseqlen_wrapper",
105 op_func=flash_attn_maxseqlen_wrapper,
106 fake_impl=flash_attn_maxseqlen_wrapper_fake,
107 )
108
109
110 def vit_flash_attn_wrapper(
111 q: torch.Tensor,
112 k: torch.Tensor,
113 v: torch.Tensor,
114 batch_size: int,
115 is_rocm_aiter: bool,
116 fa_version: int | None,
117 scale: float | None = None,
118 cu_seqlens: torch.Tensor | None = None,
119 max_seqlen: torch.Tensor | None = None,
120 ) -> torch.Tensor:
121 return torch.ops.vllm.flash_attn_maxseqlen_wrapper(
122 q,
123 k,
124 v,
125 batch_size,
126 is_rocm_aiter,
127 fa_version,
128 scale,
129 cu_seqlens,
130 max_seqlen,
131 )
132
133
134 def vit_aiter_fp8_attn_wrapper(
135 q: torch.Tensor,
136 k: torch.Tensor,
137 v: torch.Tensor,
138 q_descale: torch.Tensor,
139 k_descale: torch.Tensor,
140 v_descale: torch.Tensor,
141 batch_size: int,
142 output_dtype: torch.dtype,
143 scale: float | None = None,
144 cu_seqlens: torch.Tensor | None = None,
145 max_seqlen: torch.Tensor | None = None,
146 ) -> torch.Tensor:
147 return rocm_aiter_ops.fp8_attn_wrapper(
148 q,
149 k,
150 v,
151 q_descale,
152 k_descale,
153 v_descale,
154 batch_size,
155 output_dtype,
156 scale,
157 cu_seqlens,
158 max_seqlen,
159 )
160
161
162 def triton_attn_wrapper(
163 q: torch.Tensor,
164 k: torch.Tensor,
165 v: torch.Tensor,
166 batch_size: int,
167 scale: float | None = None,
168 cu_seqlens: torch.Tensor | None = None,
169 max_seqlen: torch.Tensor | None = None,
170 ) -> torch.Tensor:
171 from vllm.v1.attention.ops.triton_prefill_attention import context_attention_fwd
172
173 q_len = q.size(1)
174 if cu_seqlens is None:
175 cu_seqlens = torch.arange(
176 0, (batch_size + 1) * q_len, step=q_len, dtype=torch.int32, device=q.device
177 )
178 if max_seqlen is None:
179 max_seqlen = q_len
180 else:
181 # `context_attention_fwd` needs a Python int.
182 with gpu_sync_allowed():
183 max_seqlen = max_seqlen.item()
184
185 q, k, v = (einops.rearrange(x, "b s ... -> (b s) ...") for x in [q, k, v])
186 output = torch.empty_like(q, memory_format=torch.contiguous_format)
187 context_attention_fwd(
188 q,
189 k,
190 v,
191 output,
192 b_start_loc=cu_seqlens[:-1],
193 b_seq_len=cu_seqlens[1:] - cu_seqlens[:-1],
194 max_input_len=max_seqlen,
195 is_causal=False,
196 sliding_window_q=None,
197 sliding_window_k=None,
198 softmax_scale=scale,
199 )
200
201 context_layer = einops.rearrange(output, "(b s) h d -> b s h d", b=batch_size)
202 return context_layer
203
204
205 def triton_attn_wrapper_fake(
206 q: torch.Tensor,
207 k: torch.Tensor,
208 v: torch.Tensor,
209 batch_size: int,
210 scale: float | None = None,
211 cu_seqlens: torch.Tensor | None = None,
212 max_seqlen: torch.Tensor | None = None,
213 ) -> torch.Tensor:
214 return torch.empty_like(q, memory_format=torch.contiguous_format)
215
216
217 direct_register_custom_op(
218 op_name="triton_attn_wrapper",
219 op_func=triton_attn_wrapper,
220 fake_impl=triton_attn_wrapper_fake,
221 )
222
223
224 def vit_triton_attn_wrapper(
225 q: torch.Tensor,
226 k: torch.Tensor,
227 v: torch.Tensor,
228 batch_size: int,
229 scale: float | None = None,
230 cu_seqlens: torch.Tensor | None = None,
231 max_seqlen: torch.Tensor | None = None,
232 ) -> torch.Tensor:
233 return torch.ops.vllm.triton_attn_wrapper(
234 q,
235 k,
236 v,
237 batch_size,
238 scale,
239 cu_seqlens,
240 max_seqlen,
241 )
242
243
244 def apply_sdpa(
245 q: torch.Tensor,
246 k: torch.Tensor,
247 v: torch.Tensor,
248 scale: float | None = None,
249 enable_gqa: bool = False,
250 ) -> torch.Tensor:
251 """Input shape:
252 (batch_size x seq_len x num_heads x head_size)
253 """
254 q, k, v = (einops.rearrange(x, "b s h d -> b h s d") for x in [q, k, v])
255 if current_platform.is_zen_cpu() and hasattr(torch.ops.zentorch, "zentorch_sdpa"):
256 # Schema: (query, key, value, dropout_p=0., is_causal=False, *,
257 # attn_mask=None, scale=None) -> (attention, logsumexp).
258 # No enable_gqa: GQA is inferred from key.size(1) vs query.size(1).
259 output, _ = torch.ops.zentorch.zentorch_sdpa(
260 q, k, v, dropout_p=0.0, is_causal=False, scale=scale
261 )
262 else:
263 output = F.scaled_dot_product_attention(
264 q, k, v, dropout_p=0.0, scale=scale, enable_gqa=enable_gqa
265 )
266 output = einops.rearrange(output, "b h s d -> b s h d ")
267 return output
268
269
270 # TODO: Once we have a torch 2.10, we can use tensor slices
271 # so we won't need to wrap this in custom ops
272 def torch_sdpa_wrapper(
273 q: torch.Tensor,
274 k: torch.Tensor,
275 v: torch.Tensor,
276 scale: float | None = None,
277 cu_seqlens: torch.Tensor | None = None,
278 enable_gqa: bool = False,
279 ) -> torch.Tensor:
280 # Never remove the contiguous logic for ROCm
281 # Without it, hallucinations occur with the backend
282 if current_platform.is_rocm():
283 q = q.contiguous()
284 k = k.contiguous()
285 v = v.contiguous()
286
287 if cu_seqlens is None:
288 return apply_sdpa(q, k, v, scale=scale, enable_gqa=enable_gqa).contiguous()
289
290 outputs = []
291
292 # `torch.split` needs Python int sizes.
293 with gpu_sync_allowed():
294 lens = (cu_seqlens[1:] - cu_seqlens[:-1]).tolist()
295 q_chunks = torch.split(q, lens, dim=1)
296 k_chunks = torch.split(k, lens, dim=1)
297 v_chunks = torch.split(v, lens, dim=1)
298 for q_i, k_i, v_i in zip(q_chunks, k_chunks, v_chunks):
299 output_i = apply_sdpa(q_i, k_i, v_i, scale=scale, enable_gqa=enable_gqa)
300 outputs.append(output_i)
301 context_layer = torch.cat(outputs, dim=1)
302 return context_layer
303
304
305 def torch_sdpa_wrapper_fake(
306 q: torch.Tensor,
307 k: torch.Tensor,
308 v: torch.Tensor,
309 scale: float | None = None,
310 cu_seqlens: torch.Tensor | None = None,
311 enable_gqa: bool = False,
312 ) -> torch.Tensor:
313 return torch.empty_like(q, memory_format=torch.contiguous_format)
314
315
316 direct_register_custom_op(
317 op_name="torch_sdpa_wrapper",
318 op_func=torch_sdpa_wrapper,
319 fake_impl=torch_sdpa_wrapper_fake,
320 )
321
322
323 def vit_torch_sdpa_wrapper(
324 q: torch.Tensor,
325 k: torch.Tensor,
326 v: torch.Tensor,
327 scale: float | None = None,
328 cu_seqlens: torch.Tensor | None = None,
329 enable_gqa: bool = False,
330 ) -> torch.Tensor:
331 return torch.ops.vllm.torch_sdpa_wrapper(
332 q, k, v, scale, cu_seqlens, enable_gqa=enable_gqa
333 )
334
335
336 def flashinfer_wrapper(
337 q: torch.Tensor,
338 k: torch.Tensor,
339 v: torch.Tensor,
340 scale: float,
341 workspace_buffer: torch.Tensor,
342 cu_seqlens: torch.Tensor | None = None,
343 max_seqlen: torch.Tensor | None = None,
344 sequence_lengths: torch.Tensor | None = None,
345 q_scale: torch.Tensor | None = None,
346 k_scale: torch.Tensor | None = None,
347 v_scale: torch.Tensor | None = None,
348 o_data_type: torch.dtype | None = None,
349 ) -> torch.Tensor:
350 from flashinfer.prefill import cudnn_batch_prefill_with_kv_cache
351
352 is_reshaped = q.dim() == 4
353
354 if is_reshaped:
355 reshape_batch_size = q.shape[0]
356 q, k, v = (einops.rearrange(x, "b s ... -> (b s) ...") for x in [q, k, v])
357 # cuDNN <= 9.10.2.21 requires q, k to be contiguous
358 # this comes with no cost for ViTs with RoPE because
359 # RoPE has already made q and k contiguous.
360 q, k = q.contiguous(), k.contiguous()
361
362 assert cu_seqlens is not None
363 assert max_seqlen is not None
364 assert sequence_lengths is not None
365 assert len(cu_seqlens) % 2 == 0, "cu_seqlens must be divisible by 2"
366 cu_seqlength = len(cu_seqlens) // 2
367 batch_offsets_qko = cu_seqlens[:cu_seqlength].view(-1, 1, 1, 1)
368 batch_offsets_v = cu_seqlens[cu_seqlength:].view(-1, 1, 1, 1)
369 sequence_lengths = sequence_lengths.view(-1, 1, 1, 1)
370 # `cudnn_batch_prefill_with_kv_cache` needs Python ints for the
371 # max-token-per-seq bounds.
372 with gpu_sync_allowed():
373 max_seqlen = max_seqlen.item()
374
375 output_buffer = torch.empty_like(
376 q,
377 dtype=o_data_type,
378 memory_format=torch.contiguous_format,
379 )
380 output, _ = cudnn_batch_prefill_with_kv_cache(
381 q,
382 k,
383 v,
384 scale,
385 workspace_buffer,
386 max_token_per_sequence=max_seqlen,
387 max_sequence_kv=max_seqlen,
388 actual_seq_lens_q=sequence_lengths,
389 actual_seq_lens_kv=sequence_lengths,
390 causal=False,
391 return_lse=False,
392 batch_offsets_q=batch_offsets_qko,
393 batch_offsets_k=batch_offsets_qko,
394 batch_offsets_v=batch_offsets_v,
395 batch_offsets_o=batch_offsets_qko,
396 q_scale=q_scale,
397 k_scale=k_scale,
398 v_scale=v_scale,
399 o_data_type=o_data_type,
400 out=output_buffer,
401 )
402
403 if is_reshaped:
404 output = einops.rearrange(output, "(b s) h d -> b s h d", b=reshape_batch_size)
405
406 return output
407
408
409 def vit_flashinfer_wrapper_fake(
410 q: torch.Tensor,
411 k: torch.Tensor,
412 v: torch.Tensor,
413 scale: float,
414 workspace_buffer: torch.Tensor,
415 cu_seqlens: torch.Tensor | None = None,
416 max_seqlen: torch.Tensor | None = None,
417 sequence_lengths: torch.Tensor | None = None,
418 q_scale: torch.Tensor | None = None,
419 k_scale: torch.Tensor | None = None,
420 v_scale: torch.Tensor | None = None,
421 o_data_type: torch.dtype | None = None,
422 ) -> torch.Tensor:
423 return torch.empty_like(
424 q,
425 dtype=o_data_type,
426 memory_format=torch.contiguous_format,
427 )
428
429
430 direct_register_custom_op(
431 op_name="flashinfer_wrapper",
432 op_func=flashinfer_wrapper,
433 fake_impl=vit_flashinfer_wrapper_fake,
434 )
435
436
437 def vit_flashinfer_wrapper(
438 q: torch.Tensor,
439 k: torch.Tensor,
440 v: torch.Tensor,
441 scale: float,
442 workspace_buffer: torch.Tensor,
443 cu_seqlens: torch.Tensor | None = None,
444 max_seqlen: torch.Tensor | None = None,
445 sequence_lengths: torch.Tensor | None = None,
446 q_scale: torch.Tensor | None = None,
447 k_scale: torch.Tensor | None = None,
448 v_scale: torch.Tensor | None = None,
449 o_data_type: torch.dtype | None = None,
450 ) -> torch.Tensor:
451 return torch.ops.vllm.flashinfer_wrapper(
452 q,
453 k,
454 v,
455 scale,
456 workspace_buffer,
457 cu_seqlens,
458 max_seqlen,
459 sequence_lengths,
460 q_scale,
461 k_scale,
462 v_scale,
463 o_data_type,
464 )