A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 14, 05:30 UTC
[V0 Deprecation] Remove V0 FlashInfer attention backend (#22776)
Signed-off-by: Woosuk Kwon <woosuk.kwon@berkeley.edu>
Woosuk Kwon committed
Aug 18, 2025 at 19:54 UTC
14006840eacf74f83e0d486eca6a24e75cafa6d3
8 files changed
+9
-1133
tests/basic_correctness/test_basic_correctness.py
+1
-8
@@ -12,7 +12,6 @@ import pytest
12
import torch
13
14
from vllm import LLM, envs
15
-from vllm.platforms import current_platform
15
from vllm.v1.engine.llm_engine import LLMEngine as LLMEngineV1
16
17
from ..conftest import HfRunner, VllmRunner
@@ -78,11 +77,7 @@ def test_models(
77
"VLLM_USE_V1") and envs.VLLM_USE_V1:
78
pytest.skip("enable_prompt_embeds is not supported in v1.")
79
81
- if backend == "FLASHINFER" and current_platform.is_rocm():
82
- pytest.skip("Flashinfer does not support ROCm/HIP.")
83
-
84
- if backend in ("XFORMERS",
85
- "FLASHINFER") and model == "google/gemma-2-2b-it":
80
+ if backend == "XFORMERS" and model == "google/gemma-2-2b-it":
81
pytest.skip(
82
f"{backend} does not support gemma2 with full context length.")
83
@@ -141,8 +136,6 @@ def test_models(
136
("meta-llama/Llama-3.2-1B-Instruct", "mp", "", "L4", {}),
137
("distilbert/distilgpt2", "ray", "", "A100", {}),
138
("distilbert/distilgpt2", "mp", "", "A100", {}),
144
- ("distilbert/distilgpt2", "mp", "FLASHINFER", "A100", {}),
145
- ("meta-llama/Meta-Llama-3-8B", "ray", "FLASHINFER", "A100", {}),
139
])
140
@pytest.mark.parametrize("enable_prompt_embeds", [True, False])
141
def test_models_distributed(
tests/compile/test_basic_correctness.py
+1
-1
@@ -34,7 +34,7 @@ class TestSetting:
34
model_args=["--max-model-len", "2048"],
35
pp_size=2,
36
tp_size=2,
37
- attn_backend="FLASHINFER",
37
+ attn_backend="FLASH_ATTN",
38
method="generate",
39
fullgraph=True,
40
),
tests/core/block/e2e/test_correctness_sliding_window.py
+2
-6
@@ -32,7 +32,7 @@ BLOCK_SIZE = 16
32
@pytest.mark.parametrize("test_llm_kwargs", [{}])
33
@pytest.mark.parametrize("batch_size", [5])
34
@pytest.mark.parametrize("seed", [1])
35
-@pytest.mark.parametrize("backend", ["FLASH_ATTN", "FLASHINFER", "XFORMERS"])
35
+@pytest.mark.parametrize("backend", ["FLASH_ATTN", "XFORMERS"])
36
def test_sliding_window_retrieval(baseline_llm_generator, test_llm_generator,
37
batch_size, seed, backend, monkeypatch):
38
"""
@@ -43,8 +43,6 @@ def test_sliding_window_retrieval(baseline_llm_generator, test_llm_generator,
43
44
Additionally, we compare the results of the v1 and v2 managers.
45
"""
46
- if backend == "FLASHINFER" and current_platform.is_rocm():
47
- pytest.skip("Flashinfer does not support ROCm/HIP.")
46
if backend == "XFORMERS" and current_platform.is_rocm():
47
pytest.skip("Xformers does not support ROCm/HIP.")
48
@@ -96,7 +94,7 @@ def test_sliding_window_retrieval(baseline_llm_generator, test_llm_generator,
94
@pytest.mark.parametrize("test_llm_kwargs", [{"enable_chunked_prefill": True}])
95
@pytest.mark.parametrize("batch_size", [5])
96
@pytest.mark.parametrize("seed", [1])
99
-@pytest.mark.parametrize("backend", ["FLASH_ATTN", "FLASHINFER", "XFORMERS"])
97
+@pytest.mark.parametrize("backend", ["FLASH_ATTN", "XFORMERS"])
98
def test_sliding_window_chunked_prefill(test_llm_generator, batch_size, seed,
99
backend, monkeypatch):
100
"""
@@ -107,8 +105,6 @@ def test_sliding_window_chunked_prefill(test_llm_generator, batch_size, seed,
105
The results with and without chunked prefill are not the same due to
106
numerical instabilities.
107
"""
110
- if backend == "FLASHINFER" and current_platform.is_rocm():
111
- pytest.skip("Flashinfer does not support ROCm/HIP.")
108
if backend == "XFORMERS" and current_platform.is_rocm():
109
pytest.skip("Xformers does not support ROCm/HIP.")
110
override_backend_env_variable(monkeypatch, backend)
tests/distributed/test_pp_cudagraph.py
-1
@@ -17,7 +17,6 @@ if TYPE_CHECKING:
17
])
18
@pytest.mark.parametrize("ATTN_BACKEND", [
19
"FLASH_ATTN",
20
- "FLASHINFER",
20
])
21
@create_new_process_for_each_test()
22
def test_pp_cudagraph(
tests/kernels/attention/test_attention_selector.py
+3
@@ -81,6 +81,9 @@ def test_env(
81
m.setenv(STR_BACKEND_ENV_VAR, name)
82
m.setenv("VLLM_MLA_DISABLE", "1" if use_mla else "0")
83
84
+ if name == "FLASHINFER" and not use_v1:
85
+ pytest.skip("FlashInfer backend is only available on V1 engine")
86
+
87
if device == "cpu":
88
if not use_v1:
89
pytest.skip("CPU backend only supports V1")
tests/models/quantization/test_fp8.py
+1
-4
@@ -32,7 +32,7 @@ from ..utils import check_logprobs_close
32
# Due to low-precision numerical divergence, we only test logprob of 4 tokens
33
@pytest.mark.parametrize("max_tokens", [4])
34
@pytest.mark.parametrize("enforce_eager", [True])
35
-@pytest.mark.parametrize("backend", ["FLASH_ATTN", "XFORMERS", "FLASHINFER"])
35
+@pytest.mark.parametrize("backend", ["FLASH_ATTN", "XFORMERS"])
36
# NOTE: Increasing this in this suite will fail CI because we currently cannot
37
# reset distributed env properly. Use a value > 1 just when you test.
38
@pytest.mark.parametrize("tensor_parallel_size", [1])
@@ -57,9 +57,6 @@ def test_models(
57
numerical sensitive kernels.
58
"""
59
60
- if backend == "FLASHINFER" and current_platform.is_rocm():
61
- pytest.skip("Flashinfer does not support ROCm/HIP.")
62
-
60
if kv_cache_dtype == "fp8_e5m2" and current_platform.is_rocm():
61
pytest.skip(
62
f"{kv_cache_dtype} is currently not supported on ROCm/HIP.")
vllm/attention/backends/flashinfer.py
deleted
-1098
@@ -1,1098 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-
4
-import dataclasses
5
-from collections import defaultdict
6
-from contextlib import contextmanager
7
-from dataclasses import dataclass
8
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type
9
-
10
-from vllm.multimodal import MultiModalPlaceholderMap
11
-
12
-try:
13
- from flashinfer import BatchDecodeWithPagedKVCacheWrapper
14
- from flashinfer.decode import (CUDAGraphBatchDecodeWithPagedKVCacheWrapper,
15
- trtllm_batch_decode_with_kv_cache)
16
- from flashinfer.prefill import BatchPrefillWithPagedKVCacheWrapper
17
-
18
- from vllm.vllm_flash_attn import flash_attn_varlen_func
19
- FLASHINFER_WORKSPACE_BUFFER_SIZE = 256 * 1024 * 1024
20
-except ImportError:
21
- # Avoid turning these types into variables during type checking
22
- if not TYPE_CHECKING:
23
- BatchDecodeWithPagedKVCacheWrapper = None
24
- CUDAGraphBatchDecodeWithPagedKVCacheWrapper = None
25
- BatchPrefillWithPagedKVCacheWrapper = None
26
- trtllm_batch_decode_with_kv_cache = None
27
- FLASHINFER_WORKSPACE_BUFFER_SIZE = 0
28
- raise ImportError("FlashInfer is not installed. Please install it from "
29
- "https://github.com/flashinfer-ai/flashinfer") from None
30
-
31
-import torch
32
-
33
-import vllm.envs as envs
34
-from vllm import _custom_ops as ops
35
-from vllm.attention.backends.abstract import (AttentionBackend, AttentionImpl,
36
- AttentionLayer,
37
- AttentionMetadata,
38
- AttentionMetadataBuilder,
39
- AttentionState, AttentionType)
40
-from vllm.attention.backends.utils import (PAD_SLOT_ID, compute_slot_mapping,
41
- compute_slot_mapping_start_idx,
42
- is_block_tables_empty)
43
-from vllm.attention.layer import Attention
44
-from vllm.attention.ops.paged_attn import PagedAttention
45
-from vllm.config import VllmConfig, get_layers_from_vllm_config
46
-from vllm.logger import init_logger
47
-from vllm.utils import (async_tensor_h2d, get_kv_cache_torch_dtype,
48
- make_tensor_with_pad)
49
-from vllm.utils.flashinfer import use_trtllm_attention
50
-
51
-logger = init_logger(__name__)
52
-
53
-if TYPE_CHECKING:
54
- from vllm.worker.model_runner import ModelInputForGPUBuilder
55
-
56
-
57
-class FlashInferBackend(AttentionBackend):
58
-
59
- @staticmethod
60
- def get_name() -> str:
61
- return "FLASHINFER"
62
-
63
- @staticmethod
64
- def get_impl_cls() -> Type["FlashInferImpl"]:
65
- return FlashInferImpl
66
-
67
- @staticmethod
68
- def get_metadata_cls() -> Type["AttentionMetadata"]:
69
- return FlashInferMetadata
70
-
71
- @staticmethod
72
- def get_builder_cls() -> Type["FlashInferMetadataBuilder"]:
73
- return FlashInferMetadataBuilder
74
-
75
- @staticmethod
76
- def get_state_cls() -> Type["FlashInferState"]:
77
- return FlashInferState
78
-
79
- @staticmethod
80
- def get_kv_cache_shape(
81
- num_blocks: int,
82
- block_size: int,
83
- num_kv_heads: int,
84
- head_size: int,
85
- ) -> Tuple[int, ...]:
86
- return (num_blocks, 2, block_size, num_kv_heads, head_size)
87
-
88
- @staticmethod
89
- def get_kv_cache_stride_order() -> Tuple[int, ...]:
90
- cache_layout = FlashInferState.get_kv_cache_layout()
91
- assert (cache_layout in ("NHD", "HND"))
92
- stride_order = (0, 1, 2, 3, 4) if cache_layout == "NHD" else (0, 1, 3,
93
- 2, 4)
94
- return stride_order
95
-
96
- @staticmethod
97
- def swap_blocks(
98
- src_kv_cache: torch.Tensor,
99
- dst_kv_cache: torch.Tensor,
100
- src_to_dst: torch.Tensor,
101
- ) -> None:
102
- PagedAttention.swap_blocks(src_kv_cache, dst_kv_cache, src_to_dst)
103
-
104
- @staticmethod
105
- def copy_blocks(
106
- kv_caches: List[torch.Tensor],
107
- src_to_dists: torch.Tensor,
108
- ) -> None:
109
- PagedAttention.copy_blocks(kv_caches, src_to_dists)
110
-
111
- @staticmethod
112
- def get_supported_head_sizes() -> List[int]:
113
- return [64, 128, 256]
114
-
115
- @staticmethod
116
- def get_fp8_dtype_for_flashinfer(kv_cache_dtype: str) -> torch.dtype:
117
- if kv_cache_dtype in ("fp8", "fp8_e4m3"):
118
- return torch.float8_e4m3fn
119
- elif kv_cache_dtype == "fp8_e5m2":
120
- return torch.float8_e5m2
121
- else:
122
- raise ValueError(f"Unrecognized FP8 dtype: {kv_cache_dtype}")
123
-
124
-
125
-@dataclass
126
-class PerLayerParameters:
127
- """
128
- Currently, FlashInfer backend only support models in which all layers share
129
- the same values for the following hyperparameters.
130
- """
131
-
132
- window_left: int
133
- logits_soft_cap: Optional[float]
134
- sm_scale: float
135
-
136
-
137
-def get_per_layer_parameters(
138
- vllm_config: VllmConfig) -> Dict[str, PerLayerParameters]:
139
- """
140
- Scan all attention layers and determine some hyperparameters
141
- to use during `plan`.
142
- """
143
-
144
- layers = get_layers_from_vllm_config(vllm_config, Attention)
145
- per_layer_params: Dict[str, PerLayerParameters] = {}
146
-
147
- for key, layer in layers.items():
148
- impl = layer.impl
149
- assert isinstance(impl, FlashInferImpl)
150
-
151
- # Infer hyperparameters from the attention layer
152
- window_size = impl.sliding_window
153
- window_left = window_size[0] if window_size is not None else -1
154
- logits_soft_cap = impl.logits_soft_cap
155
- sm_scale = impl.scale
156
-
157
- per_layer_params[key] = PerLayerParameters(window_left,
158
- logits_soft_cap, sm_scale)
159
-
160
- return per_layer_params
161
-
162
-
163
-def infer_global_hyperparameters(
164
- per_layer_params: Dict[str, PerLayerParameters]) -> PerLayerParameters:
165
- """
166
- Currently, FlashInfer backend only support models in which all layers share
167
- the same values for the following hyperparameters:
168
- - `window_left`
169
- - `logits_soft_cap`
170
- - `sm_scale`
171
-
172
- So this function asserts that all layers share the same values for these
173
- hyperparameters and returns the global values.
174
- """
175
-
176
- assert len(per_layer_params) > 0, "No attention layers found in the model."
177
-
178
- param_sets = list(per_layer_params.values())
179
- global_params = param_sets[0]
180
- for params in param_sets:
181
- assert params == global_params, (
182
- "FlashInfer backend currently only supports models in which all "
183
- "layers share the same values for the following hyperparameters: "
184
- "`window_left`, `logits_soft_cap`, `sm_scale`.")
185
-
186
- return global_params
187
-
188
-
189
-class FlashInferState(AttentionState):
190
-
191
- def __init__(self, runner):
192
- self.runner = runner
193
- self._is_graph_capturing = False
194
- self._workspace_buffer = None
195
- self._decode_wrapper = None
196
- self._prefill_wrapper = None
197
-
198
- # Global hyperparameters shared by all attention layers
199
- self.global_hyperparameters: Optional[PerLayerParameters] = None
200
-
201
- self.vllm_config = self.runner.vllm_config
202
- self._kv_cache_layout = None
203
-
204
- def _get_workspace_buffer(self):
205
- if self._workspace_buffer is None:
206
- self._workspace_buffer = torch.zeros(
207
- FLASHINFER_WORKSPACE_BUFFER_SIZE,
208
- dtype=torch.uint8,
209
- device=self.runner.device)
210
- return self._workspace_buffer
211
-
212
- @staticmethod
213
- def get_kv_cache_layout():
214
- from vllm.v1.attention.backends.utils import _KV_CACHE_LAYOUT_OVERRIDE
215
- if _KV_CACHE_LAYOUT_OVERRIDE is not None:
216
- logger.info_once("Using KV cache layout %s",
217
- _KV_CACHE_LAYOUT_OVERRIDE)
218
- return _KV_CACHE_LAYOUT_OVERRIDE
219
- cache_layout = envs.VLLM_KV_CACHE_LAYOUT
220
- if cache_layout is None:
221
- logger.info_once("Using default KV cache layout NHD")
222
- return "NHD"
223
- logger.info_once("Using KV cache layout %s", cache_layout)
224
- return cache_layout
225
-
226
- def _get_prefill_wrapper(self):
227
- if self._prefill_wrapper is None:
228
- self._prefill_wrapper = BatchPrefillWithPagedKVCacheWrapper(
229
- self._get_workspace_buffer(), self.get_kv_cache_layout())
230
- return self._prefill_wrapper
231
-
232
- def _get_decode_wrapper(self):
233
- if self._decode_wrapper is None:
234
- num_qo_heads = (self.runner.model_config.get_num_attention_heads(
235
- self.runner.parallel_config))
236
- num_kv_heads = self.runner.model_config.get_num_kv_heads(
237
- self.runner.parallel_config)
238
- use_tensor_cores = envs.VLLM_FLASHINFER_FORCE_TENSOR_CORES or (
239
- num_qo_heads // num_kv_heads > 4)
240
- self._decode_wrapper = BatchDecodeWithPagedKVCacheWrapper(
241
- self._get_workspace_buffer(),
242
- self.get_kv_cache_layout(),
243
- use_tensor_cores=use_tensor_cores)
244
- return self._decode_wrapper
245
-
246
- @contextmanager
247
- def graph_capture(self, max_batch_size: int):
248
- self._is_graph_capturing = True
249
- self._graph_decode_wrapper = None
250
- self._graph_slot_mapping = torch.full((max_batch_size, ),
251
- PAD_SLOT_ID,
252
- dtype=torch.long,
253
- device=self.runner.device)
254
- self._graph_seq_lens = torch.ones(max_batch_size,
255
- dtype=torch.int32,
256
- device=self.runner.device)
257
- self._graph_block_tables = torch.from_numpy(
258
- self.runner.graph_block_tables).to(device=self.runner.device)
259
- self._graph_decode_workspace_buffer = self._get_workspace_buffer()
260
- self._graph_indices_buffer = torch.empty(
261
- max_batch_size * self.runner.cache_config.num_gpu_blocks,
262
- dtype=torch.int32,
263
- device=self.runner.device)
264
- self._graph_indptr_buffer = torch.empty(max_batch_size + 1,
265
- dtype=torch.int32,
266
- device=self.runner.device)
267
- self._graph_last_page_len_buffer = torch.empty(
268
- max_batch_size, dtype=torch.int32, device=self.runner.device)
269
- yield
270
- self._is_graph_capturing = False
271
- del self._graph_slot_mapping
272
- del self._graph_seq_lens
273
- del self._graph_block_tables
274
- del self._graph_decode_workspace_buffer
275
- del self._graph_indices_buffer
276
- del self._graph_indptr_buffer
277
- del self._graph_last_page_len_buffer
278
- del self._graph_decode_wrapper
279
-
280
- def graph_clone(self, batch_size: int):
281
- assert self._is_graph_capturing
282
- state = self.__class__(self.runner)
283
- state._workspace_buffer = self._graph_decode_workspace_buffer
284
- state._decode_wrapper = self._graph_decode_wrapper
285
- state._prefill_wrapper = self._get_prefill_wrapper()
286
- return state
287
-
288
- def graph_capture_get_metadata_for_batch(
289
- self, batch_size: int, is_encoder_decoder_model: bool = False):
290
- assert self._is_graph_capturing
291
- _indptr_buffer = self._graph_indptr_buffer[:batch_size + 1]
292
- _last_page_len_buffer = self._graph_last_page_len_buffer[:batch_size]
293
-
294
- num_qo_heads = (self.runner.model_config.get_num_attention_heads(
295
- self.runner.parallel_config))
296
- num_kv_heads = self.runner.model_config.get_num_kv_heads(
297
- self.runner.parallel_config)
298
- use_tensor_cores = envs.VLLM_FLASHINFER_FORCE_TENSOR_CORES or (
299
- num_qo_heads // num_kv_heads > 4)
300
- self._graph_decode_wrapper = \
301
- CUDAGraphBatchDecodeWithPagedKVCacheWrapper(
302
- self._graph_decode_workspace_buffer, _indptr_buffer,
303
- self._graph_indices_buffer, _last_page_len_buffer,
304
- self.get_kv_cache_layout(),
305
- use_tensor_cores)
306
- if self.runner.kv_cache_dtype.startswith("fp8"):
307
- kv_cache_dtype = FlashInferBackend.get_fp8_dtype_for_flashinfer(
308
- self.runner.kv_cache_dtype)
309
- else:
310
- kv_cache_dtype = get_kv_cache_torch_dtype(
311
- self.runner.kv_cache_dtype, self.runner.model_config.dtype)
312
-
313
- paged_kv_indptr_tensor_host = torch.arange(0,
314
- batch_size + 1,
315
- dtype=torch.int32)
316
- paged_kv_indices_tensor_host = torch.arange(0,
317
- batch_size,
318
- dtype=torch.int32)
319
- paged_kv_last_page_len_tensor_host = torch.full((batch_size, ),
320
- self.runner.block_size,
321
- dtype=torch.int32)
322
- query_start_loc_host = torch.arange(0,
323
- batch_size + 1,
324
- dtype=torch.int32)
325
-
326
- global_params = infer_global_hyperparameters(
327
- get_per_layer_parameters(self.vllm_config))
328
-
329
- attn_metadata = self.runner.attn_backend.make_metadata(
330
- num_prefills=0,
331
- slot_mapping=self._graph_slot_mapping[:batch_size],
332
- multi_modal_placeholder_index_maps=None,
333
- enable_kv_scales_calculation=False,
334
- num_prefill_tokens=0,
335
- num_decode_tokens=batch_size,
336
- max_prefill_seq_len=0,
337
- max_decode_seq_len=0,
338
- seq_lens_tensor=self._graph_seq_lens,
339
- block_tables=self._graph_block_tables,
340
- paged_kv_indptr=paged_kv_indptr_tensor_host,
341
- paged_kv_indices=paged_kv_indices_tensor_host,
342
- paged_kv_last_page_len=paged_kv_last_page_len_tensor_host,
343
- num_qo_heads=num_qo_heads,
344
- num_kv_heads=num_kv_heads,
345
- head_dim=self.runner.model_config.get_head_size(),
346
- page_size=self.runner.block_size,
347
- seq_start_loc=None,
348
- query_start_loc=query_start_loc_host,
349
- device=self.runner.device,
350
- data_type=kv_cache_dtype,
351
- q_data_type=self.runner.model_config.dtype,
352
- use_cuda_graph=True,
353
- decode_wrapper=self._graph_decode_wrapper,
354
- prefill_wrapper=None,
355
- **dataclasses.asdict(global_params),
356
- )
357
- attn_metadata.begin_forward()
358
- return attn_metadata
359
-
360
- def get_graph_input_buffers(self,
361
- attn_metadata,
362
- is_encoder_decoder_model: bool = False):
363
- return {
364
- "block_tables": attn_metadata.block_tables,
365
- "seq_lens_tensor": attn_metadata.seq_lens_tensor,
366
- "slot_mapping": attn_metadata.slot_mapping,
367
- }
368
-
369
- def prepare_graph_input_buffers(self,
370
- input_buffers,
371
- attn_metadata,
372
- is_encoder_decoder_model: bool = False):
373
- # FlashInfer-specific logic: copy additional tensors
374
- num_total_blocks = attn_metadata.decode_metadata.seq_lens_tensor.shape[
375
- 0]
376
- input_buffers["seq_lens_tensor"][:num_total_blocks].copy_(
377
- attn_metadata.seq_lens_tensor, non_blocking=True)
378
- input_buffers["block_tables"][:num_total_blocks].copy_(
379
- attn_metadata.block_tables, non_blocking=True)
380
-
381
- def begin_forward(self, model_input):
382
- assert not self._is_graph_capturing
383
- state = self
384
- use_cuda_graph = model_input.attn_metadata.use_cuda_graph
385
- is_decode = model_input.attn_metadata.num_prefills == 0
386
- # In case of multistep chunked-prefill, there might be prefill requests
387
- # scheduled while CUDA graph mode is enabled. We don't run graph in that
388
- # case.
389
- if use_cuda_graph and is_decode:
390
- if model_input.inputs_embeds is None:
391
- batch_size = model_input.input_tokens.shape[0]
392
- state = (
393
- self.runner.graph_runners[model_input.virtual_engine][(
394
- batch_size, False)].attn_state)
395
- else:
396
- batch_size = model_input.inputs_embeds.shape[0]
397
- state = (
398
- self.runner.graph_runners[model_input.virtual_engine][(
399
- batch_size, True)].attn_state)
400
-
401
- model_input.attn_metadata.prefill_wrapper = state._get_prefill_wrapper(
402
- )
403
- model_input.attn_metadata.decode_wrapper = state._get_decode_wrapper()
404
- model_input.attn_metadata.begin_forward()
405
-
406
-
407
-@dataclass
408
-class FlashInferMetadata(AttentionMetadata):
409
- # Maximum sequence length among prefill batch. 0 if there are decoding
410
- # requests only.
411
- max_prefill_seq_len: int
412
- max_decode_seq_len: int
413
-
414
- # Number of query tokens for each request in the batch.
415
- # Currently, we require that all requests have the same number of query
416
- # tokens during the decoding phase. When speculavie decoding is enabled,
417
- # decode_query_len might be greater than 1. In all other cases, it is 1.
418
- decode_query_len: Optional[int] = 1
419
-
420
- use_cuda_graph: bool = True
421
-
422
- prefill_wrapper: Optional[BatchPrefillWithPagedKVCacheWrapper] = None
423
- decode_wrapper: Optional[BatchDecodeWithPagedKVCacheWrapper] = None
424
-
425
- # Metadata for the prefill stage
426
- seq_start_loc: Optional[torch.Tensor] = None
427
- query_start_loc: Optional[torch.Tensor] = None
428
- block_tables: Optional[torch.Tensor] = None
429
-
430
- # used for GPU operations
431
- seq_lens_tensor: Optional[torch.Tensor] = None
432
- block_table_bound: Optional[torch.Tensor] = None
433
-
434
- # An example for paged_kv_indices, paged_kv_indptr:
435
- # request 1, page indices [0, 5, 8]
436
- # request 2, page indices [1, 6, 7]
437
- # request 3, page indices [3, 4]
438
- # paged_kv_indices is a concatenation of page indices of all requests:
439
- # [0, 5, 8, 1, 6, 7, 3, 4]
440
- # paged_kv_indptr is used to index into paged_kv_indices:
441
- # [0, 3, 6, 8]
442
- # The indptr of the paged kv cache, shape: [batch_size + 1]
443
- paged_kv_indptr: Optional[torch.Tensor] = None
444
- # The page indices of the paged kv cache
445
- paged_kv_indices: Optional[torch.Tensor] = None
446
- # The number of entries in the last page of each request in
447
- # the paged kv cache, shape: [batch_size]
448
- paged_kv_last_page_len: Optional[torch.Tensor] = None
449
- # The number of query/output heads
450
- num_qo_heads: Optional[int] = None
451
- # The number of key/value heads
452
- num_kv_heads: Optional[int] = None
453
- # The dimension of the attention heads
454
- head_dim: Optional[int] = None
455
- # Block size of vllm
456
- page_size: Optional[int] = None
457
- # The data type of the paged kv cache
458
- data_type: torch.dtype = None
459
- # The data type of the query
460
- q_data_type: torch.dtype = None
461
- # FlashInfer 0.2 encourages passing host tensors
462
- device: torch.device = torch.device("cpu")
463
- is_profile_run: bool = False
464
-
465
- # The FlashInfer backend currently supports only models in which all layers
466
- # share the same following hyperparameters:
467
-
468
- # The left (inclusive) window size for the attention window, when
469
- # set to `-1`, the window size will be set to the full length of
470
- # the sequence. Defaults to `-1`.
471
- window_left: int = -1
472
- # The attention logits soft capping value (used in Gemini, Grok and
473
- # Gemma-2, etc.), if not provided, will be set to `0`. If greater
474
- # than 0, the logits will be capped according to formula:
475
- # $$\texttt{logits\_soft\_cap} \times
476
- # \mathrm{tanh}(x / \texttt{logits\_soft\_cap})$$,
477
- # where $x$ is the input logits.
478
- logits_soft_cap: Optional[float] = None
479
- # The scale used in softmax, if not provided, will be set to
480
- # `1.0 / sqrt(head_dim)`.
481
- sm_scale: Optional[float] = None
482
-
483
- def __post_init__(self):
484
- # Refer to
485
- # https://github.com/flashinfer-ai/flashinfer/blob/3d55c71a62052c590c130897d3a3db49b14fcc34/include/flashinfer/utils.cuh#L157
486
- supported_head_sizes = FlashInferBackend.get_supported_head_sizes()
487
- if self.head_dim is not None and self.head_dim \
488
- not in supported_head_sizes:
489
- raise ValueError(
490
- f"Only {supported_head_sizes} are supported for head_dim,",
491
- f" received {self.head_dim}.")
492
-
493
- def begin_forward(self):
494
- if self.num_prefill_tokens > 0:
495
- if self.paged_kv_indices is None:
496
- return
497
-
498
- assert self.prefill_wrapper is not None
499
- assert self.query_start_loc is not None
500
- assert self.paged_kv_indices is not None
501
- assert self.paged_kv_indptr is not None
502
- assert self.paged_kv_last_page_len is not None
503
- assert self.block_table_bound is not None
504
- assert self.seq_lens_tensor is not None
505
- self.query_start_loc = self.query_start_loc[:self.num_prefills + 1]
506
- batch_size = self.query_start_loc.shape[0] - 1
507
- assert batch_size >= 0
508
- # We will use flash attention for profiling to
509
- # determine the number of blocks. Therefore,
510
- # we don't need to prepare the input for flashinfer for profile run.
511
- if not self.is_profile_run:
512
- self.paged_kv_indptr = self.paged_kv_indptr.to(self.device)
513
- self.paged_kv_last_page_len = self.paged_kv_last_page_len.to(
514
- self.device)
515
- self.block_table_bound = self.block_table_bound.to(self.device)
516
- self.seq_lens_tensor = self.seq_lens_tensor.to(self.device)
517
- self.paged_kv_indices = self.paged_kv_indices.to(self.device)
518
- self.prefill_wrapper.plan(
519
- self.query_start_loc,
520
- self.paged_kv_indptr[:self.num_prefills + 1],
521
- self.paged_kv_indices,
522
- self.paged_kv_last_page_len[:self.num_prefills],
523
- self.num_qo_heads,
524
- self.num_kv_heads,
525
- self.head_dim,
526
- self.page_size,
527
- causal=True,
528
- sm_scale=self.sm_scale,
529
- window_left=self.window_left,
530
- logits_soft_cap=self.logits_soft_cap,
531
- q_data_type=self.q_data_type,
532
- kv_data_type=self.data_type)
533
- if self.num_decode_tokens > 0:
534
- assert self.paged_kv_indices is not None
535
- assert self.paged_kv_indptr is not None
536
- assert self.paged_kv_last_page_len is not None
537
- self.paged_kv_indices = self.paged_kv_indices.to(self.device)
538
- self.paged_kv_indptr = self.paged_kv_indptr.to(self.device)
539
- self.paged_kv_last_page_len = self.paged_kv_last_page_len.to(
540
- self.device)
541
- # handle model warmup path
542
- if self.block_table_bound is not None:
543
- self.block_table_bound = self.block_table_bound.to(self.device)
544
- if self.seq_lens_tensor is not None:
545
- self.seq_lens_tensor = self.seq_lens_tensor.to(self.device)
546
-
547
- assert self.decode_wrapper is not None
548
- self.decode_wrapper.plan(
549
- self.paged_kv_indptr[self.num_prefills:],
550
- self.paged_kv_indices,
551
- self.paged_kv_last_page_len[self.num_prefills:],
552
- self.num_qo_heads,
553
- self.num_kv_heads,
554
- self.head_dim,
555
- self.page_size,
556
- # Disable flashinfer's pos encoding and use vllm's rope.
557
- pos_encoding_mode="NONE",
558
- window_left=self.window_left,
559
- logits_soft_cap=self.logits_soft_cap,
560
- sm_scale=self.sm_scale,
561
- # kv-cache data type.
562
- kv_data_type=self.data_type,
563
- # query data type.
564
- q_data_type=self.q_data_type)
565
-
566
- def asdict_zerocopy(self,
567
- skip_fields: Optional[Set[str]] = None
568
- ) -> Dict[str, Any]:
569
- if skip_fields is None:
570
- skip_fields = set()
571
- # We need to skip the prefill/decode_wrapper field since it cannot be
572
- # broadcasted with nccl when TP is enabled.
573
- skip_fields.add('prefill_wrapper')
574
- skip_fields.add('decode_wrapper')
575
- return super().asdict_zerocopy(skip_fields)
576
-
577
- @property
578
- def prefill_metadata(self) -> Optional["FlashInferMetadata"]:
579
- if self.num_prefills == 0:
580
- return None
581
- return self
582
-
583
- @property
584
- def decode_metadata(self) -> Optional["FlashInferMetadata"]:
585
- if self.num_decode_tokens == 0:
586
- return None
587
- return self
588
-
589
-
590
-class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
591
-
592
- def __init__(self, input_builder: "ModelInputForGPUBuilder"):
593
-
594
- self.input_builder = input_builder
595
- self.runner = input_builder.runner
596
-
597
- self.sliding_window = input_builder.sliding_window
598
- self.block_size = input_builder.block_size
599
-
600
- # Global hyperparameters shared by all attention layers
601
- self.global_hyperparameters: Optional[PerLayerParameters] = None
602
-
603
- self.vllm_config = self.runner.vllm_config
604
-
605
- def prepare(self):
606
- self.slot_mapping: List[int] = []
607
- self.prefill_seq_lens: List[int] = []
608
- self.context_lens: List[int] = []
609
- self.block_tables: List[List[int]] = []
610
- self.curr_seq_lens: List[int] = []
611
- self.multimodal_placeholder_maps: Dict[
612
- str,
613
- MultiModalPlaceholderMap] = defaultdict(MultiModalPlaceholderMap)
614
- self.num_prefills = 0
615
- self.num_prefill_tokens = 0
616
- self.num_decode_tokens = 0
617
-
618
- # Please follow https://docs.flashinfer.ai/tutorials/kv_layout.html#page-layout
619
- # for the precise definition of the following fields.
620
- # An example:
621
- # request 1, page indices [0, 5, 8]
622
- # request 2, page indices [1, 6, 7]
623
- # request 3, page indices [3, 4]
624
- # paged_kv_indices is a concatenation of page indices of all requests:
625
- # [0, 5, 8, 1, 6, 7, 3, 4]
626
- # paged_kv_indptr is used to index into paged_kv_indices:
627
- # [0, 3, 6, 8]
628
- self.paged_kv_indices: List[int] = []
629
- # 0 at the beginning of paged_kv_indptr indicates the start of the
630
- # first request’s page indices in the paged_kv_indices list.
631
- self.paged_kv_indptr: List[int] = [0]
632
- # paged_kv_last_page_len is the length of the last page of each request
633
- self.paged_kv_last_page_len: List[int] = []
634
- self.total_blocks = 0
635
- self.is_profile_run: bool = False
636
-
637
- if self.global_hyperparameters is None:
638
- # Infer global hyperparameters, since currently we only support
639
- # models in which all layers share the same values for the
640
- # following hyperparameters:
641
- # - `window_left`
642
- # - `logits_soft_cap`
643
- # - `sm_scale`
644
- inferred_params = infer_global_hyperparameters(
645
- get_per_layer_parameters(self.vllm_config))
646
- self.global_hyperparameters = inferred_params
647
- self.window_left = inferred_params.window_left
648
- self.logits_soft_cap = inferred_params.logits_soft_cap
649
- self.sm_scale = inferred_params.sm_scale
650
-
651
- def _add_seq_group(
652
- self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
653
- chunked_prefill_enabled: bool):
654
- """Add a sequence group to the metadata. Specifically update/append
655
- 1. context length.
656
- 2. block table.
657
- 3. slot mapping.
658
- """
659
- is_prompt = inter_data.is_prompt
660
- block_tables = inter_data.block_tables
661
- computed_block_nums = inter_data.computed_block_nums
662
-
663
- for (seq_id, token_len, seq_len, curr_seq_len, query_len, context_len,
664
- curr_sliding_window_block) in zip(
665
- inter_data.seq_ids, [len(t) for t in inter_data.input_tokens],
666
- inter_data.orig_seq_lens, inter_data.seq_lens,
667
- inter_data.query_lens, inter_data.context_lens,
668
- inter_data.curr_sliding_window_blocks):
669
- self.context_lens.append(context_len)
670
- if is_prompt:
671
- mm_maps = inter_data.multi_modal_placeholder_maps
672
- if mm_maps:
673
- for modality, placeholders in mm_maps.items():
674
- self.multimodal_placeholder_maps[modality].extend(
675
- placeholders)
676
- self.num_prefills += 1
677
- self.num_prefill_tokens += token_len
678
- self.prefill_seq_lens.append(seq_len)
679
- else:
680
- assert query_len == 1, (
681
- "seq_len: {}, context_len: {}, query_len: {}".format(
682
- seq_len, context_len, query_len))
683
- self.num_decode_tokens += query_len
684
- self.curr_seq_lens.append(curr_seq_len)
685
-
686
- # Compute block table.
687
- # TODO(sang): Combine chunked prefill and prefix caching by
688
- # only allowing multiple of block_size chunk size.
689
- # NOTE: This only works for oooooooxxx style attention.
690
- block_table = []
691
- if inter_data.prefix_cache_hit:
692
- block_table = computed_block_nums
693
- elif ((chunked_prefill_enabled or not is_prompt)
694
- and block_tables is not None):
695
- block_table = block_tables[seq_id][-curr_sliding_window_block:]
696
- self.block_tables.append(block_table)
697
-
698
- is_profile_run = is_block_tables_empty(block_tables)
699
-
700
- # Compute slot mapping.
701
- start_idx = compute_slot_mapping_start_idx(is_prompt, query_len,
702
- context_len,
703
- self.sliding_window)
704
- compute_slot_mapping(is_profile_run, self.slot_mapping, seq_id,
705
- seq_len, context_len, start_idx,
706
- self.block_size, inter_data.block_tables)
707
-
708
- # It is not necessary to add paged_kv_indices, paged_kv_indptr,
709
- # and paged_kv_last_page_len for profile run because we will
710
- # create dummy inputs.
711
- if is_profile_run:
712
- self.is_profile_run = is_profile_run
713
- return
714
-
715
- block_table = block_tables[seq_id]
716
- self._update_paged_kv_tensors(block_table, seq_len)
717
-
718
- def _update_paged_kv_tensors(self, block_table: List[int], seq_len: int):
719
- # Get the number of valid blocks based on sequence length.
720
- # If seq_len = 16, block_size = 16,
721
- # block_table_bound is 1 with 1 valid block.
722
- # If seq_len = 15, block_size = 16,
723
- # block_table_bound is 0 + 1 with 1 valid block.
724
- self.total_blocks += len(block_table)
725
- block_table_bound = seq_len // self.block_size + 1 \
726
- if seq_len % self.block_size != 0 \
727
- else seq_len // self.block_size
728
- self.paged_kv_indices.extend(block_table[:block_table_bound])
729
- self.paged_kv_indptr.append(self.paged_kv_indptr[-1] +
730
- block_table_bound)
731
-
732
- last_page_len = seq_len % self.block_size
733
- if last_page_len == 0:
734
- last_page_len = self.block_size
735
- self.paged_kv_last_page_len.append(last_page_len)
736
-
737
- def build(self, seq_lens: List[int], query_lens: List[int],
738
- cuda_graph_pad_size: int, batch_size: int):
739
- """Build attention metadata with on-device tensors.
740
-
741
- Args:
742
- seq_lens: The maybe padded sequence lengths of the input sequences.
743
- query_lens: The query lengths of the input sequences.
744
- cuda_graph_pad_size: The padding size for cuda graph.
745
- -1 if cuda graph is not used.
746
- batch_size: The maybe padded batch size.
747
- """
748
- for inter_data in self.input_builder.inter_data_list:
749
- self._add_seq_group(inter_data,
750
- self.input_builder.chunked_prefill_enabled)
751
-
752
- device = self.runner.device
753
- use_captured_graph = cuda_graph_pad_size != -1
754
-
755
- max_prefill_seq_len = max(self.prefill_seq_lens, default=0)
756
- max_decode_seq_len = max(self.curr_seq_lens, default=0)
757
- num_decode_tokens = self.num_decode_tokens
758
- decode_query_len = max(query_lens[self.num_prefills:], default=1)
759
-
760
- if use_captured_graph:
761
- self.slot_mapping.extend([PAD_SLOT_ID] * cuda_graph_pad_size)
762
- self.block_tables.extend([] * cuda_graph_pad_size)
763
- num_decode_tokens = batch_size - self.num_prefill_tokens
764
-
765
- # The shape of graph_block_tables is
766
- # [max batch size, max context len // block size].
767
- input_block_tables = self.runner.graph_block_tables[:batch_size]
768
- max_blocks = input_block_tables.shape[1]
769
- for i, block_table in enumerate(self.block_tables):
770
- if block_table:
771
- num_blocks = len(block_table)
772
- if num_blocks <= max_blocks:
773
- input_block_tables[i, :num_blocks] = block_table
774
- else:
775
- # It may be possible to have more blocks allocated due
776
- # to lookahead slots of multi-step, however, they are
777
- # not used anyway, so can be safely ignored.
778
- input_block_tables[
779
- i, :max_blocks] = block_table[:max_blocks]
780
-
781
- block_tables = torch.from_numpy(input_block_tables).to(
782
- device, non_blocking=True)
783
-
784
- last_paged_kv_indptr = self.paged_kv_indptr[-1]
785
- self.paged_kv_indptr.extend([last_paged_kv_indptr] *
786
- cuda_graph_pad_size)
787
- self.paged_kv_last_page_len.extend([0] * cuda_graph_pad_size)
788
- else:
789
- block_tables = make_tensor_with_pad(
790
- self.block_tables,
791
- pad=0,
792
- dtype=torch.int,
793
- device=device,
794
- )
795
-
796
- assert device is not None
797
- seq_lens_tensor = async_tensor_h2d(seq_lens, torch.int, device,
798
- self.runner.pin_memory)
799
- query_lens_tensor = async_tensor_h2d(query_lens, torch.long, device,
800
- self.runner.pin_memory)
801
- slot_mapping_tensor = async_tensor_h2d(self.slot_mapping, torch.long,
802
- device, self.runner.pin_memory)
803
- query_start_loc = torch.zeros(query_lens_tensor.shape[0] + 1,
804
- dtype=torch.int32,
805
- device=device)
806
- seq_start_loc = torch.zeros(seq_lens_tensor.shape[0] + 1,
807
- dtype=torch.int32,
808
- device=device)
809
- placeholder_index_maps = {
810
- modality: placeholder_map.index_map()
811
- for modality, placeholder_map in
812
- self.multimodal_placeholder_maps.items()
813
- }
814
- torch.cumsum(seq_lens_tensor,
815
- dim=0,
816
- dtype=seq_start_loc.dtype,
817
- out=seq_start_loc[1:])
818
- torch.cumsum(query_lens_tensor,
819
- dim=0,
820
- dtype=query_start_loc.dtype,
821
- out=query_start_loc[1:])
822
-
823
- if len(self.paged_kv_indptr) > 0:
824
- # extend to the maximum number of blocks as returned by the
825
- # scheduler
826
- self.paged_kv_indices.extend(
827
- [0] * (self.total_blocks - len(self.paged_kv_indices)))
828
- paged_kv_indices_tensor = torch.tensor(self.paged_kv_indices,
829
- device="cpu",
830
- dtype=torch.int)
831
- paged_kv_indptr_tensor = torch.tensor(self.paged_kv_indptr,
832
- device="cpu",
833
- dtype=torch.int)
834
- paged_kv_last_page_len_tensor = torch.tensor(
835
- self.paged_kv_last_page_len, device="cpu", dtype=torch.int)
836
- block_table_bound_tensor = torch.zeros(len(self.paged_kv_indptr) -
837
- 1,
838
- device="cpu",
839
- dtype=torch.int)
840
- else:
841
- paged_kv_indices_tensor = None
842
- paged_kv_indptr_tensor = None
843
- paged_kv_last_page_len_tensor = None
844
- block_table_bound_tensor = None
845
-
846
- if self.runner.kv_cache_dtype.startswith("fp8"):
847
- kv_cache_dtype = FlashInferBackend.get_fp8_dtype_for_flashinfer(
848
- self.runner.kv_cache_dtype)
849
- else:
850
- kv_cache_dtype = get_kv_cache_torch_dtype(
851
- self.runner.kv_cache_dtype, self.runner.model_config.dtype)
852
-
853
- return FlashInferMetadata(
854
- decode_query_len=decode_query_len,
855
- num_prefills=self.num_prefills,
856
- slot_mapping=slot_mapping_tensor,
857
- multi_modal_placeholder_index_maps=placeholder_index_maps,
858
- enable_kv_scales_calculation=False,
859
- num_prefill_tokens=self.num_prefill_tokens,
860
- num_decode_tokens=num_decode_tokens,
861
- max_prefill_seq_len=max_prefill_seq_len,
862
- max_decode_seq_len=max_decode_seq_len,
863
- block_tables=block_tables,
864
- paged_kv_indptr=paged_kv_indptr_tensor,
865
- paged_kv_indices=paged_kv_indices_tensor,
866
- paged_kv_last_page_len=paged_kv_last_page_len_tensor,
867
- block_table_bound=block_table_bound_tensor,
868
- seq_lens_tensor=seq_lens_tensor,
869
- num_qo_heads=self.runner.model_config.get_num_attention_heads(
870
- self.runner.parallel_config),
871
- num_kv_heads=self.runner.model_config.get_num_kv_heads(
872
- self.runner.parallel_config),
873
- head_dim=self.runner.model_config.get_head_size(),
874
- page_size=self.block_size,
875
- seq_start_loc=seq_start_loc,
876
- query_start_loc=query_start_loc,
877
- device=device,
878
- data_type=kv_cache_dtype,
879
- q_data_type=self.runner.model_config.dtype,
880
- use_cuda_graph=use_captured_graph,
881
- is_profile_run=self.is_profile_run,
882
- window_left=self.window_left,
883
- logits_soft_cap=self.logits_soft_cap,
884
- sm_scale=self.sm_scale,
885
- )
886
-
887
-
888
-class FlashInferImpl(AttentionImpl):
889
-
890
- def __init__(
891
- self,
892
- num_heads: int,
893
- head_size: int,
894
- scale: float,
895
- num_kv_heads: int,
896
- alibi_slopes: Optional[List[float]],
897
- sliding_window: Optional[int],
898
- kv_cache_dtype: str,
899
- logits_soft_cap: Optional[float] = None,
900
- attn_type: str = AttentionType.DECODER,
901
- kv_sharing_target_layer_name: Optional[str] = None,
902
- use_irope: bool = False,
903
- ) -> None:
904
- if kv_sharing_target_layer_name is not None:
905
- raise NotImplementedError("KV sharing is not supported in V0 "
906
- "FLASHINFER backend.")
907
- if use_irope:
908
- logger.warning_once(
909
- "Using irope in FlashInfer is not supported yet, it will fall"
910
- " back to global attention for long context.")
911
- self.num_heads = num_heads
912
- self.head_size = head_size
913
- self.scale = float(scale)
914
- self.num_kv_heads = num_kv_heads
915
- if alibi_slopes is not None:
916
- alibi_slopes = torch.tensor(alibi_slopes, dtype=torch.float32)
917
- self.alibi_slopes = alibi_slopes
918
- self.sliding_window = ((sliding_window - 1,
919
- 0) if sliding_window is not None else (-1, -1))
920
- self.kv_cache_dtype = kv_cache_dtype
921
- self.logits_soft_cap = logits_soft_cap
922
-
923
- self.num_queries_per_kv = self.num_heads // self.num_kv_heads
924
-
925
- if attn_type != AttentionType.DECODER:
926
- raise NotImplementedError("Encoder self-attention and "
927
- "encoder/decoder cross-attention "
928
- "are not implemented for "
929
- "FlashInferImpl")
930
-
931
- def forward(
932
- self,
933
- layer: AttentionLayer,
934
- query: torch.Tensor,
935
- key: torch.Tensor,
936
- value: torch.Tensor,
937
- kv_cache: torch.Tensor,
938
- attn_metadata: FlashInferMetadata,
939
- output: Optional[torch.Tensor] = None,
940
- output_scale: Optional[torch.Tensor] = None,
941
- ) -> torch.Tensor:
942
-
943
- if output_scale is not None:
944
- raise NotImplementedError(
945
- "fused output quantization is not yet supported"
946
- " for FlashInferImpl")
947
-
948
- # TODO: directly write to output tensor
949
- num_heads: int = self.num_heads
950
- head_size: int = self.head_size
951
- num_kv_heads: int = self.num_kv_heads
952
- kv_cache_dtype: str = self.kv_cache_dtype
953
- softmax_scale: float = self.scale
954
- window_size = self.sliding_window
955
- alibi_slopes = self.alibi_slopes
956
- logits_soft_cap = self.logits_soft_cap
957
-
958
- num_tokens, hidden_size = query.shape
959
- query = query.view(-1, num_heads, head_size)
960
- key = key.view(-1, num_kv_heads, head_size)
961
- value = value.view(-1, num_kv_heads, head_size)
962
-
963
- if kv_cache.numel() > 0:
964
- # Use the same reshape and cache kernel as flash attention.
965
- ops.reshape_and_cache_flash(
966
- key,
967
- value,
968
- kv_cache[:, 0],
969
- kv_cache[:, 1],
970
- attn_metadata.slot_mapping.flatten(),
971
- kv_cache_dtype,
972
- layer._k_scale,
973
- layer._v_scale,
974
- )
975
- # The FlashInfer api requires data to be in fp8_e4m3 or fp8_e5m2
976
- # to process the cache when the kv_cache_dtype is fp8
977
- if kv_cache_dtype.startswith("fp8"):
978
- torch_dtype = FlashInferBackend.get_fp8_dtype_for_flashinfer(
979
- kv_cache_dtype)
980
- kv_cache = kv_cache.view(torch_dtype)
981
-
982
- num_prefill_tokens = attn_metadata.num_prefill_tokens
983
- num_decode_tokens = attn_metadata.num_decode_tokens
984
- assert key.shape[0] == num_prefill_tokens + num_decode_tokens, \
985
- f"key : {key.shape} : #prefill tokens {num_prefill_tokens} : #decode tokens {num_decode_tokens}" # noqa
986
- assert value.shape[0] == num_prefill_tokens + num_decode_tokens, \
987
- f"value : {value.shape} : #prefill toks {num_prefill_tokens} : #decode toks {num_decode_tokens}" # noqa
988
- query = query.contiguous(
989
- ) # Flashinfer requires query to be contiguous
990
- # Query for decode. KV is not needed because it is already cached.
991
- # QKV for prefill.
992
- decode_query = query[num_prefill_tokens:]
993
- query = query[:num_prefill_tokens]
994
-
995
- key = key[:num_prefill_tokens]
996
- value = value[:num_prefill_tokens]
997
-
998
- assert query.shape[0] == num_prefill_tokens
999
- assert decode_query.shape[0] == num_decode_tokens
1000
-
1001
- window_left = window_size[0] if window_size is not None else -1
1002
-
1003
- prefill_output: Optional[torch.Tensor] = None
1004
- if num_decode_tokens > 0:
1005
- decode_output = torch.empty(decode_query.shape,
1006
- dtype=decode_query.dtype,
1007
- device=decode_query.device)
1008
- else:
1009
- decode_output = None
1010
- stride_order = FlashInferBackend.get_kv_cache_stride_order()
1011
- if prefill_meta := attn_metadata.prefill_metadata:
1012
- # We will use flash attention for prefill
1013
- # when kv_cache is not provided.
1014
- # This happens when vllm runs the profiling to
1015
- # determine the number of blocks.
1016
- if kv_cache.numel() == 0:
1017
- prefill_output = flash_attn_varlen_func(
1018
- q=query,
1019
- k=key,
1020
- v=value,
1021
- cu_seqlens_q=prefill_meta.seq_start_loc,
1022
- cu_seqlens_k=prefill_meta.seq_start_loc,
1023
- max_seqlen_q=prefill_meta.max_prefill_seq_len,
1024
- max_seqlen_k=prefill_meta.max_prefill_seq_len,
1025
- softmax_scale=softmax_scale,
1026
- causal=True,
1027
- window_size=window_size,
1028
- alibi_slopes=alibi_slopes,
1029
- )
1030
- else:
1031
- assert prefill_meta is not None
1032
- assert prefill_meta.prefill_wrapper is not None
1033
-
1034
- assert prefill_meta.prefill_wrapper._causal
1035
- assert prefill_meta.prefill_wrapper._window_left == window_left
1036
- assert prefill_meta.prefill_wrapper._logits_soft_cap == (
1037
- logits_soft_cap or 0.0)
1038
- assert prefill_meta.prefill_wrapper._sm_scale == softmax_scale
1039
-
1040
- prefill_output = prefill_meta.prefill_wrapper.run(
1041
- query,
1042
- kv_cache.permute(*stride_order),
1043
- k_scale=layer._k_scale_float,
1044
- v_scale=layer._v_scale_float,
1045
- )
1046
- if decode_meta := attn_metadata.decode_metadata:
1047
- assert decode_meta is not None
1048
- assert decode_meta.decode_wrapper is not None
1049
-
1050
- assert decode_meta.decode_wrapper._window_left == window_left
1051
- assert decode_meta.decode_wrapper._logits_soft_cap == (
1052
- logits_soft_cap or 0.0)
1053
- assert decode_meta.decode_wrapper._sm_scale == softmax_scale
1054
- # TODO: @pavanimajety Remove this once the switch happens
1055
- # inside flashinfer.
1056
- if not use_trtllm_attention(
1057
- num_decode_tokens, attn_metadata.max_decode_seq_len,
1058
- kv_cache_dtype, attn_metadata.num_qo_heads,
1059
- attn_metadata.num_kv_heads, attn_metadata.head_dim):
1060
- decode_meta.decode_wrapper.run(
1061
- decode_query,
1062
- kv_cache.permute(*stride_order),
1063
- k_scale=layer._k_scale_float,
1064
- v_scale=layer._v_scale_float,
1065
- out=decode_output,
1066
- )
1067
- else:
1068
- workspace_buffer = (
1069
- decode_meta.decode_wrapper._float_workspace_buffer)
1070
- assert FlashInferState.get_kv_cache_layout() == "HND"
1071
- trtllm_batch_decode_with_kv_cache(
1072
- query=decode_query,
1073
- kv_cache=kv_cache.permute(*stride_order),
1074
- workspace_buffer=workspace_buffer,
1075
- block_tables=attn_metadata.block_tables,
1076
- seq_lens=decode_meta.seq_lens_tensor,
1077
- max_seq_len=attn_metadata.max_decode_seq_len,
1078
- bmm1_scale=layer._k_scale_float * softmax_scale,
1079
- bmm2_scale=layer._v_scale_float,
1080
- out=decode_output,
1081
- )
1082
-
1083
- if prefill_output is None and decode_output is not None:
1084
- # Decode only batch.
1085
- output, num_tokens = decode_output, num_decode_tokens
1086
- elif decode_output is None and prefill_output is not None:
1087
- # Prefill only batch.
1088
- output, num_tokens = prefill_output, num_prefill_tokens
1089
- else:
1090
- # Chunked prefill batch does not work with speculative decoding in
1091
- # FlashInfer backend, so the query length for decode should be 1.
1092
- assert prefill_output is not None
1093
- assert decode_output is not None
1094
- assert decode_meta is not None
1095
- assert decode_meta.decode_query_len == 1
1096
- decode_output = decode_output.squeeze(1)
1097
- output = torch.cat([prefill_output, decode_output], dim=0)
1098
- return output.view(num_tokens, hidden_size)
vllm/platforms/cuda.py
+1
-15
@@ -350,17 +350,7 @@ class CudaPlatformBase(Platform):
350
return FLEX_ATTENTION_V1
351
352
# Backends for V0 engine
353
- if selected_backend == _Backend.FLASHINFER:
354
- logger.info("Using FlashInfer backend.")
355
- if cls.has_device_capability(100):
356
- from vllm.v1.attention.backends.utils import (
357
- set_kv_cache_layout)
358
- logger.info_once(
359
- "Using HND KV cache layout on V1 engine by default for "
360
- "Blackwell (SM 10.0) GPUs.")
361
- set_kv_cache_layout("HND")
362
- return "vllm.attention.backends.flashinfer.FlashInferBackend"
363
- elif selected_backend == _Backend.XFORMERS:
353
+ if selected_backend == _Backend.XFORMERS:
354
logger.info("Using XFormers backend.")
355
return "vllm.attention.backends.xformers.XFormersBackend"
356
elif selected_backend == _Backend.DUAL_CHUNK_FLASH_ATTN:
@@ -416,10 +406,6 @@ class CudaPlatformBase(Platform):
406
if (fp8_kv_cache and not flash_attn_supports_fp8()):
407
logger.info(
408
"Cannot use FlashAttention backend for FP8 KV cache.")
419
- logger.warning(
420
- "Please use FlashInfer backend with FP8 KV Cache for "
421
- "better performance by setting environment variable "
422
- "VLLM_ATTENTION_BACKEND=FLASHINFER")
409
target_backend = _Backend.XFORMERS
410
except ImportError:
411
logger.info(