@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c99db8c8dd
Fork 0

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 core (#25321)

Signed-off-by: Woosuk Kwon <woosuk@thinkingmachines.ai> Signed-off-by: Woosuk Kwon <woosuk.kwon@berkeley.edu>

Woosuk Kwon committed Sep 20, 2025 at 19:58 UTC c99db8c8ddd50c0d30c14057012bd27b42d32b2d
29 files changed +24 -9020
.buildkite/test-pipeline.yaml
-3
@@ -148,7 +148,6 @@ steps:
148 num_gpus: 4
149 source_file_dependencies:
150 - vllm/distributed/
151 - - vllm/core/
151 - tests/distributed/test_utils
152 - tests/distributed/test_pynccl
153 - tests/distributed/test_events
@@ -867,8 +866,6 @@ steps:
866 - tests/distributed/
867 - vllm/compilation
868 - vllm/worker/worker_base.py
870 - - vllm/worker/worker.py
871 - - vllm/worker/model_runner.py
869 - entrypoints/llm/test_collective_rpc.py
870 - tests/v1/test_async_llm_dp.py
871 - tests/v1/test_external_lb_dp.py
.github/CODEOWNERS
-2
@@ -4,10 +4,8 @@
4 # This lists cover the "core" components of vLLM that require careful review
5 /vllm/attention @LucasWilkinson
6 /vllm/attention/backends/abstract.py @WoosukKwon @zhuohan123 @youkaichao @alexm-redhat @comaniac @njhill
7 -/vllm/core @zhuohan123 @youkaichao @alexm-redhat @comaniac @njhill
7 /vllm/executor/executor_base.py @zhuohan123 @youkaichao @alexm-redhat @comaniac @njhill @22quinn
8 /vllm/worker/worker_base.py @zhuohan123 @youkaichao @alexm-redhat @comaniac @njhill @22quinn
10 -/vllm/worker/worker.py @zhuohan123 @youkaichao @alexm-redhat @comaniac @njhill
9 /vllm/model_executor/layers/fused_moe @mgoin
10 /vllm/model_executor/layers/sampler.py @zhuohan123 @youkaichao @alexm-redhat @comaniac @njhill @NickLucche
11 /vllm/model_executor/layers/quantization @mgoin @robertgshaw2-redhat @tlrmchlsmth @yewentao256
pyproject.toml
-2
@@ -70,7 +70,6 @@ line-length = 80
70 "vllm/_version.py" = ["ALL"]
71 # Python 3.8 typing - skip V0 code
72 "vllm/attention/**/*.py" = ["UP006", "UP035"]
73 -"vllm/core/**/*.py" = ["UP006", "UP035"]
73 "vllm/engine/**/*.py" = ["UP006", "UP035"]
74 "vllm/executor/**/*.py" = ["UP006", "UP035"]
75 "vllm/worker/**/*.py" = ["UP006", "UP035"]
@@ -117,7 +116,6 @@ files = [
116 "vllm/*.py",
117 "vllm/assets",
118 "vllm/entrypoints",
120 - "vllm/core",
119 "vllm/inputs",
120 "vllm/logging_utils",
121 "vllm/multimodal",
vllm/attention/backends/differential_flash_attn.py
+4 -8
@@ -4,7 +4,7 @@
4 from collections import defaultdict
5 from dataclasses import dataclass
6 from itertools import accumulate
7 -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type
7 +from typing import Any, Dict, List, Optional, Tuple, Type
8
9 import torch
10 from einops import rearrange
@@ -34,9 +34,6 @@ from vllm.utils import async_tensor_h2d, make_tensor_with_pad
34 from vllm.vllm_flash_attn import (flash_attn_varlen_func,
35 flash_attn_with_kvcache)
36
37 -if TYPE_CHECKING:
38 - from vllm.worker.model_runner import ModelInputForGPUBuilder
39 -
37 logger = init_logger(__name__)
38
39
@@ -329,7 +326,7 @@ class DifferentialFlashAttentionMetadata(AttentionMetadata):
326 class DifferentialFlashAttentionMetadataBuilder(
327 AttentionMetadataBuilder[DifferentialFlashAttentionMetadata]):
328
332 - def __init__(self, input_builder: "ModelInputForGPUBuilder"):
329 + def __init__(self, input_builder):
330 self.input_builder = input_builder
331 self.runner = input_builder.runner
332 self.sliding_window = input_builder.sliding_window
@@ -350,9 +347,8 @@ class DifferentialFlashAttentionMetadataBuilder(
347 self.num_decode_tokens = 0
348 self.has_prefix_cache_hit = False
349
353 - def _add_seq_group(
354 - self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
355 - chunked_prefill_enabled: bool, prefix_cache_hit: bool):
350 + def _add_seq_group(self, inter_data, chunked_prefill_enabled: bool,
351 + prefix_cache_hit: bool):
352 """Add a sequence group to the metadata. Specifically update/append
353 1. context length.
354 2. block table.
vllm/attention/backends/dual_chunk_flash_attn.py
+3 -7
@@ -4,7 +4,7 @@
4 """
5 import math
6 from dataclasses import dataclass
7 -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type
7 +from typing import Any, Dict, List, Optional, Tuple, Type
8
9 import torch
10 import torch.distributed
@@ -22,9 +22,6 @@ from vllm.utils import async_tensor_h2d
22 from vllm.vllm_flash_attn import (flash_attn_varlen_func,
23 flash_attn_with_kvcache, sparse_attn_func)
24
25 -if TYPE_CHECKING:
26 - from vllm.worker.model_runner import ModelInputForGPUBuilder
27 -
25 logger = init_logger(__name__)
26
27
@@ -224,9 +221,8 @@ class DualChunkFlashAttentionMetadataBuilder(FlashAttentionMetadataBuilder):
221 super().prepare()
222 self.orig_seq_lens: List[int] = []
223
227 - def _add_seq_group(
228 - self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
229 - chunked_prefill_enabled: bool, prefix_cache_hit: bool):
224 + def _add_seq_group(self, inter_data, chunked_prefill_enabled: bool,
225 + prefix_cache_hit: bool):
226 super()._add_seq_group(inter_data, chunked_prefill_enabled,
227 prefix_cache_hit)
228 for prompt_len, seq_len in zip(inter_data.prompt_lens,
vllm/attention/backends/flash_attn.py
+4 -8
@@ -4,7 +4,7 @@
4 from collections import defaultdict
5 from dataclasses import dataclass
6 from itertools import accumulate
7 -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
7 +from typing import Dict, List, Optional, Tuple, Type
8
9 import torch
10
@@ -31,9 +31,6 @@ from vllm.utils import async_tensor_h2d, make_tensor_with_pad
31 from vllm.vllm_flash_attn import (flash_attn_varlen_func,
32 flash_attn_with_kvcache)
33
34 -if TYPE_CHECKING:
35 - from vllm.worker.model_runner import ModelInputForGPUBuilder
36 -
34 logger = init_logger(__name__)
35
36
@@ -312,7 +309,7 @@ class FlashAttentionMetadata(AttentionMetadata):
309 class FlashAttentionMetadataBuilder(
310 AttentionMetadataBuilder[FlashAttentionMetadata]):
311
315 - def __init__(self, input_builder: "ModelInputForGPUBuilder"):
312 + def __init__(self, input_builder):
313 self.input_builder = input_builder
314 self.runner = input_builder.runner
315 self.sliding_window = input_builder.sliding_window
@@ -332,9 +329,8 @@ class FlashAttentionMetadataBuilder(
329 self.num_decode_tokens = 0
330 self.has_prefix_cache_hit = False
331
335 - def _add_seq_group(
336 - self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
337 - chunked_prefill_enabled: bool, prefix_cache_hit: bool):
332 + def _add_seq_group(self, inter_data, chunked_prefill_enabled: bool,
333 + prefix_cache_hit: bool):
334 """Add a sequence group to the metadata. Specifically update/append
335 1. context length.
336 2. block table.
vllm/attention/backends/mla/common.py
+4 -9
@@ -193,8 +193,7 @@ from collections import defaultdict
193 from contextlib import contextmanager
194 from dataclasses import dataclass
195 from itertools import accumulate
196 -from typing import (TYPE_CHECKING, Any, Dict, Generic, List, Optional, Tuple,
197 - Type, TypeVar)
196 +from typing import Any, Dict, Generic, List, Optional, Tuple, Type, TypeVar
197
198 import torch
199
@@ -233,9 +232,6 @@ except ImportError:
232 except ImportError:
233 flash_attn_varlen_func = None
234
236 -if TYPE_CHECKING:
237 - from vllm.worker.model_runner import ModelInputForGPUBuilder
238 -
235 is_hip = current_platform.is_rocm()
236
237
@@ -638,7 +634,7 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[T], Generic[T]):
634 """
635 BLOCK_TABLE_EXTENDER: list[list[int]] = []
636
641 - def __init__(self, input_builder: "ModelInputForGPUBuilder"):
637 + def __init__(self, input_builder):
638 self.input_builder = input_builder
639 self.runner = input_builder.runner
640 self.sliding_window = input_builder.sliding_window
@@ -668,9 +664,8 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[T], Generic[T]):
664 self.num_decode_tokens = 0
665 self.has_prefix_cache_hit = False
666
671 - def _add_seq_group(
672 - self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
673 - chunked_prefill_enabled: bool, prefix_cache_hit: bool):
667 + def _add_seq_group(self, inter_data, chunked_prefill_enabled: bool,
668 + prefix_cache_hit: bool):
669 """Add a sequence group to the metadata. Specifically update/append
670 1. context length.
671 2. block table.
vllm/attention/backends/placeholder_attn.py
+3 -8
@@ -4,7 +4,7 @@
4 from collections import defaultdict
5 from dataclasses import dataclass
6 from itertools import accumulate
7 -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
7 +from typing import Dict, List, Optional, Tuple, Type
8
9 import torch
10
@@ -13,9 +13,6 @@ from vllm.attention.backends.abstract import (AttentionBackend, AttentionImpl,
13 AttentionMetadataBuilder)
14 from vllm.attention.backends.utils import CommonAttentionState
15 from vllm.multimodal import MultiModalPlaceholderMap
16 -
17 -if TYPE_CHECKING:
18 - from vllm.worker.model_runner import (ModelInputForGPUBuilder)
16 from vllm.utils import async_tensor_h2d
17
18 # Placeholder attention backend for models like Mamba and pooling models that
@@ -204,7 +201,7 @@ class PlaceholderAttentionMetadata(AttentionMetadata):
201 class PlaceholderAttentionMetadataBuilder(
202 AttentionMetadataBuilder[PlaceholderAttentionMetadata]):
203
207 - def __init__(self, input_builder: "ModelInputForGPUBuilder"):
204 + def __init__(self, input_builder):
205
206 self.input_builder = input_builder
207 self.runner = input_builder.runner
@@ -220,9 +217,7 @@ class PlaceholderAttentionMetadataBuilder(
217 self.num_prefill_tokens = 0
218 self.num_decode_tokens = 0
219
223 - def _add_seq_group(
224 - self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
225 - chunked_prefill_enabled: bool):
220 + def _add_seq_group(self, inter_data, chunked_prefill_enabled: bool):
221 """Add a sequence group to the metadata. Specifically update/append
222 1. context length.
223 """
vllm/attention/backends/rocm_aiter_mla.py
+2 -5
@@ -3,7 +3,7 @@
3
4 from contextlib import contextmanager
5 from dataclasses import dataclass
6 -from typing import TYPE_CHECKING, Optional, Type, Union
6 +from typing import Optional, Type, Union
7
8 import torch
9
@@ -19,9 +19,6 @@ from vllm.attention.backends.utils import (compute_slot_mapping,
19 from vllm.attention.ops.rocm_aiter_mla import (aiter_mla_decode_fwd,
20 get_aiter_mla_metadata)
21
22 -if TYPE_CHECKING:
23 - from vllm.worker.model_runner import ModelInputForGPUBuilder
24 -
22
23 def is_aiter_mla_enabled() -> bool:
24 return envs.VLLM_ROCM_USE_AITER \
@@ -110,7 +107,7 @@ class AiterMLAMetadata(MLACommonMetadata):
107 class AiterMLAMetadataBuilder(MLACommonMetadataBuilder[AiterMLAMetadata]):
108 BLOCK_TABLE_EXTENDER: list[list[int]] = [[]]
109
113 - def __init__(self, input_builder: "ModelInputForGPUBuilder"):
110 + def __init__(self, input_builder):
111 super().__init__(input_builder)
112 assert self.block_size == 1, "AITER MLA requires only block size 1."
113
vllm/attention/backends/utils.py
+2 -7
@@ -35,9 +35,6 @@ PAD_SLOT_ID = -1
35 # if we have at least this many elements. Could be tuned further.
36 _COMPUTE_SLOT_MAPPING_NUMPY_NUMEL = 256
37
38 -if TYPE_CHECKING:
39 - from vllm.worker.model_runner import ModelInputForGPUBuilder
40 -
38
39 def is_block_tables_empty(block_tables: Union[None, Dict]):
40 """
@@ -129,7 +126,7 @@ class CommonMetadataBuilder(AttentionMetadataBuilder[TAttentionMetadata]):
126
127 _metadata_cls: Type[TAttentionMetadata]
128
132 - def __init__(self, input_builder: "ModelInputForGPUBuilder"):
129 + def __init__(self, input_builder):
130 self.input_builder = input_builder
131 self.runner = input_builder.runner
132
@@ -149,9 +146,7 @@ class CommonMetadataBuilder(AttentionMetadataBuilder[TAttentionMetadata]):
146 self.num_prefill_tokens = 0
147 self.num_decode_tokens = 0
148
152 - def _add_seq_group(
153 - self, inter_data: "ModelInputForGPUBuilder.InterDataForSeqGroup",
154 - chunked_prefill_enabled: bool):
149 + def _add_seq_group(self, inter_data, chunked_prefill_enabled: bool):
150 is_prompt = inter_data.is_prompt
151 block_tables = inter_data.block_tables
152
vllm/core/__init__.py
vllm/core/block/__init__.py
vllm/core/block/block_table.py deleted
-399
@@ -1,399 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import math
5 -from typing import List, Optional
6 -
7 -from vllm.core.block.common import BlockList
8 -from vllm.core.block.interfaces import Block, DeviceAwareBlockAllocator
9 -from vllm.utils import Device, cdiv, chunk_list
10 -
11 -
12 -class BlockTable:
13 - """A class to manage blocks for a specific sequence.
14 -
15 - The BlockTable maps a sequence of tokens to a list of blocks, where each
16 - block represents a contiguous memory allocation for a portion of the
17 - sequence. The blocks are managed by a DeviceAwareBlockAllocator, which is
18 - responsible for allocating and freeing memory for the blocks.
19 -
20 - Args:
21 - block_size (int): The maximum number of tokens that can be stored in a
22 - single block.
23 - block_allocator (DeviceAwareBlockAllocator): The block allocator used to
24 - manage memory for the blocks.
25 - _blocks (Optional[List[Block]], optional): An optional list of existing
26 - blocks to initialize the BlockTable with. If not provided, an empty
27 - BlockTable is created.
28 - max_block_sliding_window (Optional[int], optional): The number of
29 - blocks to keep around for each sequence. If None, all blocks
30 - are kept (eg., when sliding window is not used).
31 - It should at least fit the sliding window size of the model.
32 -
33 - Attributes:
34 - _block_size (int): The maximum number of tokens that can be stored in a
35 - single block.
36 - _allocator (DeviceAwareBlockAllocator): The block allocator used to
37 - manage memory for the blocks.
38 - _blocks (Optional[List[Block]]): The list of blocks managed by this
39 - BlockTable.
40 - _num_full_slots (int): The number of tokens currently stored in the
41 - blocks.
42 - """
43 -
44 - def __init__(
45 - self,
46 - block_size: int,
47 - block_allocator: DeviceAwareBlockAllocator,
48 - _blocks: Optional[List[Block]] = None,
49 - max_block_sliding_window: Optional[int] = None,
50 - ):
51 - self._block_size = block_size
52 - self._allocator = block_allocator
53 - if _blocks is None:
54 - _blocks = []
55 - self._blocks: BlockList = BlockList(_blocks)
56 -
57 - self._max_block_sliding_window = max_block_sliding_window
58 - self._num_full_slots = self._get_num_token_ids()
59 -
60 - @staticmethod
61 - def get_num_required_blocks(token_ids: List[int],
62 - block_size: int,
63 - num_lookahead_slots: int = 0) -> int:
64 - """Calculates the minimum number of blocks required to store a given
65 - sequence of token IDs along with any look-ahead slots that may be
66 - required (like in multi-step + chunked-prefill).
67 -
68 - This assumes worst-case scenario, where every block requires a new
69 - allocation (e.g. ignoring prefix caching).
70 -
71 - Args:
72 - token_ids (List[int]): The sequence of token IDs to be stored.
73 - block_size (int): The maximum number of tokens that can be stored in
74 - a single block.
75 - num_lookahead_slots (int): look-ahead slots that the sequence may
76 - require.
77 -
78 - Returns:
79 - int: The minimum number of blocks required to store the given
80 - sequence of token IDs along with any required look-ahead slots.
81 - """
82 - return cdiv(len(token_ids) + num_lookahead_slots, block_size)
83 -
84 - def allocate(self,
85 - token_ids: List[int],
86 - device: Device = Device.GPU,
87 - extra_hash: Optional[int] = None) -> None:
88 - """Allocates memory blocks for storing the given sequence of token IDs.
89 -
90 - This method allocates the required number of blocks to store the given
91 - sequence of token IDs.
92 -
93 - Args:
94 - token_ids (List[int]): The sequence of token IDs to be stored.
95 - device (Device, optional): The device on which the blocks should be
96 - allocated. Defaults to Device.GPU.
97 - extra_hash (Optional[int]): The hash value of additional
98 - factors, such as adapters, that influence the block hash
99 - in the prefixcaching block.
100 - """
101 - assert not self._is_allocated
102 - assert token_ids
103 - blocks = self._allocate_blocks_for_token_ids(prev_block=None,
104 - token_ids=token_ids,
105 - device=device,
106 - extra_hash=extra_hash)
107 - self.update(blocks)
108 - self._num_full_slots = len(token_ids)
109 -
110 - def update(self, blocks: List[Block]) -> None:
111 - """Resets the table to the newly provided blocks
112 - (with their corresponding block ids)
113 - """
114 - self._blocks.update(blocks)
115 -
116 - def append_token_ids(self,
117 - token_ids: List[int],
118 - num_lookahead_slots: int = 0,
119 - num_computed_slots: Optional[int] = None,
120 - extra_hash: Optional[int] = None) -> None:
121 - """Appends a sequence of token IDs to the existing blocks in the
122 - BlockTable.
123 -
124 - This method appends the given sequence of token IDs to the existing
125 - blocks in the BlockTable. If there is not enough space in the existing
126 - blocks, new blocks are allocated using the `ensure_num_empty_slots`
127 - method to accommodate the additional tokens.
128 -
129 - The token IDs are divided into chunks of size `block_size` (except for
130 - the first chunk, which may be smaller), and each chunk is appended to a
131 - separate block.
132 -
133 - Args:
134 - token_ids (List[int]): The sequence of token IDs to be appended.
135 - num_computed_slots (Optional[int]): The number of KV cache slots
136 - that are already filled (computed).
137 - When sliding window is enabled, this is used to compute how many
138 - blocks to drop at the front of the sequence.
139 - Without sliding window, None can be passed.
140 - Without chunked prefill, it should be the same as
141 - _num_full_slots.
142 - extra_hash (Optional[int]): The hash value of additional
143 - factors such as adapters that influence the block, apart
144 - from the token_ids.
145 - """
146 - assert self._is_allocated, "no blocks have been allocated"
147 - assert len(self._blocks) > 0
148 -
149 - # Drop blocks that are no longer needed due to sliding window
150 - if self._max_block_sliding_window is not None:
151 - null_block = self._allocator.allocate_or_get_null_block()
152 - assert num_computed_slots is not None
153 - end_block_idx = (num_computed_slots //
154 - self._block_size) - self._max_block_sliding_window
155 - for idx in range(0, end_block_idx):
156 - b = self._blocks[idx]
157 - if b is not null_block:
158 - self._allocator.free(b)
159 - self._blocks[idx] = null_block
160 -
161 - # Ensure there are enough empty slots for the new tokens plus
162 - # lookahead slots
163 - self.ensure_num_empty_slots(num_empty_slots=len(token_ids) +
164 - num_lookahead_slots,
165 - extra_hash=extra_hash)
166 -
167 - # Update the blocks with the new tokens
168 - first_block_idx = self._num_full_slots // self._block_size
169 - token_blocks = self._chunk_token_blocks_for_append(token_ids)
170 -
171 - for i, token_block in enumerate(token_blocks):
172 - self._blocks.append_token_ids(first_block_idx + i, token_block)
173 -
174 - self._num_full_slots += len(token_ids)
175 -
176 - def ensure_num_empty_slots(self,
177 - num_empty_slots: int,
178 - extra_hash: Optional[int] = None) -> None:
179 - """Ensures that the BlockTable has at least the specified number of
180 - empty slots available.
181 -
182 - This method checks if the BlockTable has enough empty slots (i.e.,
183 - available space) to accommodate the requested number of tokens. If not,
184 - it allocates additional blocks on the GPU to ensure that the required
185 - number of empty slots is available.
186 -
187 - Args:
188 - num_empty_slots (int): The minimum number of empty slots required.
189 - extra_hash (Optional[int]): The hash value of additional
190 - factors such as adapters that influence the block, apart
191 - from the token_ids.
192 - """
193 - # Currently the block table only supports
194 - # appending tokens to GPU blocks.
195 - device = Device.GPU
196 - assert self._is_allocated
197 -
198 - if self._num_empty_slots >= num_empty_slots:
199 - return
200 -
201 - slots_to_allocate = num_empty_slots - self._num_empty_slots
202 - blocks_to_allocate = cdiv(slots_to_allocate, self._block_size)
203 -
204 - for _ in range(blocks_to_allocate):
205 - assert len(self._blocks) > 0
206 - self._blocks.append(
207 - self._allocator.allocate_mutable_block(
208 - prev_block=self._blocks[-1],
209 - device=device,
210 - extra_hash=extra_hash))
211 -
212 - def fork(self) -> "BlockTable":
213 - """Creates a new BlockTable instance with a copy of the blocks from the
214 - current instance.
215 -
216 - This method creates a new BlockTable instance with the same block size,
217 - block allocator, and a copy of the blocks from the current instance. The
218 - new BlockTable has its own independent set of blocks, but shares the
219 - same underlying memory allocation with the original BlockTable.
220 -
221 - Returns:
222 - BlockTable: A new BlockTable instance with a copy of the blocks from
223 - the current instance.
224 - """
225 - assert self._is_allocated
226 - assert len(self._blocks) > 0
227 - forked_blocks = self._allocator.fork(self._blocks[-1])
228 - return BlockTable(
229 - block_size=self._block_size,
230 - block_allocator=self._allocator,
231 - _blocks=forked_blocks,
232 - max_block_sliding_window=self._max_block_sliding_window,
233 - )
234 -
235 - def free(self) -> None:
236 - """Frees the memory occupied by the blocks in the BlockTable.
237 -
238 - This method iterates over all the blocks in the `_blocks` list and calls
239 - the `free` method of the `_allocator` object to release the memory
240 - occupied by each block. After freeing all the blocks, the `_blocks` list
241 - is set to `None`.
242 - """
243 - for block in self.blocks:
244 - self._allocator.free(block)
245 - self._blocks.reset()
246 -
247 - @property
248 - def physical_block_ids(self) -> List[int]:
249 - """Returns a list of physical block indices for the blocks in the
250 - BlockTable.
251 -
252 - This property returns a list of integers, where each integer represents
253 - the physical block index of a corresponding block in the `_blocks` list.
254 - The physical block index is a unique identifier for the memory location
255 - occupied by the block.
256 -
257 - Returns:
258 - List[int]: A list of physical block indices for the blocks in the
259 - BlockTable.
260 - """
261 - return self._blocks.ids()
262 -
263 - def get_unseen_token_ids(self, sequence_token_ids: List[int]) -> List[int]:
264 - """Get the number of "unseen" tokens in the sequence.
265 -
266 - Unseen tokens are tokens in the sequence corresponding to this block
267 - table, but are not yet appended to this block table.
268 -
269 - Args:
270 - sequence_token_ids (List[int]): The list of token ids in the
271 - sequence.
272 -
273 - Returns:
274 - List[int]: The postfix of sequence_token_ids that has not yet been
275 - appended to the block table.
276 - """
277 -
278 - # Since the block table is append-only, the unseen token ids are the
279 - # ones after the appended ones.
280 - return sequence_token_ids[self.num_full_slots:]
281 -
282 - def _allocate_blocks_for_token_ids(
283 - self,
284 - prev_block: Optional[Block],
285 - token_ids: List[int],
286 - device: Device,
287 - extra_hash: Optional[int] = None) -> List[Block]:
288 - blocks: List[Block] = []
289 -
290 - block_token_ids = []
291 - tail_token_ids = []
292 - for cur_token_ids in chunk_list(token_ids, self._block_size):
293 - if len(cur_token_ids) == self._block_size:
294 - block_token_ids.append(cur_token_ids)
295 - else:
296 - tail_token_ids.append(cur_token_ids)
297 -
298 - if block_token_ids:
299 - blocks.extend(
300 - self._allocator.allocate_immutable_blocks(
301 - prev_block,
302 - block_token_ids=block_token_ids,
303 - device=device,
304 - extra_hash=extra_hash))
305 - prev_block = blocks[-1]
306 -
307 - if tail_token_ids:
308 - assert len(tail_token_ids) == 1
309 - cur_token_ids = tail_token_ids[0]
310 -
311 - block = self._allocator.allocate_mutable_block(
312 - prev_block=prev_block, device=device, extra_hash=extra_hash)
313 - block.append_token_ids(cur_token_ids)
314 -
315 - blocks.append(block)
316 -
317 - return blocks
318 -
319 - def _get_all_token_ids(self) -> List[int]:
320 - # NOTE: This function is O(seq_len); use sparingly.
321 - token_ids: List[int] = []
322 -
323 - if not self._is_allocated:
324 - return token_ids
325 -
326 - for block in self.blocks:
327 - token_ids.extend(block.token_ids)
328 -
329 - return token_ids
330 -
331 - def _get_num_token_ids(self) -> int:
332 - res = 0
333 - for block in self.blocks:
334 - res += len(block.token_ids)
335 -
336 - return res
337 -
338 - @property
339 - def _is_allocated(self) -> bool:
340 - return len(self._blocks) > 0
341 -
342 - @property
343 - def blocks(self) -> List[Block]:
344 - return self._blocks.list()
345 -
346 - @property
347 - def _num_empty_slots(self) -> int:
348 - assert self._is_allocated
349 - return len(self._blocks) * self._block_size - self._num_full_slots
350 -
351 - @property
352 - def num_full_slots(self) -> int:
353 - """Returns the total number of tokens currently stored in the
354 - BlockTable.
355 -
356 - Returns:
357 - int: The total number of tokens currently stored in the BlockTable.
358 - """
359 - return self._num_full_slots
360 -
361 - def get_num_blocks_touched_by_append_slots(
362 - self, token_ids: List[int], num_lookahead_slots: int) -> int:
363 - """Determine how many blocks will be "touched" by appending the token
364 - ids.
365 -
366 - This is required for the scheduler to determine whether a sequence can
367 - continue generation, or if it must be preempted.
368 - """
369 - # Math below is equivalent to:
370 - # all_token_ids = token_ids + [-1] * num_lookahead_slots
371 - # token_blocks = self._chunk_token_blocks_for_append(all_token_ids)
372 - # return len(token_blocks)
373 -
374 - num_token_ids = len(token_ids) + num_lookahead_slots
375 - first_chunk_size = self._block_size - (self._num_full_slots %
376 - self._block_size)
377 - num_token_blocks = (1 + math.ceil(
378 - (num_token_ids - first_chunk_size) / self._block_size))
379 - return num_token_blocks
380 -
381 - def _chunk_token_blocks_for_append(
382 - self, token_ids: List[int]) -> List[List[int]]:
383 - """Split the token ids into block-sized chunks so they can be easily
384 - appended to blocks. The first such "token block" may have less token ids
385 - than the block size, since the last allocated block may be partially
386 - full.
387 -
388 - If no token ids are provided, then no chunks are returned.
389 - """
390 -
391 - if not token_ids:
392 - return []
393 -
394 - first_chunk_size = self._block_size - (self._num_full_slots %
395 - self._block_size)
396 - token_blocks = [token_ids[:first_chunk_size]]
397 - token_blocks.extend(
398 - chunk_list(token_ids[first_chunk_size:], self._block_size))
399 - return token_blocks
vllm/core/block/common.py deleted
-371
@@ -1,371 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from collections import deque
5 -from dataclasses import dataclass
6 -from typing import Deque, Dict, Iterable, List, Optional, Protocol, Tuple
7 -
8 -from vllm.core.block.interfaces import Block, BlockAllocator
9 -
10 -BlockId = int
11 -RefCount = int
12 -
13 -
14 -class RefCounterProtocol(Protocol):
15 -
16 - def incr(self, block_id: BlockId) -> RefCount:
17 - raise NotImplementedError
18 -
19 - def decr(self, block_id: BlockId) -> RefCount:
20 - raise NotImplementedError
21 -
22 - def get(self, block_id: BlockId) -> RefCount:
23 - raise NotImplementedError
24 -
25 -
26 -class RefCounter(RefCounterProtocol):
27 - """A class for managing reference counts for a set of block indices.
28 -
29 - The RefCounter class maintains a dictionary that maps block indices to their
30 - corresponding reference counts. It provides methods to increment, decrement,
31 - and retrieve the reference count for a given block index.
32 -
33 - Args:
34 - all_block_indices (Iterable[BlockId]): An iterable of block indices
35 - to initialize the reference counter with.
36 - """
37 -
38 - def __init__(self, all_block_indices: Iterable[BlockId]):
39 - deduped = set(all_block_indices)
40 - self._refcounts: Dict[BlockId, RefCount] = {
41 - index: 0
42 - for index in deduped
43 - }
44 -
45 - def incr(self, block_id: BlockId) -> RefCount:
46 - assert block_id in self._refcounts
47 - pre_incr_refcount = self._refcounts[block_id]
48 -
49 - assert pre_incr_refcount >= 0
50 -
51 - post_incr_refcount = pre_incr_refcount + 1
52 - self._refcounts[block_id] = post_incr_refcount
53 - return post_incr_refcount
54 -
55 - def decr(self, block_id: BlockId) -> RefCount:
56 - assert block_id in self._refcounts
57 - refcount = self._refcounts[block_id]
58 -
59 - assert refcount > 0
60 - refcount -= 1
61 -
62 - self._refcounts[block_id] = refcount
63 -
64 - return refcount
65 -
66 - def get(self, block_id: BlockId) -> RefCount:
67 - assert block_id in self._refcounts
68 - return self._refcounts[block_id]
69 -
70 - def as_readonly(self) -> "ReadOnlyRefCounter":
71 - return ReadOnlyRefCounter(self)
72 -
73 -
74 -class ReadOnlyRefCounter(RefCounterProtocol):
75 - """A read-only view of the RefCounter class.
76 -
77 - The ReadOnlyRefCounter class provides a read-only interface to access the
78 - reference counts maintained by a RefCounter instance. It does not allow
79 - modifications to the reference counts.
80 -
81 - Args:
82 - refcounter (RefCounter): The RefCounter instance to create a read-only
83 - view for.
84 - """
85 -
86 - def __init__(self, refcounter: RefCounter):
87 - self._refcounter = refcounter
88 -
89 - def incr(self, block_id: BlockId) -> RefCount:
90 - raise ValueError("Incr not allowed")
91 -
92 - def decr(self, block_id: BlockId) -> RefCount:
93 - raise ValueError("Decr not allowed")
94 -
95 - def get(self, block_id: BlockId) -> RefCount:
96 - return self._refcounter.get(block_id)
97 -
98 -
99 -class CopyOnWriteTracker:
100 - """A class for tracking and managing copy-on-write operations for blocks.
101 -
102 - The CopyOnWriteTracker class maintains a mapping of source block indices to
103 - their corresponding copy-on-write destination block indices. It works in
104 - conjunction with a RefCounter.
105 -
106 - Args:
107 - refcounter (RefCounter): The reference counter used to track block
108 - reference counts.
109 - """
110 -
111 - def __init__(self, refcounter: RefCounterProtocol):
112 - self._copy_on_writes: List[Tuple[BlockId, BlockId]] = []
113 - self._refcounter = refcounter
114 -
115 - def is_appendable(self, block: Block) -> bool:
116 - """Checks if the block is shared or not. If shared, then it cannot
117 - be appended and needs to be duplicated via copy-on-write
118 - """
119 - block_id = block.block_id
120 - if block_id is None:
121 - return True
122 -
123 - refcount = self._refcounter.get(block_id)
124 - return refcount <= 1
125 -
126 - def record_cow(self, src_block_id: Optional[BlockId],
127 - trg_block_id: Optional[BlockId]) -> None:
128 - """Records a copy-on-write operation from source to target block id
129 - Args:
130 - src_block_id (BlockId): The source block id from which to copy
131 - the data
132 - trg_block_id (BlockId): The target block id to which the data
133 - is copied
134 - """
135 - assert src_block_id is not None
136 - assert trg_block_id is not None
137 - self._copy_on_writes.append((src_block_id, trg_block_id))
138 -
139 - def clear_cows(self) -> List[Tuple[BlockId, BlockId]]:
140 - """Clears the copy-on-write tracking information and returns the current
141 - state.
142 -
143 - This method returns a list mapping source block indices to
144 - destination block indices for the current copy-on-write operations.
145 - It then clears the internal tracking information.
146 -
147 - Returns:
148 - List[Tuple[BlockId, BlockId]]: A list mapping source
149 - block indices to destination block indices for the
150 - current copy-on-write operations.
151 - """
152 - cows = self._copy_on_writes
153 - self._copy_on_writes = []
154 - return cows
155 -
156 -
157 -class BlockPool:
158 - """Used to pre-allocate block objects, in order to avoid excessive python
159 - object allocations/deallocations.
160 - The pool starts from "pool_size" objects and will increase to more objects
161 - if necessary
162 -
163 - Note that multiple block objects may point to the same physical block id,
164 - which is why this pool is needed, so that it will be easier to support
165 - prefix caching and more complicated sharing of physical blocks.
166 - """
167 -
168 - def __init__(self, block_size: int, create_block: Block.Factory,
169 - allocator: BlockAllocator, pool_size: int):
170 - self._block_size = block_size
171 - self._create_block = create_block
172 - self._allocator = allocator
173 - self._pool_size = pool_size
174 - assert self._pool_size >= 0
175 -
176 - self._free_ids: Deque[int] = deque(range(self._pool_size))
177 - self._pool = []
178 - for i in range(self._pool_size):
179 - self._pool.append(
180 - self._create_block(prev_block=None,
181 - token_ids=[],
182 - block_size=self._block_size,
183 - allocator=self._allocator,
184 - block_id=None,
185 - extra_hash=None))
186 -
187 - def increase_pool(self):
188 - """Doubles the internal pool size
189 - """
190 - cur_pool_size = self._pool_size
191 - new_pool_size = cur_pool_size * 2
192 - self._pool_size = new_pool_size
193 -
194 - self._free_ids += deque(range(cur_pool_size, new_pool_size))
195 -
196 - for i in range(cur_pool_size, new_pool_size):
197 - self._pool.append(
198 - self._create_block(prev_block=None,
199 - token_ids=[],
200 - block_size=self._block_size,
201 - allocator=self._allocator,
202 - block_id=None,
203 - extra_hash=None))
204 -
205 - def init_block(self,
206 - prev_block: Optional[Block],
207 - token_ids: List[int],
208 - block_size: int,
209 - physical_block_id: Optional[int],
210 - extra_hash: Optional[int] = None) -> Block:
211 - if len(self._free_ids) == 0:
212 - self.increase_pool()
213 - assert len(self._free_ids) > 0
214 -
215 - pool_id = self._free_ids.popleft()
216 -
217 - block = self._pool[pool_id]
218 - block.__init__( # type: ignore[misc]
219 - prev_block=prev_block,
220 - token_ids=token_ids,
221 - block_size=block_size,
222 - allocator=block._allocator, # type: ignore[attr-defined]
223 - block_id=physical_block_id,
224 - extra_hash=extra_hash)
225 - block.pool_id = pool_id # type: ignore[attr-defined]
226 - return block
227 -
228 - def free_block(self, block: Block) -> None:
229 - self._free_ids.appendleft(block.pool_id) # type: ignore[attr-defined]
230 -
231 -
232 -class BlockList:
233 - """This class is an optimization to allow fast-access to physical
234 - block ids. It maintains a block id list that is updated with the
235 - block list and this avoids the need to reconstruct the block id
236 - list on every iteration of the block manager
237 - """
238 -
239 - def __init__(self, blocks: List[Block]):
240 - self._blocks: List[Block] = []
241 - self._block_ids: List[int] = []
242 -
243 - self.update(blocks)
244 -
245 - def _add_block_id(self, block_id: Optional[BlockId]) -> None:
246 - assert block_id is not None
247 - self._block_ids.append(block_id)
248 -
249 - def _update_block_id(self, block_index: int,
250 - new_block_id: Optional[BlockId]) -> None:
251 - assert new_block_id is not None
252 - self._block_ids[block_index] = new_block_id
253 -
254 - def update(self, blocks: List[Block]):
255 - self._blocks = blocks
256 -
257 - # Cache block ids for fast query
258 - self._block_ids = []
259 - for block in self._blocks:
260 - self._add_block_id(block.block_id)
261 -
262 - def append_token_ids(self, block_index: int, token_ids: List[int]) -> None:
263 - block = self._blocks[block_index]
264 - prev_block_id = block.block_id
265 -
266 - block.append_token_ids(token_ids)
267 -
268 - # CoW or promotion may update the internal block_id
269 - if prev_block_id != block.block_id:
270 - self._update_block_id(block_index, block.block_id)
271 -
272 - def append(self, new_block: Block):
273 - self._blocks.append(new_block)
274 - self._add_block_id(new_block.block_id)
275 -
276 - def __len__(self) -> int:
277 - return len(self._blocks)
278 -
279 - def __getitem__(self, block_index: int) -> Block:
280 - return self._blocks[block_index]
281 -
282 - def __setitem__(self, block_index: int, new_block: Block) -> None:
283 - self._blocks[block_index] = new_block
284 - self._update_block_id(block_index, new_block.block_id)
285 -
286 - def reset(self):
287 - self._blocks = []
288 - self._block_ids = []
289 -
290 - def list(self) -> List[Block]:
291 - return self._blocks
292 -
293 - def ids(self) -> List[int]:
294 - return self._block_ids
295 -
296 -
297 -@dataclass
298 -class CacheMetricData:
299 - """A utility dataclass to maintain cache metric.
300 - To avoid overflow, we maintain the hit rate in block granularity, so that
301 - we can maintain a single hit rate for n_completed_block x block_size,
302 - and calculate the real time hit rate by the following:
303 - BS = The number of queries per block.
304 - nB = The number of completed blocks.
305 - HR = hit rate of (nB x BS) queries.
306 - Q = current number of queries (< BS).
307 - H = current number of hits (< BS).
308 - hit rate = ((HR x nB) + (H / Q) x (Q / BS)) / (nB + Q / BS)
309 - """
310 - num_completed_blocks: int = 0
311 - completed_block_cache_hit_rate: float = 0.0
312 - num_incompleted_block_queries: int = 0
313 - num_incompleted_block_hit: int = 0
314 - block_size: int = 1000
315 -
316 - def query(self, hit: bool):
317 - self.num_incompleted_block_queries += 1
318 - self.num_incompleted_block_hit += 1 if hit else 0
319 -
320 - # When a block is completed, update the cache hit rate
321 - # and reset the incomplete numbers.
322 - if self.num_incompleted_block_queries == self.block_size:
323 - hit_rate = (self.num_incompleted_block_hit /
324 - self.num_incompleted_block_queries)
325 - self.completed_block_cache_hit_rate = (
326 - self.completed_block_cache_hit_rate * self.num_completed_blocks
327 - + hit_rate) / (self.num_completed_blocks + 1)
328 - self.num_incompleted_block_queries = 0
329 - self.num_incompleted_block_hit = 0
330 - self.num_completed_blocks += 1
331 -
332 - def get_hit_rate(self):
333 - incomplete_ratio = self.num_incompleted_block_queries / self.block_size
334 - total_blocks = self.num_completed_blocks + incomplete_ratio
335 - if total_blocks == 0:
336 - return 0.0
337 -
338 - completed_block_hit, incompleted_block_hit = 0.0, 0.0
339 - if self.num_completed_blocks > 0:
340 - completed_block_hit = (self.completed_block_cache_hit_rate *
341 - self.num_completed_blocks)
342 - if self.num_incompleted_block_queries > 0:
343 - incompleted_hit_rate = (self.num_incompleted_block_hit /
344 - self.num_incompleted_block_queries)
345 - incompleted_block_hit = (incompleted_hit_rate * incomplete_ratio)
346 - return (completed_block_hit + incompleted_block_hit) / total_blocks
347 -
348 -
349 -def get_all_blocks_recursively(last_block: Block) -> List[Block]:
350 - """Retrieves all the blocks in a sequence starting from the last block.
351 -
352 - This function recursively traverses the sequence of blocks in reverse order,
353 - starting from the given last block, and returns a list of all the blocks in
354 - the sequence.
355 -
356 - Args:
357 - last_block (Block): The last block in the sequence.
358 -
359 - Returns:
360 - List[Block]: A list of all the blocks in the sequence, in the order they
361 - appear.
362 - """
363 -
364 - def recurse(block: Block, lst: List[Block]) -> None:
365 - if block.prev_block is not None:
366 - recurse(block.prev_block, lst)
367 - lst.append(block)
368 -
369 - all_blocks: List[Block] = []
370 - recurse(last_block, all_blocks)
371 - return all_blocks
vllm/core/block/cpu_gpu_block_allocator.py deleted
-439
@@ -1,439 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from typing import Dict, FrozenSet, List, Optional, Tuple
5 -
6 -from vllm.core.block.interfaces import (Block, BlockAllocator, BlockId,
7 - DeviceAwareBlockAllocator)
8 -from vllm.core.block.naive_block import NaiveBlock, NaiveBlockAllocator
9 -from vllm.core.block.prefix_caching_block import PrefixCachingBlockAllocator
10 -from vllm.utils import Device
11 -
12 -
13 -class CpuGpuBlockAllocator(DeviceAwareBlockAllocator):
14 - """A block allocator that can allocate blocks on both CPU and GPU memory.
15 -
16 - This class implements the `DeviceAwareBlockAllocator` interface and provides
17 - functionality for allocating and managing blocks of memory on both CPU and
18 - GPU devices.
19 -
20 - The `CpuGpuBlockAllocator` maintains separate memory pools for CPU and GPU
21 - blocks, and allows for allocation, deallocation, forking, and swapping of
22 - blocks across these memory pools.
23 - """
24 -
25 - @staticmethod
26 - def create(
27 - allocator_type: str,
28 - num_gpu_blocks: int,
29 - num_cpu_blocks: int,
30 - block_size: int,
31 - ) -> DeviceAwareBlockAllocator:
32 - """Creates a CpuGpuBlockAllocator instance with the specified
33 - configuration.
34 -
35 - This static method creates and returns a CpuGpuBlockAllocator instance
36 - based on the provided parameters. It initializes the CPU and GPU block
37 - allocators with the specified number of blocks, block size, and
38 - allocator type.
39 -
40 - Args:
41 - allocator_type (str): The type of block allocator to use for CPU
42 - and GPU blocks. Currently supported values are "naive" and
43 - "prefix_caching".
44 - num_gpu_blocks (int): The number of blocks to allocate for GPU
45 - memory.
46 - num_cpu_blocks (int): The number of blocks to allocate for CPU
47 - memory.
48 - block_size (int): The size of each block in number of tokens.
49 -
50 - Returns:
51 - DeviceAwareBlockAllocator: A CpuGpuBlockAllocator instance with the
52 - specified configuration.
53 -
54 - Notes:
55 - - The block IDs are assigned contiguously, with GPU block IDs coming
56 - before CPU block IDs.
57 - """
58 - reserved_blocks = 0
59 - block_ids = list(
60 - range(reserved_blocks, num_gpu_blocks + num_cpu_blocks))
61 - num_gpu_blocks -= reserved_blocks
62 - gpu_block_ids = block_ids[:num_gpu_blocks]
63 - cpu_block_ids = block_ids[num_gpu_blocks:]
64 -
65 - if allocator_type == "naive":
66 - gpu_allocator: BlockAllocator = NaiveBlockAllocator(
67 - create_block=NaiveBlock, # type: ignore
68 - num_blocks=num_gpu_blocks,
69 - block_size=block_size,
70 - block_ids=gpu_block_ids,
71 - )
72 -
73 - cpu_allocator: BlockAllocator = NaiveBlockAllocator(
74 - create_block=NaiveBlock, # type: ignore
75 - num_blocks=num_cpu_blocks,
76 - block_size=block_size,
77 - block_ids=cpu_block_ids,
78 - )
79 - elif allocator_type == "prefix_caching":
80 - gpu_allocator = PrefixCachingBlockAllocator(
81 - num_blocks=num_gpu_blocks,
82 - block_size=block_size,
83 - block_ids=gpu_block_ids,
84 - )
85 -
86 - cpu_allocator = PrefixCachingBlockAllocator(
87 - num_blocks=num_cpu_blocks,
88 - block_size=block_size,
89 - block_ids=cpu_block_ids,
90 - )
91 - else:
92 - raise ValueError(f"Unknown allocator type {allocator_type=}")
93 -
94 - return CpuGpuBlockAllocator(
95 - cpu_block_allocator=cpu_allocator,
96 - gpu_block_allocator=gpu_allocator,
97 - )
98 -
99 - def __init__(self, cpu_block_allocator: BlockAllocator,
100 - gpu_block_allocator: BlockAllocator):
101 - assert not (
102 - cpu_block_allocator.all_block_ids
103 - & gpu_block_allocator.all_block_ids
104 - ), "cpu and gpu block allocators can't have intersection of block ids"
105 -
106 - self._allocators = {
107 - Device.CPU: cpu_block_allocator,
108 - Device.GPU: gpu_block_allocator,
109 - }
110 -
111 - self._swap_mapping: Dict[int, int] = {}
112 - self._null_block: Optional[Block] = None
113 -
114 - self._block_ids_to_allocator: Dict[int, BlockAllocator] = {}
115 - for _, allocator in self._allocators.items():
116 - for block_id in allocator.all_block_ids:
117 - self._block_ids_to_allocator[block_id] = allocator
118 -
119 - def allocate_or_get_null_block(self) -> Block:
120 - if self._null_block is None:
121 - self._null_block = NullBlock(
122 - self.allocate_mutable_block(None, Device.GPU))
123 - return self._null_block
124 -
125 - def allocate_mutable_block(self,
126 - prev_block: Optional[Block],
127 - device: Device,
128 - extra_hash: Optional[int] = None) -> Block:
129 - """Allocates a new mutable block on the specified device.
130 -
131 - Args:
132 - prev_block (Optional[Block]): The previous block to in the sequence.
133 - Used for prefix hashing.
134 - device (Device): The device on which to allocate the new block.
135 - extra_hash (Optional[int]): The hash value of additional
136 - factors, such as adapters, that influence the block hash
137 - in the prefix caching block.
138 -
139 - Returns:
140 - Block: The newly allocated mutable block.
141 - """
142 - return self._allocators[device].allocate_mutable_block(
143 - prev_block, extra_hash=extra_hash)
144 -
145 - def allocate_immutable_blocks(
146 - self,
147 - prev_block: Optional[Block],
148 - block_token_ids: List[List[int]],
149 - device: Device,
150 - extra_hash: Optional[int] = None) -> List[Block]:
151 - """Allocates a new group of immutable blocks with the provided block
152 - token IDs on the specified device.
153 -
154 - Args:
155 - prev_block (Optional[Block]): The previous block in the sequence.
156 - Used for prefix hashing.
157 - block_token_ids (List[int]): The list of block token IDs to be
158 - stored in the new blocks.
159 - device (Device): The device on which to allocate the new block.
160 - extra_hash (Optional[int]): The hash value of additional
161 - factors, such as adapters, that influence the block hash
162 - in the prefix caching block.
163 -
164 - Returns:
165 - List[Block]: The newly allocated list of immutable blocks
166 - containing the provided block token IDs.
167 - """
168 - return self._allocators[device].allocate_immutable_blocks(
169 - prev_block, block_token_ids, extra_hash=extra_hash)
170 -
171 - def allocate_immutable_block(self,
172 - prev_block: Optional[Block],
173 - token_ids: List[int],
174 - device: Device,
175 - extra_hash: Optional[int] = None) -> Block:
176 - """Allocates a new immutable block with the provided token IDs on the
177 - specified device.
178 -
179 - Args:
180 - prev_block (Optional[Block]): The previous block in the sequence.
181 - Used for prefix hashing.
182 - token_ids (List[int]): The list of token IDs to be stored in the new
183 - block.
184 - device (Device): The device on which to allocate the new block.
185 - extra_hash (Optional[int]): The hash value of additional
186 - factors, such as adapters, that influence the block hash
187 - in the prefix caching block.
188 -
189 - Returns:
190 - Block: The newly allocated immutable block containing the provided
191 - token IDs.
192 - """
193 - return self._allocators[device].allocate_immutable_block(
194 - prev_block, token_ids, extra_hash=extra_hash)
195 -
196 - def free(self, block: Block) -> None:
197 - """Frees the memory occupied by the given block.
198 -
199 - Args:
200 - block (Block): The block to be freed.
201 - """
202 - # Null block should never be freed
203 - if isinstance(block, NullBlock):
204 - return
205 - block_id = block.block_id
206 - assert block_id is not None
207 - allocator = self._block_ids_to_allocator[block_id]
208 - allocator.free(block)
209 -
210 - def fork(self, last_block: Block) -> List[Block]:
211 - """Creates a new sequence of blocks that shares the same underlying
212 - memory as the original sequence.
213 -
214 - Args:
215 - last_block (Block): The last block in the original sequence.
216 -
217 - Returns:
218 - List[Block]: A new list of blocks that shares the same memory as the
219 - original sequence.
220 - """
221 - # do not attempt to fork the null block
222 - assert not isinstance(last_block, NullBlock)
223 - block_id = last_block.block_id
224 - assert block_id is not None
225 - allocator = self._block_ids_to_allocator[block_id]
226 - return allocator.fork(last_block)
227 -
228 - def get_num_free_blocks(self, device: Device) -> int:
229 - """Returns the number of free blocks available on the specified device.
230 -
231 - Args:
232 - device (Device): The device for which to query the number of free
233 - blocks. AssertionError is raised if None is passed.
234 -
235 - Returns:
236 - int: The number of free blocks available on the specified device.
237 - """
238 - return self._allocators[device].get_num_free_blocks()
239 -
240 - def get_num_total_blocks(self, device: Device) -> int:
241 - return self._allocators[device].get_num_total_blocks()
242 -
243 - def get_physical_block_id(self, device: Device, absolute_id: int) -> int:
244 - """Returns the zero-offset block id on certain device given the
245 - absolute block id.
246 -
247 - Args:
248 - device (Device): The device for which to query relative block id.
249 - absolute_id (int): The absolute block id for the block in
250 - whole allocator.
251 -
252 - Returns:
253 - int: The zero-offset block id on certain device.
254 - """
255 - return self._allocators[device].get_physical_block_id(absolute_id)
256 -
257 - def swap(self, blocks: List[Block], src_device: Device,
258 - dst_device: Device) -> Dict[int, int]:
259 - """Execute the swap for the given blocks from source_device
260 - on to dest_device, save the current swap mapping and append
261 - them to the accumulated `self._swap_mapping` for each
262 - scheduling move.
263 -
264 - Args:
265 - blocks: List of blocks to be swapped.
266 - src_device (Device): Device to swap the 'blocks' from.
267 - dst_device (Device): Device to swap the 'blocks' to.
268 -
269 - Returns:
270 - Dict[int, int]: Swap mapping from source_device
271 - on to dest_device.
272 - """
273 - src_block_ids = [block.block_id for block in blocks]
274 - self._allocators[src_device].swap_out(blocks)
275 - self._allocators[dst_device].swap_in(blocks)
276 - dst_block_ids = [block.block_id for block in blocks]
277 -
278 - current_swap_mapping: Dict[int, int] = {}
279 - for src_block_id, dst_block_id in zip(src_block_ids, dst_block_ids):
280 - if src_block_id is not None and dst_block_id is not None:
281 - self._swap_mapping[src_block_id] = dst_block_id
282 - current_swap_mapping[src_block_id] = dst_block_id
283 - return current_swap_mapping
284 -
285 - def get_num_full_blocks_touched(self, blocks: List[Block],
286 - device: Device) -> int:
287 - """Returns the number of full blocks that will be touched by
288 - swapping in/out the given blocks on to the 'device'.
289 -
290 - Args:
291 - blocks: List of blocks to be swapped.
292 - device (Device): Device to swap the 'blocks' on.
293 -
294 - Returns:
295 - int: the number of full blocks that will be touched by
296 - swapping in/out the given blocks on to the 'device'.
297 - Non full blocks are ignored when deciding the number
298 - of blocks to touch.
299 - """
300 - return self._allocators[device].get_num_full_blocks_touched(blocks)
301 -
302 - def clear_copy_on_writes(self) -> List[Tuple[int, int]]:
303 - """Clears the copy-on-write (CoW) state and returns the mapping of
304 - source to destination block IDs.
305 -
306 - Returns:
307 - List[Tuple[int, int]]: A list mapping source block IDs to
308 - destination block IDs.
309 - """
310 - # CoW only supported on GPU
311 - device = Device.GPU
312 - return self._allocators[device].clear_copy_on_writes()
313 -
314 - def mark_blocks_as_accessed(self, block_ids: List[int],
315 - now: float) -> None:
316 - """Mark blocks as accessed, only use for prefix caching."""
317 - # Prefix caching only supported on GPU.
318 - device = Device.GPU
319 - return self._allocators[device].mark_blocks_as_accessed(block_ids, now)
320 -
321 - def mark_blocks_as_computed(self, block_ids: List[int]) -> None:
322 - """Mark blocks as accessed, only use for prefix caching."""
323 - # Prefix caching only supported on GPU.
324 - device = Device.GPU
325 - return self._allocators[device].mark_blocks_as_computed(block_ids)
326 -
327 - def get_common_computed_block_ids(
328 - self, computed_seq_block_ids: List[List[int]]) -> List[int]:
329 - # Prefix caching only supported on GPU.
330 - device = Device.GPU
331 - return self._allocators[device].get_common_computed_block_ids(
332 - computed_seq_block_ids)
333 -
334 - @property
335 - def all_block_ids(self) -> FrozenSet[int]:
336 - return frozenset(self._block_ids_to_allocator.keys())
337 -
338 - def get_prefix_cache_hit_rate(self, device: Device) -> float:
339 - """Prefix cache hit rate. -1 means not supported or disabled."""
340 - assert device in self._allocators
341 - return self._allocators[device].get_prefix_cache_hit_rate()
342 -
343 - def reset_prefix_cache(self, device: Optional[Device] = None) -> bool:
344 - """Reset prefix cache for specified or all devices."""
345 - if device:
346 - return self._allocators[device].reset_prefix_cache()
347 - success = True
348 - for allocator in self._allocators.values():
349 - success = success and allocator.reset_prefix_cache()
350 - return success
351 -
352 - def get_and_reset_swaps(self) -> List[Tuple[int, int]]:
353 - """Returns and clears the mapping of source to destination block IDs.
354 - Will be called after every swapping operations for now, and after every
355 - schedule when BlockManagerV2 become default. Currently not useful.
356 -
357 - Returns:
358 - List[Tuple[int, int]]: A mapping of source to destination block IDs.
359 - """
360 - mapping = self._swap_mapping.copy()
361 - self._swap_mapping.clear()
362 - return list(mapping.items())
363 -
364 - def find_cached_blocks_prefix(
365 - self,
366 - block_hashes: List[int],
367 - device: Device = Device.GPU,
368 - ) -> List[int]:
369 - return self._allocators[device].find_cached_blocks_prefix(block_hashes)
370 -
371 -
372 -class NullBlock(Block):
373 - """
374 - Null blocks are used as a placeholders for KV cache blocks that have
375 - been dropped due to sliding window.
376 - This implementation just wraps an ordinary block and prevents it from
377 - being modified. It also allows for testing if a block is NullBlock
378 - via isinstance().
379 - """
380 -
381 - def __init__(self, proxy: Block):
382 - super().__init__()
383 - self._proxy = proxy
384 -
385 - def append_token_ids(self, token_ids: List[BlockId]):
386 - raise ValueError("null block should not be modified")
387 -
388 - @property
389 - def block_id(self):
390 - return self._proxy.block_id
391 -
392 - @block_id.setter
393 - def block_id(self, value: Optional[BlockId]):
394 - raise ValueError("null block should not be modified")
395 -
396 - @property
397 - def token_ids(self) -> List[BlockId]:
398 - return self._proxy.token_ids
399 -
400 - @property
401 - def num_tokens_total(self) -> int:
402 - raise NotImplementedError(
403 - "num_tokens_total is not used for null block")
404 -
405 - @property
406 - def num_empty_slots(self) -> BlockId:
407 - return self._proxy.num_empty_slots
408 -
409 - @property
410 - def is_full(self):
411 - return self._proxy.is_full
412 -
413 - @property
414 - def prev_block(self):
415 - return self._proxy.prev_block
416 -
417 - @property
418 - def extra_hash(self):
419 - return None
420 -
421 - @property
422 - def computed(self):
423 - return self._proxy.computed
424 -
425 - @computed.setter
426 - def computed(self, value):
427 - self._proxy.computed = value
428 -
429 - @property
430 - def last_accessed(self) -> float:
431 - return self._proxy.last_accessed
432 -
433 - @last_accessed.setter
434 - def last_accessed(self, last_accessed_ts: float):
435 - self._proxy.last_accessed = last_accessed_ts
436 -
437 - @property
438 - def content_hash(self):
439 - return self._proxy.content_hash
vllm/core/block/interfaces.py deleted
-319
@@ -1,319 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from abc import ABC, abstractmethod
5 -from typing import Dict, FrozenSet, List, Optional, Protocol, Tuple
6 -
7 -from vllm.utils import Device
8 -
9 -BlockId = int
10 -
11 -
12 -class Block(ABC):
13 -
14 - @abstractmethod
15 - def append_token_ids(self, token_ids: List[int]) -> None:
16 - pass
17 -
18 - @property
19 - @abstractmethod
20 - def block_id(self) -> Optional[int]:
21 - pass
22 -
23 - @block_id.setter
24 - @abstractmethod
25 - def block_id(self, value: Optional[int]) -> None:
26 - """NOTE: Do not use this API outside Block."""
27 - self._block_id = value
28 -
29 - @property
30 - @abstractmethod
31 - def token_ids(self) -> List[int]:
32 - pass
33 -
34 - @property
35 - @abstractmethod
36 - def num_tokens_total(self) -> int:
37 - """The number of tokens till the current block (inclusive)
38 - """
39 - pass
40 -
41 - @property
42 - @abstractmethod
43 - def num_empty_slots(self) -> int:
44 - pass
45 -
46 - @property
47 - @abstractmethod
48 - def is_full(self) -> bool:
49 - pass
50 -
51 - @property
52 - @abstractmethod
53 - def prev_block(self) -> Optional["Block"]:
54 - pass
55 -
56 - @property
57 - @abstractmethod
58 - def extra_hash(self) -> Optional[int]:
59 - return None
60 -
61 - @property
62 - @abstractmethod
63 - def computed(self) -> bool:
64 - raise NotImplementedError
65 -
66 - @computed.setter
67 - @abstractmethod
68 - def computed(self, value) -> bool:
69 - """Should be only used by PrefixCacingAllocator"""
70 - raise NotImplementedError
71 -
72 - @property
73 - @abstractmethod
74 - def last_accessed(self) -> float:
75 - raise NotImplementedError
76 -
77 - @last_accessed.setter
78 - @abstractmethod
79 - def last_accessed(self, last_accessed_ts: float):
80 - raise NotImplementedError
81 -
82 - class Factory(Protocol):
83 -
84 - @abstractmethod
85 - def __call__(
86 - self,
87 - prev_block: Optional["Block"],
88 - token_ids: List[int],
89 - block_size: int,
90 - allocator: "BlockAllocator",
91 - block_id: Optional[int] = None,
92 - computed: bool = False,
93 - extra_hash: Optional[int] = None,
94 - ) -> "Block":
95 - pass
96 -
97 - @property
98 - @abstractmethod
99 - def content_hash(self) -> Optional[int]:
100 - """Return the content-based hash of the current block, or None if it is
101 - not yet defined or not supported.
102 -
103 - For the content-based hash to be defined, the current block must be
104 - full.
105 - """
106 - return None
107 -
108 -
109 -class BlockAllocator(ABC):
110 -
111 - @abstractmethod
112 - def allocate_mutable_block(self, prev_block: Optional[Block],
113 - extra_hash: Optional[int]) -> Block:
114 - pass
115 -
116 - @abstractmethod
117 - def allocate_immutable_block(self, prev_block: Optional[Block],
118 - token_ids: List[int],
119 - extra_hash: Optional[int]) -> Block:
120 - pass
121 -
122 - @abstractmethod
123 - def allocate_immutable_blocks(self, prev_block: Optional[Block],
124 - block_token_ids: List[List[int]],
125 - extra_hash: Optional[int]) -> List[Block]:
126 - pass
127 -
128 - @abstractmethod
129 - def free(self, block: Block) -> None:
130 - pass
131 -
132 - @abstractmethod
133 - def fork(self, last_block: Block) -> List[Block]:
134 - pass
135 -
136 - @abstractmethod
137 - def get_num_total_blocks(self) -> int:
138 - pass
139 -
140 - @abstractmethod
141 - def get_num_free_blocks(self) -> int:
142 - pass
143 -
144 - @abstractmethod
145 - def get_physical_block_id(self, absolute_id: int) -> int:
146 - pass
147 -
148 - @abstractmethod
149 - def swap_out(self, blocks: List[Block]) -> None:
150 - pass
151 -
152 - @abstractmethod
153 - def swap_in(self, blocks: List[Block]) -> None:
154 - pass
155 -
156 - @property
157 - @abstractmethod
158 - def all_block_ids(self) -> FrozenSet[int]:
159 - pass
160 -
161 - @abstractmethod
162 - def clear_copy_on_writes(self) -> List[Tuple[int, int]]:
163 - pass
164 -
165 - @abstractmethod
166 - def mark_blocks_as_accessed(self, block_ids: List[int],
167 - now: float) -> None:
168 - pass
169 -
170 - @abstractmethod
171 - def mark_blocks_as_computed(self, block_ids: List[int]) -> None:
172 - pass
173 -
174 - @abstractmethod
175 - def get_common_computed_block_ids(
176 - self, computed_seq_block_ids: List[List[int]]) -> List[int]:
177 - pass
178 -
179 - @abstractmethod
180 - def cow_block_if_not_appendable(self, block: Block) -> BlockId:
181 - """NOTE: This should not be used besides Block"""
182 - pass
183 -
184 - @abstractmethod
185 - def promote_to_immutable_block(self, block: Block) -> BlockId:
186 - """NOTE: This should not be used besides Block"""
187 - pass
188 -
189 - @abstractmethod
190 - def get_num_full_blocks_touched(self, blocks: List[Block]) -> int:
191 - pass
192 -
193 - @abstractmethod
194 - def get_prefix_cache_hit_rate(self) -> float:
195 - """Prefix cache hit rate. -1 means not supported or disabled."""
196 - pass
197 -
198 - @abstractmethod
199 - def reset_prefix_cache(self) -> bool:
200 - """Reset prefix cache."""
201 - pass
202 -
203 - class NoFreeBlocksError(ValueError):
204 - pass
205 -
206 - @abstractmethod
207 - def find_cached_blocks_prefix(
208 - self,
209 - block_hashes: List[int],
210 - ) -> List[int]:
211 - pass
212 -
213 -
214 -class DeviceAwareBlockAllocator(ABC):
215 -
216 - @abstractmethod
217 - def allocate_mutable_block(self,
218 - prev_block: Optional[Block],
219 - device: Device,
220 - extra_hash: Optional[int] = None) -> Block:
221 - pass
222 -
223 - @abstractmethod
224 - def allocate_immutable_block(self,
225 - prev_block: Optional[Block],
226 - token_ids: List[int],
227 - device: Device,
228 - extra_hash: Optional[int] = None) -> Block:
229 - pass
230 -
231 - @abstractmethod
232 - def allocate_immutable_blocks(
233 - self,
234 - prev_block: Optional[Block],
235 - block_token_ids: List[List[int]],
236 - device: Device,
237 - extra_hash: Optional[int] = None,
238 - ) -> List[Block]:
239 - pass
240 -
241 - @abstractmethod
242 - def get_num_free_blocks(self, device: Device) -> int:
243 - pass
244 -
245 - @abstractmethod
246 - def get_num_total_blocks(self, device: Device) -> int:
247 - pass
248 -
249 - @abstractmethod
250 - def free(self, block: Block) -> None:
251 - pass
252 -
253 - @abstractmethod
254 - def fork(self, last_block: Block) -> List[Block]:
255 - pass
256 -
257 - @property
258 - @abstractmethod
259 - def all_block_ids(self) -> FrozenSet[int]:
260 - pass
261 -
262 - @abstractmethod
263 - def clear_copy_on_writes(self) -> List[Tuple[int, int]]:
264 - pass
265 -
266 - @abstractmethod
267 - def mark_blocks_as_accessed(self, block_ids: List[int],
268 - now: float) -> None:
269 - pass
270 -
271 - @abstractmethod
272 - def mark_blocks_as_computed(self, block_ids: List[int]) -> None:
273 - pass
274 -
275 - @abstractmethod
276 - def get_common_computed_block_ids(
277 - self, computed_seq_block_ids: List[List[int]]) -> List[int]:
278 - pass
279 -
280 - @abstractmethod
281 - def get_num_full_blocks_touched(self, blocks: List[Block],
282 - device: Device) -> int:
283 - pass
284 -
285 - @abstractmethod
286 - def swap(self, blocks: List[Block], src_device: Device,
287 - dst_device: Device) -> Dict[int, int]:
288 - pass
289 -
290 - @abstractmethod
291 - def get_physical_block_id(self, device: Device, absolute_id: int) -> int:
292 - pass
293 -
294 - @abstractmethod
295 - def allocate_or_get_null_block(self) -> Block:
296 - """
297 - Null blocks are used as a placeholders for KV cache blocks that have
298 - been dropped due to sliding window.
299 - There is at most one null block per allocator.
300 - """
301 - pass
302 -
303 - @abstractmethod
304 - def get_prefix_cache_hit_rate(self, device: Device) -> float:
305 - """Prefix cache hit rate. -1 means not supported or disabled."""
306 - pass
307 -
308 - @abstractmethod
309 - def reset_prefix_cache(self, device: Optional[Device] = None) -> bool:
310 - """Reset prefix cache."""
311 - pass
312 -
313 - @abstractmethod
314 - def find_cached_blocks_prefix(
315 - self,
316 - block_hashes: List[int],
317 - device: Device = Device.GPU,
318 - ) -> List[int]:
319 - pass
vllm/core/block/naive_block.py deleted
-466
@@ -1,466 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from collections import deque
5 -from typing import Deque, FrozenSet, Iterable, List, Optional, Tuple, Union
6 -
7 -from vllm.core.block.common import (BlockPool, CopyOnWriteTracker, RefCounter,
8 - get_all_blocks_recursively)
9 -from vllm.core.block.interfaces import Block, BlockAllocator, BlockId, Device
10 -
11 -Refcount = int
12 -
13 -
14 -class NaiveBlockAllocator(BlockAllocator):
15 - """A simple block allocator that manages blocks of memory without prefix
16 - caching.
17 -
18 - Args:
19 - create_block (Block.Factory): A factory function for creating new
20 - blocks. This is used when a NaiveBlockAllocator is composed within
21 - a prefix caching allocator -- the naive block allocator must
22 - construct prefix caching blocks (but shouldn't know anything else
23 - about them).
24 - num_blocks (int): The total number of blocks to manage.
25 - block_size (int): The size of each block in tokens.
26 - block_ids (Optional[Iterable[int]], optional): An optional iterable of
27 - block IDs. If not provided, block IDs will be assigned sequentially
28 - from 0 to num_blocks - 1.
29 - """
30 -
31 - def __init__(
32 - self,
33 - create_block: Block.Factory,
34 - num_blocks: int,
35 - block_size: int,
36 - block_ids: Optional[Iterable[int]] = None,
37 - block_pool: Optional[BlockPool] = None,
38 - ):
39 - if block_ids is None:
40 - block_ids = range(num_blocks)
41 -
42 - self._free_block_indices: Deque[BlockId] = deque(block_ids)
43 - self._all_block_indices = frozenset(block_ids)
44 - assert len(self._all_block_indices) == num_blocks
45 -
46 - self._refcounter = RefCounter(
47 - all_block_indices=self._free_block_indices)
48 - self._block_size = block_size
49 -
50 - self._cow_tracker = CopyOnWriteTracker(
51 - refcounter=self._refcounter.as_readonly())
52 -
53 - if block_pool is None:
54 - extra_factor = 4
55 - # Pre-allocate "num_blocks * extra_factor" block objects.
56 - # The "* extra_factor" is a buffer to allow more block objects
57 - # than physical blocks
58 - self._block_pool = BlockPool(self._block_size, create_block, self,
59 - num_blocks * extra_factor)
60 - else:
61 - # In this case, the block pool is provided by the caller,
62 - # which means that there is most likely a need to share
63 - # a block pool between allocators
64 - self._block_pool = block_pool
65 -
66 - def allocate_immutable_block(self,
67 - prev_block: Optional[Block],
68 - token_ids: List[int],
69 - extra_hash: Optional[int] = None,
70 - device: Optional[Device] = None) -> Block:
71 - """Allocates a new immutable block with the given token IDs, linked to
72 - the previous block.
73 -
74 - Args:
75 - prev_block (Optional[Block]): The previous block in the sequence. If
76 - None, then the block to be allocated is the first block in the
77 - sequence.
78 - token_ids (List[int]): The token IDs to be stored in the new block.
79 -
80 - Returns:
81 - Block: The newly allocated immutable block.
82 - """
83 - assert device is None
84 - block = self.allocate_mutable_block(prev_block=prev_block)
85 - block.append_token_ids(token_ids)
86 - return block
87 -
88 - def allocate_immutable_blocks(
89 - self,
90 - prev_block: Optional[Block],
91 - block_token_ids: List[List[int]],
92 - extra_hash: Optional[int] = None,
93 - device: Optional[Device] = None) -> List[Block]:
94 - assert device is None
95 - num_blocks = len(block_token_ids)
96 -
97 - block_ids = []
98 - for i in range(num_blocks):
99 - block_ids.append(self._allocate_block_id())
100 -
101 - blocks = []
102 - for i in range(num_blocks):
103 - prev_block = self._block_pool.init_block(
104 - prev_block=prev_block,
105 - token_ids=block_token_ids[i],
106 - block_size=self._block_size,
107 - physical_block_id=block_ids[i])
108 - blocks.append(prev_block)
109 -
110 - return blocks
111 -
112 - def allocate_mutable_block(self,
113 - prev_block: Optional[Block],
114 - extra_hash: Optional[int] = None,
115 - device: Optional[Device] = None) -> Block:
116 - """Allocates a new mutable block, linked to the previous block.
117 -
118 - Args:
119 - prev_block (Optional[Block]): The previous block in the sequence. If
120 - None, then the block to be allocated is the first block in the
121 - sequence.
122 -
123 - Returns:
124 - Block: The newly allocated mutable block.
125 - """
126 - assert device is None
127 - block_id = self._allocate_block_id()
128 - block = self._block_pool.init_block(prev_block=prev_block,
129 - token_ids=[],
130 - block_size=self._block_size,
131 - physical_block_id=block_id)
132 - return block
133 -
134 - def _allocate_block_id(self) -> BlockId:
135 - if not self._free_block_indices:
136 - raise BlockAllocator.NoFreeBlocksError()
137 -
138 - block_id = self._free_block_indices.popleft()
139 - self._refcounter.incr(block_id)
140 - return block_id
141 -
142 - def _free_block_id(self, block: Union[Block, BlockId]) -> None:
143 - if isinstance(block, Block):
144 - block_id = block.block_id
145 - block.block_id = None
146 - else:
147 - block_id = block
148 - assert block_id is not None
149 -
150 - refcount = self._refcounter.decr(block_id)
151 - if refcount == 0:
152 - self._free_block_indices.appendleft(block_id)
153 -
154 - def free(self, block: Block, keep_block_object: bool = False) -> None:
155 - # Release the physical block id
156 - self._free_block_id(block)
157 -
158 - # Release the block object
159 - if not keep_block_object:
160 - self._block_pool.free_block(block)
161 -
162 - def free_block_id(self, block_id: BlockId) -> None:
163 - self._free_block_id(block_id)
164 -
165 - def fork(self, last_block: Block) -> List[Block]:
166 - """Creates a new sequence of blocks that shares the same underlying
167 - memory as the original sequence.
168 -
169 - Args:
170 - last_block (Block): The last block in the original sequence.
171 -
172 - Returns:
173 - List[Block]: The new sequence of blocks that shares the same memory
174 - as the original sequence.
175 - """
176 - source_blocks = get_all_blocks_recursively(last_block)
177 -
178 - forked_blocks: List[Block] = []
179 - prev_block = None
180 - for block in source_blocks:
181 -
182 - # Increment refcount for each block.
183 - assert block.block_id is not None
184 - refcount = self._refcounter.incr(block.block_id)
185 - assert refcount != 1, "can't fork freed block"
186 -
187 - forked_block = self._block_pool.init_block(
188 - prev_block=prev_block,
189 - token_ids=block.token_ids,
190 - block_size=self._block_size,
191 - physical_block_id=block.block_id)
192 -
193 - forked_blocks.append(forked_block)
194 - prev_block = forked_blocks[-1]
195 -
196 - return forked_blocks
197 -
198 - def get_num_free_blocks(self) -> int:
199 - return len(self._free_block_indices)
200 -
201 - def get_num_total_blocks(self) -> int:
202 - return len(self._all_block_indices)
203 -
204 - def get_physical_block_id(self, absolute_id: int) -> int:
205 - """Returns the zero-offset block id on certain block allocator
206 - given the absolute block id.
207 -
208 - Args:
209 - absolute_id (int): The absolute block id for the block
210 - in whole allocator.
211 -
212 - Returns:
213 - int: The zero-offset block id on certain device.
214 - """
215 - return sorted(self._all_block_indices).index(absolute_id)
216 -
217 - @property
218 - def refcounter(self):
219 - return self._refcounter
220 -
221 - @property
222 - def all_block_ids(self) -> FrozenSet[int]:
223 - return self._all_block_indices
224 -
225 - def cow_block_if_not_appendable(self, block: Block) -> BlockId:
226 - """Performs a copy-on-write operation on the given block if it is not
227 - appendable.
228 -
229 - Args:
230 - block (Block): The block to check for copy-on-write.
231 -
232 - Returns:
233 - BlockId: The block index of the new block if a copy-on-write
234 - operation was performed, or the original block index if
235 - no copy-on-write was necessary.
236 - """
237 - src_block_id = block.block_id
238 - assert src_block_id is not None
239 -
240 - if self._cow_tracker.is_appendable(block):
241 - return src_block_id
242 -
243 - self._free_block_id(block)
244 - trg_block_id = self._allocate_block_id()
245 -
246 - self._cow_tracker.record_cow(src_block_id, trg_block_id)
247 -
248 - return trg_block_id
249 -
250 - def clear_copy_on_writes(self) -> List[Tuple[BlockId, BlockId]]:
251 - """Returns the copy-on-write source->destination mapping and clears it.
252 -
253 - Returns:
254 - List[Tuple[BlockId, BlockId]]: A list mapping source
255 - block indices to destination block indices.
256 - """
257 - return self._cow_tracker.clear_cows()
258 -
259 - def mark_blocks_as_accessed(self, block_ids: List[int],
260 - now: float) -> None:
261 - """Mark blocks as accessed, used in prefix caching.
262 -
263 - Since the naive allocator does not implement prefix caching, we do
264 - nothing.
265 - """
266 - pass
267 -
268 - def mark_blocks_as_computed(self, block_ids: List[int]) -> None:
269 - """Mark blocks as computed, used in prefix caching.
270 -
271 - Since the naive allocator does not implement prefix caching, we do
272 - nothing.
273 - """
274 - pass
275 -
276 - def get_common_computed_block_ids(
277 - self, computed_seq_block_ids: List[List[int]]) -> List[int]:
278 - """Determine blocks that can be skipped in prefill.
279 -
280 - Since the naive allocator does not support prefix caching, always return
281 - an empty list.
282 - """
283 - return []
284 -
285 - def promote_to_immutable_block(self, block: Block) -> BlockId:
286 - raise NotImplementedError("There is no promotion for naive blocks")
287 -
288 - def get_num_full_blocks_touched(self, blocks: List[Block]) -> int:
289 - """Returns the number of full blocks that will be touched by
290 - swapping in/out.
291 -
292 - Args:
293 - blocks: List of blocks to be swapped.
294 - Returns:
295 - int: the number of full blocks that will be touched by
296 - swapping in/out the given blocks. Non full blocks are ignored
297 - when deciding the number of blocks to touch.
298 - """
299 - # NOTE: for naive block, we use set to eliminate common blocks among
300 - # seqs, also we compare the empty slots in the mutable blocks with
301 - # lookahead slots to get the number of unique new block that are
302 - # needed.
303 - old_block_set = set()
304 - for block in blocks:
305 - if block.is_full:
306 - old_block_set.add(block)
307 - return len(old_block_set)
308 -
309 - def swap_out(self, blocks: List[Block]) -> None:
310 - for block in blocks:
311 - self._free_block_id(block)
312 -
313 - def swap_in(self, blocks: List[Block]) -> None:
314 - for block in blocks:
315 - # Here we allocate either immutable or mutable block and then
316 - # extract its block_id. Note that the block object is released
317 - # and the block_id is assigned to "block" to allow reusing the
318 - # existing "block" object
319 - if block.is_full:
320 - tmp_block = self.allocate_immutable_block(
321 - prev_block=block.prev_block, token_ids=block.token_ids)
322 - else:
323 - tmp_block = self.allocate_mutable_block(
324 - prev_block=block.prev_block)
325 - tmp_block.append_token_ids(block.token_ids)
326 -
327 - block_id = tmp_block.block_id
328 - tmp_block.block_id = None
329 - self._block_pool.free_block(tmp_block)
330 -
331 - block.block_id = block_id # Assign block_id
332 -
333 - def get_prefix_cache_hit_rate(self) -> float:
334 - return -1
335 -
336 - def reset_prefix_cache(self) -> bool:
337 - """No prefix cache for naive block allocator."""
338 - return True
339 -
340 - def find_cached_blocks_prefix(self, block_hashes: List[int]) -> List[int]:
341 - # Not applicable for naive block allocator.
342 - return []
343 -
344 -
345 -class NaiveBlock(Block):
346 - """An implementation of the Block class that does not support prefix
347 - caching.
348 -
349 - The NaiveBlock class represents a block of token IDs with a fixed size. It
350 - provides methods for appending token IDs to the block and manages copy-on
351 - -write operations when necessary.
352 -
353 - Args:
354 - prev_block (Block): The previous block in the sequence.
355 - token_ids (List[int]): The initial token IDs to be stored in the block.
356 - block_size (int): The maximum number of token IDs that can be stored in
357 - the block.
358 - allocator (BlockAllocator): The block allocator associated with this
359 - block.
360 - block_id (Optional[int], optional): The physical block index
361 - of this block. Defaults to None, which means no allocation has been
362 - made.
363 - _cow_target (Optional[Block], optional): The copy-on-write target block.
364 - If not provided, it defaults to self.
365 - """
366 -
367 - def __init__(self,
368 - prev_block: Optional[Block],
369 - token_ids: List[int],
370 - block_size: int,
371 - allocator: BlockAllocator,
372 - block_id: Optional[int] = None,
373 - _cow_target: Optional[Block] = None,
374 - extra_hash: Optional[int] = None):
375 - self._token_ids: List[int] = []
376 - self._block_size = block_size
377 - self._prev_block = prev_block
378 - self._block_id = block_id
379 - self._allocator = allocator
380 - self._cow_target = _cow_target if _cow_target is not None else self
381 -
382 - self._append_token_ids_no_cow(token_ids)
383 -
384 - def append_token_ids(self, token_ids: List[int]) -> None:
385 - """Appends the given token IDs to the block and performs a
386 - copy-on-write if necessary.
387 -
388 - Args:
389 - token_ids (Optional[List[int]]): The token IDs to be appended
390 - to the block.
391 - """
392 - self._append_token_ids_no_cow(token_ids)
393 -
394 - if self._block_id is not None:
395 - self._block_id = (self._allocator.cow_block_if_not_appendable(
396 - self._cow_target))
397 -
398 - def _append_token_ids_no_cow(self, token_ids: List[int]) -> None:
399 - """Appends the given token IDs to the block
400 -
401 - Args:
402 - token_ids (List[int]): The token IDs to be appended to the block.
403 - """
404 - if len(token_ids) == 0:
405 - return
406 -
407 - assert len(token_ids) <= self.num_empty_slots
408 -
409 - self._token_ids.extend(token_ids)
410 -
411 - @property
412 - def computed(self) -> bool:
413 - raise NotImplementedError
414 -
415 - @computed.setter
416 - def computed(self, value) -> None:
417 - raise NotImplementedError
418 -
419 - @property
420 - def last_accessed(self) -> float:
421 - raise NotImplementedError
422 -
423 - @last_accessed.setter
424 - def last_accessed(self, last_accessed_ts: float):
425 - raise NotImplementedError
426 -
427 - @property
428 - def block_id(self) -> Optional[int]:
429 - return self._block_id
430 -
431 - @block_id.setter
432 - def block_id(self, value: Optional[int]) -> None:
433 - self._block_id = value
434 -
435 - @property
436 - def is_full(self) -> bool:
437 - return self.num_empty_slots == 0
438 -
439 - @property
440 - def num_empty_slots(self) -> int:
441 - return self._block_size - len(self.token_ids)
442 -
443 - @property
444 - def token_ids(self) -> List[int]:
445 - return self._token_ids
446 -
447 - @property
448 - def num_tokens_total(self) -> int:
449 - raise NotImplementedError(
450 - "num_tokens_total is not used for naive block")
451 -
452 - @property
453 - def block_size(self) -> int:
454 - return self._block_size
455 -
456 - @property
457 - def prev_block(self) -> Optional["Block"]:
458 - return self._prev_block
459 -
460 - @property
461 - def extra_hash(self):
462 - return None
463 -
464 - @property
465 - def content_hash(self) -> Optional[int]:
466 - return None
vllm/core/block/prefix_caching_block.py deleted
-1135
@@ -1,1135 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -"""Token blocks."""
4 -import sys
5 -from bisect import bisect_left
6 -from os.path import commonprefix
7 -from typing import (Callable, Dict, FrozenSet, Iterable, List, Optional, Set,
8 - Tuple)
9 -
10 -from vllm.core.block.common import (CacheMetricData, CopyOnWriteTracker,
11 - get_all_blocks_recursively)
12 -from vllm.core.block.interfaces import (Block, BlockAllocator, BlockId, Device,
13 - DeviceAwareBlockAllocator)
14 -from vllm.core.block.naive_block import (BlockPool, NaiveBlock,
15 - NaiveBlockAllocator)
16 -from vllm.core.evictor import EvictionPolicy, Evictor, make_evictor
17 -from vllm.logger import init_logger
18 -from vllm.sequence import Sequence
19 -
20 -PrefixHash = int
21 -
22 -# By default, we init our block access time as _DEFAULT_LAST_ACCESSED_TIME
23 -# so that if we find one block is still hold _DEFAULT_LAST_ACCESSED_TIME,
24 -# then we know this block hasn't been accessed yet.
25 -_DEFAULT_LAST_ACCESSED_TIME = -1
26 -
27 -logger = init_logger(__name__)
28 -
29 -
30 -class BlockTracker:
31 - """Used to track the status of a block inside the prefix caching allocator
32 - """
33 - __slots__ = ("active", "last_accessed", "computed")
34 -
35 - def reset(self):
36 - self.last_accessed: float = _DEFAULT_LAST_ACCESSED_TIME
37 - self.computed: bool = False
38 -
39 - def __init__(self):
40 - self.active: bool = False
41 - self.reset()
42 -
43 - def enable(self):
44 - assert not self.active
45 - self.active = True
46 - self.reset()
47 -
48 - def disable(self):
49 - assert self.active
50 - self.active = False
51 - self.reset()
52 -
53 -
54 -class PrefixCachingBlockAllocator(BlockAllocator):
55 - """A block allocator that implements prefix caching.
56 -
57 - The PrefixCachingBlockAllocator maintains a cache of blocks based on their
58 - content hash. It reuses blocks with the same content hash to avoid redundant
59 - memory allocation. The allocator also supports copy-on-write operations.
60 -
61 - Args:
62 - num_blocks (int): The total number of blocks to manage.
63 - block_size (int): The size of each block in tokens.
64 - block_ids (Optional[Iterable[int]], optional): An optional iterable of
65 - block IDs. If not provided, block IDs will be assigned sequentially
66 - from 0 to num_blocks - 1.
67 - """
68 -
69 - # Note that we use 'None' as a string here instead of None because
70 - # as of Python 3.12, hash(None) returns a constant predictable value.
71 - # This could possibly make it easier to find and exploit hash
72 - # collisions. 'None' as a string will be hashed differently per process,
73 - # but consistently within the same process. This is the same as the
74 - # behavior of None prior to Python 3.12.
75 - _none_hash: int = hash('None')
76 -
77 - # Implements Block.Factory.
78 - def __init__(
79 - self,
80 - num_blocks: int,
81 - block_size: int,
82 - block_ids: Optional[Iterable[int]] = None,
83 - eviction_policy: EvictionPolicy = EvictionPolicy.LRU,
84 - ):
85 - if block_ids is None:
86 - block_ids = range(num_blocks)
87 -
88 - self._block_size = block_size
89 -
90 - # A mapping of prefix hash to block index. All blocks which have a
91 - # prefix hash will be in this dict, even if they have refcount 0.
92 - self._cached_blocks: Dict[PrefixHash, BlockId] = {}
93 -
94 - # A list of immutable block IDs that have been touched by scheduler
95 - # and should be marked as computed after an entire batch of sequences
96 - # are scheduled.
97 - self._touched_blocks: Set[BlockId] = set()
98 -
99 - # Used to track status of each physical block id
100 - self._block_tracker: Dict[BlockId, BlockTracker] = {}
101 - for block_id in block_ids:
102 - self._block_tracker[block_id] = BlockTracker()
103 -
104 - # Pre-allocate "num_blocks * extra_factor" block objects.
105 - # The "* extra_factor" is a buffer to allow more block objects
106 - # than physical blocks
107 - extra_factor = 4
108 - self._block_pool = BlockPool(self._block_size, self._create_block,
109 - self, num_blocks * extra_factor)
110 -
111 - # An allocator for blocks that do not have prefix hashes.
112 - self._hashless_allocator = NaiveBlockAllocator(
113 - create_block=self._create_block, # type: ignore
114 - num_blocks=num_blocks,
115 - block_size=block_size,
116 - block_ids=block_ids,
117 - block_pool=self._block_pool, # Share block pool here
118 - )
119 -
120 - # Evitor used to maintain how we want to handle those computed blocks
121 - # if we find memory pressure is high.
122 - self.eviction_policy = eviction_policy
123 - self.evictor: Evictor = make_evictor(self.eviction_policy)
124 -
125 - # We share the refcounter between allocators. This allows us to promote
126 - # blocks originally allocated in the hashless allocator to immutable
127 - # blocks.
128 - self._refcounter = self._hashless_allocator.refcounter
129 -
130 - self._cow_tracker = CopyOnWriteTracker(
131 - refcounter=self._refcounter.as_readonly())
132 -
133 - self.metric_data = CacheMetricData()
134 -
135 - def _create_block(
136 - self,
137 - prev_block: Optional[Block],
138 - token_ids: List[int],
139 - block_size: int,
140 - allocator: BlockAllocator,
141 - block_id: Optional[int] = None,
142 - computed: bool = False,
143 - extra_hash: Optional[int] = None,
144 - ) -> Block:
145 - # Bind block to self.
146 - allocator = self
147 -
148 - return PrefixCachingBlock(
149 - prev_block=prev_block,
150 - token_ids=token_ids,
151 - block_size=block_size,
152 - block_id=block_id,
153 - allocator=allocator,
154 - computed=computed,
155 - extra_hash=extra_hash,
156 - )
157 -
158 - def allocate_immutable_block(self,
159 - prev_block: Optional[Block],
160 - token_ids: List[int],
161 - extra_hash: Optional[int] = None,
162 - device: Optional[Device] = None) -> Block:
163 - """Allocates an immutable block with the given token IDs, reusing cached
164 - blocks if possible.
165 -
166 - Args:
167 - prev_block (Optional[Block]): The previous block in the sequence.
168 - token_ids (List[int]): The token IDs to be stored in the block.
169 -
170 - Returns:
171 - Block: The allocated immutable block.
172 - """
173 - assert device is None
174 - assert_prefix_caching_block_or_none(prev_block)
175 -
176 - # First, try to create a block that points to cached data
177 - block = self._block_pool.init_block(prev_block=prev_block,
178 - token_ids=token_ids,
179 - block_size=self._block_size,
180 - physical_block_id=None,
181 - extra_hash=extra_hash)
182 - assert block.content_hash is not None
183 -
184 - cached_block_id = self._cached_blocks.get(block.content_hash, None)
185 - if cached_block_id is not None:
186 - self.metric_data.query(hit=True)
187 - block.block_id = cached_block_id
188 - self._incr_refcount_cached_block(block)
189 - return block
190 - self.metric_data.query(hit=False)
191 - self._block_pool.free_block(block)
192 -
193 - # No cached block => Allocate a new block
194 - block = self.allocate_mutable_block(prev_block, extra_hash=extra_hash)
195 - block.append_token_ids(token_ids)
196 - return block
197 -
198 - def allocate_immutable_blocks(
199 - self,
200 - prev_block: Optional[Block],
201 - block_token_ids: List[List[int]],
202 - extra_hash: Optional[int] = None,
203 - device: Optional[Device] = None) -> List[Block]:
204 - blocks = []
205 - for token_ids in block_token_ids:
206 - prev_block = self.allocate_immutable_block(prev_block=prev_block,
207 - token_ids=token_ids,
208 - device=device,
209 - extra_hash=extra_hash)
210 - blocks.append(prev_block)
211 - return blocks
212 -
213 - def allocate_mutable_block(self,
214 - prev_block: Optional[Block],
215 - extra_hash: Optional[int] = None,
216 - device: Optional[Device] = None) -> Block:
217 - """Allocates a mutable block. If there are no free blocks, this will
218 - evict unused cached blocks.
219 -
220 - Args:
221 - prev_block (Block): The previous block in the sequence.
222 - None is not allowed unlike it is super class.
223 -
224 - Returns:
225 - Block: The allocated mutable block.
226 - """
227 - assert device is None
228 - assert_prefix_caching_block_or_none(prev_block)
229 -
230 - block_id = self._allocate_block_id()
231 - block = self._block_pool.init_block(prev_block=prev_block,
232 - token_ids=[],
233 - block_size=self._block_size,
234 - physical_block_id=block_id,
235 - extra_hash=extra_hash)
236 - assert not block.computed
237 - assert block.content_hash is None
238 - return block
239 -
240 - def _incr_refcount_cached_block(self, block: Block) -> None:
241 - # Set this block to be "computed" since it is pointing to a
242 - # cached block id (which was already computed)
243 - block.computed = True
244 -
245 - block_id = block.block_id
246 - assert block_id is not None
247 -
248 - refcount = self._refcounter.incr(block_id)
249 - if refcount == 1:
250 - # In case a cached block was evicted, restore its tracking
251 - if block_id in self.evictor:
252 - self.evictor.remove(block_id)
253 -
254 - self._track_block_id(block_id, computed=True)
255 -
256 - def _decr_refcount_cached_block(self, block: Block) -> None:
257 - # Ensure this is immutable/cached block
258 - assert block.content_hash is not None
259 -
260 - block_id = block.block_id
261 - assert block_id is not None
262 -
263 - refcount = self._refcounter.decr(block_id)
264 - if refcount > 0:
265 - block.block_id = None
266 - return
267 - else:
268 - assert refcount == 0
269 -
270 - # No longer used
271 - assert block.content_hash in self._cached_blocks
272 -
273 - # Add the cached block to the evictor
274 - # (This keeps the cached block around so it can be reused)
275 - self.evictor.add(block_id, block.content_hash, block.num_tokens_total,
276 - self._block_tracker[block_id].last_accessed)
277 -
278 - # Stop tracking the block
279 - self._untrack_block_id(block_id)
280 -
281 - block.block_id = None
282 -
283 - def _decr_refcount_hashless_block(self, block: Block) -> None:
284 - block_id = block.block_id
285 - assert block_id is not None
286 -
287 - # We may have a fork case where block is shared,
288 - # in which case, we cannot remove it from tracking
289 - refcount = self._refcounter.get(block_id)
290 - if refcount == 1:
291 - self._untrack_block_id(block_id)
292 -
293 - # Decrement refcount of the block_id, but do not free the block object
294 - # itself (will be handled by the caller)
295 - self._hashless_allocator.free(block, keep_block_object=True)
296 -
297 - def _allocate_block_id(self) -> BlockId:
298 - """First tries to allocate a block id from the hashless allocator,
299 - and if there are no blocks, then tries to evict an unused cached block.
300 - """
301 - hashless_block_id = self._maybe_allocate_hashless_block_id()
302 - if hashless_block_id is not None:
303 - return hashless_block_id
304 -
305 - evicted_block_id = self._maybe_allocate_evicted_block_id()
306 - if evicted_block_id is not None:
307 - return evicted_block_id
308 -
309 - # No block available in hashless allocator, nor in unused cache blocks.
310 - raise BlockAllocator.NoFreeBlocksError()
311 -
312 - def _maybe_allocate_hashless_block_id(self) -> Optional[BlockId]:
313 - try:
314 - # Allocate mutable block and extract its block_id
315 - block = self._hashless_allocator.allocate_mutable_block(
316 - prev_block=None)
317 - block_id = block.block_id
318 - self._block_pool.free_block(block)
319 -
320 - self._track_block_id(block_id, computed=False)
321 - return block_id
322 - except BlockAllocator.NoFreeBlocksError:
323 - return None
324 -
325 - def _maybe_allocate_evicted_block_id(self) -> Optional[BlockId]:
326 - if self.evictor.num_blocks == 0:
327 - return None
328 -
329 - # Here we get an evicted block, which is only added
330 - # into evictor if its ref counter is 0
331 - # and since its content would be changed, we need
332 - # to remove it from _cached_blocks's tracking list
333 - block_id, content_hash_to_evict = self.evictor.evict()
334 -
335 - # Sanity checks
336 - assert content_hash_to_evict in self._cached_blocks
337 - _block_id = self._cached_blocks[content_hash_to_evict]
338 - assert self._refcounter.get(_block_id) == 0
339 - assert _block_id == block_id
340 -
341 - self._cached_blocks.pop(content_hash_to_evict)
342 -
343 - self._refcounter.incr(block_id)
344 - self._track_block_id(block_id, computed=False)
345 -
346 - return block_id
347 -
348 - def _free_block_id(self, block: Block) -> None:
349 - """Decrements the refcount of the block. The block may be in two
350 - possible states: (1) immutable/cached or (2) mutable/hashless.
351 - In the first case, the refcount is decremented directly and the block
352 - may be possibly added to the evictor. In other case, hashless
353 - allocator free(..) with keep_block_object=True is called to only free
354 - the block id (since the block object may be reused by the caller)
355 - """
356 - block_id = block.block_id
357 - assert block_id is not None, "Freeing unallocated block is undefined"
358 -
359 - if block.content_hash is not None:
360 - # Immutable: This type of block is always cached, and we want to
361 - # keep it in the evictor for future reuse
362 - self._decr_refcount_cached_block(block)
363 - else:
364 - # Mutable: This type of block is not cached, so we release it
365 - # directly to the hashless allocator
366 - self._decr_refcount_hashless_block(block)
367 -
368 - assert block.block_id is None
369 -
370 - def free(self, block: Block, keep_block_object: bool = False) -> None:
371 - """Release the block (look at free_block_id(..) docs)
372 - """
373 - # Release the physical block index
374 - self._free_block_id(block)
375 -
376 - # Release the block object to the pool
377 - if not keep_block_object:
378 - self._block_pool.free_block(block)
379 -
380 - def fork(self, last_block: Block) -> List[Block]:
381 - """Creates a new sequence of blocks that shares the same underlying
382 - memory as the original sequence.
383 -
384 - Args:
385 - last_block (Block): The last block in the original sequence.
386 -
387 - Returns:
388 - List[Block]: The new sequence of blocks that shares the same memory
389 - as the original sequence.
390 - """
391 - source_blocks = get_all_blocks_recursively(last_block)
392 -
393 - forked_blocks: List[Block] = []
394 - prev_block = None
395 - for block in source_blocks:
396 - block_id = block.block_id
397 - assert block_id is not None
398 -
399 - refcount = self._refcounter.incr(block_id)
400 - assert refcount != 1, "can't fork free'd block_id = {}".format(
401 - block_id)
402 -
403 - forked_block = self._block_pool.init_block(
404 - prev_block=prev_block,
405 - token_ids=block.token_ids,
406 - block_size=self._block_size,
407 - physical_block_id=block_id,
408 - extra_hash=block.extra_hash)
409 -
410 - forked_blocks.append(forked_block)
411 - prev_block = forked_blocks[-1]
412 -
413 - return forked_blocks
414 -
415 - def get_num_free_blocks(self, device: Optional[Device] = None) -> int:
416 - assert device is None
417 - # The number of free blocks is the number of hashless free blocks
418 - # plus the number of blocks evictor could free from its list.
419 - return self._hashless_allocator.get_num_free_blocks(
420 - ) + self.evictor.num_blocks
421 -
422 - def get_num_total_blocks(self) -> int:
423 - return self._hashless_allocator.get_num_total_blocks()
424 -
425 - def get_physical_block_id(self, absolute_id: int) -> int:
426 - """Returns the zero-offset block id on certain block allocator
427 - given the absolute block id.
428 -
429 - Args:
430 - absolute_id (int): The absolute block id for the block
431 - in whole allocator.
432 -
433 - Returns:
434 - int: The rzero-offset block id on certain device.
435 - """
436 - return sorted(self.all_block_ids).index(absolute_id)
437 -
438 - @property
439 - def all_block_ids(self) -> FrozenSet[int]:
440 - return self._hashless_allocator.all_block_ids
441 -
442 - def get_prefix_cache_hit_rate(self) -> float:
443 - return self.metric_data.get_hit_rate()
444 -
445 - def reset_prefix_cache(self) -> bool:
446 - """Reset prefix cache. This function may be used in RLHF
447 - flows to invalid prefix caching after the weights are updated,
448 - or used for resetting prefix caching status for benchmarking.
449 -
450 - Returns:
451 - bool: True if the prefix cache is successfully reset,
452 - False otherwise.
453 - """
454 - num_used_blocks = (self.get_num_total_blocks() -
455 - self.get_num_free_blocks())
456 - if num_used_blocks > 0:
457 - logger.warning(
458 - "Failed to reset prefix cache because some "
459 - "blocks (%d) are not freed yet", num_used_blocks)
460 - return False
461 -
462 - # Free all blocks in the evictor.
463 - while (block_id :=
464 - self._maybe_allocate_evicted_block_id()) is not None:
465 - self._hashless_allocator.free_block_id(block_id)
466 -
467 - # Should not have any cached blocks because all blocks are evicted.
468 - assert not self._cached_blocks
469 -
470 - # Reset the evictor.
471 - self.evictor = make_evictor(self.eviction_policy)
472 -
473 - # Reset the block tracker.
474 - for block_id in self._block_tracker:
475 - self._block_tracker[block_id] = BlockTracker()
476 -
477 - # Reset the metrics.
478 - self.metric_data = CacheMetricData()
479 -
480 - logger.info("Successfully reset prefix cache")
481 - return True
482 -
483 - def is_block_cached(self, block: Block) -> bool:
484 - assert block.content_hash is not None
485 - return block.content_hash in self._cached_blocks
486 -
487 - def promote_to_immutable_block(self, block: Block) -> BlockId:
488 - """Once a mutable block is full, it can be promoted to an immutable
489 - block. This means that its content can be referenced by future blocks
490 - having the same prefix.
491 -
492 - Note that if we already have a cached block with the same content, we
493 - will replace the newly-promoted block's mapping with the existing cached
494 - block id.
495 -
496 - Args:
497 - block: The mutable block to be promoted.
498 -
499 - Returns:
500 - BlockId: Either the original block index, or the block index of
501 - the previously cached block matching the same content.
502 - """
503 - # Ensure block can be promoted
504 - assert block.content_hash is not None
505 - assert block.block_id is not None
506 - assert self._refcounter.get(block.block_id) > 0
507 -
508 - if block.content_hash not in self._cached_blocks:
509 - # No cached content hash => Set this block as cached.
510 - # Note that this block cannot be marked as computed yet
511 - # because other sequences in the same batch cannot reuse
512 - # this block.
513 - self._cached_blocks[block.content_hash] = block.block_id
514 - # Mark this block as touched so that it can be marked as
515 - # computed after the entire batch of sequences are scheduled.
516 - self._touched_blocks.add(block.block_id)
517 - return block.block_id
518 -
519 - # Reuse the cached content hash
520 - self._decr_refcount_hashless_block(block)
521 - block.block_id = self._cached_blocks[block.content_hash]
522 -
523 - # Increment refcount of the cached block and (possibly) restore
524 - # it from the evictor.
525 - # Note that in this case, the block is marked as computed
526 - self._incr_refcount_cached_block(block)
527 -
528 - return block.block_id
529 -
530 - def cow_block_if_not_appendable(self, block: Block) -> BlockId:
531 - """Performs a copy-on-write operation on the given block if it is not
532 - appendable.
533 -
534 - Args:
535 - block (Block): The block to check for copy-on-write.
536 -
537 - Returns:
538 - BlockId: The block index of the new block if a copy-on-write
539 - operation was performed, or the original block index if
540 - no copy-on-write was necessary.
541 - """
542 - src_block_id = block.block_id
543 - assert src_block_id is not None
544 -
545 - if self._cow_tracker.is_appendable(block):
546 - return src_block_id
547 -
548 - self._free_block_id(block)
549 - trg_block_id = self._allocate_block_id()
550 -
551 - self._cow_tracker.record_cow(src_block_id, trg_block_id)
552 -
553 - return trg_block_id
554 -
555 - def clear_copy_on_writes(self) -> List[Tuple[BlockId, BlockId]]:
556 - """Returns the copy-on-write source->destination mapping and clears it.
557 -
558 - Returns:
559 - List[Tuple[BlockId, BlockId]]: A list mapping source
560 - block indices to destination block indices.
561 - """
562 - return self._cow_tracker.clear_cows()
563 -
564 - def mark_blocks_as_accessed(self, block_ids: List[int],
565 - now: float) -> None:
566 - """Mark blocks as accessed, used in prefix caching.
567 -
568 - If the block is added into evictor, we need to update corresponding
569 - info in evictor's metadata.
570 - """
571 -
572 - for block_id in block_ids:
573 - if self._block_tracker[block_id].active:
574 - self._block_tracker[block_id].last_accessed = now
575 - elif block_id in self.evictor:
576 - self.evictor.update(block_id, now)
577 - else:
578 - raise ValueError(
579 - "Mark block as accessed which is not belonged to GPU")
580 -
581 - def mark_blocks_as_computed(self, block_ids: List[int]) -> None:
582 - # Mark all touched blocks as computed.
583 - for block_id in self._touched_blocks:
584 - self._block_tracker[block_id].computed = True
585 - self._touched_blocks.clear()
586 -
587 - def _track_block_id(self, block_id: Optional[BlockId],
588 - computed: bool) -> None:
589 - assert block_id is not None
590 - self._block_tracker[block_id].enable()
591 - self._block_tracker[block_id].computed = computed
592 -
593 - def _untrack_block_id(self, block_id: Optional[BlockId]) -> None:
594 - assert block_id is not None
595 - self._block_tracker[block_id].disable()
596 -
597 - def block_is_computed(self, block_id: int) -> bool:
598 - if self._block_tracker[block_id].active:
599 - return self._block_tracker[block_id].computed
600 - else:
601 - return block_id in self.evictor
602 -
603 - def get_common_computed_block_ids(
604 - self, computed_seq_block_ids: List[List[int]]) -> List[int]:
605 - """Return the block ids that are common for a given sequence group.
606 -
607 - Only those blocks that are immutable and already be marked
608 - compyted would be taken consideration.
609 - """
610 -
611 - # NOTE We exclude the last block to avoid the case where the entire
612 - # prompt is cached. This would cause erroneous behavior in model
613 - # runner.
614 -
615 - # It returns a list of int although type annotation says list of string.
616 - if len(computed_seq_block_ids) == 1:
617 - return computed_seq_block_ids[0]
618 -
619 - return commonprefix([
620 - ids for ids in computed_seq_block_ids # type: ignore
621 - if ids
622 - ])
623 -
624 - def get_num_full_blocks_touched(self, blocks: List[Block]) -> int:
625 - """Returns the number of full blocks that will be touched by
626 - swapping in/out.
627 -
628 - Args:
629 - blocks: List of blocks to be swapped.
630 - Returns:
631 - int: the number of full blocks that will be touched by
632 - swapping in/out the given blocks. Non full blocks are ignored
633 - when deciding the number of blocks to touch.
634 - """
635 - num_touched_blocks: int = 0
636 - for block in blocks:
637 - # If the block has a match in the cache and the cached
638 - # block is not referenced, then we still count it as a
639 - # touched block
640 - if block.is_full and (not self.is_block_cached(block) or \
641 - (block.content_hash is not None and \
642 - self._cached_blocks[block.content_hash] in \
643 - self.evictor)):
644 - num_touched_blocks += 1
645 - return num_touched_blocks
646 -
647 - def swap_out(self, blocks: List[Block]) -> None:
648 - """Execute the swap out actions. Basically just free the
649 - given blocks.
650 -
651 - Args:
652 - blocks: List of blocks to be swapped out.
653 - """
654 - for block in blocks:
655 - self._free_block_id(block)
656 -
657 - def swap_in(self, blocks: List[Block]) -> None:
658 - """Execute the swap in actions. Change the block id from
659 - old allocator to current allocator for each block to finish
660 - the block table update.
661 -
662 - Args:
663 - blocks: List of blocks to be swapped in.
664 - """
665 - for block in blocks:
666 - # Here we allocate either immutable or mutable block and then
667 - # extract its block_id. Note that the block object is released
668 - # and the block_id is assigned to "block" to allow reusing the
669 - # existing "block" object
670 - if block.is_full:
671 - tmp_block = self.allocate_immutable_block(
672 - prev_block=block.prev_block,
673 - token_ids=block.token_ids,
674 - extra_hash=block.extra_hash)
675 - else:
676 - tmp_block = self.allocate_mutable_block(
677 - prev_block=block.prev_block, extra_hash=block.extra_hash)
678 - tmp_block.append_token_ids(block.token_ids)
679 -
680 - block_id = tmp_block.block_id
681 - self._block_pool.free_block(tmp_block)
682 -
683 - block.block_id = block_id # Assign block_id
684 -
685 - def find_cached_blocks_prefix(self, block_hashes: List[int]) -> List[int]:
686 - """
687 - Given a list of block hashes, return the prefix of the block hashes that
688 - are all cached.
689 -
690 - Since a block's block hash includes the hashes of all previous blocks,
691 - and we only allocate/deallocate blocks in the entire sequence, so if a
692 - block is cached, then all previous blocks are also cached. With this
693 - property, we can use binary search to find the prefix of cached blocks.
694 -
695 - Args:
696 - block_hashes (List[int]): The list of block hashes.
697 -
698 - Returns:
699 - List[int]: The prefix of the `block_hashes` that are cached.
700 - """
701 -
702 - def _block_is_cached(block_hash: PrefixHash) -> bool:
703 - if block_hash not in self._cached_blocks:
704 - return False
705 -
706 - cached_block_id = self._cached_blocks[block_hash]
707 - # We only consider the blocks that are marked as computed.
708 - return self.block_is_computed(cached_block_id)
709 -
710 - def _bisect_left(a, x, key: Callable[[PrefixHash], bool]) -> int:
711 -
712 - # python <= 3.10 don't have the key argument
713 - if sys.version_info < (3, 10):
714 - a = [key(e) for e in a]
715 - return bisect_left(a, x)
716 - else:
717 - return bisect_left(a, x, key=key)
718 -
719 - # Look for the first block that's not cached, and returns the prefix
720 - # i.e. blocks that are cached.
721 - idx = _bisect_left(block_hashes,
722 - True,
723 - key=lambda x: not _block_is_cached(x))
724 - return block_hashes[:idx]
725 -
726 -
727 -class PrefixCachingBlock(Block):
728 - """A block implementation that supports prefix caching.
729 -
730 - The PrefixCachingBlock class represents a block of token IDs with prefix
731 - caching capabilities. It wraps a NaiveBlock internally and provides
732 - additional functionality for content hashing and promoting immutable blocks
733 - with the prefix caching allocator.
734 -
735 - Args:
736 - prev_block (Optional[PrefixCachingBlock]): The previous block in the
737 - sequence.
738 - token_ids (List[int]): The initial token IDs to be stored in the block.
739 - block_size (int): The maximum number of token IDs that can be stored in
740 - the block.
741 - allocator (BlockAllocator): The prefix
742 - caching block allocator associated with this block.
743 - block_id (Optional[int], optional): The physical block index
744 - of this block. Defaults to None.
745 - extra_hash (Optional[int]): The hash value of additional factors
746 - such as adapters that influence the block, apart from the token_ids.
747 - """
748 -
749 - # Note that we use 'None' as a string here instead of None because
750 - # as of Python 3.12, hash(None) returns a constant predictable value.
751 - # This could possibly make it easier to find and exploit hash
752 - # collisions. 'None' as a string will be hashed differently per process,
753 - # but consistently within the same process. This is the same as the
754 - # behavior of None prior to Python 3.12.
755 - _none_hash: int = hash('None')
756 -
757 - def __init__(
758 - self,
759 - prev_block: Optional[Block],
760 - token_ids: List[int],
761 - block_size: int,
762 - allocator: BlockAllocator,
763 - block_id: Optional[int] = None,
764 - computed: bool = False,
765 - extra_hash: Optional[int] = None,
766 - ):
767 - assert isinstance(allocator, PrefixCachingBlockAllocator), (
768 - "Currently this class is only tested with "
769 - "PrefixCachingBlockAllocator. Got instead allocator = {}".format(
770 - allocator))
771 - assert_prefix_caching_block_or_none(prev_block)
772 -
773 - self._prev_block = prev_block
774 - self._cached_content_hash: Optional[int] = None
775 - self._cached_num_tokens_total: int = 0
776 - self._allocator = allocator
777 - self._last_accessed: float = _DEFAULT_LAST_ACCESSED_TIME
778 - self._computed = computed
779 - self._extra_hash = extra_hash
780 -
781 - # On the first time, we create the block object, and next we only
782 - # reinitialize it
783 - if hasattr(self, "_block"):
784 - self._block.__init__( # type: ignore[has-type]
785 - prev_block=prev_block,
786 - token_ids=token_ids,
787 - block_size=block_size,
788 - block_id=block_id,
789 - allocator=self._allocator)
790 - else:
791 - self._block = NaiveBlock(prev_block=prev_block,
792 - token_ids=token_ids,
793 - block_size=block_size,
794 - block_id=block_id,
795 - allocator=self._allocator)
796 -
797 - self._update_num_tokens_total()
798 -
799 - def _update_num_tokens_total(self):
800 - """Incrementally computes the number of tokens that there is
801 - till the current block (included)
802 - """
803 - res = 0
804 -
805 - # Add all previous blocks
806 - if self._prev_block is not None:
807 - res += self._prev_block.num_tokens_total
808 -
809 - # Add current block
810 - res += len(self.token_ids)
811 -
812 - self._cached_num_tokens_total = res
813 -
814 - @property
815 - def computed(self) -> bool:
816 - return self._computed
817 -
818 - @computed.setter
819 - def computed(self, value) -> None:
820 - self._computed = value
821 -
822 - @property
823 - def last_accessed(self) -> float:
824 - return self._last_accessed
825 -
826 - @last_accessed.setter
827 - def last_accessed(self, last_accessed_ts: float):
828 - self._last_accessed = last_accessed_ts
829 -
830 - def append_token_ids(self, token_ids: List[int]) -> None:
831 - """Appends the given token IDs to the block and registers the block as
832 - immutable if the block becomes full.
833 -
834 - Args:
835 - token_ids (List[int]): The token IDs to be appended to the block.
836 - """
837 - # Ensure this is mutable block (not promoted)
838 - assert self.content_hash is None
839 - assert not self.computed
840 -
841 - if len(token_ids) == 0:
842 - return
843 -
844 - # Ensure there are input tokens
845 - assert token_ids, "Got token_ids = {}".format(token_ids)
846 -
847 - # Naive block handles CoW.
848 - self._block.append_token_ids(token_ids)
849 - self._update_num_tokens_total()
850 -
851 - # If the content hash is present, then the block can be made immutable.
852 - # Register ourselves with the allocator, potentially replacing the
853 - # physical block index.
854 - if self.content_hash is not None:
855 - self.block_id = self._allocator.promote_to_immutable_block(self)
856 -
857 - @property
858 - def block_id(self) -> Optional[int]:
859 - return self._block.block_id
860 -
861 - @block_id.setter
862 - def block_id(self, value) -> None:
863 - self._block.block_id = value
864 -
865 - @property
866 - def is_full(self) -> bool:
867 - return self._block.is_full
868 -
869 - @property
870 - def num_empty_slots(self) -> int:
871 - return self._block.num_empty_slots
872 -
873 - @property
874 - def num_tokens_total(self) -> int:
875 - return self._cached_num_tokens_total
876 -
877 - @property
878 - def block_size(self) -> int:
879 - return self._block.block_size
880 -
881 - @property
882 - def token_ids(self) -> List[int]:
883 - return self._block.token_ids
884 -
885 - @property
886 - def prev_block(self) -> Optional[Block]:
887 - return self._prev_block
888 -
889 - @property
890 - def extra_hash(self) -> Optional[int]:
891 - return self._extra_hash
892 -
893 - @property
894 - def content_hash(self) -> Optional[int]:
895 - """Return the content-based hash of the current block, or None if it is
896 - not yet defined.
897 -
898 - For the content-based hash to be defined, the current block must be
899 - full.
900 - """
901 - # If the hash is already computed, return it.
902 - if self._cached_content_hash is not None:
903 - return self._cached_content_hash
904 -
905 - # We cannot compute a hash for the current block because it is not full.
906 - if not self.is_full:
907 - return None
908 -
909 - is_first_block = self._prev_block is None
910 - prev_block_hash = (
911 - self._none_hash if is_first_block else
912 - self._prev_block.content_hash # type: ignore
913 - )
914 -
915 - # Previous block exists but does not yet have a hash.
916 - # Return no hash in this case.
917 - if prev_block_hash == self._none_hash and not is_first_block:
918 - return None
919 -
920 - self._cached_content_hash = PrefixCachingBlock.hash_block_tokens(
921 - is_first_block,
922 - prev_block_hash,
923 - cur_block_token_ids=self.token_ids,
924 - extra_hash=self._extra_hash)
925 - return self._cached_content_hash
926 -
927 - @classmethod
928 - def hash_block_tokens(cls,
929 - is_first_block: bool,
930 - prev_block_hash: Optional[int],
931 - cur_block_token_ids: List[int],
932 - extra_hash: Optional[int] = None) -> int:
933 - """Computes a hash value corresponding to the contents of a block and
934 - the contents of the preceding block(s). The hash value is used for
935 - prefix caching.
936 -
937 - Parameters:
938 - - is_first_block (bool): A flag indicating if the block is the first in
939 - the sequence.
940 - - prev_block_hash (Optional[int]): The hash of the previous block. None
941 - if this is the first block.
942 - - cur_block_token_ids (List[int]): A list of token ids in the current
943 - block. The current block is assumed to be full.
944 - - extra_hash (Optional[int]): The hash value of additional factors
945 - such as adapters that influence the block, apart from the token_ids.
946 -
947 - Returns:
948 - - int: The computed hash value for the block.
949 - """
950 - if is_first_block and prev_block_hash is None:
951 - prev_block_hash = cls._none_hash
952 - return hash((is_first_block, prev_block_hash, *cur_block_token_ids,
953 - extra_hash))
954 -
955 -
956 -class ComputedBlocksTracker:
957 - """
958 - Tracks the computed blocks for each sequence.
959 -
960 - Internally, it maintains a map from sequence id to the list of block hashes
961 - for the sequence. We cache the hashes of the full blocks for each sequence,
962 - and make sure the hash is calculated in the same way as the allocator.
963 - When a sequence is being decoded, we also update the sequence's hash
964 - accordingly and incrementally.
965 -
966 - From the sequence hash, with prefix caching enabled, we could also calculate
967 - the number of cached tokens for the sequence by looking up the number of
968 - cached block hashes in the allocator.
969 - """
970 -
971 - # Note that we use 'None' as a string here instead of None because
972 - # as of Python 3.12, hash(None) returns a constant predictable value.
973 - # This could possibly make it easier to find and exploit hash
974 - # collisions. 'None' as a string will be hashed differently per process,
975 - # but consistently within the same process. This is the same as the
976 - # behavior of None prior to Python 3.12.
977 - _none_hash: int = hash('None')
978 -
979 - def __init__(
980 - self,
981 - allocator: DeviceAwareBlockAllocator,
982 - block_size: int,
983 - enable_caching: bool,
984 - ):
985 - self._allocator = allocator
986 - self._block_size = block_size
987 - self._enable_caching = enable_caching
988 -
989 - # A map from seq_id to the list of block hashes for the
990 - # sequence. This is so that we don't have to recompute the block hashes
991 - # for the sequence when we need to check if the sequence is cached.
992 - # Note a block that's not full will not have its hash calculated and
993 - # recorded.
994 - self._seq_id_to_blocks_hashes: Dict[int, List[int]] = {}
995 -
996 - # A map from seq_id to the number of tokens that are cached for the
997 - # sequence.
998 - # We need this so that a sequence in continuous prefill doesn't
999 - # accidentally see its cached token count change. See comments in
1000 - # `get_num_cached_tokens` for more details.
1001 - self._seq_id_to_num_tokens_computed: Dict[int, int] = {}
1002 -
1003 - def _update_seq_hashes(self, seq: Sequence) -> None:
1004 - """Incrementally update the sequence's block hashes and record them."""
1005 - assert self._enable_caching
1006 -
1007 - block_hashes_recorded = self._seq_id_to_blocks_hashes.get(
1008 - seq.seq_id, [])
1009 - cur_num_blocks_recorded = len(block_hashes_recorded)
1010 - token_ids = seq.get_token_ids()
1011 - assert len(token_ids) >= cur_num_blocks_recorded * self._block_size, (
1012 - f"The sequence has {len(token_ids)} tokens, but"
1013 - f" already recorded {cur_num_blocks_recorded} blocks. "
1014 - "This should not happen since we assume blocks are "
1015 - "only appended other than recomputation. When the sequence is "
1016 - "recomputed, we should have removed the info of the old blocks.")
1017 - # Update the computed block hashes for the sequence. Since only full
1018 - # blocks are considered as "computed", we take floor here.
1019 - num_computed_blocks = len(token_ids) // self._block_size
1020 -
1021 - # We need to know the hash of the previous block to compute the hash of
1022 - # the current block so that blocks could be uniquely identified across
1023 - # sequences of prefixes.
1024 - prev_block_hash = (self._none_hash if cur_num_blocks_recorded == 0 else
1025 - block_hashes_recorded[-1])
1026 - # Only update the computed block hashes for the new blocks
1027 - for i in range(cur_num_blocks_recorded, num_computed_blocks):
1028 - assert len(token_ids) >= (i + 1) * self._block_size
1029 - block_token_ids = token_ids[i * self._block_size:(i + 1) *
1030 - self._block_size]
1031 -
1032 - # NOTE: If there are any factors affecting the block besides
1033 - # token_ids, they should be added as input to extra_hash.
1034 - extra_hash = seq.extra_hash()
1035 -
1036 - # This has to be kept in sync with the allocator's hash
1037 - # calculation.
1038 - block_hash = PrefixCachingBlock.hash_block_tokens(
1039 - is_first_block=prev_block_hash == self._none_hash,
1040 - prev_block_hash=prev_block_hash,
1041 - cur_block_token_ids=block_token_ids,
1042 - extra_hash=extra_hash,
1043 - )
1044 - block_hashes_recorded.append(block_hash)
1045 - prev_block_hash = block_hash
1046 -
1047 - self._seq_id_to_blocks_hashes[seq.seq_id] = block_hashes_recorded
1048 -
1049 - def get_num_cached_tokens(self, seq: Sequence) -> int:
1050 - if not self._enable_caching:
1051 - return 0
1052 -
1053 - # We always try to update the sequence hashes on the fly.
1054 - # This is to ensure that we don't miss any cached tokens for the
1055 - # sequence during decode.
1056 - # This routine should only update hash for any new blocks too.
1057 - self._update_seq_hashes(seq)
1058 -
1059 - num_computed_tokens_prev = self._seq_id_to_num_tokens_computed.get(
1060 - seq.seq_id, None)
1061 -
1062 - # TODO(rickyx): This hack could be removed once we mark blocks as
1063 - # computed correctly with chunked prefills.
1064 - if num_computed_tokens_prev is not None and seq.is_prefill():
1065 - # For a sequence that is still in prefill, we don't
1066 - # recompute the number of cached tokens.
1067 - # This also handles correctly chunked prefill since currently
1068 - # we mark blocks as computed even if the sequence is still partially
1069 - # prefilled. So a continuously prefilled sequence should not
1070 - # see its cached token count change while running.
1071 - return num_computed_tokens_prev
1072 -
1073 - block_hashes = self._seq_id_to_blocks_hashes[seq.seq_id]
1074 -
1075 - # This is O(logN), where N is the number of blocks.
1076 - num_cached_blocks = len(
1077 - self._allocator.find_cached_blocks_prefix(block_hashes))
1078 - num_cached_tokens = num_cached_blocks * self._block_size
1079 - self._seq_id_to_num_tokens_computed[seq.seq_id] = num_cached_tokens
1080 - return num_cached_tokens
1081 -
1082 - def remove_seq(self, seq_id: int) -> None:
1083 - """Stop tracking the sequence."""
1084 - if not self._enable_caching:
1085 - return
1086 - assert seq_id in self._seq_id_to_blocks_hashes
1087 - del self._seq_id_to_blocks_hashes[seq_id]
1088 -
1089 - assert seq_id in self._seq_id_to_num_tokens_computed
1090 - del self._seq_id_to_num_tokens_computed[seq_id]
1091 -
1092 -
1093 -class LastAccessBlocksTracker:
1094 - """Manages the last access time of the tracked sequences, in order to allow
1095 - an efficient update of allocator's block last access times
1096 - """
1097 -
1098 - def __init__(self, allocator):
1099 - self._allocator = allocator
1100 - self._seq_last_access: Dict[int, Optional[float]] = {}
1101 -
1102 - def add_seq(self, seq_id: int) -> None:
1103 - """Start tracking seq_id
1104 - """
1105 - assert seq_id not in self._seq_last_access
1106 - self._seq_last_access[seq_id] = None
1107 -
1108 - def remove_seq(self, seq_id: int) -> None:
1109 - """Stop tracking seq_id
1110 - """
1111 - assert seq_id in self._seq_last_access
1112 - del self._seq_last_access[seq_id]
1113 -
1114 - def update_last_access(self, seq_id: int, time: float) -> None:
1115 - assert seq_id in self._seq_last_access
1116 - self._seq_last_access[seq_id] = time
1117 -
1118 - def update_seq_blocks_last_access(self, seq_id: int,
1119 - block_ids: List[int]) -> None:
1120 - assert seq_id in self._seq_last_access
1121 -
1122 - ts = self._seq_last_access[seq_id]
1123 -
1124 - if ts is None:
1125 - # No last access was recorded, no need to update.
1126 - return
1127 -
1128 - self._allocator.mark_blocks_as_accessed(block_ids, ts)
1129 -
1130 -
1131 -def assert_prefix_caching_block_or_none(block: Optional[Block]):
1132 - if block is None:
1133 - return
1134 - assert isinstance(block,
1135 - PrefixCachingBlock), "Got block = {}".format(block)
vllm/core/block/utils.py deleted
-28
@@ -1,28 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -"""Block manager utils."""
4 -from vllm.sequence import SequenceGroup
5 -from vllm.utils import (STR_NOT_IMPL_ENC_DEC_PREFIX_CACHE,
6 - STR_NOT_IMPL_ENC_DEC_SWA)
7 -
8 -
9 -def check_no_caching_or_swa_for_blockmgr_encdec(
10 - block_mgr, seq_group: SequenceGroup) -> None:
11 - '''
12 - Enforce that prefix caching & sliding-window attention (SWA)
13 - are currently unsupported *specifically* for encoder/decoder models.
14 -
15 - Raises NotImplementedError if unsupported scenario is detected.
16 -
17 - Arguments:
18 -
19 - * block_mgr: BlockSpaceManager instance
20 - * seq_group: SequenceGroup passed to block_mgr
21 - '''
22 -
23 - if seq_group.is_encoder_decoder():
24 - if block_mgr.max_block_sliding_window is not None:
25 - raise NotImplementedError(STR_NOT_IMPL_ENC_DEC_SWA)
26 -
27 - if block_mgr.enable_caching:
28 - raise NotImplementedError(STR_NOT_IMPL_ENC_DEC_PREFIX_CACHE)
vllm/core/block_manager.py deleted
-523
@@ -1,523 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -"""A block manager that manages token blocks."""
4 -from typing import Dict, List, Optional
5 -from typing import Sequence as GenericSequence
6 -from typing import Tuple
7 -
8 -from vllm.core.block.block_table import BlockTable
9 -from vllm.core.block.cpu_gpu_block_allocator import CpuGpuBlockAllocator
10 -from vllm.core.block.interfaces import Block
11 -from vllm.core.block.prefix_caching_block import (ComputedBlocksTracker,
12 - LastAccessBlocksTracker)
13 -from vllm.core.block.utils import check_no_caching_or_swa_for_blockmgr_encdec
14 -from vllm.core.interfaces import AllocStatus, BlockSpaceManager
15 -from vllm.sequence import Sequence, SequenceGroup, SequenceStatus
16 -from vllm.utils import Device
17 -
18 -SeqId = int
19 -EncoderSeqId = str
20 -
21 -
22 -class SelfAttnBlockSpaceManager(BlockSpaceManager):
23 - """BlockSpaceManager which manages the allocation of KV cache.
24 -
25 - It owns responsibility for allocation, swapping, allocating memory for
26 - autoregressively-generated tokens, and other advanced features such as
27 - prefix caching, forking/copy-on-write, and sliding-window memory allocation.
28 -
29 - This class implements the design described in
30 - https://github.com/vllm-project/vllm/pull/3492.
31 -
32 - Lookahead slots
33 - The block manager has the notion of a "lookahead slot". These are slots
34 - in the KV cache that are allocated for a sequence. Unlike the other
35 - allocated slots, the content of these slots is undefined -- the worker
36 - may use the memory allocations in any way.
37 -
38 - In practice, a worker could use these lookahead slots to run multiple
39 - forward passes for a single scheduler invocation. Each successive
40 - forward pass would write KV activations to the corresponding lookahead
41 - slot. This allows low inter-token latency use-cases, where the overhead
42 - of continuous batching scheduling is amortized over >1 generated tokens.
43 -
44 - Speculative decoding uses lookahead slots to store KV activations of
45 - proposal tokens.
46 -
47 - See https://github.com/vllm-project/vllm/pull/3250 for more information
48 - on lookahead scheduling.
49 -
50 - Args:
51 - block_size (int): The size of each memory block.
52 - num_gpu_blocks (int): The number of memory blocks allocated on GPU.
53 - num_cpu_blocks (int): The number of memory blocks allocated on CPU.
54 - watermark (float, optional): The threshold used for memory swapping.
55 - Defaults to 0.01.
56 - sliding_window (Optional[int], optional): The size of the sliding
57 - window. Defaults to None.
58 - enable_caching (bool, optional): Flag indicating whether caching is
59 - enabled. Defaults to False.
60 - """
61 -
62 - def __init__(
63 - self,
64 - block_size: int,
65 - num_gpu_blocks: int,
66 - num_cpu_blocks: int,
67 - watermark: float = 0.01,
68 - sliding_window: Optional[int] = None,
69 - enable_caching: bool = False,
70 - ) -> None:
71 - self.block_size = block_size
72 - self.num_total_gpu_blocks = num_gpu_blocks
73 - self.num_total_cpu_blocks = num_cpu_blocks
74 -
75 - self.sliding_window = sliding_window
76 - # max_block_sliding_window is the max number of blocks that need to be
77 - # allocated
78 - self.max_block_sliding_window = None
79 - if sliding_window is not None:
80 - # +1 here because // rounds down
81 - num_blocks = sliding_window // block_size + 1
82 - # +1 here because the last block may not be full,
83 - # and so the sequence stretches one more block at the beginning
84 - # For example, if sliding_window is 3 and block_size is 4,
85 - # we may need 2 blocks when the second block only holds 1 token.
86 - self.max_block_sliding_window = num_blocks + 1
87 -
88 - self.watermark = watermark
89 - assert watermark >= 0.0
90 -
91 - self.enable_caching = enable_caching
92 -
93 - self.watermark_blocks = int(watermark * num_gpu_blocks)
94 -
95 - self.block_allocator = CpuGpuBlockAllocator.create(
96 - allocator_type="prefix_caching" if enable_caching else "naive",
97 - num_gpu_blocks=num_gpu_blocks,
98 - num_cpu_blocks=num_cpu_blocks,
99 - block_size=block_size,
100 - )
101 -
102 - self.block_tables: Dict[SeqId, BlockTable] = {}
103 - self.cross_block_tables: Dict[EncoderSeqId, BlockTable] = {}
104 -
105 - self._computed_blocks_tracker = ComputedBlocksTracker(
106 - self.block_allocator, self.block_size, self.enable_caching)
107 - self._last_access_blocks_tracker = LastAccessBlocksTracker(
108 - self.block_allocator)
109 -
110 - def can_allocate(self,
111 - seq_group: SequenceGroup,
112 - num_lookahead_slots: int = 0) -> AllocStatus:
113 - # FIXME(woosuk): Here we assume that all sequences in the group share
114 - # the same prompt. This may not be true for preempted sequences.
115 -
116 - check_no_caching_or_swa_for_blockmgr_encdec(self, seq_group)
117 -
118 - seq = seq_group.get_seqs(status=SequenceStatus.WAITING)[0]
119 - num_required_blocks = BlockTable.get_num_required_blocks(
120 - seq.get_token_ids(),
121 - block_size=self.block_size,
122 - num_lookahead_slots=num_lookahead_slots,
123 - )
124 -
125 - if seq_group.is_encoder_decoder():
126 - encoder_seq = seq_group.get_encoder_seq()
127 - assert encoder_seq is not None
128 - num_required_blocks += BlockTable.get_num_required_blocks(
129 - encoder_seq.get_token_ids(),
130 - block_size=self.block_size,
131 - )
132 -
133 - if self.max_block_sliding_window is not None:
134 - num_required_blocks = min(num_required_blocks,
135 - self.max_block_sliding_window)
136 -
137 - num_free_gpu_blocks = self.block_allocator.get_num_free_blocks(
138 - device=Device.GPU)
139 -
140 - # Use watermark to avoid frequent cache eviction.
141 - if (self.num_total_gpu_blocks - num_required_blocks
142 - < self.watermark_blocks):
143 - return AllocStatus.NEVER
144 - if num_free_gpu_blocks - num_required_blocks >= self.watermark_blocks:
145 - return AllocStatus.OK
146 - else:
147 - return AllocStatus.LATER
148 -
149 - def _allocate_sequence(self, seq: Sequence) -> BlockTable:
150 - block_table = BlockTable(
151 - block_size=self.block_size,
152 - block_allocator=self.block_allocator,
153 - max_block_sliding_window=self.max_block_sliding_window,
154 - )
155 - if seq.get_token_ids():
156 - # NOTE: If there are any factors affecting the block besides
157 - # token_ids, they should be added as input to extra_hash.
158 - extra_hash = seq.extra_hash()
159 -
160 - # Add blocks to the block table only if the sequence is non empty.
161 - block_table.allocate(token_ids=seq.get_token_ids(),
162 - extra_hash=extra_hash)
163 -
164 - return block_table
165 -
166 - def allocate(self, seq_group: SequenceGroup) -> None:
167 -
168 - # Allocate self-attention block tables for decoder sequences
169 - waiting_seqs = seq_group.get_seqs(status=SequenceStatus.WAITING)
170 - assert not (set(seq.seq_id for seq in waiting_seqs)
171 - & self.block_tables.keys()), "block table already exists"
172 -
173 - # NOTE: Here we assume that all sequences in the group have the same
174 - # prompt.
175 - seq = waiting_seqs[0]
176 - block_table: BlockTable = self._allocate_sequence(seq)
177 - self.block_tables[seq.seq_id] = block_table
178 -
179 - # Track seq
180 - self._last_access_blocks_tracker.add_seq(seq.seq_id)
181 -
182 - # Assign the block table for each sequence.
183 - for seq in waiting_seqs[1:]:
184 - self.block_tables[seq.seq_id] = block_table.fork()
185 -
186 - # Track seq
187 - self._last_access_blocks_tracker.add_seq(seq.seq_id)
188 -
189 - # Allocate cross-attention block table for encoder sequence
190 - #
191 - # NOTE: Here we assume that all sequences in the group have the same
192 - # encoder prompt.
193 - request_id = seq_group.request_id
194 -
195 - assert (request_id
196 - not in self.cross_block_tables), \
197 - "block table already exists"
198 -
199 - check_no_caching_or_swa_for_blockmgr_encdec(self, seq_group)
200 -
201 - if seq_group.is_encoder_decoder():
202 - encoder_seq = seq_group.get_encoder_seq()
203 - assert encoder_seq is not None
204 - block_table = self._allocate_sequence(encoder_seq)
205 - self.cross_block_tables[request_id] = block_table
206 -
207 - def can_append_slots(self, seq_group: SequenceGroup,
208 - num_lookahead_slots: int) -> bool:
209 - """Determine if there is enough space in the GPU KV cache to continue
210 - generation of the specified sequence group.
211 -
212 - We use a worst-case heuristic: assume each touched block will require a
213 - new allocation (either via CoW or new block). We can append slots if the
214 - number of touched blocks is less than the number of free blocks.
215 -
216 - "Lookahead slots" are slots that are allocated in addition to the slots
217 - for known tokens. The contents of the lookahead slots are not defined.
218 - This is used by speculative decoding when speculating future tokens.
219 - """
220 -
221 - num_touched_blocks = 0
222 - for seq in seq_group.get_seqs(status=SequenceStatus.RUNNING):
223 - block_table = self.block_tables[seq.seq_id]
224 -
225 - num_touched_blocks += (
226 - block_table.get_num_blocks_touched_by_append_slots(
227 - token_ids=block_table.get_unseen_token_ids(
228 - seq.get_token_ids()),
229 - num_lookahead_slots=num_lookahead_slots,
230 - ))
231 -
232 - num_free_gpu_blocks = self.block_allocator.get_num_free_blocks(
233 - Device.GPU)
234 - return num_touched_blocks <= num_free_gpu_blocks
235 -
236 - def append_slots(
237 - self,
238 - seq: Sequence,
239 - num_lookahead_slots: int,
240 - ) -> List[Tuple[int, int]]:
241 -
242 - block_table = self.block_tables[seq.seq_id]
243 -
244 - block_table.append_token_ids(
245 - token_ids=block_table.get_unseen_token_ids(seq.get_token_ids()),
246 - num_lookahead_slots=num_lookahead_slots,
247 - num_computed_slots=seq.data.get_num_computed_tokens(),
248 - extra_hash=seq.extra_hash(),
249 - )
250 - # Return any new copy-on-writes.
251 - new_cows = self.block_allocator.clear_copy_on_writes()
252 - return new_cows
253 -
254 - def free(self, seq: Sequence) -> None:
255 - seq_id = seq.seq_id
256 -
257 - if seq_id not in self.block_tables:
258 - # Already freed or haven't been scheduled yet.
259 - return
260 -
261 - # Update seq block ids with the latest access time
262 - self._last_access_blocks_tracker.update_seq_blocks_last_access(
263 - seq_id, self.block_tables[seq.seq_id].physical_block_ids)
264 -
265 - # Untrack seq
266 - self._last_access_blocks_tracker.remove_seq(seq_id)
267 - self._computed_blocks_tracker.remove_seq(seq_id)
268 -
269 - # Free table/blocks
270 - self.block_tables[seq_id].free()
271 - del self.block_tables[seq_id]
272 -
273 - def remove_seq_from_computed_blocks_tracker(self, seq: Sequence) -> None:
274 - seq_id = seq.seq_id
275 - self._computed_blocks_tracker.remove_seq(seq_id)
276 -
277 - def free_cross(self, seq_group: SequenceGroup) -> None:
278 - request_id = seq_group.request_id
279 - if request_id not in self.cross_block_tables:
280 - # Already freed or hasn't been scheduled yet.
281 - return
282 - self.cross_block_tables[request_id].free()
283 - del self.cross_block_tables[request_id]
284 -
285 - def get_block_table(self, seq: Sequence) -> List[int]:
286 - block_ids = self.block_tables[seq.seq_id].physical_block_ids
287 - return block_ids # type: ignore
288 -
289 - def get_cross_block_table(self, seq_group: SequenceGroup) -> List[int]:
290 - request_id = seq_group.request_id
291 - assert request_id in self.cross_block_tables
292 - block_ids = self.cross_block_tables[request_id].physical_block_ids
293 - assert all(b is not None for b in block_ids)
294 - return block_ids # type: ignore
295 -
296 - def access_all_blocks_in_seq(self, seq: Sequence, now: float):
297 - if self.enable_caching:
298 - # Record the latest access time for the sequence. The actual update
299 - # of the block ids is deferred to the sequence free(..) call, since
300 - # only during freeing of block ids, the blocks are actually added to
301 - # the evictor (which is when the most updated time is required)
302 - # (This avoids expensive calls to mark_blocks_as_accessed(..))
303 - self._last_access_blocks_tracker.update_last_access(
304 - seq.seq_id, now)
305 -
306 - def mark_blocks_as_computed(self, seq_group: SequenceGroup,
307 - token_chunk_size: int):
308 - # If prefix caching is enabled, mark immutable blocks as computed
309 - # right after they have been scheduled (for prefill). This assumes
310 - # the scheduler is synchronous so blocks are actually computed when
311 - # scheduling the next batch.
312 - self.block_allocator.mark_blocks_as_computed([])
313 -
314 - def get_common_computed_block_ids(
315 - self, seqs: List[Sequence]) -> GenericSequence[int]:
316 - """Determine which blocks for which we skip prefill.
317 -
318 - With prefix caching we can skip prefill for previously-generated blocks.
319 - Currently, the attention implementation only supports skipping cached
320 - blocks if they are a contiguous prefix of cached blocks.
321 -
322 - This method determines which blocks can be safely skipped for all
323 - sequences in the sequence group.
324 - """
325 - computed_seq_block_ids = []
326 - for seq in seqs:
327 - all_blocks = self.block_tables[seq.seq_id].physical_block_ids
328 - num_cached_tokens = (
329 - self._computed_blocks_tracker.get_num_cached_tokens(seq))
330 - assert num_cached_tokens % self.block_size == 0
331 - num_cached_blocks = num_cached_tokens // self.block_size
332 - computed_block_ids = all_blocks[:num_cached_blocks]
333 - computed_seq_block_ids.append(computed_block_ids)
334 -
335 - # NOTE(sang): This assumes seq_block_ids doesn't contain any None.
336 - return self.block_allocator.get_common_computed_block_ids(
337 - computed_seq_block_ids) # type: ignore
338 -
339 - def fork(self, parent_seq: Sequence, child_seq: Sequence) -> None:
340 - if parent_seq.seq_id not in self.block_tables:
341 - # Parent sequence has either been freed or never existed.
342 - return
343 - src_block_table = self.block_tables[parent_seq.seq_id]
344 - self.block_tables[child_seq.seq_id] = src_block_table.fork()
345 -
346 - # Track child seq
347 - self._last_access_blocks_tracker.add_seq(child_seq.seq_id)
348 -
349 - def can_swap_in(self, seq_group: SequenceGroup,
350 - num_lookahead_slots: int) -> AllocStatus:
351 - """Returns the AllocStatus for the given sequence_group
352 - with num_lookahead_slots.
353 -
354 - Args:
355 - seq_group (SequenceGroup): The sequence group to swap in.
356 - num_lookahead_slots (int): Number of lookahead slots used in
357 - speculative decoding, default to 0.
358 -
359 - Returns:
360 - AllocStatus: The AllocStatus for the given sequence group.
361 - """
362 - return self._can_swap(seq_group, Device.GPU, SequenceStatus.SWAPPED,
363 - num_lookahead_slots)
364 -
365 - def swap_in(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]:
366 - """Returns the block id mapping (from CPU to GPU) generated by
367 - swapping in the given seq_group with num_lookahead_slots.
368 -
369 - Args:
370 - seq_group (SequenceGroup): The sequence group to swap in.
371 -
372 - Returns:
373 - List[Tuple[int, int]]: The mapping of swapping block from CPU
374 - to GPU.
375 - """
376 - physical_block_id_mapping = []
377 - for seq in seq_group.get_seqs(status=SequenceStatus.SWAPPED):
378 - blocks = self.block_tables[seq.seq_id].blocks
379 - if len(blocks) == 0:
380 - continue
381 -
382 - seq_swap_mapping = self.block_allocator.swap(blocks=blocks,
383 - src_device=Device.CPU,
384 - dst_device=Device.GPU)
385 -
386 - # Refresh the block ids of the table (post-swap)
387 - self.block_tables[seq.seq_id].update(blocks)
388 -
389 - seq_physical_block_id_mapping = {
390 - self.block_allocator.get_physical_block_id(
391 - Device.CPU, cpu_block_id):
392 - self.block_allocator.get_physical_block_id(
393 - Device.GPU, gpu_block_id)
394 - for cpu_block_id, gpu_block_id in seq_swap_mapping.items()
395 - }
396 -
397 - physical_block_id_mapping.extend(
398 - list(seq_physical_block_id_mapping.items()))
399 -
400 - return physical_block_id_mapping
401 -
402 - def can_swap_out(self, seq_group: SequenceGroup) -> bool:
403 - """Returns whether we can swap out the given sequence_group
404 - with num_lookahead_slots.
405 -
406 - Args:
407 - seq_group (SequenceGroup): The sequence group to swap out.
408 -
409 - Returns:
410 - bool: Whether it's possible to swap out current sequence group.
411 - """
412 - alloc_status = self._can_swap(seq_group, Device.CPU,
413 - SequenceStatus.RUNNING)
414 - return alloc_status == AllocStatus.OK
415 -
416 - def swap_out(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]:
417 - """Returns the block id mapping (from GPU to CPU) generated by
418 - swapping out the given sequence_group with num_lookahead_slots.
419 -
420 - Args:
421 - seq_group (SequenceGroup): The sequence group to swap out.
422 -
423 - Returns:
424 - List[Tuple[int, int]]: The mapping of swapping block from
425 - GPU to CPU.
426 - """
427 - physical_block_id_mapping = []
428 - for seq in seq_group.get_seqs(status=SequenceStatus.RUNNING):
429 - blocks = self.block_tables[seq.seq_id].blocks
430 - if len(blocks) == 0:
431 - continue
432 -
433 - seq_swap_mapping = self.block_allocator.swap(blocks=blocks,
434 - src_device=Device.GPU,
435 - dst_device=Device.CPU)
436 -
437 - # Refresh the block ids of the table (post-swap)
438 - self.block_tables[seq.seq_id].update(blocks)
439 -
440 - seq_physical_block_id_mapping = {
441 - self.block_allocator.get_physical_block_id(
442 - Device.GPU, gpu_block_id):
443 - self.block_allocator.get_physical_block_id(
444 - Device.CPU, cpu_block_id)
445 - for gpu_block_id, cpu_block_id in seq_swap_mapping.items()
446 - }
447 -
448 - physical_block_id_mapping.extend(
449 - list(seq_physical_block_id_mapping.items()))
450 -
451 - return physical_block_id_mapping
452 -
453 - def get_num_free_gpu_blocks(self) -> int:
454 - return self.block_allocator.get_num_free_blocks(Device.GPU)
455 -
456 - def get_num_free_cpu_blocks(self) -> int:
457 - return self.block_allocator.get_num_free_blocks(Device.CPU)
458 -
459 - def get_prefix_cache_hit_rate(self, device: Device) -> float:
460 - return self.block_allocator.get_prefix_cache_hit_rate(device)
461 -
462 - def reset_prefix_cache(self, device: Optional[Device] = None) -> bool:
463 - return self.block_allocator.reset_prefix_cache(device)
464 -
465 - def _can_swap(self,
466 - seq_group: SequenceGroup,
467 - device: Device,
468 - status: SequenceStatus,
469 - num_lookahead_slots: int = 0) -> AllocStatus:
470 - """Returns the AllocStatus for swapping in/out the given sequence_group
471 - on to the 'device'.
472 -
473 - Args:
474 - seq_group (SequenceGroup): The sequence group to swap in/out.
475 - device (Device): device to swap the 'seq_group' on.
476 - status (SequenceStatus): The status of sequence which is needed
477 - for action. RUNNING for swap out and SWAPPED for swap in
478 - num_lookahead_slots (int): Number of lookahead slots used in
479 - speculative decoding, default to 0.
480 -
481 - Returns:
482 - AllocStatus: The AllocStatus for swapping in/out the given
483 - sequence_group on to the 'device'.
484 - """
485 - # First determine the number of blocks that will be touched by this
486 - # swap. Then verify if there are available blocks in the device
487 - # to perform the swap.
488 - num_blocks_touched = 0
489 - blocks: List[Block] = []
490 - for seq in seq_group.get_seqs(status=status):
491 - block_table = self.block_tables[seq.seq_id]
492 - if block_table.blocks is not None:
493 - # Compute the number blocks to touch for the tokens to be
494 - # appended. This does NOT include the full blocks that need
495 - # to be touched for the swap.
496 - num_blocks_touched += \
497 - block_table.get_num_blocks_touched_by_append_slots(
498 - block_table.get_unseen_token_ids(seq.get_token_ids()),
499 - num_lookahead_slots=num_lookahead_slots)
500 - blocks.extend(block_table.blocks)
501 - # Compute the number of full blocks to touch and add it to the
502 - # existing count of blocks to touch.
503 - num_blocks_touched += self.block_allocator.get_num_full_blocks_touched(
504 - blocks, device=device)
505 -
506 - watermark_blocks = 0
507 - if device == Device.GPU:
508 - watermark_blocks = self.watermark_blocks
509 -
510 - if self.block_allocator.get_num_total_blocks(
511 - device) < num_blocks_touched:
512 - return AllocStatus.NEVER
513 - elif self.block_allocator.get_num_free_blocks(
514 - device) - num_blocks_touched >= watermark_blocks:
515 - return AllocStatus.OK
516 - else:
517 - return AllocStatus.LATER
518 -
519 - def get_num_cached_tokens(self, seq: Sequence) -> int:
520 - """Get the number of tokens in blocks that are already computed and
521 - cached in the block manager for the sequence.
522 - """
523 - return self._computed_blocks_tracker.get_num_cached_tokens(seq)
vllm/core/evictor.py deleted
-157
@@ -1,157 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import enum
5 -import heapq
6 -from abc import ABC, abstractmethod
7 -from typing import Dict, List, Tuple
8 -
9 -
10 -class EvictionPolicy(enum.Enum):
11 - """Enum for eviction policy used by make_evictor to instantiate the correct
12 - Evictor subclass.
13 - """
14 - LRU = enum.auto()
15 -
16 -
17 -class Evictor(ABC):
18 - """The Evictor subclasses should be used by the BlockAllocator class to
19 - handle eviction of freed Blocks.
20 - """
21 -
22 - @abstractmethod
23 - def __init__(self):
24 - pass
25 -
26 - @abstractmethod
27 - def __contains__(self, block_id: int) -> bool:
28 - pass
29 -
30 - @abstractmethod
31 - def evict(self) -> Tuple[int, int]:
32 - """Runs the eviction algorithm and returns the evicted block's
33 - content hash along with physical block id along with physical block id
34 - """
35 - pass
36 -
37 - @abstractmethod
38 - def add(self, block_id: int, content_hash: int, num_hashed_tokens: int,
39 - last_accessed: float):
40 - """Adds block to the evictor, making it a candidate for eviction"""
41 - pass
42 -
43 - @abstractmethod
44 - def update(self, block_id: int, last_accessed: float):
45 - """Update corresponding block's access time in metadata"""
46 - pass
47 -
48 - @abstractmethod
49 - def remove(self, block_id: int):
50 - """Remove a given block id from the cache."""
51 - pass
52 -
53 - @property
54 - @abstractmethod
55 - def num_blocks(self) -> int:
56 - pass
57 -
58 -
59 -class BlockMetaData:
60 - """Data structure for storing key data describe cached block, so that
61 - evictor could use to make its decision which one to choose for eviction
62 -
63 - Here we use physical block id as the dict key, as there maybe several
64 - blocks with the same content hash, but their physical id is unique.
65 - """
66 -
67 - def __init__(self, content_hash: int, num_hashed_tokens: int,
68 - last_accessed: float):
69 - self.content_hash = content_hash
70 - self.num_hashed_tokens = num_hashed_tokens
71 - self.last_accessed = last_accessed
72 -
73 -
74 -class LRUEvictor(Evictor):
75 - """Evicts in a least-recently-used order using the last_accessed timestamp
76 - that's recorded in the Block. If there are multiple blocks with
77 - the same last_accessed time, then the one with the largest num_hashed_tokens
78 - will be evicted. If two blocks each have the lowest last_accessed time and
79 - highest num_hashed_tokens value, then one will be chosen arbitrarily
80 - """
81 -
82 - # CLEANUP_THRESHOLD determines the maximum allowable size of the priority
83 - # queue relative to the free table size. When this threshold is exceeded,
84 - # a cleanup operation is triggered to reduce memory usage.
85 - CLEANUP_THRESHOLD = 50
86 -
87 - def __init__(self):
88 - self.free_table: Dict[int, BlockMetaData] = {}
89 - self.priority_queue = []
90 -
91 - def __contains__(self, block_id: int) -> bool:
92 - return block_id in self.free_table
93 -
94 - def evict(self) -> Tuple[int, int]:
95 - if len(self.free_table) == 0:
96 - raise ValueError("No usable cache memory left")
97 -
98 - while self.priority_queue:
99 - # We do not remove outdated entries from the priority queue at the
100 - # time of updating the last_accessed timestamp. Instead, outdated
101 - # entries are filtered out here during eviction. Outdated entries
102 - # would either not in the free table, or have older last accessed
103 - # time.
104 - last_accessed, _, block_id, content_hash = heapq.heappop(
105 - self.priority_queue)
106 - if (block_id in self.free_table and
107 - self.free_table[block_id].last_accessed == last_accessed):
108 - self.free_table.pop(block_id)
109 - return block_id, content_hash
110 -
111 - raise ValueError("No usable cache memory left")
112 -
113 - def add(self, block_id: int, content_hash: int, num_hashed_tokens: int,
114 - last_accessed: float):
115 - self.free_table[block_id] = BlockMetaData(content_hash,
116 - num_hashed_tokens,
117 - last_accessed)
118 - heapq.heappush(
119 - self.priority_queue,
120 - (last_accessed, -num_hashed_tokens, block_id, content_hash))
121 - self._cleanup_if_necessary()
122 -
123 - def update(self, block_id: int, last_accessed: float):
124 - self.free_table[block_id].last_accessed = last_accessed
125 -
126 - def _cleanup_if_necessary(self):
127 - if len(self.priority_queue) > LRUEvictor.CLEANUP_THRESHOLD * len(
128 - self.free_table):
129 - self._cleanup()
130 -
131 - def _cleanup(self):
132 - new_priority_queue: List[Tuple[float, int, int, int]] = []
133 -
134 - for block_id, block in self.free_table.items():
135 - new_priority_queue.append(
136 - (block.last_accessed, -block.num_hashed_tokens, block_id,
137 - block.content_hash))
138 - heapq.heapify(new_priority_queue)
139 -
140 - self.priority_queue = new_priority_queue
141 -
142 - def remove(self, block_id: int):
143 - if block_id not in self.free_table:
144 - raise ValueError(
145 - "Attempting to remove block that's not in the evictor")
146 - self.free_table.pop(block_id)
147 -
148 - @property
149 - def num_blocks(self) -> int:
150 - return len(self.free_table)
151 -
152 -
153 -def make_evictor(eviction_policy: EvictionPolicy) -> Evictor:
154 - if eviction_policy == EvictionPolicy.LRU:
155 - return LRUEvictor()
156 - else:
157 - raise ValueError(f"Unknown cache eviction policy: {eviction_policy}")
vllm/core/interfaces.py deleted
-139
@@ -1,139 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import enum
5 -from abc import ABC, abstractmethod
6 -from typing import List, Optional
7 -from typing import Sequence as GenericSequence
8 -from typing import Tuple
9 -
10 -from vllm.sequence import Sequence, SequenceGroup
11 -from vllm.utils import Device
12 -
13 -
14 -class AllocStatus(enum.Enum):
15 - """Result for BlockSpaceManager.can_allocate
16 -
17 - 1. Ok: seq_group can be allocated now.
18 - 2. Later: seq_group cannot be allocated.
19 - The capacity of allocator is larger than seq_group required.
20 - 3. Never: seq_group can never be allocated.
21 - The seq_group is too large to allocated in GPU.
22 - """
23 - OK = enum.auto()
24 - LATER = enum.auto()
25 - NEVER = enum.auto()
26 -
27 -
28 -class BlockSpaceManager(ABC):
29 -
30 - @staticmethod
31 - def get_block_space_manager_class(version: str):
32 - version = version.lower()
33 -
34 - if version == "selfattn":
35 - from vllm.core.block_manager import SelfAttnBlockSpaceManager
36 - return SelfAttnBlockSpaceManager
37 -
38 - if version == "placeholder":
39 - from vllm.core.placeholder_block_space_manager import (
40 - PlaceholderBlockSpaceManager)
41 - return PlaceholderBlockSpaceManager
42 -
43 - raise ValueError(f"Unknown version {version=}")
44 -
45 - @abstractmethod
46 - def can_allocate(self,
47 - seq_group: SequenceGroup,
48 - num_lookahead_slots: int = 0) -> AllocStatus:
49 - pass
50 -
51 - @abstractmethod
52 - def allocate(self, seq_group: SequenceGroup) -> None:
53 - pass
54 -
55 - @abstractmethod
56 - def can_append_slots(self, seq_group: SequenceGroup,
57 - num_lookahead_slots: int) -> bool:
58 - pass
59 -
60 - @abstractmethod
61 - def append_slots(
62 - self,
63 - seq: Sequence,
64 - num_lookahead_slots: int,
65 - ) -> List[Tuple[int, int]]:
66 - pass
67 -
68 - @abstractmethod
69 - def fork(self, parent_seq: Sequence, child_seq: Sequence) -> None:
70 - pass
71 -
72 - @abstractmethod
73 - def can_swap_in(self, seq_group: SequenceGroup,
74 - num_lookahead_slots: int) -> AllocStatus:
75 - pass
76 -
77 - @abstractmethod
78 - def swap_in(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]:
79 - pass
80 -
81 - @abstractmethod
82 - def can_swap_out(self, seq_group: SequenceGroup) -> bool:
83 - pass
84 -
85 - @abstractmethod
86 - def swap_out(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]:
87 - pass
88 -
89 - @abstractmethod
90 - def free(self, seq: Sequence) -> None:
91 - pass
92 -
93 - @abstractmethod
94 - def get_block_table(self, seq: Sequence) -> List[int]:
95 - pass
96 -
97 - @abstractmethod
98 - def get_num_free_gpu_blocks(self) -> int:
99 - pass
100 -
101 - @abstractmethod
102 - def get_num_free_cpu_blocks(self) -> int:
103 - pass
104 -
105 - @abstractmethod
106 - def access_all_blocks_in_seq(
107 - self,
108 - seq: Sequence,
109 - access_time: float,
110 - ) -> None:
111 - pass
112 -
113 - @abstractmethod
114 - def get_common_computed_block_ids(
115 - self, seqs: List[Sequence]) -> GenericSequence[int]:
116 - pass
117 -
118 - @abstractmethod
119 - def mark_blocks_as_computed(self, seq_group: SequenceGroup,
120 - token_chunk_size: int):
121 - pass
122 -
123 - @abstractmethod
124 - def get_prefix_cache_hit_rate(self, device: Device) -> float:
125 - """Prefix cache hit rate. -1 means not supported or disabled."""
126 - pass
127 -
128 - @abstractmethod
129 - def reset_prefix_cache(self, device: Optional[Device] = None) -> bool:
130 - """Reset prefix cache for specified or all devices."""
131 - pass
132 -
133 - @abstractmethod
134 - def get_num_cached_tokens(self, seq: Sequence) -> int:
135 - pass
136 -
137 - @abstractmethod
138 - def remove_seq_from_computed_blocks_tracker(self, seq: Sequence) -> None:
139 - pass
\ No newline at end of file
vllm/core/placeholder_block_space_manager.py deleted
-103
@@ -1,103 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -from typing import List, Optional, Tuple
5 -
6 -from vllm.core.interfaces import AllocStatus, BlockSpaceManager
7 -from vllm.sequence import Sequence, SequenceGroup
8 -from vllm.utils import Device
9 -
10 -
11 -class PlaceholderBlockSpaceManager(BlockSpaceManager):
12 - """A version of BlockSpaceManager for use in environments
13 - where block management is not required.
14 - For example: pooling models or attention-free models like Mamba.
15 -
16 - This class provides the same interface as BlockSpaceManager, but its
17 - methods perform no actions or return simple values like True in specific
18 - actions. It's designed to be used in scenarios where the overhead of
19 - block management is unnecessary, such as in an embedding environment.
20 - """
21 -
22 - def __init__(
23 - self,
24 - **kwargs,
25 - ) -> None:
26 - pass
27 -
28 - def can_allocate(self,
29 - seq_group: SequenceGroup,
30 - num_lookahead_slots: int = 0) -> AllocStatus:
31 - # Always return OK for dummy purposes
32 - return AllocStatus.OK
33 -
34 - def allocate(self, seq_group: SequenceGroup) -> None:
35 - # No actual allocation logic needed
36 - pass
37 -
38 - def can_append_slots(self, seq_group: SequenceGroup,
39 - num_lookahead_slots: int) -> bool:
40 - return True
41 -
42 - def append_slots(
43 - self,
44 - seq: Sequence,
45 - num_lookahead_slots: int,
46 - ) -> List[Tuple[int, int]]:
47 - return []
48 -
49 - def fork(self, parent_seq: Sequence, child_seq: Sequence) -> None:
50 - pass
51 -
52 - def can_swap_in(self, seq_group: SequenceGroup,
53 - num_lookahead_slots: int) -> AllocStatus:
54 - return AllocStatus.OK
55 -
56 - def swap_in(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]:
57 - return None # type: ignore
58 -
59 - def can_swap_out(self, seq_group: SequenceGroup) -> bool:
60 - return True
61 -
62 - def swap_out(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]:
63 - return None # type: ignore
64 -
65 - def free(self, seq: Sequence) -> None:
66 - # No operation on free
67 - return
68 -
69 - def get_block_table(self, seq: Sequence) -> List[int]:
70 - return None # type: ignore
71 -
72 - def get_num_free_gpu_blocks(self) -> int:
73 - return 1
74 -
75 - def get_num_free_cpu_blocks(self) -> int:
76 - return 1
77 -
78 - def access_all_blocks_in_seq(
79 - self,
80 - seq: Sequence,
81 - access_time: float,
82 - ) -> None:
83 - pass
84 -
85 - def get_common_computed_block_ids(self,
86 - seq_group: List[Sequence]) -> List[int]:
87 - return []
88 -
89 - def mark_blocks_as_computed(self, seq_group: SequenceGroup,
90 - token_chunk_size: int):
91 - pass
92 -
93 - def get_prefix_cache_hit_rate(self, device: Device) -> float:
94 - return -1
95 -
96 - def reset_prefix_cache(self, device: Optional[Device] = None) -> bool:
97 - return True
98 -
99 - def get_num_cached_tokens(self, seq: Sequence) -> int:
100 - return 0
101 -
102 - def remove_seq_from_computed_blocks_tracker(self, seq: Sequence) -> None:
103 - return
vllm/core/scheduler.py deleted
-2028
@@ -1,2028 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import enum
5 -import os
6 -import random
7 -import time
8 -from collections import deque
9 -from dataclasses import dataclass, field
10 -from typing import Callable, Deque, Dict, Iterable, List, Optional
11 -from typing import Sequence as GenericSequence
12 -from typing import Set, Tuple, Union
13 -
14 -from vllm.config import CacheConfig, SchedulerConfig
15 -from vllm.config.lora import LoRAConfig
16 -from vllm.core.interfaces import AllocStatus, BlockSpaceManager
17 -from vllm.logger import init_logger
18 -from vllm.lora.request import LoRARequest
19 -from vllm.sequence import (Sequence, SequenceData, SequenceGroup,
20 - SequenceGroupBase, SequenceGroupMetadata,
21 - SequenceGroupMetadataDelta, SequenceStage,
22 - SequenceStatus)
23 -from vllm.utils import Device, PyObjectCache
24 -
25 -logger = init_logger(__name__)
26 -
27 -# Test-only. If configured, decode is preempted with
28 -# ARTIFICIAL_PREEMPTION_PROB% probability.
29 -ENABLE_ARTIFICIAL_PREEMPT = bool(
30 - os.getenv("VLLM_TEST_ENABLE_ARTIFICIAL_PREEMPT", False)) # noqa
31 -ARTIFICIAL_PREEMPTION_PROB = 0.5
32 -ARTIFICIAL_PREEMPTION_MAX_CNT = 500
33 -
34 -
35 -class PreemptionMode(enum.Enum):
36 - """Preemption modes.
37 -
38 - 1. Swapping: Swap out the blocks of the preempted sequences to CPU memory
39 - and swap them back in when the sequences are resumed.
40 - 2. Recomputation: Discard the blocks of the preempted sequences and
41 - recompute them when the sequences are resumed, treating the sequences as
42 - new prompts.
43 - """
44 -
45 - SWAP = enum.auto()
46 - RECOMPUTE = enum.auto()
47 -
48 -
49 -@dataclass
50 -class SchedulingBudget:
51 - """The available slots for scheduling.
52 -
53 - TODO(sang): Right now, the budget is request_id-aware meaning it can ignore
54 - budget update from the same request_id. It is because in normal scheduling
55 - path, we update RUNNING num_seqs ahead of time, meaning it could be
56 - updated more than once when scheduling RUNNING requests. Since this won't
57 - happen if we only have chunked prefill scheduling, we can remove this
58 - feature from the API when chunked prefill is enabled by default.
59 - """
60 -
61 - token_budget: int
62 - max_num_seqs: int
63 - _request_ids_num_batched_tokens: Set[str] = field(default_factory=set)
64 - _request_ids_num_curr_seqs: Set[str] = field(default_factory=set)
65 - # Number of cached tokens in the batch.
66 - _num_cached_tokens: int = 0
67 - # Number of actual non-cached tokens in the batch.
68 - _num_batched_tokens: int = 0
69 - _num_curr_seqs: int = 0
70 -
71 - def can_schedule(self, *, num_new_tokens: int, num_new_seqs: int):
72 - # We allow num_new_tokens to be 0 when the entire sequence has
73 - # been cached.
74 - assert num_new_tokens >= 0
75 - assert num_new_seqs != 0
76 - return (self.num_batched_tokens + num_new_tokens <= self.token_budget
77 - and self.num_curr_seqs + num_new_seqs <= self.max_num_seqs)
78 -
79 - def remaining_token_budget(self):
80 - return self.token_budget - self.num_batched_tokens
81 -
82 - def add_num_batched_tokens(self,
83 - req_id: str,
84 - num_batched_tokens: int,
85 - num_cached_tokens: int = 0):
86 - if req_id in self._request_ids_num_batched_tokens:
87 - return
88 - assert num_cached_tokens >= 0
89 - assert num_batched_tokens >= 0
90 -
91 - self._request_ids_num_batched_tokens.add(req_id)
92 - self._num_batched_tokens += num_batched_tokens
93 - self._num_cached_tokens += num_cached_tokens
94 -
95 - def subtract_num_batched_tokens(self, req_id: str,
96 - num_batched_tokens: int):
97 - if req_id in self._request_ids_num_batched_tokens:
98 - self._request_ids_num_batched_tokens.remove(req_id)
99 - self._num_batched_tokens -= num_batched_tokens
100 -
101 - def add_num_seqs(self, req_id: str, num_curr_seqs: int):
102 - if req_id in self._request_ids_num_curr_seqs:
103 - return
104 -
105 - self._request_ids_num_curr_seqs.add(req_id)
106 - self._num_curr_seqs += num_curr_seqs
107 -
108 - def subtract_num_seqs(self, req_id: str, num_curr_seqs: int):
109 - if req_id in self._request_ids_num_curr_seqs:
110 - self._request_ids_num_curr_seqs.remove(req_id)
111 - self._num_curr_seqs -= num_curr_seqs
112 -
113 - @property
114 - def num_batched_tokens(self):
115 - return self._num_batched_tokens
116 -
117 - @property
118 - def num_curr_seqs(self):
119 - return self._num_curr_seqs
120 -
121 - @property
122 - def num_cached_tokens(self):
123 - return self._num_cached_tokens
124 -
125 -
126 -@dataclass
127 -class ScheduledSequenceGroup:
128 - # A sequence group that's scheduled.
129 - seq_group: SequenceGroup
130 - # The total chunk size (number of tokens) to process for next iteration.
131 - # 1 for decoding. Same as prompt tokens for prefill, but if prefill is
132 - # chunked, it can be smaller than that.
133 - token_chunk_size: int
134 -
135 -
136 -@dataclass
137 -class SchedulerOutputs:
138 - """The scheduling decision made from a scheduler."""
139 -
140 - # Scheduled sequence groups.
141 - scheduled_seq_groups: GenericSequence[ScheduledSequenceGroup]
142 - # Number of prefill groups scheduled.
143 - num_prefill_groups: int
144 - # Total number of batched tokens.
145 - num_batched_tokens: int
146 - # Blocks to swap in. List of CPU -> GPU block number.
147 - blocks_to_swap_in: List[Tuple[int, int]]
148 - # Blocks to swap out. List of GPU -> CPU block number.
149 - blocks_to_swap_out: List[Tuple[int, int]]
150 - # Blocks to copy. Source to dest block.
151 - blocks_to_copy: List[Tuple[int, int]]
152 - # Sequence groups that are going to be ignored.
153 - ignored_seq_groups: List[SequenceGroup]
154 - # The number of slots for lookahead decoding.
155 - num_lookahead_slots: int
156 - # The number of requests in the running queue
157 - running_queue_size: int
158 - preempted: int
159 -
160 - def __post_init__(self):
161 - # Swap in and swap out should never happen at the same time.
162 - assert not (self.blocks_to_swap_in and self.blocks_to_swap_out)
163 -
164 - self.num_loras: int = len(self.lora_requests)
165 - if self.num_loras > 0:
166 - self._sort_by_lora_ids()
167 -
168 - def is_empty(self) -> bool:
169 - # NOTE: We do not consider the ignored sequence groups.
170 - return (not self.scheduled_seq_groups and not self.blocks_to_swap_in
171 - and not self.blocks_to_swap_out and not self.blocks_to_copy)
172 -
173 - def _sort_by_lora_ids(self):
174 - assert 0 <= self.num_prefill_groups <= len(self.scheduled_seq_groups)
175 -
176 - def key_fn(group: ScheduledSequenceGroup):
177 - key = (group.seq_group.lora_int_id, group.seq_group.request_id)
178 - if 0 < self.num_prefill_groups < len(self.scheduled_seq_groups):
179 - # Sort sequence groups so that all prefills come before all
180 - # decodes as required by chunked prefill.
181 - return (not group.seq_group.is_prefill(), *key)
182 - return key
183 -
184 - self.scheduled_seq_groups = sorted(self.scheduled_seq_groups,
185 - key=key_fn)
186 -
187 - @property
188 - def lora_requests(self) -> Set[LoRARequest]:
189 - return {
190 - g.seq_group.lora_request
191 - for g in self.scheduled_seq_groups
192 - if g.seq_group.lora_request is not None
193 - }
194 -
195 -
196 -@dataclass
197 -class SchedulerRunningOutputs:
198 - """The requests that are scheduled from a running queue.
199 -
200 - Could contain prefill (prefill that's chunked) or decodes. If there's not
201 - enough memory, it can be preempted (for recompute) or swapped out.
202 - """
203 -
204 - # Selected sequences that are running and in a decoding phase.
205 - decode_seq_groups: List[ScheduledSequenceGroup]
206 - # Selected sequences that are running and in a prefill phase.
207 - # I.e., it means the prefill has been chunked.
208 - prefill_seq_groups: List[ScheduledSequenceGroup]
209 - # The preempted sequences.
210 - preempted: List[SequenceGroup]
211 - # Sequences that are swapped out.
212 - swapped_out: List[SequenceGroup]
213 - # The blocks to swap out.
214 - blocks_to_swap_out: List[Tuple[int, int]]
215 - # The blocks to copy.
216 - blocks_to_copy: List[Tuple[int, int]]
217 - # The number of slots for lookahead decoding.
218 - num_lookahead_slots: int
219 -
220 - # Optimization for fast-access to seq_group lists
221 - decode_seq_groups_list: List[SequenceGroup]
222 - prefill_seq_groups_list: List[SequenceGroup]
223 -
224 - @classmethod
225 - def create_empty(cls) -> "SchedulerRunningOutputs":
226 - return SchedulerRunningOutputs(
227 - decode_seq_groups=[],
228 - prefill_seq_groups=[],
229 - preempted=[],
230 - swapped_out=[],
231 - blocks_to_swap_out=[],
232 - blocks_to_copy=[],
233 - num_lookahead_slots=0,
234 - decode_seq_groups_list=[],
235 - prefill_seq_groups_list=[],
236 - )
237 -
238 -
239 -@dataclass
240 -class SchedulerSwappedInOutputs:
241 - """The requests that are scheduled from a swap queue.
242 -
243 - Could contain prefill (prefill that's chunked) or decodes.
244 - """
245 -
246 - # Selected sequences that are going to be swapped in and is in a
247 - # decoding phase.
248 - decode_seq_groups: List[ScheduledSequenceGroup]
249 - # Selected sequences that are going to be swapped in and in a prefill
250 - # phase. I.e., it means the prefill has been chunked.
251 - prefill_seq_groups: List[ScheduledSequenceGroup]
252 - # The blocks to swap in.
253 - blocks_to_swap_in: List[Tuple[int, int]]
254 - # The blocks to copy.
255 - blocks_to_copy: List[Tuple[int, int]]
256 - # The number of slots for lookahead decoding.
257 - num_lookahead_slots: int
258 - # Infeasible sequence groups.
259 - infeasible_seq_groups: List[SequenceGroup]
260 -
261 - @classmethod
262 - def create_empty(cls) -> "SchedulerSwappedInOutputs":
263 - return SchedulerSwappedInOutputs(
264 - decode_seq_groups=[],
265 - prefill_seq_groups=[],
266 - blocks_to_swap_in=[],
267 - blocks_to_copy=[],
268 - num_lookahead_slots=0,
269 - infeasible_seq_groups=[],
270 - )
271 -
272 -
273 -@dataclass
274 -class SchedulerPrefillOutputs:
275 - """The requests that are scheduled from a waiting queue.
276 -
277 - Could contain a fresh prefill requests or preempted requests that need
278 - to be recomputed from scratch.
279 - """
280 -
281 - # Selected sequences for prefill.
282 - seq_groups: List[ScheduledSequenceGroup]
283 - # Ignored sequence groups.
284 - ignored_seq_groups: List[SequenceGroup]
285 - num_lookahead_slots: int
286 -
287 - @classmethod
288 - def create_empty(cls) -> "SchedulerPrefillOutputs":
289 - return SchedulerPrefillOutputs(
290 - seq_groups=[],
291 - ignored_seq_groups=[],
292 - num_lookahead_slots=0,
293 - )
294 -
295 -
296 -def seq_group_metadata_builder():
297 - return SequenceGroupMetadata(request_id="",
298 - is_prompt=False,
299 - seq_data={},
300 - sampling_params=None,
301 - block_tables={})
302 -
303 -
304 -def scheduler_running_outputs_builder():
305 - return SchedulerRunningOutputs(decode_seq_groups=[],
306 - prefill_seq_groups=[],
307 - preempted=[],
308 - swapped_out=[],
309 - blocks_to_swap_out=[],
310 - blocks_to_copy=[],
311 - num_lookahead_slots=0,
312 - prefill_seq_groups_list=[],
313 - decode_seq_groups_list=[])
314 -
315 -
316 -def scheduled_seq_group_builder():
317 - return ScheduledSequenceGroup(SequenceGroup.__new__(SequenceGroup),
318 - token_chunk_size=0)
319 - # return ScheduledSequenceGroup(seq_group=None, token_chunk_size=0)
320 -
321 -
322 -@dataclass
323 -class PartialPrefillMetadata:
324 - """Holds information about the partial prefills that are currently running
325 - during a single iteration of the Scheduler.
326 - When chunked prefill is enabled, we allow a certain number of seqs to be
327 - partially prefilled during each iteration. Having multiple partial prefills
328 - in flight allows us to minimize TTFT and avoid decode starvation in cases
329 - where a single sequence group with a very large prompt blocks the queue for
330 - too many iterations.
331 - The number of long prefill requests is limited so that smaller
332 - requests may jump the queue in front of them and get to the decode
333 - phase faster.
334 - """
335 -
336 - # A minimum bound on the total number of prefills to be scheduled during
337 - # this iteration
338 - schedulable_prefills: int
339 -
340 - # The number of long prefill requests currently running
341 - long_prefills: int
342 -
343 - scheduler_config: SchedulerConfig
344 -
345 - def can_schedule(self, seq_group: SequenceGroup) -> bool:
346 - """When concurrent partial prefills are enabled,
347 - we limit the number of long requests and only accept
348 - shorter requests from the queue while running them
349 - concurrently"""
350 - return not (seq_group.first_seq.get_num_new_tokens()
351 - > self.scheduler_config.long_prefill_token_threshold
352 - and self.long_prefills
353 - >= self.scheduler_config.max_long_partial_prefills
354 - and self.scheduler_config.max_num_partial_prefills > 1)
355 -
356 - def maybe_increment_partial_prefills(self,
357 - seq_group: SequenceGroup) -> None:
358 - # When a new prefill is scheduled, we need to know if it is a
359 - # long request
360 - if (seq_group.first_seq.get_num_new_tokens()
361 - > self.scheduler_config.long_prefill_token_threshold):
362 - self.long_prefills += 1
363 -
364 - @classmethod
365 - def from_queues(
366 - cls,
367 - running: Deque[SequenceGroup],
368 - waiting: Deque[SequenceGroup],
369 - scheduler_config: SchedulerConfig,
370 - ) -> "PartialPrefillMetadata":
371 - """Create a PartialPrefillMetadata object from the current state of
372 - the scheduler's queues.
373 - This accounts for the currently running prefill requests, and peeks into
374 - the waiting queue to see if there are more prefills to potentially be
375 - scheduled during this iteration."""
376 - prefills = 0
377 - long_prefills = 0
378 -
379 - waiting_long_prefills = 0
380 -
381 - for sg in running:
382 - if sg.first_seq.data.stage == SequenceStage.PREFILL:
383 - prefills += 1
384 - if (sg.first_seq.get_num_new_tokens()
385 - > scheduler_config.long_prefill_token_threshold):
386 - long_prefills += 1
387 -
388 - for sg in waiting:
389 - # Don't bother looping through the rest of the queue if we know
390 - # there are already at
391 - # least max_partial_prefills requests to fill
392 - if prefills >= scheduler_config.max_num_partial_prefills:
393 - break
394 -
395 - # Don't count long requests from the waiting queue if we aren't
396 - # going to schedule them anyway
397 - if (sg.first_seq.get_num_new_tokens()
398 - > scheduler_config.long_prefill_token_threshold):
399 - if (long_prefills + waiting_long_prefills
400 - >= scheduler_config.max_long_partial_prefills):
401 - continue
402 - waiting_long_prefills += 1
403 - prefills += 1
404 -
405 - # NB: long_prefills and waiting_long_prefills are tracked separately.
406 - # We don't account for the waiting requests here because we need to use
407 - # this metadata to track how many have actually been scheduled.
408 - return PartialPrefillMetadata(
409 - schedulable_prefills=min(
410 - prefills, scheduler_config.max_num_partial_prefills),
411 - long_prefills=long_prefills,
412 - scheduler_config=scheduler_config,
413 - )
414 -
415 -
416 -class Scheduler:
417 -
418 - def __init__(
419 - self,
420 - scheduler_config: SchedulerConfig,
421 - cache_config: CacheConfig,
422 - lora_config: Optional[LoRAConfig],
423 - pipeline_parallel_size: int = 1,
424 - output_proc_callback: Optional[Callable] = None,
425 - ) -> None:
426 - self.scheduler_config = scheduler_config
427 - self.cache_config = cache_config
428 - # Note for LoRA scheduling: the current policy is extremely
429 - # simple and NOT fair. It can lead to starvation of some
430 - # LoRAs. This should be improved in the future.
431 - self.lora_config = lora_config
432 -
433 - version = "selfattn"
434 - if (self.scheduler_config.runner_type == "pooling"
435 - or self.cache_config.is_attention_free):
436 - version = "placeholder"
437 -
438 - BlockSpaceManagerImpl = BlockSpaceManager.get_block_space_manager_class(
439 - version)
440 -
441 - num_gpu_blocks = cache_config.num_gpu_blocks
442 - if num_gpu_blocks:
443 - num_gpu_blocks //= pipeline_parallel_size
444 -
445 - num_cpu_blocks = cache_config.num_cpu_blocks
446 - if num_cpu_blocks:
447 - num_cpu_blocks //= pipeline_parallel_size
448 -
449 - # Create the block space manager.
450 - self.block_manager = BlockSpaceManagerImpl(
451 - block_size=self.cache_config.block_size,
452 - num_gpu_blocks=num_gpu_blocks,
453 - num_cpu_blocks=num_cpu_blocks,
454 - sliding_window=self.cache_config.sliding_window,
455 - enable_caching=self.cache_config.enable_prefix_caching,
456 - )
457 -
458 - # Sequence groups in the WAITING state.
459 - # Contain new prefill or preempted requests.
460 - self.waiting: Deque[SequenceGroup] = deque()
461 - # Sequence groups in the RUNNING state.
462 - # Contain decode requests.
463 - self.running: Deque[SequenceGroup] = deque()
464 - # Sequence groups in the SWAPPED state.
465 - # Contain decode requests that are swapped out.
466 - self.swapped: Deque[SequenceGroup] = deque()
467 - # Sequence groups finished requests ids since last step iteration.
468 - # It lets the model know that any state associated with these requests
469 - # can and must be released after the current step.
470 - # This is used to evict the finished requests from the Mamba cache.
471 - self._finished_requests_ids: List[str] = list()
472 - # Time at previous scheduling step
473 - self.prev_time = 0.0
474 - # Did we schedule a prompt at previous step?
475 - self.prev_prompt = False
476 - # Latency of the last prompt step
477 - self.last_prompt_latency = 0.0
478 - # preemption mode, RECOMPUTE or SWAP
479 - self.user_specified_preemption_mode = scheduler_config.preemption_mode
480 -
481 - # The following field is test-only. It is used to inject artificial
482 - # preemption.
483 - self.enable_artificial_preemption = ENABLE_ARTIFICIAL_PREEMPT
484 - self.artificial_preempt_cnt = (ARTIFICIAL_PREEMPTION_MAX_CNT
485 - if self.enable_artificial_preemption
486 - else 0)
487 - self.num_cumulative_preemption: int = 0
488 -
489 - # Used to cache python objects
490 - self._seq_group_metadata_cache: List[PyObjectCache] = []
491 - self._scheduler_running_outputs_cache: List[PyObjectCache] = []
492 - self._scheduled_seq_group_cache: List[PyObjectCache] = []
493 -
494 - # For async output processing, we need to swap cache buffers between
495 - # iterations. I.e. since the output processing is lagged one step,
496 - # we cannot reuse the cached objects immediately when the schedule()
497 - # is called again, but only when schedule() is called the second time.
498 - self.output_proc_callback = output_proc_callback
499 - self.use_async_output_proc = self.output_proc_callback is not None
500 - self.num_cache_iters = 2 if self.use_async_output_proc else 1
501 -
502 - self.cache_id = 0
503 - for i in range(self.num_cache_iters):
504 - self._seq_group_metadata_cache.append(
505 - PyObjectCache(seq_group_metadata_builder))
506 - self._scheduler_running_outputs_cache.append(
507 - PyObjectCache(scheduler_running_outputs_builder))
508 - self._scheduled_seq_group_cache.append(
509 - PyObjectCache(scheduled_seq_group_builder))
510 -
511 - # For async postprocessor, the extra decode run cannot be done
512 - # when the request reaches max_model_len. In this case, the request
513 - # will be stopped during schedule() call and added to this stop list
514 - # for processing and deallocation by the free_finished_seq_groups()
515 - self._async_stopped: List[SequenceGroup] = []
516 -
517 - # List with the chunk sizes to hand out to each sequence depending
518 - # on how many partial prefills are running. This is slightly faster than
519 - # running an integer division every time a prefill is scheduled.
520 - # This splits the budget evenly among all prefills.
521 - self.partial_prefill_budget_lookup_list = [0] * (
522 - self.scheduler_config.max_num_partial_prefills + 1)
523 - self.partial_prefill_budget_lookup_list[0] = (
524 - scheduler_config.max_num_batched_tokens)
525 - for i in range(1, self.scheduler_config.max_num_partial_prefills + 1):
526 - self.partial_prefill_budget_lookup_list[i] = (
527 - scheduler_config.max_num_batched_tokens // i)
528 -
529 - @property
530 - def next_cache_id(self):
531 - return (self.cache_id + 1) % self.num_cache_iters
532 -
533 - @property
534 - def lora_enabled(self) -> bool:
535 - return bool(self.lora_config)
536 -
537 - @property
538 - def num_decoding_tokens_per_seq(self) -> int:
539 - """The number of new tokens."""
540 - return 1
541 -
542 - def add_seq_group(self, seq_group: SequenceGroup) -> None:
543 - # Add sequence groups to the waiting queue.
544 - self.waiting.append(seq_group)
545 -
546 - def _add_seq_group_to_running(self, seq_group: SequenceGroup) -> None:
547 - # Add sequence groups to the running queue.
548 - # Only for testing purposes.
549 - self.running.append(seq_group)
550 -
551 - def _add_seq_group_to_swapped(self, seq_group: SequenceGroup) -> None:
552 - # Add sequence groups to the swapped queue.
553 - # Only for testing purposes.
554 - self.swapped.append(seq_group)
555 -
556 - def abort_seq_group(
557 - self,
558 - request_id: Union[str, Iterable[str]],
559 - seq_id_to_seq_group: Optional[Dict[str, SequenceGroupBase]] = None,
560 - ) -> None:
561 - """Aborts a sequence group with the given ID.
562 -
563 - Check if the sequence group with the given ID
564 - is present in any of the state queue.
565 - If present, remove the sequence group from the state queue.
566 - Also, if any of the sequences in the sequence group is not finished,
567 - free the sequence with status `FINISHED_ABORTED`.
568 - Otherwise, do nothing.
569 -
570 - Args:
571 - request_id: The ID(s) of the sequence group to abort.
572 - seq_id_to_seq_group: helper for groups with n>1
573 - """
574 - if isinstance(request_id, str):
575 - request_id = (request_id, )
576 - request_ids = set(request_id)
577 - seq_id_to_seq_group = seq_id_to_seq_group or {}
578 - for state_queue in [self.waiting, self.running, self.swapped]:
579 - aborted_groups: List[SequenceGroup] = []
580 - for seq_group in state_queue:
581 - # When n>1, seq_group.request_id looks like
582 - # foo_parallel_sample_0, while request_ids is just foo, and we
583 - # should resolve it as real_request_id to match.
584 - if seq_group.request_id in seq_id_to_seq_group:
585 - real_request_id = seq_id_to_seq_group[
586 - seq_group.request_id].group_id
587 - else:
588 - real_request_id = seq_group.request_id
589 - if real_request_id in request_ids:
590 - # Appending aborted group into pending list.
591 - aborted_groups.append(seq_group)
592 - # We can't remove real_request_id in request_ids here,
593 - # because there may be other seq groups sharing the same
594 - # real_request_id
595 - for aborted_group in aborted_groups:
596 - # Remove the sequence group from the state queue.
597 - state_queue.remove(aborted_group)
598 - # Remove the aborted request from the Mamba cache.
599 - self._finished_requests_ids.append(aborted_group.request_id)
600 - for seq in aborted_group.get_seqs():
601 - if seq.is_finished():
602 - continue
603 - seq.status = SequenceStatus.FINISHED_ABORTED
604 - self.free_seq(seq)
605 - if aborted_group.request_id in seq_id_to_seq_group:
606 - del seq_id_to_seq_group[aborted_group.request_id]
607 -
608 - self._free_seq_group_cross_attn_blocks(aborted_group)
609 -
610 - def _free_seq_group_cross_attn_blocks(
611 - self,
612 - seq_group: SequenceGroup,
613 - ) -> None:
614 - """
615 - Free a sequence group from a cross-attention block table.
616 - Has no effect on decoder-only models.
617 - """
618 - if seq_group.is_encoder_decoder():
619 - self.block_manager.free_cross(seq_group)
620 -
621 - def has_unfinished_seqs(self) -> bool:
622 - return (len(self.waiting) != 0 or len(self.running) != 0
623 - or len(self.swapped) != 0)
624 -
625 - def get_prefix_cache_hit_rate(self, device: Device) -> float:
626 - return self.block_manager.get_prefix_cache_hit_rate(device)
627 -
628 - def reset_prefix_cache(self, device: Optional[Device] = None) -> bool:
629 - return self.block_manager.reset_prefix_cache(device)
630 -
631 - def get_num_unfinished_seq_groups(self) -> int:
632 - return len(self.waiting) + len(self.running) + len(self.swapped)
633 -
634 - def get_and_reset_finished_requests_ids(self) -> List[str]:
635 - """Flushes the list of request ids of previously finished seq_groups."""
636 - finished_requests_ids = self._finished_requests_ids
637 - self._finished_requests_ids = list()
638 - return finished_requests_ids
639 -
640 - def _schedule_running(
641 - self,
642 - budget: SchedulingBudget,
643 - curr_loras: Optional[Set[int]],
644 - enable_chunking: bool = False,
645 - partial_prefill_metadata: Optional[PartialPrefillMetadata] = None,
646 - ) -> SchedulerRunningOutputs:
647 - """Schedule sequence groups that are running.
648 -
649 - Running queue should include decode and chunked prefill requests.
650 -
651 - Args:
652 - budget: The scheduling budget. The argument is in-place updated
653 - when any decodes are preempted.
654 - curr_loras: Currently batched lora request ids. The argument is
655 - in-place updated when any decodes are preempted.
656 - enable_chunking: If True, seq group can be chunked and only a
657 - chunked number of tokens are scheduled if
658 - `budget.num_batched_tokens` has not enough capacity to schedule
659 - all tokens.
660 - partial_prefill_metadata: information about the partial prefills
661 - that are currently running
662 -
663 - Returns:
664 - SchedulerRunningOutputs.
665 - """
666 - ret: SchedulerRunningOutputs = self._scheduler_running_outputs_cache[
667 - self.cache_id].get_object()
668 - ret.blocks_to_swap_out.clear()
669 - ret.blocks_to_copy.clear()
670 - ret.decode_seq_groups.clear()
671 - ret.prefill_seq_groups.clear()
672 - ret.preempted.clear()
673 - ret.swapped_out.clear()
674 -
675 - ret.num_lookahead_slots = self._get_num_lookahead_slots(
676 - is_prefill=False, enable_chunking=enable_chunking)
677 -
678 - ret.decode_seq_groups_list.clear()
679 - ret.prefill_seq_groups_list.clear()
680 -
681 - # Blocks that need to be swapped or copied before model execution.
682 - blocks_to_swap_out: List[Tuple[int, int]] = ret.blocks_to_swap_out
683 - blocks_to_copy: List[Tuple[int, int]] = ret.blocks_to_copy
684 -
685 - decode_seq_groups: List[ScheduledSequenceGroup] = ret.decode_seq_groups
686 - prefill_seq_groups: List[
687 - ScheduledSequenceGroup] = ret.prefill_seq_groups
688 - preempted: List[SequenceGroup] = ret.preempted
689 - swapped_out: List[SequenceGroup] = ret.swapped_out
690 -
691 - running_queue = self.running
692 - assert len(self._async_stopped) == 0
693 - while running_queue:
694 - seq_group = running_queue[0]
695 - # We discard the cached tokens info here because we don't need it
696 - # for running sequence:
697 - # 1. If a sequence is running with chunked prefill, the cached
698 - # tokens info was already used for the first prefill.
699 - # 2. If a sequence is running with non-chunked prefill, then
700 - # there it's a decoding sequence, and the cached tokens info is
701 - # irrelevant.
702 - num_uncached_new_tokens, _ = \
703 - self._get_num_new_uncached_and_cached_tokens(
704 - seq_group,
705 - SequenceStatus.RUNNING,
706 - enable_chunking,
707 - budget,
708 - partial_prefill_metadata,
709 - )
710 -
711 - num_running_tokens = num_uncached_new_tokens
712 - if num_running_tokens == 0:
713 - # No budget => Stop
714 - break
715 -
716 - running_queue.popleft()
717 -
718 - # With async postprocessor, an extra decode run is done
719 - # to process the final tokens. The check below avoids this extra
720 - # decode run when the model max len is reached, in order to avoid
721 - # a memory overflow.
722 - if (self.use_async_output_proc and seq_group.seqs[0].get_len()
723 - > self.scheduler_config.max_model_len):
724 - self._async_stopped.append(seq_group)
725 - continue
726 -
727 - # NOTE(woosuk): Preemption happens only when there is no available
728 - # slot to keep all the sequence groups in the RUNNING state.
729 - while not self._can_append_slots(seq_group, enable_chunking):
730 - budget.subtract_num_batched_tokens(seq_group.request_id,
731 - num_running_tokens)
732 - num_running_seqs = seq_group.get_max_num_running_seqs()
733 - budget.subtract_num_seqs(seq_group.request_id,
734 - num_running_seqs)
735 -
736 - if (curr_loras is not None and seq_group.lora_int_id > 0
737 - and seq_group.lora_int_id in curr_loras):
738 - curr_loras.remove(seq_group.lora_int_id)
739 -
740 - # Determine victim sequence
741 - cont_loop = True
742 - if running_queue:
743 - # Preempt the lowest-priority sequence group.
744 - victim_seq_group = running_queue.pop()
745 - else:
746 - # No other sequence group can be preempted.
747 - # Preempt the current sequence group.
748 - # Note: This is also where we stop this loop
749 - # (since there is nothing else to preempt)
750 - victim_seq_group = seq_group
751 - cont_loop = False
752 -
753 - # With async postprocessor, before preempting a sequence
754 - # we need to ensure it has no pending async postprocessor
755 - do_preempt = True
756 - if self.use_async_output_proc:
757 - assert self.output_proc_callback is not None
758 - self.output_proc_callback(
759 - request_id=victim_seq_group.request_id)
760 -
761 - # It may be that the async pending "victim_seq_group"
762 - # becomes finished, in which case we simply free it.
763 - if victim_seq_group.is_finished():
764 - self._free_finished_seq_group(victim_seq_group)
765 - do_preempt = False
766 -
767 - # Do preemption
768 - if do_preempt:
769 - preempted_mode = self._preempt(victim_seq_group,
770 - blocks_to_swap_out)
771 - if preempted_mode == PreemptionMode.RECOMPUTE:
772 - preempted.append(victim_seq_group)
773 - else:
774 - swapped_out.append(victim_seq_group)
775 -
776 - if not cont_loop:
777 - break
778 - else:
779 - self._append_slots(seq_group, blocks_to_copy, enable_chunking)
780 - is_prefill = seq_group.is_prefill()
781 -
782 - scheduled_seq_group: ScheduledSequenceGroup = (
783 - self._scheduled_seq_group_cache[
784 - self.cache_id].get_object())
785 - scheduled_seq_group.seq_group = seq_group
786 - if is_prefill:
787 - scheduled_seq_group.token_chunk_size = num_running_tokens
788 - prefill_seq_groups.append(scheduled_seq_group)
789 - ret.prefill_seq_groups_list.append(seq_group)
790 - else:
791 - scheduled_seq_group.token_chunk_size = 1
792 - decode_seq_groups.append(scheduled_seq_group)
793 - ret.decode_seq_groups_list.append(seq_group)
794 -
795 - budget.add_num_batched_tokens(seq_group.request_id,
796 - num_running_tokens)
797 - # OPTIMIZATION: Note that get_max_num_running_seqs is
798 - # expensive. For the default scheduling chase where
799 - # enable_chunking is False, num_seqs are updated before running
800 - # this method, so we don't have to update it again here.
801 - if enable_chunking:
802 - num_running_seqs = seq_group.get_max_num_running_seqs()
803 - budget.add_num_seqs(seq_group.request_id, num_running_seqs)
804 - if curr_loras is not None and seq_group.lora_int_id > 0:
805 - curr_loras.add(seq_group.lora_int_id)
806 -
807 - self._scheduler_running_outputs_cache[self.next_cache_id].reset()
808 - self._scheduled_seq_group_cache[self.next_cache_id].reset()
809 -
810 - return ret
811 -
812 - def _schedule_swapped(
813 - self,
814 - budget: SchedulingBudget,
815 - curr_loras: Optional[Set[int]],
816 - enable_chunking: bool = False,
817 - ) -> SchedulerSwappedInOutputs:
818 - """Schedule sequence groups that are swapped out.
819 -
820 - It schedules swapped requests as long as it fits `budget` and
821 - curr_loras <= max_lora from the scheduling config. The input arguments
822 - `budget` and `curr_loras` are updated based on scheduled seq_groups.
823 -
824 - Args:
825 - budget: The scheduling budget. The argument is in-place updated
826 - when any requests are swapped in.
827 - curr_loras: Currently batched lora request ids. The argument is
828 - in-place updated when any requests are swapped in.
829 - enable_chunking: If True, seq group can be chunked and only a
830 - chunked number of tokens are scheduled if
831 - `budget.num_batched_tokens` has not enough capacity to schedule
832 - all tokens.
833 -
834 - Returns:
835 - SchedulerSwappedInOutputs.
836 - """
837 - # Blocks that need to be swapped or copied before model execution.
838 - blocks_to_swap_in: List[Tuple[int, int]] = []
839 - blocks_to_copy: List[Tuple[int, int]] = []
840 - decode_seq_groups: List[ScheduledSequenceGroup] = []
841 - prefill_seq_groups: List[ScheduledSequenceGroup] = []
842 - infeasible_seq_groups: List[SequenceGroup] = []
843 -
844 - swapped_queue = self.swapped
845 -
846 - leftover_swapped: Deque[SequenceGroup] = deque()
847 - while swapped_queue:
848 - seq_group = swapped_queue[0]
849 -
850 - # If the sequence group cannot be swapped in, stop.
851 - is_prefill = seq_group.is_prefill()
852 - alloc_status = self.block_manager.can_swap_in(
853 - seq_group,
854 - self._get_num_lookahead_slots(is_prefill, enable_chunking))
855 - if alloc_status == AllocStatus.LATER:
856 - break
857 - elif alloc_status == AllocStatus.NEVER:
858 - logger.warning(
859 - "Failing the request %s because there's not enough kv "
860 - "cache blocks to run the entire sequence.",
861 - seq_group.request_id,
862 - )
863 - for seq in seq_group.get_seqs():
864 - seq.status = SequenceStatus.FINISHED_IGNORED
865 - infeasible_seq_groups.append(seq_group)
866 - swapped_queue.popleft()
867 - continue
868 -
869 - lora_int_id = 0
870 - if self.lora_enabled:
871 - lora_int_id = seq_group.lora_int_id
872 - assert curr_loras is not None
873 - assert self.lora_config is not None
874 - if (lora_int_id > 0 and (lora_int_id not in curr_loras)
875 - and len(curr_loras) >= self.lora_config.max_loras):
876 - # We don't have a space for another LoRA, so
877 - # we ignore this request for now.
878 - leftover_swapped.appendleft(seq_group)
879 - swapped_queue.popleft()
880 - continue
881 -
882 - # The total number of sequences in the RUNNING state should not
883 - # exceed the maximum number of sequences.
884 - num_new_seqs = seq_group.get_max_num_running_seqs()
885 - num_new_tokens_uncached, num_new_tokens_cached = (
886 - self._get_num_new_uncached_and_cached_tokens(
887 - seq_group, SequenceStatus.SWAPPED, enable_chunking,
888 - budget))
889 -
890 - if num_new_tokens_uncached == 0 or not budget.can_schedule(
891 - num_new_tokens=num_new_tokens_uncached,
892 - num_new_seqs=num_new_seqs,
893 - ):
894 - self.remove_seq_from_computed_blocks_tracker(
895 - seq_group, SequenceStatus.SWAPPED)
896 - break
897 -
898 - if lora_int_id > 0 and curr_loras is not None:
899 - curr_loras.add(lora_int_id)
900 - swapped_queue.popleft()
901 - self._swap_in(seq_group, blocks_to_swap_in)
902 - self._append_slots(seq_group, blocks_to_copy, enable_chunking)
903 - if is_prefill:
904 - prefill_seq_groups.append(
905 - ScheduledSequenceGroup(
906 - seq_group,
907 - token_chunk_size=num_new_tokens_uncached +
908 - num_new_tokens_cached,
909 - ))
910 - else:
911 - decode_seq_groups.append(
912 - ScheduledSequenceGroup(seq_group, token_chunk_size=1))
913 - budget.add_num_batched_tokens(
914 - seq_group.request_id,
915 - num_batched_tokens=num_new_tokens_uncached,
916 - num_cached_tokens=num_new_tokens_cached,
917 - )
918 - budget.add_num_seqs(seq_group.request_id, num_new_seqs)
919 -
920 - swapped_queue.extendleft(leftover_swapped)
921 -
922 - return SchedulerSwappedInOutputs(
923 - decode_seq_groups=decode_seq_groups,
924 - prefill_seq_groups=prefill_seq_groups,
925 - blocks_to_swap_in=blocks_to_swap_in,
926 - blocks_to_copy=blocks_to_copy,
927 - num_lookahead_slots=self._get_num_lookahead_slots(
928 - is_prefill=False, enable_chunking=enable_chunking),
929 - infeasible_seq_groups=infeasible_seq_groups,
930 - )
931 -
932 - def _get_prompt_limit(self, seq_group: SequenceGroup) -> int:
933 - if self.scheduler_config.chunked_prefill_enabled:
934 - prompt_limit = self.scheduler_config.max_model_len
935 - else:
936 - prompt_limit = min(
937 - self.scheduler_config.max_model_len,
938 - self.scheduler_config.max_num_batched_tokens,
939 - )
940 -
941 - # Model is fine tuned with long context. Return the fine tuned max_len.
942 - if seq_group.lora_request and seq_group.lora_request.long_lora_max_len:
943 - assert prompt_limit <= seq_group.lora_request.long_lora_max_len
944 - return seq_group.lora_request.long_lora_max_len
945 - else:
946 - return prompt_limit
947 -
948 - def _get_priority(self,
949 - seq_group: SequenceGroup) -> Tuple[Optional[int], float]:
950 - """Get the priority of the sequence group.
951 - Highest preference to user-defined priority, followed by arrival time.
952 - Args:
953 - seq_group: The sequence group input.
954 - Returns:
955 - The priority of the sequence group.
956 - """
957 - return seq_group.priority, seq_group.arrival_time
958 -
959 - def _schedule_priority_preemption(
960 - self,
961 - budget: SchedulingBudget,
962 - ) -> int:
963 - """Sorts waiting and running queue. Also, force preempt requests
964 - from the running queue if their priority is lower.
965 - Priority-based preemption is used with the priority policy.
966 - Args:
967 - budget: The scheduling budget. The argument is in-place updated
968 - when any requests are scheduled.
969 - Returns:
970 - A count of priority-based preemptions.
971 - """
972 -
973 - waiting_queue = self.waiting
974 -
975 - running_queue = deque(sorted(self.running, key=self._get_priority))
976 -
977 - blocks_to_swap_out: List[Tuple[int, int]] = []
978 - force_preemption_count = 0
979 -
980 - if waiting_queue:
981 - seq_group = waiting_queue.popleft()
982 - num_new_seqs = seq_group.get_max_num_running_seqs()
983 - num_new_tokens_uncached, _ = \
984 - self._get_num_new_uncached_and_cached_tokens(
985 - seq_group, SequenceStatus.WAITING, False, budget)
986 -
987 - # Only preempt if priority inversion exists
988 - while running_queue and self._get_priority(
989 - running_queue[-1]) > self._get_priority(seq_group):
990 - # Only preempt if waiting sequence cannot be allocated
991 - can_allocate = self.block_manager.can_allocate(seq_group)
992 - if (num_new_tokens_uncached > 0
993 - and can_allocate == AllocStatus.OK
994 - and budget.can_schedule(
995 - num_new_tokens=num_new_tokens_uncached,
996 - num_new_seqs=num_new_seqs,
997 - )):
998 - break
999 -
1000 - # Adjust budget to remove the victim sequence group
1001 - vseq_group = running_queue.pop()
1002 - num_running_tokens_uncached, _ = (
1003 - self._get_num_new_uncached_and_cached_tokens(
1004 - vseq_group, SequenceStatus.RUNNING, False, budget))
1005 - budget.subtract_num_batched_tokens(
1006 - vseq_group.request_id, num_running_tokens_uncached)
1007 - num_running_seqs = vseq_group.get_max_num_running_seqs()
1008 - budget.subtract_num_seqs(vseq_group.request_id,
1009 - num_running_seqs)
1010 -
1011 - # Preempt out the victim sequence group
1012 - self._preempt(vseq_group, blocks_to_swap_out)
1013 - waiting_queue.appendleft(vseq_group)
1014 - force_preemption_count += 1
1015 - # Put the sequence back into the waiting queue
1016 - waiting_queue.appendleft(seq_group)
1017 -
1018 - self.remove_seq_from_computed_blocks_tracker(
1019 - seq_group, SequenceStatus.WAITING)
1020 -
1021 - waiting_queue = deque(sorted(waiting_queue, key=self._get_priority))
1022 -
1023 - self.waiting = waiting_queue
1024 - self.running = running_queue
1025 - return force_preemption_count
1026 -
1027 - def _schedule_prefills(
1028 - self,
1029 - budget: SchedulingBudget,
1030 - curr_loras: Optional[Set[int]],
1031 - enable_chunking: bool = False,
1032 - partial_prefill_metadata: Optional[PartialPrefillMetadata] = None,
1033 - ) -> SchedulerPrefillOutputs:
1034 - """Schedule sequence groups that are in prefill stage.
1035 -
1036 - Note that the current scheduler treats PREEMPTED_FOR_RECOMPUTE
1037 - as a new prefill (that starts from beginning -> most recently generated
1038 - tokens).
1039 -
1040 - It schedules waiting requests as long as it fits `budget` and
1041 - curr_loras <= max_lora from the scheduling config. The input arguments
1042 - `budget` and `curr_loras` are updated based on scheduled seq_groups.
1043 -
1044 - Args:
1045 - budget: The scheduling budget. The argument is in-place updated
1046 - when any requests are scheduled.
1047 - curr_loras: Currently batched lora request ids. The argument is
1048 - in-place updated when any requests are scheduled.
1049 - enable_chunking: If True, seq group can be chunked and only a
1050 - chunked number of tokens are scheduled if
1051 - `budget.num_batched_tokens` has not enough capacity to schedule
1052 - all tokens.
1053 - partial_prefill_metadata: information about the partial prefills
1054 - that are currently running
1055 -
1056 - Returns:
1057 - SchedulerPrefillOutputs.
1058 - """
1059 - if budget.remaining_token_budget() == 0:
1060 - # Do nothing: Can't add any more prefill anyway
1061 - return SchedulerPrefillOutputs(
1062 - seq_groups=[],
1063 - ignored_seq_groups=[],
1064 - num_lookahead_slots=self._get_num_lookahead_slots(
1065 - is_prefill=True, enable_chunking=enable_chunking),
1066 - )
1067 - ignored_seq_groups: List[SequenceGroup] = []
1068 - seq_groups: List[ScheduledSequenceGroup] = []
1069 - using_prompt_embeds: bool = False
1070 -
1071 - waiting_queue = self.waiting
1072 -
1073 - leftover_waiting_sequences: Deque[SequenceGroup] = deque()
1074 - while self._passed_delay(time.time()) and waiting_queue:
1075 - seq_group = waiting_queue[0]
1076 -
1077 - waiting_seqs = seq_group.get_seqs(status=SequenceStatus.WAITING)
1078 - assert len(waiting_seqs) == 1, (
1079 - "Waiting sequence group should have only one prompt "
1080 - "sequence.")
1081 - if (partial_prefill_metadata is not None
1082 - and not partial_prefill_metadata.can_schedule(seq_group)):
1083 - leftover_waiting_sequences.appendleft(seq_group)
1084 - waiting_queue.popleft()
1085 - continue
1086 - num_new_tokens_uncached, num_new_tokens_cached = (
1087 - self._get_num_new_uncached_and_cached_tokens(
1088 - seq_group,
1089 - SequenceStatus.WAITING,
1090 - enable_chunking,
1091 - budget,
1092 - partial_prefill_metadata=partial_prefill_metadata,
1093 - ))
1094 - num_new_tokens = num_new_tokens_uncached + num_new_tokens_cached
1095 -
1096 - if not enable_chunking:
1097 - num_prompt_tokens = waiting_seqs[0].get_len()
1098 - assert num_new_tokens == num_prompt_tokens
1099 -
1100 - prompt_limit = self._get_prompt_limit(seq_group)
1101 - if num_new_tokens > prompt_limit:
1102 - logger.warning(
1103 - "Input prompt (%d tokens) is too long"
1104 - " and exceeds limit of %d",
1105 - num_new_tokens,
1106 - prompt_limit,
1107 - )
1108 - for seq in waiting_seqs:
1109 - seq.status = SequenceStatus.FINISHED_IGNORED
1110 - self.remove_seq_from_computed_blocks_tracker(
1111 - seq_group, SequenceStatus.FINISHED_IGNORED)
1112 - ignored_seq_groups.append(seq_group)
1113 - waiting_queue.popleft()
1114 - continue
1115 -
1116 - num_lookahead_slots: int = 0
1117 -
1118 - # If the sequence group cannot be allocated, stop.
1119 - can_allocate = self.block_manager.can_allocate(
1120 - seq_group, num_lookahead_slots=num_lookahead_slots)
1121 - if can_allocate == AllocStatus.LATER:
1122 - self.remove_seq_from_computed_blocks_tracker(
1123 - seq_group, SequenceStatus.WAITING)
1124 - break
1125 - elif can_allocate == AllocStatus.NEVER:
1126 - logger.warning(
1127 - "Input prompt (%d tokens) + lookahead slots (%d) is "
1128 - "too long and exceeds the capacity of block_manager",
1129 - num_new_tokens,
1130 - num_lookahead_slots,
1131 - )
1132 - for seq in waiting_seqs:
1133 - seq.status = SequenceStatus.FINISHED_IGNORED
1134 - self.remove_seq_from_computed_blocks_tracker(
1135 - seq_group, SequenceStatus.FINISHED_IGNORED)
1136 - ignored_seq_groups.append(seq_group)
1137 - waiting_queue.popleft()
1138 - continue
1139 -
1140 - # We cannot mix sequence groups that use prompt embeds and
1141 - # those that do not.
1142 - if len(seq_groups) == 0:
1143 - using_prompt_embeds = seq_group.uses_prompt_embeds()
1144 - if using_prompt_embeds != seq_group.uses_prompt_embeds():
1145 - self.remove_seq_from_computed_blocks_tracker(
1146 - seq_group, SequenceStatus.WAITING)
1147 - leftover_waiting_sequences.appendleft(seq_group)
1148 - waiting_queue.popleft()
1149 - continue
1150 -
1151 - lora_int_id = 0
1152 - if self.lora_enabled:
1153 - lora_int_id = seq_group.lora_int_id
1154 - assert curr_loras is not None
1155 - assert self.lora_config is not None
1156 - if (self.lora_enabled and lora_int_id > 0
1157 - and lora_int_id not in curr_loras
1158 - and len(curr_loras) >= self.lora_config.max_loras):
1159 - # We don't have a space for another LoRA, so
1160 - # we ignore this request for now.
1161 - self.remove_seq_from_computed_blocks_tracker(
1162 - seq_group, SequenceStatus.WAITING)
1163 - leftover_waiting_sequences.appendleft(seq_group)
1164 - waiting_queue.popleft()
1165 - continue
1166 -
1167 - if (budget.num_batched_tokens
1168 - >= self.scheduler_config.max_num_batched_tokens):
1169 - # We've reached the budget limit - since there might be
1170 - # continuous prefills in the running queue, we should break
1171 - # to avoid scheduling any new prefills.
1172 - self.remove_seq_from_computed_blocks_tracker(
1173 - seq_group, SequenceStatus.WAITING)
1174 - break
1175 -
1176 - num_new_seqs = seq_group.get_max_num_running_seqs()
1177 - if num_new_tokens_uncached == 0 or not budget.can_schedule(
1178 - num_new_tokens=num_new_tokens_uncached,
1179 - num_new_seqs=num_new_seqs,
1180 - ):
1181 - self.remove_seq_from_computed_blocks_tracker(
1182 - seq_group, SequenceStatus.WAITING)
1183 - break
1184 -
1185 - # Can schedule this request.
1186 - if curr_loras is not None and lora_int_id > 0:
1187 - curr_loras.add(lora_int_id)
1188 - waiting_queue.popleft()
1189 - self._allocate_and_set_running(seq_group)
1190 -
1191 - if partial_prefill_metadata is not None:
1192 - partial_prefill_metadata.maybe_increment_partial_prefills(
1193 - seq_group)
1194 -
1195 - seq_groups.append(
1196 - ScheduledSequenceGroup(seq_group=seq_group,
1197 - token_chunk_size=num_new_tokens))
1198 - budget.add_num_batched_tokens(
1199 - seq_group.request_id,
1200 - num_batched_tokens=num_new_tokens_uncached,
1201 - num_cached_tokens=num_new_tokens_cached,
1202 - )
1203 - budget.add_num_seqs(seq_group.request_id, num_new_seqs)
1204 -
1205 - # Queue requests that couldn't be scheduled.
1206 - waiting_queue.extendleft(leftover_waiting_sequences)
1207 - if len(seq_groups) > 0:
1208 - self.prev_prompt = True
1209 -
1210 - return SchedulerPrefillOutputs(
1211 - seq_groups=seq_groups,
1212 - ignored_seq_groups=ignored_seq_groups,
1213 - num_lookahead_slots=self._get_num_lookahead_slots(
1214 - is_prefill=True, enable_chunking=enable_chunking),
1215 - )
1216 -
1217 - def _schedule_default(self) -> SchedulerOutputs:
1218 - """Schedule queued requests.
1219 -
1220 - The current policy is designed to optimize the throughput. First,
1221 - it batches as many prefill requests as possible. And it schedules
1222 - decodes. If there's a pressure on GPU memory, decode requests can
1223 - be swapped or preempted.
1224 - """
1225 - # Include running requests to the budget.
1226 - budget = SchedulingBudget(
1227 - token_budget=self.scheduler_config.max_num_batched_tokens,
1228 - max_num_seqs=self.scheduler_config.max_num_seqs,
1229 - )
1230 - # Make sure we include num running seqs before scheduling prefill,
1231 - # so that we don't schedule beyond max_num_seqs for prefill.
1232 - for seq_group in self.running:
1233 - budget.add_num_seqs(seq_group.request_id,
1234 - seq_group.get_max_num_running_seqs())
1235 - curr_loras = (set(
1236 - seq_group.lora_int_id for seq_group in self.running
1237 - if seq_group.lora_int_id > 0) if self.lora_enabled else None)
1238 -
1239 - prefills = SchedulerPrefillOutputs.create_empty()
1240 - running_scheduled = SchedulerRunningOutputs.create_empty()
1241 - swapped_in = SchedulerSwappedInOutputs.create_empty()
1242 -
1243 - # If any requests are swapped, prioritized swapped requests.
1244 - if not self.swapped:
1245 - prefills = self._schedule_prefills(budget,
1246 - curr_loras,
1247 - enable_chunking=False)
1248 -
1249 - if len(prefills.seq_groups
1250 - ) == 0 and self.scheduler_config.policy == "priority":
1251 - self._schedule_priority_preemption(budget)
1252 -
1253 - # Don't schedule decodes if prefills are scheduled.
1254 - # NOTE: If `_schedule_prefills` doesn't enable chunking, self.running
1255 - # only contains decode requests, not chunked prefills.
1256 - if len(prefills.seq_groups) == 0:
1257 - running_scheduled = self._schedule_running(budget,
1258 - curr_loras,
1259 - enable_chunking=False)
1260 -
1261 - # If any sequence group is preempted, do not swap in any sequence
1262 - # group. because it means there's no slot for new running requests.
1263 - if (len(running_scheduled.preempted) +
1264 - len(running_scheduled.swapped_out) == 0):
1265 - swapped_in = \
1266 - self._schedule_swapped(budget, curr_loras)
1267 -
1268 - assert (budget.num_batched_tokens
1269 - <= self.scheduler_config.max_num_batched_tokens)
1270 - assert budget.num_curr_seqs <= self.scheduler_config.max_num_seqs
1271 -
1272 - # Update waiting requests.
1273 - self.waiting.extendleft(running_scheduled.preempted)
1274 - # Update new running requests.
1275 - if len(prefills.seq_groups) > 0:
1276 - self.running.extend([s.seq_group for s in prefills.seq_groups])
1277 -
1278 - self.running.extend(running_scheduled.decode_seq_groups_list)
1279 -
1280 - if len(swapped_in.decode_seq_groups) > 0:
1281 - self.running.extend(
1282 - [s.seq_group for s in swapped_in.decode_seq_groups])
1283 -
1284 - # Update swapped requests.
1285 - self.swapped.extend(running_scheduled.swapped_out)
1286 - preempted = len(running_scheduled.preempted) + len(
1287 - running_scheduled.swapped_out)
1288 -
1289 - # There should be no prefill from running queue because this policy
1290 - # doesn't allow chunked prefills.
1291 - assert len(running_scheduled.prefill_seq_groups) == 0
1292 - assert len(swapped_in.prefill_seq_groups) == 0
1293 -
1294 - # Merge lists
1295 - num_prefill_groups = len(prefills.seq_groups)
1296 - ignored_seq_groups_for_embeds = list[SequenceGroup]()
1297 - if num_prefill_groups > 0:
1298 - scheduled_seq_groups = prefills.seq_groups
1299 - scheduled_seq_groups.extend(running_scheduled.decode_seq_groups)
1300 - ignored_seq_groups_for_embeds.clear()
1301 - else:
1302 - scheduled_seq_groups = running_scheduled.decode_seq_groups
1303 - if len(scheduled_seq_groups) > 0:
1304 - using_prompt_embeds = scheduled_seq_groups[
1305 - 0].seq_group.uses_prompt_embeds()
1306 - ignored_seq_groups_for_embeds.clear()
1307 - indices_ignored = list[int]()
1308 - for i, schedule_seq_group in enumerate(scheduled_seq_groups):
1309 - if using_prompt_embeds !=\
1310 - schedule_seq_group.seq_group.uses_prompt_embeds():
1311 - ignored_seq_groups_for_embeds.append(
1312 - schedule_seq_group.seq_group)
1313 - indices_ignored.append(i)
1314 - if len(ignored_seq_groups_for_embeds) > 0:
1315 - scheduled_seq_groups = [
1316 - group for i, group in enumerate(scheduled_seq_groups)
1317 - if i not in indices_ignored
1318 - ]
1319 - else:
1320 - ignored_seq_groups_for_embeds.clear()
1321 -
1322 - scheduled_seq_groups.extend(swapped_in.decode_seq_groups)
1323 -
1324 - blocks_to_copy = running_scheduled.blocks_to_copy
1325 - blocks_to_copy.extend(swapped_in.blocks_to_copy)
1326 -
1327 - ignored_seq_groups = prefills.ignored_seq_groups
1328 - ignored_seq_groups.extend(ignored_seq_groups_for_embeds)
1329 - ignored_seq_groups.extend(swapped_in.infeasible_seq_groups)
1330 -
1331 - return SchedulerOutputs(
1332 - scheduled_seq_groups=scheduled_seq_groups,
1333 - num_prefill_groups=num_prefill_groups,
1334 - num_batched_tokens=budget.num_batched_tokens +
1335 - budget.num_cached_tokens,
1336 - blocks_to_swap_in=swapped_in.blocks_to_swap_in,
1337 - blocks_to_swap_out=running_scheduled.blocks_to_swap_out,
1338 - blocks_to_copy=blocks_to_copy,
1339 - ignored_seq_groups=ignored_seq_groups,
1340 - num_lookahead_slots=running_scheduled.num_lookahead_slots,
1341 - running_queue_size=len(self.running),
1342 - preempted=preempted,
1343 - )
1344 -
1345 - def _schedule_chunked_prefill(self) -> SchedulerOutputs:
1346 - """Schedule queued requests.
1347 -
1348 - Chunked prefill allows to chunk prefill requests, batch them together
1349 - with decode requests. This policy 1. schedule as many decoding requests
1350 - as possible. 2. schedule chunked prefill requests that are not
1351 - finished. 3. schedule swapped request. 4. schedule new prefill
1352 - requests.
1353 -
1354 - The policy can sustain the high GPU utilization because it can put
1355 - prefill and decodes requests to the same batch, while it improves
1356 - inter token latency because decodes requests don't need to be blocked
1357 - by prefill requests.
1358 - """
1359 - budget = SchedulingBudget(
1360 - token_budget=self.scheduler_config.max_num_batched_tokens,
1361 - max_num_seqs=self.scheduler_config.max_num_seqs,
1362 - )
1363 - curr_loras: Set[int] = set()
1364 -
1365 - prefills = SchedulerPrefillOutputs.create_empty()
1366 - swapped_in = SchedulerSwappedInOutputs.create_empty()
1367 -
1368 - # Create partial prefill metadata
1369 - partial_prefill_metadata = PartialPrefillMetadata.from_queues(
1370 - running=self.running,
1371 - waiting=self.waiting,
1372 - scheduler_config=self.scheduler_config,
1373 - )
1374 -
1375 - # Decoding should be always scheduled first by fcfs.
1376 - running_scheduled = self._schedule_running(
1377 - budget,
1378 - curr_loras,
1379 - enable_chunking=True,
1380 - partial_prefill_metadata=partial_prefill_metadata,
1381 - )
1382 -
1383 - # Schedule swapped out requests.
1384 - # If preemption happens, it means we don't have space for swap-in.
1385 - if len(running_scheduled.preempted) + len(
1386 - running_scheduled.swapped_out) == 0:
1387 - swapped_in = self._schedule_swapped(budget, curr_loras)
1388 -
1389 - prefills = self._schedule_prefills(
1390 - budget,
1391 - curr_loras,
1392 - enable_chunking=True,
1393 - partial_prefill_metadata=partial_prefill_metadata,
1394 - )
1395 -
1396 - assert (budget.num_batched_tokens
1397 - <= self.scheduler_config.max_num_batched_tokens)
1398 - assert budget.num_curr_seqs <= self.scheduler_config.max_num_seqs
1399 -
1400 - # Update waiting requests.
1401 - self.waiting.extendleft(running_scheduled.preempted)
1402 -
1403 - # Update new running requests.
1404 - # By default, vLLM scheduler prioritizes prefills.
1405 - # Once chunked prefill is enabled,
1406 - # the policy is changed to prioritize decode requests.
1407 - self.running.extend(
1408 - [s.seq_group for s in swapped_in.decode_seq_groups])
1409 - self.running.extend(
1410 - [s.seq_group for s in swapped_in.prefill_seq_groups])
1411 - self.running.extend(
1412 - [s.seq_group for s in running_scheduled.decode_seq_groups])
1413 - # Because multiple prefills may be running concurrently, we need to
1414 - # make sure that prefills which are scheduled to finish are listed
1415 - # before those that won't. This is so that on the next scheduling
1416 - # iteration when they have transitioned to the decode stage, they are
1417 - # properly prioritized over sequences that are still in the prefill
1418 - # stage.
1419 - self.running.extend(
1420 - self._order_finishing_prefills_first(
1421 - running_scheduled.prefill_seq_groups))
1422 - self.running.extend([s.seq_group for s in prefills.seq_groups])
1423 -
1424 - # Update swapped requests.
1425 - self.swapped.extend(running_scheduled.swapped_out)
1426 - # Put prefills first due to Attention backend ordering assumption.
1427 - scheduled_seq_groups = (prefills.seq_groups +
1428 - running_scheduled.prefill_seq_groups +
1429 - swapped_in.prefill_seq_groups +
1430 - running_scheduled.decode_seq_groups +
1431 - swapped_in.decode_seq_groups)
1432 - num_prefill_groups = (len(prefills.seq_groups) +
1433 - len(swapped_in.prefill_seq_groups) +
1434 - len(running_scheduled.prefill_seq_groups))
1435 - return SchedulerOutputs(
1436 - scheduled_seq_groups=scheduled_seq_groups,
1437 - num_prefill_groups=num_prefill_groups,
1438 - num_batched_tokens=budget.num_batched_tokens +
1439 - budget.num_cached_tokens,
1440 - blocks_to_swap_in=swapped_in.blocks_to_swap_in,
1441 - blocks_to_swap_out=running_scheduled.blocks_to_swap_out,
1442 - blocks_to_copy=running_scheduled.blocks_to_copy +
1443 - swapped_in.blocks_to_copy,
1444 - ignored_seq_groups=prefills.ignored_seq_groups +
1445 - swapped_in.infeasible_seq_groups,
1446 - num_lookahead_slots=0,
1447 - running_queue_size=len(self.running),
1448 - preempted=(len(running_scheduled.preempted) +
1449 - len(running_scheduled.swapped_out)),
1450 - )
1451 -
1452 - def _order_finishing_prefills_first(
1453 - self, scheduled_prefill_seqs: List[ScheduledSequenceGroup]
1454 - ) -> List[SequenceGroup]:
1455 - """Returns a list of prefilling SequenceGroups where sequences that are
1456 - scheduled to finish prefilling are listed first"""
1457 - finishing = [
1458 - s.seq_group for s in scheduled_prefill_seqs
1459 - if s.seq_group.get_num_uncomputed_tokens() == s.token_chunk_size
1460 - ]
1461 - not_finishing = [
1462 - s.seq_group for s in scheduled_prefill_seqs
1463 - if s.seq_group.get_num_uncomputed_tokens() != s.token_chunk_size
1464 - ]
1465 - return finishing + not_finishing
1466 -
1467 - def _schedule(self) -> SchedulerOutputs:
1468 - """Schedule queued requests."""
1469 - if self.scheduler_config.chunked_prefill_enabled:
1470 - return self._schedule_chunked_prefill()
1471 - else:
1472 - return self._schedule_default()
1473 -
1474 - def _can_append_slots(self, seq_group: SequenceGroup,
1475 - enable_chunking: bool) -> bool:
1476 - """Determine whether or not we have enough space in the KV cache to
1477 - continue generation of the sequence group.
1478 - """
1479 - # It is True only for testing case to trigger artificial preemption.
1480 - if (self.enable_artificial_preemption
1481 - and random.uniform(0, 1) < ARTIFICIAL_PREEMPTION_PROB
1482 - and self.artificial_preempt_cnt > 0):
1483 - self.artificial_preempt_cnt -= 1
1484 - return False
1485 -
1486 - is_prefill = seq_group.is_prefill()
1487 - num_lookahead_slots = self._get_num_lookahead_slots(
1488 - is_prefill, enable_chunking)
1489 -
1490 - return self.block_manager.can_append_slots(
1491 - seq_group=seq_group, num_lookahead_slots=num_lookahead_slots)
1492 -
1493 - def _allow_async_output_proc(self, seq_group: SequenceGroup) -> bool:
1494 - # async_output_proc is allowed only when we have a single sequence
1495 - # in the sequence group
1496 - no_single_seq = seq_group.sampling_params is None or (
1497 - seq_group.sampling_params.n == 1)
1498 - return no_single_seq
1499 -
1500 - def schedule(
1501 - self
1502 - ) -> Tuple[List[SequenceGroupMetadata], SchedulerOutputs, bool]:
1503 - # Schedule sequence groups.
1504 - # This function call changes the internal states of the scheduler
1505 - # such as self.running, self.swapped, and self.waiting.
1506 - scheduler_start_time = time.perf_counter()
1507 -
1508 - scheduler_outputs: SchedulerOutputs = self._schedule()
1509 - now = time.time()
1510 -
1511 - if not self.cache_config.enable_prefix_caching:
1512 - common_computed_block_nums = []
1513 -
1514 - allow_async_output_proc: bool = self.use_async_output_proc
1515 -
1516 - # Create input data structures.
1517 - seq_group_metadata_list: List[SequenceGroupMetadata] = []
1518 - for i, scheduled_seq_group in enumerate(
1519 - scheduler_outputs.scheduled_seq_groups):
1520 - seq_group = scheduled_seq_group.seq_group
1521 - token_chunk_size = scheduled_seq_group.token_chunk_size
1522 - seq_group.maybe_set_first_scheduled_time(now)
1523 -
1524 - seq_group_metadata = self._seq_group_metadata_cache[
1525 - self.cache_id].get_object()
1526 - seq_group_metadata.seq_data.clear()
1527 - seq_group_metadata.block_tables.clear()
1528 -
1529 - # seq_id -> SequenceData
1530 - seq_data: Dict[int, SequenceData] = {}
1531 - # seq_id -> physical block numbers
1532 - block_tables: Dict[int, List[int]] = {}
1533 -
1534 - if seq_group.is_encoder_decoder():
1535 - # Encoder associated with SequenceGroup
1536 - encoder_seq = seq_group.get_encoder_seq()
1537 - assert encoder_seq is not None
1538 - encoder_seq_data = encoder_seq.data
1539 - # Block table for cross-attention
1540 - # Also managed at SequenceGroup level
1541 - cross_block_table = self.block_manager.get_cross_block_table(
1542 - seq_group)
1543 - else:
1544 - encoder_seq_data = None
1545 - cross_block_table = None
1546 -
1547 - for seq in seq_group.get_seqs(status=SequenceStatus.RUNNING):
1548 - seq_id = seq.seq_id
1549 - seq_data[seq_id] = seq.data
1550 - block_tables[seq_id] = self.block_manager.get_block_table(seq)
1551 - self.block_manager.access_all_blocks_in_seq(seq, now)
1552 -
1553 - if self.cache_config.enable_prefix_caching:
1554 - common_computed_block_nums = (
1555 - self.block_manager.get_common_computed_block_ids(
1556 - seq_group.get_seqs(status=SequenceStatus.RUNNING)))
1557 -
1558 - do_sample = True
1559 - is_prompt = seq_group.is_prefill()
1560 - # We should send the metadata to workers when the first prefill
1561 - # is sent. Subsequent requests could be chunked prefill or decode.
1562 - is_first_prefill = False
1563 - if is_prompt:
1564 - seqs = seq_group.get_seqs()
1565 - # Prefill has only 1 sequence.
1566 - assert len(seqs) == 1
1567 - num_computed_tokens = seqs[0].data.get_num_computed_tokens()
1568 - is_first_prefill = num_computed_tokens == 0
1569 - # In the next iteration, all prompt tokens are not computed.
1570 - # It means the prefill is chunked, and we don't need sampling.
1571 - # NOTE: We use get_len instead of get_prompt_len because when
1572 - # a sequence is preempted, prefill includes previous generated
1573 - # output tokens.
1574 - if (token_chunk_size + num_computed_tokens
1575 - < seqs[0].data.get_len()):
1576 - do_sample = False
1577 -
1578 - # It assumes the scheduled_seq_groups is ordered by
1579 - # prefill < decoding.
1580 - if is_first_prefill or not self.scheduler_config.send_delta_data:
1581 - seq_group_metadata = SequenceGroupMetadata(
1582 - request_id=seq_group.request_id,
1583 - is_prompt=is_prompt,
1584 - seq_data=seq_data,
1585 - sampling_params=seq_group.sampling_params,
1586 - block_tables=block_tables,
1587 - do_sample=do_sample,
1588 - pooling_params=seq_group.pooling_params,
1589 - token_chunk_size=token_chunk_size,
1590 - lora_request=seq_group.lora_request,
1591 - computed_block_nums=common_computed_block_nums,
1592 - encoder_seq_data=encoder_seq_data,
1593 - cross_block_table=cross_block_table,
1594 - state=seq_group.state,
1595 - # `multi_modal_data` will only be present for the 1st comm
1596 - # between engine and worker.
1597 - # the subsequent comms can still use delta, but
1598 - # `multi_modal_data` will be None.
1599 - multi_modal_data=(seq_group.multi_modal_data
1600 - if scheduler_outputs.num_prefill_groups
1601 - > 0 else None),
1602 - multi_modal_placeholders=(
1603 - seq_group.multi_modal_placeholders
1604 - if scheduler_outputs.num_prefill_groups > 0 else None),
1605 - )
1606 - else:
1607 - # When SPMD mode is enabled, we only send delta data except for
1608 - # the first request to reduce serialization cost.
1609 - seq_data_delta = {}
1610 - for id, data in seq_data.items():
1611 - seq_data_delta[id] = data.get_delta_and_reset()
1612 - seq_group_metadata = SequenceGroupMetadataDelta(
1613 - seq_data_delta,
1614 - seq_group.request_id,
1615 - block_tables,
1616 - is_prompt,
1617 - do_sample=do_sample,
1618 - token_chunk_size=token_chunk_size,
1619 - computed_block_nums=common_computed_block_nums,
1620 - )
1621 - seq_group_metadata_list.append(seq_group_metadata)
1622 -
1623 - if allow_async_output_proc:
1624 - allow_async_output_proc = self._allow_async_output_proc(
1625 - seq_group)
1626 -
1627 - # Now that the batch has been created, we can assume all blocks in the
1628 - # batch will have been computed before the next scheduling invocation.
1629 - # This is because the engine assumes that a failure in model execution
1630 - # will crash the vLLM instance / will not retry.
1631 - for scheduled_seq_group in scheduler_outputs.scheduled_seq_groups:
1632 - self.block_manager.mark_blocks_as_computed(
1633 - scheduled_seq_group.seq_group,
1634 - scheduled_seq_group.token_chunk_size)
1635 -
1636 - self._seq_group_metadata_cache[self.next_cache_id].reset()
1637 -
1638 - scheduler_time = time.perf_counter() - scheduler_start_time
1639 - # Add this to scheduler time to all the sequences that are currently
1640 - # running. This will help estimate if the scheduler is a significant
1641 - # component in the e2e latency.
1642 - for seq_group in self.running:
1643 - if seq_group is not None and seq_group.metrics is not None:
1644 - if seq_group.metrics.scheduler_time is not None:
1645 - seq_group.metrics.scheduler_time += scheduler_time
1646 - else:
1647 - seq_group.metrics.scheduler_time = scheduler_time
1648 -
1649 - # Move to next cache (if exists)
1650 - self.cache_id = self.next_cache_id
1651 -
1652 - # Return results
1653 - return (seq_group_metadata_list, scheduler_outputs,
1654 - allow_async_output_proc)
1655 -
1656 - def fork_seq(self, parent_seq: Sequence, child_seq: Sequence) -> None:
1657 - self.block_manager.fork(parent_seq, child_seq)
1658 -
1659 - def free_seq(self, seq: Sequence) -> None:
1660 - """Free a sequence from a block table."""
1661 - self.block_manager.free(seq)
1662 -
1663 - def remove_seq_from_computed_blocks_tracker(
1664 - self, seq_group: SequenceGroup,
1665 - status: Optional[SequenceStatus]) -> None:
1666 - seqs = seq_group.get_seqs(status=status)
1667 - for seq in seqs:
1668 - self._remove_seq_from_computed_blocks_tracker(seq)
1669 -
1670 - def _remove_seq_from_computed_blocks_tracker(self, seq: Sequence) -> None:
1671 - """
1672 - Free a sequence computed blocks tracker _seq_id_to_blocks_hashes
1673 - and _seq_id_to_num_tokens_computed.
1674 - """
1675 - self.block_manager.remove_seq_from_computed_blocks_tracker(seq)
1676 -
1677 - def _free_finished_seqs(self, seq_group: SequenceGroup) -> None:
1678 - """Free finished seqs in a sequence group."""
1679 - for seq in seq_group.get_seqs():
1680 - if seq.is_finished():
1681 - self.free_seq(seq)
1682 -
1683 - def _free_finished_seq_group(self, seq_group: SequenceGroup) -> None:
1684 - if seq_group.is_finished():
1685 - # Free cross-attention block table, if it exists
1686 - self._free_seq_group_cross_attn_blocks(seq_group)
1687 -
1688 - # Add the finished requests to the finished requests list.
1689 - # This list will be used to update the Mamba cache in the
1690 - # next step.
1691 - self._finished_requests_ids.append(seq_group.request_id)
1692 -
1693 - # Free finished seqs
1694 - self._free_finished_seqs(seq_group)
1695 -
1696 - def free_finished_seq_groups(self) -> None:
1697 - remaining: Deque[SequenceGroup] = deque()
1698 - for seq_group in self.running:
1699 - self._free_finished_seq_group(seq_group)
1700 - if not seq_group.is_finished():
1701 - remaining.append(seq_group)
1702 -
1703 - self.running = remaining
1704 -
1705 - # Handle async stopped sequence groups
1706 - # (ones that reached max model len)
1707 - if self._async_stopped:
1708 - for seq_group in self._async_stopped:
1709 - self._free_seq_group_cross_attn_blocks(seq_group)
1710 - self._finished_requests_ids.append(seq_group.request_id)
1711 -
1712 - # Free finished seqs
1713 - self._free_finished_seqs(seq_group)
1714 -
1715 - self._async_stopped.clear()
1716 -
1717 - def _allocate_and_set_running(self, seq_group: SequenceGroup) -> None:
1718 - self.block_manager.allocate(seq_group)
1719 - for seq in seq_group.get_seqs(status=SequenceStatus.WAITING):
1720 - seq.status = SequenceStatus.RUNNING
1721 -
1722 - def _append_slots(
1723 - self,
1724 - seq_group: SequenceGroup,
1725 - blocks_to_copy: List[Tuple[int, int]],
1726 - enable_chunking: bool = False,
1727 - ) -> None:
1728 - """Appends new slots to the sequences in the given sequence group.
1729 -
1730 - Args:
1731 - seq_group (SequenceGroup): The sequence group containing the
1732 - sequences to append slots to.
1733 - blocks_to_copy (List[Tuple[int, int]]): A list of tuple of two
1734 - ints, the first int is the source block index, and the second
1735 - int is the destination block index. This list is updated with
1736 - the new source and destination block indices for the appended
1737 - slots.
1738 - enable_chunking (bool): True if chunked prefill is enabled.
1739 - """
1740 - is_prefill: bool = seq_group.is_prefill()
1741 - num_lookahead_slots: int = self._get_num_lookahead_slots(
1742 - is_prefill, enable_chunking)
1743 -
1744 - seq_status: Optional[SequenceStatus] = SequenceStatus.RUNNING
1745 - for seq in seq_group.get_seqs(status=seq_status):
1746 - cows = self.block_manager.append_slots(seq, num_lookahead_slots)
1747 - if len(cows) > 0:
1748 - blocks_to_copy.extend(cows)
1749 -
1750 - def _preempt(self, seq_group: SequenceGroup,
1751 - blocks_to_swap_out: List[Tuple[int, int]]) -> PreemptionMode:
1752 - # If preemption mode is not specified, we determine the mode as follows:
1753 - # We use recomputation by default since it incurs lower overhead than
1754 - # swapping. However, when the sequence group has multiple sequences
1755 - # (e.g., beam search), recomputation is not currently supported. In
1756 - # such a case, we use swapping instead.
1757 - # FIXME(woosuk): This makes our scheduling policy a bit bizarre.
1758 - # As swapped sequences are prioritized over waiting sequences,
1759 - # sequence groups with multiple sequences are implicitly prioritized
1760 - # over sequence groups with a single sequence.
1761 - # TODO(woosuk): Support recomputation for sequence groups with multiple
1762 - # sequences. This may require a more sophisticated CUDA kernel.
1763 - if self.user_specified_preemption_mode is None:
1764 - if seq_group.get_max_num_running_seqs() == 1:
1765 - preemption_mode = PreemptionMode.RECOMPUTE
1766 - else:
1767 - preemption_mode = PreemptionMode.SWAP
1768 -
1769 - elif self.user_specified_preemption_mode == "swap":
1770 - preemption_mode = PreemptionMode.SWAP
1771 - else:
1772 - preemption_mode = PreemptionMode.RECOMPUTE
1773 -
1774 - if self.num_cumulative_preemption % 50 == 0:
1775 - logger.warning(
1776 - "Sequence group %s is preempted by %s mode because there is "
1777 - "not enough KV cache space. This can affect the end-to-end "
1778 - "performance. Increase gpu_memory_utilization or "
1779 - "tensor_parallel_size to provide more KV cache memory. "
1780 - "total_num_cumulative_preemption=%d",
1781 - seq_group.request_id,
1782 - preemption_mode,
1783 - self.num_cumulative_preemption + 1,
1784 - )
1785 - self.num_cumulative_preemption += 1
1786 -
1787 - if preemption_mode == PreemptionMode.RECOMPUTE:
1788 - self._preempt_by_recompute(seq_group)
1789 - elif preemption_mode == PreemptionMode.SWAP:
1790 - self._preempt_by_swap(seq_group, blocks_to_swap_out)
1791 - else:
1792 - raise AssertionError("Invalid preemption mode.")
1793 - return preemption_mode
1794 -
1795 - def _preempt_by_recompute(
1796 - self,
1797 - seq_group: SequenceGroup,
1798 - ) -> None:
1799 - seqs = seq_group.get_seqs(status=SequenceStatus.RUNNING)
1800 - assert len(seqs) == 1
1801 - for seq in seqs:
1802 - seq.status = SequenceStatus.WAITING
1803 - self.free_seq(seq)
1804 - seq.reset_state_for_recompute()
1805 - self._free_seq_group_cross_attn_blocks(seq_group)
1806 -
1807 - def _preempt_by_swap(
1808 - self,
1809 - seq_group: SequenceGroup,
1810 - blocks_to_swap_out: List[Tuple[int, int]],
1811 - ) -> None:
1812 - self._swap_out(seq_group, blocks_to_swap_out)
1813 -
1814 - def _swap_in(
1815 - self,
1816 - seq_group: SequenceGroup,
1817 - blocks_to_swap_in: List[Tuple[int, int]],
1818 - ) -> None:
1819 - mapping = self.block_manager.swap_in(seq_group)
1820 - blocks_to_swap_in.extend(mapping)
1821 - for seq in seq_group.get_seqs(status=SequenceStatus.SWAPPED):
1822 - seq.status = SequenceStatus.RUNNING
1823 -
1824 - def _swap_out(
1825 - self,
1826 - seq_group: SequenceGroup,
1827 - blocks_to_swap_out: List[Tuple[int, int]],
1828 - ) -> None:
1829 - if not self.block_manager.can_swap_out(seq_group):
1830 - # FIXME(woosuk): Abort the sequence group instead of aborting the
1831 - # entire engine.
1832 - raise RuntimeError(
1833 - "Aborted due to the lack of CPU swap space. Please increase "
1834 - "the swap space to avoid this error.")
1835 - mapping = self.block_manager.swap_out(seq_group)
1836 - blocks_to_swap_out.extend(mapping)
1837 - for seq in seq_group.get_seqs(status=SequenceStatus.RUNNING):
1838 - seq.status = SequenceStatus.SWAPPED
1839 -
1840 - def _passed_delay(self, now: float) -> bool:
1841 - if self.prev_prompt:
1842 - self.last_prompt_latency = now - self.prev_time
1843 - self.prev_time, self.prev_prompt = now, False
1844 - # Delay scheduling prompts to let waiting queue fill up
1845 - if self.scheduler_config.delay_factor > 0 and self.waiting:
1846 - earliest_arrival_time = min(
1847 - [e.metrics.arrival_time for e in self.waiting])
1848 - passed_delay = ((now - earliest_arrival_time)
1849 - > (self.scheduler_config.delay_factor *
1850 - self.last_prompt_latency) or not self.running)
1851 - else:
1852 - passed_delay = True
1853 - return passed_delay
1854 -
1855 - def _get_num_lookahead_slots(self, is_prefill: bool,
1856 - enable_chunking: bool) -> int:
1857 - """The number of slots to allocate per sequence per step, beyond known
1858 - token ids. Speculative decoding uses these slots to store KV activations
1859 - of tokens which may or may not be accepted.
1860 - """
1861 - return 0
1862 -
1863 - def _get_num_new_uncached_and_cached_tokens(
1864 - self,
1865 - seq_group: SequenceGroup,
1866 - status: SequenceStatus,
1867 - enable_chunking: bool,
1868 - budget: SchedulingBudget,
1869 - partial_prefill_metadata: Optional[PartialPrefillMetadata] = None,
1870 - ) -> Tuple[int, int]:
1871 - """
1872 - Returns the number of new uncached and cached tokens to schedule for a
1873 - given sequence group that's in a given `status`.
1874 -
1875 - The API could chunk the number of tokens to compute based on `budget`
1876 - if `enable_chunking` is True. If a sequence group has multiple
1877 - sequences (e.g., running beam search), it means it is in decoding
1878 - phase, so chunking doesn't happen.
1879 -
1880 - Returns (0, 0) if the new token cannot be computed due to token budget.
1881 -
1882 - The cached tokens's blocks are already computed, and the attention
1883 - backend will reuse the cached blocks rather than recomputing them. So
1884 - the scheduler could schedule these cached tokens "for free".
1885 -
1886 - Args:
1887 - seq_group: The sequence group to get the number of new tokens to
1888 - schedule.
1889 - status: The status of the sequences to get the number of new tokens
1890 - to schedule.
1891 - enable_chunking: Whether to chunk the number of tokens to compute.
1892 - budget: The budget to chunk the number of tokens to compute.
1893 - partial_prefill_metadata: information about the partial prefills
1894 - that are currently running
1895 -
1896 -
1897 - Returns:
1898 - A tuple of two ints. The first int is the number of new uncached
1899 - tokens to schedule. The second int is the number of cached tokens.
1900 - If no more new tokens can be scheduled, returns (0, 0).
1901 - """
1902 - num_cached_new_tokens = 0
1903 - num_uncached_new_tokens = 0
1904 -
1905 - seqs = seq_group.get_seqs(status=status)
1906 - # Compute the number of new uncached and cached tokens for
1907 - # each sequence.
1908 - for seq in seqs:
1909 - if not seq.is_prefill():
1910 - # Decode sequences should always just have 1 uncached token
1911 - # TODO(rickyx): Actually is this still correct for multi-step?
1912 - num_uncached_new_tokens += 1
1913 - continue
1914 -
1915 - num_computed_tokens_seq = seq.get_num_computed_tokens()
1916 - all_num_new_tokens_seq = seq.get_len() - num_computed_tokens_seq
1917 - if not self.cache_config.enable_prefix_caching:
1918 - # If prefix caching is not enabled, all new tokens are uncached.
1919 - num_uncached_new_tokens += all_num_new_tokens_seq
1920 - continue
1921 -
1922 - # NOTE: the cache token might be currently in a block that's in an
1923 - # evictor meaning that it's not yet allocated. However, we don't
1924 - # exclude such tokens in the cache count because it will be
1925 - # guaranteed to be allocated later if the sequence can be allocated.
1926 - num_cached_tokens_seq = self.block_manager.get_num_cached_tokens(
1927 - seq)
1928 -
1929 - # Sanity check.
1930 - if num_cached_tokens_seq < num_computed_tokens_seq:
1931 - # This should only happen with chunked prefill, and
1932 - # the seq is still in prefill. The `num_cached_tokens_seq`
1933 - # is the value we calculated on scheduling the first prefill.
1934 - # For subsequent continuous prefill steps, we cached the
1935 - # number of cache tokens for the sequence so the cached token
1936 - # count could be less than the number of computed tokens.
1937 - # See comments on `ComputedBlocksTracker` for more details.
1938 - assert (
1939 - seq.is_prefill() and seq.status == SequenceStatus.RUNNING
1940 - and self.scheduler_config.chunked_prefill_enabled
1941 - ), ("Number of cached tokens should not be less than the "
1942 - "number of computed tokens for a sequence that's still "
1943 - f"in prefill. But there are {num_cached_tokens_seq} cached "
1944 - f"tokens and {num_computed_tokens_seq} computed tokens "
1945 - f"for sequence {seq.seq_id}.")
1946 -
1947 - num_cached_new_tokens_seq = max(
1948 - 0, num_cached_tokens_seq - num_computed_tokens_seq)
1949 - num_uncached_new_tokens_seq = (all_num_new_tokens_seq -
1950 - num_cached_new_tokens_seq)
1951 -
1952 - num_uncached_new_tokens += num_uncached_new_tokens_seq
1953 - num_cached_new_tokens += num_cached_new_tokens_seq
1954 -
1955 - if num_uncached_new_tokens == 0 and num_cached_new_tokens > 0:
1956 - # For a fully cached hit sequence, we actually need to recompute the
1957 - # last token. So we need at least 1 uncached token to schedule.
1958 - # See ModelRunner._compute_for_prefix_cache_hit for more details.
1959 - num_uncached_new_tokens = 1
1960 - num_cached_new_tokens -= 1
1961 -
1962 - if enable_chunking and len(seqs) == 1:
1963 - # Chunk if a running request cannot fit in the given budget.
1964 - # If number of seq > 1, it means it is doing beam search
1965 - # in a decode phase. Do not chunk.
1966 - num_uncached_new_tokens = self._chunk_new_tokens_to_schedule(
1967 - self.scheduler_config,
1968 - self.cache_config,
1969 - budget,
1970 - self._get_prompt_limit(seq_group),
1971 - num_uncached_new_tokens,
1972 - self.partial_prefill_budget_lookup_list,
1973 - partial_prefill_metadata,
1974 - )
1975 -
1976 - return num_uncached_new_tokens, num_cached_new_tokens
1977 -
1978 - @staticmethod
1979 - def _chunk_new_tokens_to_schedule(
1980 - scheduler_config: SchedulerConfig,
1981 - cache_config: CacheConfig,
1982 - budget: SchedulingBudget,
1983 - prompt_limit: int,
1984 - num_new_tokens: int,
1985 - partial_prefill_budget_lookup_list: List[int],
1986 - partial_prefill_metadata: Optional[PartialPrefillMetadata] = None,
1987 - ) -> int:
1988 - """
1989 - Chunks the number of new tokens to schedule based on the budget when
1990 - chunked prefill is enabled.
1991 -
1992 - Args:
1993 - scheduler_config: The scheduler config.
1994 - cache_config: The cache config.
1995 - budget: The budget to chunk the number of tokens to compute.
1996 - prompt_limit: The maximum number of tokens allowed in a prompt.
1997 - num_new_tokens: The number of new tokens to schedule.
1998 -
1999 - Returns:
2000 - The number of new tokens to schedule after chunking.
2001 - """
2002 - remaining_token_budget = budget.remaining_token_budget()
2003 -
2004 - # Get the number of tokens to allocate to this prefill slot
2005 - prefill_slot_budget = (
2006 - remaining_token_budget if partial_prefill_metadata is None else
2007 - partial_prefill_budget_lookup_list[
2008 - partial_prefill_metadata.schedulable_prefills])
2009 -
2010 - if cache_config.enable_prefix_caching:
2011 - # When prefix caching is enabled and we're partially prefilling
2012 - # a sequence, we always allocate a number of new tokens that is
2013 - # divisible by the block size to avoid partial block matching.
2014 - block_size = cache_config.block_size
2015 - # Don't exceed either the total budget or slot budget.
2016 - # Take min of those and get the next lowest multiple of the
2017 - # block size:
2018 - remaining_token_budget = (
2019 - min(remaining_token_budget, prefill_slot_budget) //
2020 - block_size) * block_size
2021 - # NB: In the case where num_new_tokens < budget, we are
2022 - # finishing prefill for this sequence, so we do not need to
2023 - # allocate a full block.
2024 -
2025 - num_new_tokens = min(num_new_tokens, remaining_token_budget,
2026 - prefill_slot_budget)
2027 -
2028 - return num_new_tokens
vllm/engine/protocol.py
+1 -7
@@ -7,13 +7,11 @@ from typing import Any, AsyncGenerator, Iterable, Mapping, Optional, Union
7
8 from vllm.beam_search import BeamSearchSequence, create_sort_beams_key_function
9 from vllm.config import ModelConfig, VllmConfig
10 -from vllm.core.scheduler import SchedulerOutputs
10 from vllm.inputs.data import PromptType, TokensPrompt
11 from vllm.inputs.parse import is_explicit_encoder_decoder_prompt
12 from vllm.inputs.preprocess import InputPreprocessor
13 from vllm.logger import init_logger
14 from vllm.lora.request import LoRARequest
16 -from vllm.model_executor.layers.sampler import SamplerOutput
15 from vllm.outputs import CompletionOutput, PoolingRequestOutput, RequestOutput
16 from vllm.plugins.io_processors.interface import IOProcessor
17 from vllm.pooling_params import PoolingParams
@@ -266,11 +264,7 @@ class EngineClient(ABC):
264 ...
265
266 @abstractmethod
269 - async def do_log_stats(
270 - self,
271 - scheduler_outputs: Optional[SchedulerOutputs] = None,
272 - model_output: Optional[list[SamplerOutput]] = None,
273 - ) -> None:
267 + async def do_log_stats(self) -> None:
268 ...
269
270 @abstractmethod
vllm/v1/engine/async_llm.py
+1 -5
@@ -601,11 +601,7 @@ class AsyncLLM(EngineClient):
601 async def is_tracing_enabled(self) -> bool:
602 return self.observability_config.otlp_traces_endpoint is not None
603
604 - async def do_log_stats(
605 - self,
606 - scheduler_outputs=None,
607 - model_output=None,
608 - ) -> None:
604 + async def do_log_stats(self) -> None:
605 if self.logger_manager:
606 self.logger_manager.log()
607
vllm/worker/cache_engine.py deleted
-145
@@ -1,145 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -"""CacheEngine class for managing the KV cache."""
4 -from typing import List
5 -
6 -import torch
7 -
8 -from vllm.attention import get_attn_backend
9 -from vllm.config import CacheConfig, DeviceConfig, ModelConfig, ParallelConfig
10 -from vllm.logger import init_logger
11 -from vllm.utils import (STR_DTYPE_TO_TORCH_DTYPE, LayerBlockType,
12 - get_dtype_size, is_pin_memory_available)
13 -
14 -logger = init_logger(__name__)
15 -
16 -
17 -class CacheEngine:
18 - """Manages the KV cache.
19 -
20 - This class is responsible for initializing and managing the GPU and CPU KV
21 - caches. It also provides methods for performing KV cache operations, such
22 - as swapping and copying.
23 - """
24 -
25 - def __init__(
26 - self,
27 - cache_config: CacheConfig,
28 - model_config: ModelConfig,
29 - parallel_config: ParallelConfig,
30 - device_config: DeviceConfig,
31 - ) -> None:
32 - self.cache_config = cache_config
33 - self.model_config = model_config
34 - self.parallel_config = parallel_config
35 - self.device_config = device_config
36 -
37 - self.head_size = model_config.get_head_size()
38 - # Models like Jamba, have mixed typed layers, E.g Mamba
39 - self.num_attention_layers = model_config.get_num_layers_by_block_type(
40 - parallel_config, LayerBlockType.attention)
41 - self.num_kv_heads = model_config.get_num_kv_heads(parallel_config)
42 -
43 - self.block_size = cache_config.block_size
44 - self.num_gpu_blocks = cache_config.num_gpu_blocks
45 - if self.num_gpu_blocks:
46 - self.num_gpu_blocks //= parallel_config.pipeline_parallel_size
47 - self.num_cpu_blocks = cache_config.num_cpu_blocks
48 - if self.num_cpu_blocks:
49 - self.num_cpu_blocks //= parallel_config.pipeline_parallel_size
50 -
51 - if cache_config.cache_dtype == "auto":
52 - self.dtype = model_config.dtype
53 - else:
54 - self.dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
55 -
56 - # Get attention backend.
57 - self.attn_backend = get_attn_backend(self.head_size,
58 - model_config.dtype,
59 - cache_config.cache_dtype,
60 - self.block_size,
61 - model_config.is_attention_free,
62 - use_mla=model_config.use_mla)
63 -
64 - # Initialize the cache.
65 - self.gpu_cache = self._allocate_kv_cache(
66 - self.num_gpu_blocks, self.device_config.device_type)
67 - self.cpu_cache = self._allocate_kv_cache(self.num_cpu_blocks, "cpu")
68 -
69 - def _allocate_kv_cache(
70 - self,
71 - num_blocks: int,
72 - device: str,
73 - ) -> List[torch.Tensor]:
74 - """Allocates KV cache on the specified device."""
75 - kv_cache_generic_shape = self.attn_backend.get_kv_cache_shape(
76 - num_blocks, self.block_size, self.num_kv_heads, self.head_size)
77 - pin_memory = is_pin_memory_available() if device == "cpu" else False
78 - kv_cache: List[torch.Tensor] = []
79 - try:
80 - kv_cache_stride_order = self.attn_backend.get_kv_cache_stride_order(
81 - )
82 - except (AttributeError, NotImplementedError):
83 - kv_cache_stride_order = tuple(range(len(kv_cache_generic_shape)))
84 -
85 - # The allocation respects the backend-defined stride order to ensure
86 - # the semantic remains consistent for each backend. We first obtain the
87 - # generic kv cache shape and then permute it according to the stride
88 - # order which could result in a non-contiguous tensor.
89 - kv_cache_allocation_shape = tuple(kv_cache_generic_shape[i]
90 - for i in kv_cache_stride_order)
91 -
92 - for _ in range(self.num_attention_layers):
93 - # null block in CpuGpuBlockAllocator requires at least that
94 - # block to be zeroed-out.
95 - # We zero-out everything for simplicity.
96 - layer_kv_cache = torch.zeros(
97 - kv_cache_allocation_shape,
98 - dtype=self.dtype,
99 - pin_memory=pin_memory,
100 - device=device).permute(*kv_cache_stride_order)
101 -
102 - # view back to (TOTAL_PAGES, PAGE_SIZE, entry_shape...) for cases
103 - # when entry_shape is higher than 1D
104 - kv_cache.append(layer_kv_cache)
105 - return kv_cache
106 -
107 - def swap_in(self, src_to_dst: torch.Tensor) -> None:
108 - for i in range(self.num_attention_layers):
109 - self.attn_backend.swap_blocks(self.cpu_cache[i], self.gpu_cache[i],
110 - src_to_dst)
111 -
112 - def swap_out(self, src_to_dst: torch.Tensor) -> None:
113 - for i in range(self.num_attention_layers):
114 - self.attn_backend.swap_blocks(self.gpu_cache[i], self.cpu_cache[i],
115 - src_to_dst)
116 -
117 - def copy(self, src_to_dsts: torch.Tensor) -> None:
118 - self.attn_backend.copy_blocks(self.gpu_cache, src_to_dsts)
119 -
120 - @staticmethod
121 - def get_cache_block_size(
122 - cache_config: CacheConfig,
123 - model_config: ModelConfig,
124 - parallel_config: ParallelConfig,
125 - ) -> int:
126 - head_size = model_config.get_head_size()
127 - num_heads = model_config.get_num_kv_heads(parallel_config)
128 - num_attention_layers = model_config.get_num_layers_by_block_type(
129 - parallel_config, LayerBlockType.attention)
130 -
131 - if cache_config.cache_dtype == "auto":
132 - dtype = model_config.dtype
133 - else:
134 - dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
135 -
136 - key_cache_entry = num_heads * head_size
137 -
138 - # For MLA there is no value cache, since the latent vector
139 - # is joint keys and values.
140 - value_cache_entry = key_cache_entry if not model_config.use_mla else 0
141 - total = num_attention_layers * cache_config.block_size * \
142 - (key_cache_entry + value_cache_entry)
143 -
144 - dtype_size = get_dtype_size(dtype)
145 - return dtype_size * total
vllm/worker/model_runner.py deleted
-2031
@@ -1,2031 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -
4 -import dataclasses
5 -import gc
6 -import inspect
7 -import itertools
8 -import time
9 -import weakref
10 -from contextlib import contextmanager
11 -from dataclasses import dataclass
12 -from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set,
13 - Tuple, Type, TypeVar, Union)
14 -
15 -import numpy as np
16 -import torch
17 -import torch.distributed
18 -import torch.nn as nn
19 -from tqdm.auto import tqdm
20 -
21 -from vllm.attention import AttentionMetadata, get_attn_backend
22 -from vllm.attention.backends.abstract import AttentionState
23 -from vllm.attention.backends.utils import CommonAttentionState
24 -from vllm.compilation.counter import compilation_counter
25 -from vllm.config import CompilationLevel, VllmConfig
26 -from vllm.core.scheduler import SchedulerOutputs
27 -from vllm.distributed import broadcast_tensor_dict, get_pp_group
28 -from vllm.distributed.kv_transfer import get_kv_transfer_group
29 -from vllm.distributed.parallel_state import (get_tensor_model_parallel_rank,
30 - graph_capture)
31 -from vllm.forward_context import get_forward_context, set_forward_context
32 -from vllm.inputs import INPUT_REGISTRY, InputRegistry
33 -from vllm.logger import init_logger
34 -from vllm.lora.layers import LoRAMapping
35 -from vllm.lora.request import LoRARequest
36 -from vllm.lora.worker_manager import LRUCacheWorkerLoRAManager
37 -from vllm.model_executor import SamplingMetadata, SamplingMetadataCache
38 -from vllm.model_executor.layers.rotary_embedding import MRotaryEmbedding
39 -from vllm.model_executor.layers.sampler import (Sampler, SamplerOutput,
40 - get_sampler)
41 -from vllm.model_executor.model_loader import get_model
42 -from vllm.model_executor.model_loader.tensorizer import TensorizerConfig
43 -from vllm.model_executor.models import (supports_lora, supports_mrope,
44 - supports_multimodal)
45 -from vllm.model_executor.models.utils import set_cpu_offload_max_bytes
46 -from vllm.multimodal import (MULTIMODAL_REGISTRY, BatchedTensorInputs,
47 - MultiModalKwargs, MultiModalPlaceholderMap,
48 - MultiModalRegistry)
49 -from vllm.sampling_params import SamplingParams
50 -from vllm.sequence import IntermediateTensors, SequenceGroupMetadata
51 -from vllm.utils import (DeviceMemoryProfiler, GiB_bytes, PyObjectCache,
52 - async_tensor_h2d, flatten_2d_lists,
53 - is_pin_memory_available, supports_dynamo,
54 - weak_ref_tensor)
55 -from vllm.worker.model_runner_base import (
56 - InputProcessingError, ModelRunnerBase, ModelRunnerInputBase,
57 - ModelRunnerInputBuilderBase, _add_attn_metadata_broadcastable_dict,
58 - _add_sampling_metadata_broadcastable_dict,
59 - _init_attn_metadata_from_tensor_dict,
60 - _init_sampling_metadata_from_tensor_dict)
61 -
62 -if TYPE_CHECKING:
63 - from vllm.attention.backends.abstract import AttentionBackend
64 -
65 -logger = init_logger(__name__)
66 -
67 -LORA_WARMUP_RANK = 8
68 -
69 -_NUM_WARMUP_ITERS = 2
70 -
71 -TModelInputForGPU = TypeVar('TModelInputForGPU', bound="ModelInputForGPU")
72 -
73 -# For now, bump up cache limits for recompilations during CUDA graph warmups.
74 -torch._dynamo.config.cache_size_limit = 128
75 -torch._dynamo.config.accumulated_cache_size_limit = 128
76 -
77 -
78 -@dataclass(frozen=True)
79 -class ModelInputForGPU(ModelRunnerInputBase):
80 - """
81 - This base class contains metadata needed for the base model forward pass
82 - but not metadata for possible additional steps, e.g., sampling. Model
83 - runners that run additional steps should subclass this method to add
84 - additional fields.
85 - """
86 - input_tokens: Optional[torch.Tensor] = None
87 - inputs_embeds: Optional[torch.Tensor] = None
88 - input_positions: Optional[torch.Tensor] = None
89 - seq_lens: Optional[List[int]] = None
90 - query_lens: Optional[List[int]] = None
91 - lora_mapping: Optional["LoRAMapping"] = None
92 - lora_requests: Optional[Set[LoRARequest]] = None
93 - attn_metadata: Optional["AttentionMetadata"] = None
94 - multi_modal_kwargs: Optional[BatchedTensorInputs] = None
95 - request_ids_to_seq_ids: Optional[Dict[str, List[int]]] = None
96 - finished_requests_ids: Optional[List[str]] = None
97 - virtual_engine: int = 0
98 - async_callback: Optional[Callable] = None
99 - scheduler_outputs: Optional[SchedulerOutputs] = None
100 - previous_hidden_states: Optional[torch.Tensor] = None
101 -
102 - def as_broadcastable_tensor_dict(self) -> Dict[str, Any]:
103 - tensor_dict = {
104 - "input_tokens": self.input_tokens,
105 - "inputs_embeds": self.inputs_embeds,
106 - "input_positions": self.input_positions,
107 - "lora_requests": self.lora_requests,
108 - "lora_mapping": self.lora_mapping,
109 - "multi_modal_kwargs": self.multi_modal_kwargs,
110 - "virtual_engine": self.virtual_engine,
111 - "request_ids_to_seq_ids": self.request_ids_to_seq_ids,
112 - "finished_requests_ids": self.finished_requests_ids,
113 - }
114 - _add_attn_metadata_broadcastable_dict(tensor_dict, self.attn_metadata)
115 - return tensor_dict
116 -
117 - @classmethod
118 - def from_broadcasted_tensor_dict(
119 - cls: Type[TModelInputForGPU],
120 - tensor_dict: Dict[str, Any],
121 - attn_backend: Optional["AttentionBackend"] = None,
122 - ) -> TModelInputForGPU:
123 - if attn_backend is not None:
124 - tensor_dict = _init_attn_metadata_from_tensor_dict(
125 - attn_backend, tensor_dict)
126 - return cls(**tensor_dict)
127 -
128 - # Exclude `async_callback` to be able to pickle this object
129 - def __getstate__(self):
130 - state = self.__dict__.copy()
131 - del state["async_callback"]
132 - return state
133 -
134 - # TODO: What happens when we depickle this object?
135 - # How can we update this callback to properly pass it to the engine?
136 - def __setstate__(self, state):
137 - self.__dict__.update(state)
138 - self.__dict__.update({'async_callback': None})
139 -
140 -
141 -@dataclass(frozen=True)
142 -class ModelInputForGPUWithSamplingMetadata(ModelInputForGPU):
143 - """
144 - Used by the ModelRunner.
145 - """
146 - sampling_metadata: Optional["SamplingMetadata"] = None
147 - # Used for speculative decoding. We do not broadcast it because it is only
148 - # used by the driver worker.
149 - is_prompt: Optional[bool] = None
150 -
151 - def as_broadcastable_tensor_dict(self) -> Dict[str, Any]:
152 - tensor_dict = {
153 - "input_tokens": self.input_tokens,
154 - "inputs_embeds": self.inputs_embeds,
155 - "input_positions": self.input_positions,
156 - "lora_requests": self.lora_requests,
157 - "lora_mapping": self.lora_mapping,
158 - "multi_modal_kwargs": self.multi_modal_kwargs,
159 - "virtual_engine": self.virtual_engine,
160 - "request_ids_to_seq_ids": self.request_ids_to_seq_ids,
161 - "finished_requests_ids": self.finished_requests_ids,
162 - }
163 - _add_attn_metadata_broadcastable_dict(tensor_dict, self.attn_metadata)
164 - _add_sampling_metadata_broadcastable_dict(tensor_dict,
165 - self.sampling_metadata)
166 - return tensor_dict
167 -
168 - @classmethod
169 - def from_broadcasted_tensor_dict(
170 - cls,
171 - tensor_dict: Dict[str, Any],
172 - attn_backend: Optional["AttentionBackend"] = None,
173 - ) -> "ModelInputForGPUWithSamplingMetadata":
174 - tensor_dict = _init_sampling_metadata_from_tensor_dict(tensor_dict)
175 - if attn_backend is not None:
176 - tensor_dict = _init_attn_metadata_from_tensor_dict(
177 - attn_backend, tensor_dict)
178 - return cls(**tensor_dict)
179 -
180 -
181 -class ModelInputForGPUBuilder(ModelRunnerInputBuilderBase[ModelInputForGPU]):
182 - """Build ModelInputForGPU from SequenceGroupMetadata."""
183 -
184 - # Note: ideally we would be using a dataclass(kw_only=True)
185 - # here, so that this can be subclassed easily,
186 - # but kw_only is not supported in python<3.10.
187 - class InterDataForSeqGroup:
188 - """Intermediate data for the current sequence group."""
189 -
190 - def simple_reinit(self):
191 - self.input_tokens[0].clear() # type: ignore
192 - self.inputs_embeds = None # type: ignore
193 - self.input_positions[0].clear() # type: ignore
194 - self.mrope_input_positions = None # type: ignore
195 - self.seq_lens[0] = 0 # type: ignore
196 - self.orig_seq_lens[0] = 0 # type: ignore
197 - self.prompt_lens[0] = 0 # type: ignore
198 - self.query_lens[0] = 0 # type: ignore
199 - self.context_lens[0] = 0 # type: ignore
200 - self.curr_sliding_window_blocks[0] = 0 # type: ignore
201 - self.lora_index_mapping.clear() # type: ignore
202 - self.lora_prompt_mapping.clear() # type: ignore
203 - self.lora_requests.clear() # type: ignore
204 -
205 - def __init__(
206 - self,
207 - *,
208 - # From sequence group metadata.
209 - request_id: str,
210 - seq_ids: List[int],
211 - is_prompt: bool,
212 - block_tables: Optional[Dict[int, List[int]]],
213 - computed_block_nums: List[int],
214 - n_seqs: int = 0,
215 -
216 - # Input tokens and positions.
217 - input_tokens: Optional[List[List[int]]] = None,
218 - inputs_embeds: Optional[torch.Tensor] = None,
219 - input_positions: Optional[List[List[int]]] = None,
220 - mrope_input_positions: Optional[List[List[List[int]]]] = None,
221 -
222 - # The sequence length (may be capped to the sliding window).
223 - seq_lens: Optional[List[int]] = None,
224 - # The original sequence length (before applying sliding window).
225 - # This is used to compute slot mapping.
226 - orig_seq_lens: Optional[List[int]] = None,
227 - # This is used in the dual-chunk flash attention backend.
228 - prompt_lens: Optional[List[int]] = None,
229 - # The query length.
230 - query_lens: Optional[List[int]] = None,
231 - # The number of tokens that are already computed.
232 - context_lens: Optional[List[int]] = None,
233 - # The current sliding window block.
234 - curr_sliding_window_blocks: Optional[List[int]] = None,
235 -
236 - # LoRA inputs.
237 - lora_index_mapping: Optional[List[List[int]]] = None,
238 - lora_prompt_mapping: Optional[List[List[int]]] = None,
239 - lora_requests: Optional[Set[LoRARequest]] = None,
240 -
241 - # Multi-modal inputs.
242 - multi_modal_kwargs: Optional[MultiModalKwargs] = None,
243 - multi_modal_placeholder_maps: Optional[Dict[
244 - str, MultiModalPlaceholderMap]] = None,
245 -
246 - # Whether the prefix cache is hit (prefill only).
247 - prefix_cache_hit: bool = False,
248 - reinit: bool = False,
249 - reinit_use_defaults: bool = False,
250 - encoder_seq_len: int = 0,
251 - ):
252 - if reinit:
253 - assert len(self.seq_ids) == len(seq_ids) # type: ignore
254 - for i, seq_id in enumerate(seq_ids):
255 - self.seq_ids[i] = seq_id # type: ignore
256 - else:
257 - self.seq_ids = seq_ids
258 -
259 - self.request_id = request_id
260 - self.is_prompt = is_prompt
261 - self.block_tables = block_tables
262 - self.computed_block_nums = computed_block_nums
263 - self.n_seqs = n_seqs
264 - self.encoder_seq_len = encoder_seq_len
265 -
266 - if reinit:
267 - if len(self.seq_ids) == 1 and reinit_use_defaults:
268 - self.simple_reinit()
269 - else:
270 - if input_tokens:
271 - self.input_tokens = input_tokens
272 - else:
273 - for seq_id in range(len(self.seq_ids)):
274 - self.input_tokens[seq_id].clear()
275 -
276 - self.inputs_embeds = inputs_embeds
277 -
278 - if input_positions:
279 - self.input_positions = input_positions
280 - else:
281 - for seq_id in range(len(self.seq_ids)):
282 - self.input_positions[seq_id].clear()
283 -
284 - self.mrope_input_positions = None
285 -
286 - if seq_lens:
287 - self.seq_lens = seq_lens
288 - else:
289 - for seq_id in range(len(self.seq_ids)):
290 - self.seq_lens[seq_id] = 0
291 -
292 - if orig_seq_lens:
293 - self.orig_seq_lens = orig_seq_lens
294 - else:
295 - for seq_id in range(len(self.seq_ids)):
296 - self.orig_seq_lens[seq_id] = 0
297 -
298 - if prompt_lens:
299 - self.prompt_lens = prompt_lens
300 - else:
301 - for seq_id in range(len(self.seq_ids)):
302 - self.prompt_lens[seq_id] = 0
303 -
304 - if query_lens:
305 - self.query_lens = query_lens
306 - else:
307 - for seq_id in range(len(self.seq_ids)):
308 - self.query_lens[seq_id] = 0
309 -
310 - if context_lens:
311 - self.context_lens = context_lens
312 - else:
313 - for seq_id in range(len(self.seq_ids)):
314 - self.context_lens[seq_id] = 0
315 -
316 - if curr_sliding_window_blocks:
317 - self.curr_sliding_window_blocks = \
318 - curr_sliding_window_blocks
319 - else:
320 - for seq_id in range(len(self.seq_ids)):
321 - self.curr_sliding_window_blocks[seq_id] = 0
322 -
323 - if lora_index_mapping:
324 - self.lora_index_mapping = lora_index_mapping
325 - else:
326 - self.lora_index_mapping.clear()
327 -
328 - if lora_prompt_mapping:
329 - self.lora_prompt_mapping = lora_prompt_mapping
330 - else:
331 - self.lora_prompt_mapping.clear()
332 -
333 - if lora_requests:
334 - self.lora_requests = lora_requests
335 - else:
336 - self.lora_requests.clear()
337 -
338 - else:
339 - self.input_tokens = input_tokens or []
340 - self.inputs_embeds = inputs_embeds
341 - self.input_positions = input_positions or []
342 - self.mrope_input_positions = mrope_input_positions or None
343 - self.seq_lens = seq_lens or []
344 - self.orig_seq_lens = orig_seq_lens or []
345 - self.prompt_lens = prompt_lens or []
346 - self.query_lens = query_lens or []
347 - self.context_lens = context_lens or []
348 - self.curr_sliding_window_blocks = \
349 - curr_sliding_window_blocks or []
350 -
351 - self.lora_index_mapping = lora_index_mapping or []
352 - self.lora_prompt_mapping = lora_prompt_mapping or []
353 - self.lora_requests = lora_requests or set()
354 -
355 - self.multi_modal_kwargs = multi_modal_kwargs
356 - self.multi_modal_placeholder_maps = multi_modal_placeholder_maps
357 - self.prefix_cache_hit = prefix_cache_hit
358 -
359 - self.n_seqs = len(self.seq_ids)
360 -
361 - if not reinit:
362 - self.__post_init__()
363 -
364 - def __post_init__(self):
365 - self.n_seqs = len(self.seq_ids)
366 -
367 - self.input_tokens = [[] for _ in range(self.n_seqs)]
368 - self.input_positions = [[] for _ in range(self.n_seqs)]
369 - self.mrope_input_positions = None
370 - self.seq_lens = [0] * self.n_seqs
371 - self.orig_seq_lens = [0] * self.n_seqs
372 - self.prompt_lens = [0] * self.n_seqs
373 - self.query_lens = [0] * self.n_seqs
374 - self.context_lens = [0] * self.n_seqs
375 - self.curr_sliding_window_blocks = [0] * self.n_seqs
376 -
377 - self.lora_index_mapping = []
378 - self.lora_prompt_mapping = []
379 -
380 - def __repr__(self) -> str:
381 - return (f"InterDataForSeqGroup("
382 - f"request_id={self.request_id}, "
383 - f"seq_ids={self.seq_ids}, "
384 - f"is_prompt={self.is_prompt}, "
385 - f"block_tables={self.block_tables}, "
386 - f"computed_block_nums={self.computed_block_nums}, "
387 - f"n_seqs={self.n_seqs}, "
388 - f"input_tokens={self.input_tokens}, "
389 - f"inputs_embeds.shape="
390 - f"{getattr(self.inputs_embeds, 'shape', None)}, "
391 - f"input_positions={self.input_positions}, "
392 - f"mrope_input_positions={self.mrope_input_positions}, "
393 - f"seq_lens={self.seq_lens}, "
394 - f"orig_seq_lens={self.orig_seq_lens}, "
395 - f"query_lens={self.query_lens}, "
396 - f"context_lens={self.context_lens}, "
397 - f"multi_modal_kwargs={self.multi_modal_kwargs}")
398 -
399 - def gen_inter_data_builder(self, num_seqs: int):
400 - return lambda: ModelInputForGPUBuilder.InterDataForSeqGroup(
401 - request_id="",
402 - seq_ids=[0] * num_seqs,
403 - is_prompt=True,
404 - block_tables=None,
405 - computed_block_nums=[])
406 -
407 - def init_cached_inter_data(self, *args, **kwargs):
408 - assert len(args) == 0
409 - assert "seq_ids" in kwargs
410 - seq_ids = kwargs["seq_ids"]
411 - num_seqs = len(seq_ids)
412 -
413 - # The inter-data cache is per model_runner
414 - inter_data_cache = self.runner.inter_data_cache
415 - if num_seqs not in inter_data_cache:
416 - inter_data_cache[num_seqs] = PyObjectCache(
417 - self.gen_inter_data_builder(num_seqs))
418 -
419 - obj = inter_data_cache[num_seqs].get_object()
420 - obj.__init__(*args, **kwargs)
421 - return obj
422 -
423 - def reset_cached_inter_data(self):
424 - for cache in self.runner.inter_data_cache.values():
425 - cache.reset()
426 -
427 - def __init__(self,
428 - runner: "GPUModelRunnerBase",
429 - finished_requests_ids: Optional[List[str]] = None):
430 - super().__init__()
431 - # Compute functions for each sequence in a sequence group.
432 - # WARNING: The order of the functions matters!
433 - self.per_seq_compute_fns = [
434 - self._compute_lens,
435 - self._compute_for_prefix_cache_hit,
436 - self._compute_for_sliding_window,
437 - self._compute_lora_input,
438 - ]
439 - # Compute functions for each sequence group.
440 - # WARNING: The order of the functions matters!
441 - self.per_seq_group_compute_fns = [
442 - self._compute_multi_modal_input,
443 - ]
444 -
445 - self.runner = runner
446 - self.model_input_cls = self.runner._model_input_cls
447 - self.attn_backend = self.runner.attn_backend
448 - self.scheduler_config = self.runner.scheduler_config
449 - self.sliding_window = self.runner.sliding_window
450 - self.block_size = self.runner.block_size
451 - self.enable_lora = self.runner.lora_config is not None
452 -
453 - # Attention metadata inputs.
454 - if self.attn_backend is not None:
455 - # spec decode (e.g. Medusa) does not have atten backend
456 - self.attn_metadata_builder = self.attn_backend.get_builder_cls()(
457 - weakref.proxy(self))
458 -
459 - # Engine/Model configurations.
460 - self.chunked_prefill_enabled = (
461 - self.scheduler_config is not None
462 - and self.scheduler_config.chunked_prefill_enabled)
463 - if self.sliding_window is not None:
464 - self.sliding_window_blocks = (
465 - self.sliding_window + self.block_size - 1) // self.block_size
466 - self.block_aligned_sliding_window = \
467 - self.sliding_window_blocks * self.block_size
468 -
469 - def prepare(self,
470 - finished_requests_ids: Optional[List[str]] = None) -> None:
471 - self.finished_requests_ids = finished_requests_ids
472 -
473 - # if the current batch is decode-only.
474 - # will be set to False if there is any non-decode request.
475 - self.decode_only = True
476 -
477 - # Intermediate data (data in CPU before going to GPU) for
478 - # the current sequence group.
479 - self.inter_data_list: List[
480 - ModelInputForGPUBuilder.InterDataForSeqGroup] = []
481 -
482 - self.attn_metadata_builder.prepare()
483 -
484 - def _compute_lens(self, inter_data: InterDataForSeqGroup, seq_idx: int,
485 - seq_group_metadata: SequenceGroupMetadata):
486 - """Compute context length, sequence length and tokens
487 - for the given sequence data.
488 - """
489 - seq_data = seq_group_metadata.seq_data[inter_data.seq_ids[seq_idx]]
490 - token_chunk_size = seq_group_metadata.token_chunk_size
491 -
492 - # Compute context length (the number of tokens that are
493 - # already computed) and sequence length (total number of tokens).
494 -
495 - seq_len = seq_data.get_len()
496 - if inter_data.is_prompt:
497 - context_len = seq_data.get_num_computed_tokens()
498 - seq_len = min(seq_len, context_len + token_chunk_size)
499 - elif self.runner.model_config.is_encoder_decoder:
500 - context_len = seq_len - 1
501 - else:
502 - context_len = seq_data.get_num_computed_tokens()
503 -
504 - # Compute tokens.
505 - if seq_data.prompt_embeds is None:
506 - tokens = seq_data.get_token_ids()[context_len:seq_len]
507 - prompt_embeds = None
508 - else:
509 - tokens = [0] * (seq_len - context_len)
510 - prompt_embeds = seq_data.get_token_embeddings(
511 - )[context_len:seq_len]
512 -
513 - inter_data.seq_lens[seq_idx] = seq_len
514 - inter_data.orig_seq_lens[seq_idx] = seq_len
515 - inter_data.prompt_lens[seq_idx] = seq_data.get_prompt_len()
516 - inter_data.context_lens[seq_idx] = context_len
517 - inter_data.input_tokens[seq_idx].extend(tokens)
518 - inter_data.inputs_embeds = prompt_embeds
519 - inter_data.input_positions[seq_idx].extend(range(context_len, seq_len))
520 - inter_data.query_lens[seq_idx] = seq_len - context_len
521 -
522 - if seq_data.mrope_position_delta is not None:
523 - if inter_data.mrope_input_positions is None:
524 - inter_data.mrope_input_positions = [None] * inter_data.n_seqs
525 -
526 - inter_data.mrope_input_positions[
527 - seq_idx] = MRotaryEmbedding.get_next_input_positions(
528 - seq_data.mrope_position_delta,
529 - context_len,
530 - seq_len,
531 - )
532 -
533 - def _compute_for_prefix_cache_hit(
534 - self, inter_data: InterDataForSeqGroup, seq_idx: int,
535 - seq_group_metadata: SequenceGroupMetadata):
536 - """Check if hit prefix cache (i.e., some blocks are already computed).
537 - If hit, update input tokens and positions to only compute the
538 - remaining blocks.
539 - """
540 - computed_block_nums = inter_data.computed_block_nums
541 -
542 - # Note that prefix caching does not support sliding window.
543 - prefix_cache_hit = (computed_block_nums is not None
544 - and len(computed_block_nums) > 0
545 - and self.sliding_window is None
546 - and inter_data.is_prompt)
547 - inter_data.prefix_cache_hit = prefix_cache_hit
548 -
549 - if not prefix_cache_hit:
550 - return
551 -
552 - assert computed_block_nums is not None
553 - # The cache hit prompt tokens in this sequence. Note that
554 - # this may be larger than the sequence length if chunked
555 - # prefill is enabled.
556 - prefix_cache_len = len(computed_block_nums) * self.block_size
557 - seq_group_metadata.seq_data[inter_data.seq_ids[
558 - seq_idx]].update_num_cached_tokens(prefix_cache_len)
559 -
560 - # The number of so far computed prompt tokens in this sequence.
561 - context_len = inter_data.context_lens[seq_idx]
562 - # The total number of prompt tokens in this sequence.
563 - # When chunked prefill is enabled, this is the token number of
564 - # computed chunks + current chunk.
565 - seq_len = inter_data.seq_lens[seq_idx]
566 - if prefix_cache_len <= context_len:
567 - # We already passed the cache hit region,
568 - # so do normal computation.
569 - pass
570 - elif context_len < prefix_cache_len < seq_len:
571 - # Partial hit. Compute the missing part.
572 - uncomputed_start = prefix_cache_len - context_len
573 - inter_data.input_tokens[seq_idx] = inter_data.input_tokens[
574 - seq_idx][uncomputed_start:]
575 - inter_data.input_positions[seq_idx] = inter_data.input_positions[
576 - seq_idx][uncomputed_start:]
577 - context_len = prefix_cache_len
578 -
579 - inter_data.context_lens[seq_idx] = context_len
580 - inter_data.query_lens[
581 - seq_idx] = inter_data.seq_lens[seq_idx] - context_len
582 - elif seq_len <= prefix_cache_len:
583 - # Full hit. Only compute the last token to avoid
584 - # erroneous behavior. FIXME: Ideally we should directly
585 - # mark all tokens as computed in the scheduler and do not
586 - # schedule this sequence, so this case should not happen.
587 - inter_data.input_tokens[seq_idx] = inter_data.input_tokens[
588 - seq_idx][-1:]
589 - inter_data.input_positions[seq_idx] = inter_data.input_positions[
590 - seq_idx][-1:]
591 - inter_data.query_lens[seq_idx] = 1
592 - inter_data.context_lens[seq_idx] = inter_data.seq_lens[seq_idx] - 1
593 -
594 - def _compute_for_sliding_window(self, inter_data: InterDataForSeqGroup,
595 - seq_idx: int,
596 - seq_group_metadata: SequenceGroupMetadata):
597 - """Update seq_len and curr_sliding_window_block for the given
598 - sequence data (only required by decoding) if sliding window is enabled.
599 - """
600 - curr_sliding_window_block = 0
601 - sliding_seq_len = inter_data.seq_lens[seq_idx]
602 - if not inter_data.is_prompt and self.sliding_window is not None:
603 - # TODO(sang): This is a hack to make sliding window work with
604 - # paged attn. We can remove it if we make paged attn kernel
605 - # to properly handle slinding window attn.
606 - curr_sliding_window_block = self.sliding_window_blocks
607 - # number of elements in last block
608 - suff_len = inter_data.seq_lens[seq_idx] % self.block_size
609 - sliding_seq_len = min(inter_data.seq_lens[seq_idx],
610 - self.block_aligned_sliding_window + suff_len)
611 - if suff_len > 0:
612 - curr_sliding_window_block += 1
613 -
614 - inter_data.curr_sliding_window_blocks[
615 - seq_idx] = curr_sliding_window_block
616 - inter_data.seq_lens[seq_idx] = sliding_seq_len
617 -
618 - def _compute_lora_input(self, inter_data: InterDataForSeqGroup,
619 - seq_idx: int,
620 - seq_group_metadata: SequenceGroupMetadata):
621 - """If LoRA is enabled, compute LoRA index and prompt mapping."""
622 - if not self.enable_lora:
623 - return
624 -
625 - lora_id = seq_group_metadata.lora_int_id
626 - if lora_id > 0:
627 - inter_data.lora_requests.add(seq_group_metadata.lora_request)
628 - query_len = inter_data.query_lens[seq_idx]
629 - inter_data.lora_index_mapping.append([lora_id] * query_len)
630 - sampling_params = seq_group_metadata.sampling_params
631 - if sampling_params and sampling_params.prompt_logprobs is not None:
632 - inter_data.lora_prompt_mapping.append([lora_id] * query_len)
633 - elif not self.chunked_prefill_enabled or seq_group_metadata.do_sample:
634 - inter_data.lora_prompt_mapping.append([lora_id])
635 - else:
636 - inter_data.lora_prompt_mapping.append([])
637 -
638 - def _compute_multi_modal_input(self, inter_data: InterDataForSeqGroup,
639 - seq_group_metadata: SequenceGroupMetadata):
640 - """If multi-modal data is given, add it to the input."""
641 - # NOTE: mm_kwargs only includes the subset of multi-modal items that
642 - # intersect with the current prefill positions.
643 - positions = inter_data.input_positions[0]
644 - mm_kwargs, placeholder_maps = MultiModalPlaceholderMap.from_seq_group(
645 - seq_group_metadata,
646 - range(positions[0], positions[0] + len(positions)))
647 -
648 - # M-RoPE requires mrope_positions even for plain text; return early
649 - # when mm_kwargs is empty only if inter_data.is_prompt is False.
650 - if not mm_kwargs and not inter_data.is_prompt:
651 - return
652 -
653 - inter_data.multi_modal_kwargs = mm_kwargs
654 - inter_data.multi_modal_placeholder_maps = placeholder_maps
655 -
656 - # special processing for mrope position deltas.
657 - if self.runner.model_config.uses_mrope:
658 - image_grid_thw = mm_kwargs.get("image_grid_thw", None)
659 - video_grid_thw = mm_kwargs.get("video_grid_thw", None)
660 - audio_feature_lengths = mm_kwargs.get("audio_feature_lengths",
661 - None)
662 -
663 - second_per_grid_ts = mm_kwargs.get("second_per_grid_ts", None)
664 - use_audio_in_video = mm_kwargs.get("use_audio_in_video", False)
665 - hf_config = self.runner.model_config.hf_config
666 -
667 - inter_data.mrope_input_positions = [None] * inter_data.n_seqs
668 - for seq_idx in range(inter_data.n_seqs):
669 - seq_data = seq_group_metadata.seq_data[
670 - inter_data.seq_ids[seq_idx]]
671 - token_ids = seq_data.get_token_ids()
672 -
673 - if supports_mrope(self.runner.model):
674 - mrope_input_positions, mrope_position_delta = \
675 - self.runner.model.get_mrope_input_positions(
676 - token_ids,
677 - hf_config=hf_config,
678 - image_grid_thw=image_grid_thw,
679 - video_grid_thw=video_grid_thw,
680 - second_per_grid_ts=second_per_grid_ts,
681 - context_len=inter_data.context_lens[seq_idx],
682 - seq_len=inter_data.seq_lens[seq_idx],
683 - audio_feature_lengths=audio_feature_lengths,
684 - use_audio_in_video=use_audio_in_video,
685 - )
686 - mrope_input_positions = mrope_input_positions.tolist()
687 - else:
688 - mrope_input_positions, mrope_position_delta = \
689 - MRotaryEmbedding.get_input_positions(
690 - token_ids,
691 - hf_config=hf_config,
692 - image_grid_thw=image_grid_thw,
693 - video_grid_thw=video_grid_thw,
694 - second_per_grid_ts=second_per_grid_ts,
695 - context_len=inter_data.context_lens[seq_idx],
696 - seq_len=inter_data.seq_lens[seq_idx],
697 - audio_feature_lengths=audio_feature_lengths,
698 - use_audio_in_video=use_audio_in_video,
699 - )
700 -
701 - seq_data.mrope_position_delta = mrope_position_delta
702 - inter_data.mrope_input_positions[
703 - seq_idx] = mrope_input_positions
704 -
705 - def add_seq_group(self, seq_group_metadata: SequenceGroupMetadata):
706 - """Add a sequence group to the builder."""
707 - seq_ids = seq_group_metadata.seq_data.keys()
708 - n_seqs = len(seq_ids)
709 - is_prompt = seq_group_metadata.is_prompt
710 -
711 - if is_prompt:
712 - assert n_seqs == 1
713 - self.decode_only = False
714 -
715 - encoder_seq_len = 0
716 -
717 - if self.runner.model_config.is_encoder_decoder:
718 - encoder_seq_len = seq_group_metadata.encoder_seq_data.get_len()
719 -
720 - inter_data = self.init_cached_inter_data(
721 - request_id=seq_group_metadata.request_id,
722 - seq_ids=seq_ids,
723 - is_prompt=is_prompt,
724 - block_tables=seq_group_metadata.block_tables,
725 - computed_block_nums=seq_group_metadata.computed_block_nums,
726 - reinit=True,
727 - reinit_use_defaults=True,
728 - encoder_seq_len=encoder_seq_len)
729 -
730 - self.inter_data_list.append(inter_data)
731 -
732 - for seq_idx in range(n_seqs):
733 - for per_seq_fn in self.per_seq_compute_fns:
734 - per_seq_fn(inter_data, seq_idx, seq_group_metadata)
735 - for per_seq_group_fn in self.per_seq_group_compute_fns:
736 - per_seq_group_fn(inter_data, seq_group_metadata)
737 -
738 - def _use_captured_graph(self,
739 - batch_size: int,
740 - decode_only: bool,
741 - max_decode_seq_len: int,
742 - max_encoder_seq_len: int = 0) -> bool:
743 - return (decode_only and not self.runner.model_config.enforce_eager
744 - and max_decode_seq_len <= self.runner.max_seq_len_to_capture
745 - and max_encoder_seq_len <= self.runner.max_seq_len_to_capture
746 - and batch_size <= self.runner.max_batchsize_to_capture)
747 -
748 - def _get_cuda_graph_pad_size(self,
749 - num_seqs: int,
750 - max_decode_seq_len: int,
751 - max_encoder_seq_len: int = 0) -> int:
752 - """
753 - Determine the number of padding sequences required for running in
754 - CUDA graph mode. Returns -1 if CUDA graphs cannot be used.
755 -
756 - In the multi-step + chunked-prefill case, only the first step
757 - has Prefills (if any). The rest of the steps are guaranteed to be all
758 - decodes. In this case, we set up the padding as if all the sequences
759 - are decodes so we may run all steps except the first step in CUDA graph
760 - mode.
761 -
762 - Args:
763 - num_seqs (int): Number of sequences scheduled to run.
764 - max_decode_seq_len (int): Greatest of all the decode sequence
765 - lengths. Used only in checking the viablility of using
766 - CUDA graphs.
767 - max_encoder_seq_len (int, optional): Greatest of all the encode
768 - sequence lengths. Defaults to 0. Used only in checking the
769 - viability of using CUDA graphs.
770 - Returns:
771 - int: Returns the determined number of padding sequences. If
772 - CUDA graphs is not viable, returns -1.
773 - """
774 - decode_only = self.decode_only
775 - if not decode_only:
776 - # Early exit so we can treat num_seqs as the batch_size below.
777 - return -1
778 -
779 - # batch_size out of this function refers to the number of input
780 - # tokens being scheduled. This conflation of num_seqs as batch_size
781 - # is valid as this is a decode-only case.
782 - batch_size = num_seqs
783 - if not self._use_captured_graph(batch_size, decode_only,
784 - max_decode_seq_len,
785 - max_encoder_seq_len):
786 - return -1
787 -
788 - graph_batch_size = self.runner.vllm_config.pad_for_cudagraph(
789 - batch_size)
790 - assert graph_batch_size >= batch_size
791 - return graph_batch_size - batch_size
792 -
793 - def build(self) -> ModelInputForGPU:
794 - """Finalize the builder intermediate data and
795 - create on-device tensors.
796 - """
797 - # Combine and flatten intermediate data.
798 - input_tokens = list[int]()
799 - inputs_embeds_list = list[torch.Tensor]()
800 - for inter_data in self.inter_data_list:
801 - for cur_input_tokens in inter_data.input_tokens:
802 - input_tokens.extend(cur_input_tokens)
803 - if inter_data.inputs_embeds is not None:
804 - inputs_embeds_list.append(
805 - inter_data.inputs_embeds.to(
806 - dtype=self.runner.model_config.dtype,
807 - device=self.runner.device))
808 - inputs_embeds: Optional[torch.Tensor]
809 - if len(inputs_embeds_list) == 0:
810 - inputs_embeds = None
811 - else:
812 - inputs_embeds = torch.cat(inputs_embeds_list, dim=0).to(
813 - dtype=self.runner.model_config.dtype,
814 - device=self.runner.device)
815 - assert len(inputs_embeds) == len(input_tokens)
816 -
817 - if not input_tokens and inputs_embeds is None:
818 - # This may happen when all prefill requests hit
819 - # prefix caching and there is no decode request.
820 - return self.model_input_cls()
821 -
822 - mrope_input_positions: Optional[List[List[int]]] = None
823 - if any(inter_data.mrope_input_positions is not None
824 - for inter_data in self.inter_data_list):
825 - mrope_input_positions = [[] for _ in range(3)]
826 - for idx in range(3):
827 - for inter_data in self.inter_data_list:
828 - msections = inter_data.mrope_input_positions
829 - if msections is None:
830 - for _seq_input_positions in inter_data.input_positions:
831 - mrope_input_positions[idx].extend(
832 - _seq_input_positions)
833 - else:
834 - for _seq_mrope_input_positions in msections:
835 - mrope_input_positions[idx].extend(
836 - _seq_mrope_input_positions[idx])
837 - input_positions = None
838 - else:
839 - input_positions = []
840 - for inter_data in self.inter_data_list:
841 - for cur_input_positions in inter_data.input_positions:
842 - input_positions.extend(cur_input_positions)
843 -
844 - seq_lens = []
845 - query_lens = []
846 - max_decode_seq_len = 0
847 - max_encoder_seq_len = 0
848 - for inter_data in self.inter_data_list:
849 - seq_lens.extend(inter_data.seq_lens)
850 - query_lens.extend(inter_data.query_lens)
851 - if not inter_data.is_prompt:
852 - max_decode_seq_len = max(max_decode_seq_len,
853 - max(inter_data.seq_lens))
854 - if self.runner.model_config.is_encoder_decoder:
855 - max_encoder_seq_len = max(max_encoder_seq_len,
856 - inter_data.encoder_seq_len)
857 -
858 - # Mapping from request IDs to sequence IDs. Used for Jamba models
859 - # that manages the cache by itself.
860 - request_ids_to_seq_ids = {
861 - data.request_id: data.seq_ids
862 - for data in self.inter_data_list
863 - }
864 -
865 - cuda_graph_pad_size = self._get_cuda_graph_pad_size(
866 - num_seqs=len(seq_lens),
867 - max_decode_seq_len=max_decode_seq_len,
868 - max_encoder_seq_len=max_encoder_seq_len)
869 -
870 - batch_size = len(input_tokens)
871 - if cuda_graph_pad_size != -1:
872 - # If cuda graph can be used, pad tensors accordingly.
873 - # See `capture_model` API for more details.
874 - # vLLM uses cuda graph only for decoding requests.
875 - batch_size += cuda_graph_pad_size
876 -
877 - # Tokens and positions.
878 - if cuda_graph_pad_size:
879 - input_tokens.extend(itertools.repeat(0, cuda_graph_pad_size))
880 - assert self.runner.device is not None
881 - input_tokens_tensor = async_tensor_h2d(input_tokens, torch.long,
882 - self.runner.device,
883 - self.runner.pin_memory)
884 -
885 - if mrope_input_positions is not None:
886 - for idx in range(3):
887 - mrope_input_positions[idx].extend(
888 - itertools.repeat(0, cuda_graph_pad_size))
889 - input_positions_tensor = async_tensor_h2d(mrope_input_positions,
890 - torch.long,
891 - self.runner.device,
892 - self.runner.pin_memory)
893 - else:
894 - input_positions.extend(itertools.repeat(0, cuda_graph_pad_size))
895 - input_positions_tensor = async_tensor_h2d(input_positions,
896 - torch.long,
897 - self.runner.device,
898 - self.runner.pin_memory)
899 - # Sequence and query lengths.
900 - if cuda_graph_pad_size:
901 - seq_lens.extend(itertools.repeat(1, cuda_graph_pad_size))
902 -
903 - # Attention metadata.
904 - attn_metadata = self.attn_metadata_builder.build(
905 - seq_lens, query_lens, cuda_graph_pad_size, batch_size)
906 -
907 - # LoRA data.
908 - lora_requests = set()
909 - lora_mapping = None
910 - if self.enable_lora:
911 - lora_requests = set(r for data in self.inter_data_list
912 - for r in data.lora_requests)
913 - lora_index_mapping = flatten_2d_lists([
914 - flatten_2d_lists(inter_data.lora_index_mapping)
915 - for inter_data in self.inter_data_list
916 - ])
917 - if cuda_graph_pad_size:
918 - lora_index_mapping.extend(
919 - itertools.repeat(0, cuda_graph_pad_size))
920 - lora_prompt_mapping = flatten_2d_lists([
921 - flatten_2d_lists(inter_data.lora_prompt_mapping)
922 - for inter_data in self.inter_data_list
923 - ])
924 -
925 - lora_mapping = LoRAMapping(
926 - **dict(index_mapping=lora_index_mapping,
927 - prompt_mapping=lora_prompt_mapping,
928 - is_prefill=not self.decode_only))
929 -
930 - # Multi-modal data.
931 - multi_modal_kwargs_list = [
932 - data.multi_modal_kwargs for data in self.inter_data_list
933 - if data.multi_modal_kwargs is not None
934 - ]
935 - multi_modal_kwargs = MultiModalKwargs.batch(multi_modal_kwargs_list)
936 -
937 - return self.model_input_cls(
938 - input_tokens=input_tokens_tensor,
939 - inputs_embeds=inputs_embeds,
940 - input_positions=input_positions_tensor,
941 - attn_metadata=attn_metadata,
942 - seq_lens=seq_lens,
943 - query_lens=query_lens,
944 - lora_mapping=lora_mapping,
945 - lora_requests=lora_requests,
946 - multi_modal_kwargs=multi_modal_kwargs,
947 - request_ids_to_seq_ids=request_ids_to_seq_ids,
948 - finished_requests_ids=self.finished_requests_ids)
949 -
950 -
951 -class GPUModelRunnerBase(ModelRunnerBase[TModelInputForGPU]):
952 - """
953 - Helper class for shared methods between GPU model runners.
954 - """
955 - _model_input_cls: Type[TModelInputForGPU]
956 - _builder_cls: Type[ModelInputForGPUBuilder]
957 - builder: ModelInputForGPUBuilder
958 -
959 - def __init__(
960 - self,
961 - vllm_config: VllmConfig,
962 - kv_cache_dtype: Optional[str] = "auto",
963 - is_driver_worker: bool = False,
964 - return_hidden_states: bool = False,
965 - input_registry: InputRegistry = INPUT_REGISTRY,
966 - mm_registry: MultiModalRegistry = MULTIMODAL_REGISTRY,
967 - ):
968 -
969 - ModelRunnerBase.__init__(self, vllm_config)
970 - model_config = self.model_config
971 - cache_config = self.cache_config
972 -
973 - self.is_driver_worker = is_driver_worker
974 - self.return_hidden_states = return_hidden_states
975 -
976 - self.device = self.device_config.device
977 - self.pin_memory = is_pin_memory_available()
978 -
979 - self.kv_cache_dtype = kv_cache_dtype
980 - self.sliding_window = model_config.get_sliding_window()
981 - self.block_size = cache_config.block_size
982 - self.max_seq_len_to_capture = self.model_config.max_seq_len_to_capture
983 - self.max_batchsize_to_capture = \
984 - self.vllm_config.compilation_config.max_capture_size
985 -
986 - #
987 - self.graph_runners: List[Dict[Tuple[int, bool], CUDAGraphRunner]] = [
988 - {} for _ in range(self.parallel_config.pipeline_parallel_size)
989 - ]
990 - self.graph_memory_pool: Optional[Tuple[
991 - int, int]] = None # Set during graph capture.
992 -
993 - self.has_inner_state = model_config.has_inner_state
994 -
995 - self.in_profile_run = False
996 -
997 - # When using CUDA graph, the input block tables must be padded to
998 - # max_seq_len_to_capture. However, creating the block table in
999 - # Python can be expensive. To optimize this, we cache the block table
1000 - # in numpy and only copy the actual input content at every iteration.
1001 - # The shape of the cached block table will be
1002 - # (max batch size to capture, max seq len to capture / block size).
1003 - self.graph_block_tables = np.zeros(
1004 - (self.max_batchsize_to_capture, self.get_max_block_per_batch()),
1005 - dtype=np.int32)
1006 -
1007 - self.cross_layer_shared_graph_block_tables = np.zeros(
1008 - (self.max_batchsize_to_capture, self.get_max_block_per_batch()),
1009 - dtype=np.int32)
1010 -
1011 - # Attention-free but stateful models like Mamba need a placeholder attn
1012 - # backend, as the attention metadata is needed to manage internal state.
1013 - # However we must bypass attention selection altogether for some models
1014 - # used for speculative decoding to avoid a divide-by-zero in
1015 - # model_config.get_head_size()
1016 - num_attn_heads = self.model_config.get_num_attention_heads(
1017 - self.parallel_config)
1018 - needs_attn_backend = (num_attn_heads != 0
1019 - or self.model_config.is_attention_free)
1020 -
1021 - self.attn_backend = get_attn_backend(
1022 - self.model_config.get_head_size(),
1023 - self.model_config.dtype,
1024 - self.kv_cache_dtype,
1025 - self.block_size,
1026 - self.model_config.is_attention_free,
1027 - use_mla=self.model_config.use_mla,
1028 - ) if needs_attn_backend else None
1029 - if self.attn_backend:
1030 - self.attn_state = self.attn_backend.get_state_cls()(
1031 - weakref.proxy(self))
1032 - else:
1033 - self.attn_state = CommonAttentionState(weakref.proxy(self))
1034 -
1035 - # Multi-modal data support
1036 - self.input_registry = input_registry
1037 - self.mm_registry = mm_registry
1038 -
1039 - # Lazy initialization
1040 - self.model: nn.Module # Set after load_model
1041 - # Set after load_model.
1042 - self.lora_manager: Optional[LRUCacheWorkerLoRAManager] = None
1043 - self.sampler = get_sampler()
1044 -
1045 - set_cpu_offload_max_bytes(
1046 - int(self.cache_config.cpu_offload_gb * 1024**3))
1047 -
1048 - # Used to cache python objects
1049 - self.inter_data_cache: Dict[int, PyObjectCache] = {}
1050 -
1051 - # Using the PythonizationCache in Pipeline-Parallel clobbers the
1052 - # SequenceGroupToSample object. In Pipeline-Parallel, we have
1053 - # more than 1 Scheduler, resulting in a potential back-to-back
1054 - # prepare_model_inputs() call. This clobbers the cached
1055 - # SequenceGroupToSample objects, as we reset the cache during
1056 - # every prepare_model_inputs() call.
1057 - self.sampling_metadata_cache: SamplingMetadataCache = \
1058 - SamplingMetadataCache() \
1059 - if self.parallel_config.pipeline_parallel_size == 1 else None
1060 -
1061 - if hasattr(self, "_builder_cls"):
1062 - # multi-step model runner does not have `_builder_cls`
1063 - self.builder = self._builder_cls(weakref.proxy(self))
1064 -
1065 - def load_model(self) -> None:
1066 - logger.info("Starting to load model %s...", self.model_config.model)
1067 - with DeviceMemoryProfiler(self.device) as m:
1068 - time_before_load = time.perf_counter()
1069 - self.model = get_model(vllm_config=self.vllm_config)
1070 - if self.lora_config:
1071 - assert supports_lora(
1072 - self.model
1073 - ), f"{self.model.__class__.__name__} does not support LoRA yet."
1074 -
1075 - if supports_multimodal(self.model):
1076 - logger.warning(
1077 - "Regarding multimodal models, vLLM currently "
1078 - "only supports adding LoRA to language model.")
1079 -
1080 - self.lora_manager = LRUCacheWorkerLoRAManager(
1081 - self.vllm_config,
1082 - self.device,
1083 - self.model.embedding_modules,
1084 - self.model.embedding_padding_modules,
1085 - )
1086 -
1087 - self.model = self.lora_manager.create_lora_manager(self.model)
1088 - time_after_load = time.perf_counter()
1089 -
1090 - self.model_memory_usage = m.consumed_memory
1091 - logger.info("Model loading took %.4f GiB and %.6f seconds",
1092 - self.model_memory_usage / GiB_bytes,
1093 - time_after_load - time_before_load)
1094 -
1095 -
1096 - if self.vllm_config.compilation_config.level ==\
1097 - CompilationLevel.DYNAMO_AS_IS and supports_dynamo():
1098 - backend = self.vllm_config.compilation_config.init_backend(
1099 - self.vllm_config)
1100 - compilation_counter.dynamo_as_is_count += 1
1101 - self.model = torch.compile(self.model,
1102 - fullgraph=True,
1103 - backend=backend)
1104 -
1105 - def get_model(self) -> nn.Module:
1106 - return self.model
1107 -
1108 - def save_sharded_state(
1109 - self,
1110 - path: str,
1111 - pattern: Optional[str] = None,
1112 - max_size: Optional[int] = None,
1113 - ) -> None:
1114 - from vllm.model_executor.model_loader import ShardedStateLoader
1115 - ShardedStateLoader.save_model(
1116 - self.model,
1117 - path,
1118 - pattern=pattern,
1119 - max_size=max_size,
1120 - )
1121 -
1122 - def save_tensorized_model(
1123 - self,
1124 - tensorizer_config: TensorizerConfig,
1125 - ) -> None:
1126 - from vllm.model_executor.model_loader import TensorizerLoader
1127 - TensorizerLoader.save_model(
1128 - self.model,
1129 - tensorizer_config=tensorizer_config,
1130 - model_config=self.model_config,
1131 - )
1132 -
1133 - def get_max_block_per_batch(self) -> int:
1134 - block_size = self.block_size
1135 - return (self.max_seq_len_to_capture + block_size - 1) // block_size
1136 -
1137 - def _prepare_model_input_tensors(
1138 - self,
1139 - seq_group_metadata_list: List[SequenceGroupMetadata],
1140 - finished_requests_ids: Optional[List[str]] = None
1141 - ) -> TModelInputForGPU:
1142 - """Helper method to prepare the model input based on a given sequence
1143 - group. Prepares metadata needed for the base model forward pass but not
1144 - metadata for possible additional steps, e.g., sampling.
1145 -
1146 - The API assumes seq_group_metadata_list is sorted by prefill -> decode.
1147 -
1148 - The result tensors and data structure also batches input in prefill
1149 - -> decode order. For example,
1150 -
1151 - - input_tokens[:num_prefill_tokens] contains prefill tokens.
1152 - - input_tokens[num_prefill_tokens:] contains decode tokens.
1153 -
1154 - If cuda graph is required, this API automatically pads inputs.
1155 - """
1156 - self.builder.prepare(finished_requests_ids)
1157 - for seq_group_metadata in seq_group_metadata_list:
1158 - try:
1159 - self.builder.add_seq_group(seq_group_metadata)
1160 - except Exception as e:
1161 - # Raise an exception that tracks the ID of the bad request
1162 - raise InputProcessingError(seq_group_metadata.request_id,
1163 - str(e)) from e
1164 -
1165 - self.builder.reset_cached_inter_data()
1166 -
1167 - return self.builder.build() # type: ignore
1168 -
1169 - @contextmanager
1170 - def set_in_profile_run(self):
1171 - self.in_profile_run = True
1172 - try:
1173 - yield
1174 - finally:
1175 - self.in_profile_run = False
1176 -
1177 - @torch.inference_mode()
1178 - def profile_run(self) -> None:
1179 - max_num_batched_tokens = \
1180 - self.scheduler_config.max_num_batched_tokens
1181 - max_num_seqs = self.scheduler_config.max_num_seqs
1182 - self._dummy_run(max_num_batched_tokens, max_num_seqs)
1183 -
1184 - def _add_dummy_loras(self, num_loras: int) -> list[LoRARequest]:
1185 - assert num_loras > 0
1186 - assert self.lora_manager is not None
1187 -
1188 - dummy_lora_requests: list[LoRARequest] = []
1189 - with self.lora_manager.dummy_lora_cache():
1190 - for idx in range(num_loras):
1191 - lora_id = idx + 1
1192 - dummy_lora_request = LoRARequest(
1193 - lora_name=f"warmup_{lora_id}",
1194 - lora_int_id=lora_id,
1195 - lora_path="/not/a/real/path",
1196 - )
1197 - self.lora_manager.add_dummy_lora(dummy_lora_request,
1198 - rank=LORA_WARMUP_RANK)
1199 - dummy_lora_requests.append(dummy_lora_request)
1200 - return dummy_lora_requests
1201 -
1202 - def _remove_dummy_loras(self):
1203 - # Remove dummy loras.
1204 - assert self.lora_manager is not None
1205 - self.remove_all_loras()
1206 -
1207 - def _dummy_run(self,
1208 - max_num_batched_tokens: int,
1209 - max_num_seqs: int = 1) -> None:
1210 - with self.set_in_profile_run():
1211 - # Enable top-k sampling to reflect the accurate memory usage.
1212 - sampling_params = \
1213 - SamplingParams(top_p=0.99, top_k=self.vocab_size - 1)
1214 -
1215 - # This represents the maximum number of different requests
1216 - # that will have unique loras, and therefore the max amount of
1217 - # memory consumption. Create dummy lora request copies from the
1218 - # lora request passed in, which contains a lora from the lora
1219 - # warmup path.
1220 - dummy_lora_requests: List[LoRARequest] = []
1221 - dummy_lora_requests_per_seq: List[LoRARequest] = []
1222 - if self.lora_config:
1223 - dummy_lora_requests = self._add_dummy_loras(
1224 - self.lora_config.max_loras)
1225 - assert len(dummy_lora_requests) == self.lora_config.max_loras
1226 - dummy_lora_requests_per_seq = [
1227 - dummy_lora_requests[idx % len(dummy_lora_requests)]
1228 - for idx in range(max_num_seqs)
1229 - ]
1230 -
1231 - # Profile memory usage with max_num_sequences sequences and the
1232 - # total number of tokens equal to max_num_batched_tokens.
1233 - seqs: List[SequenceGroupMetadata] = []
1234 - # Additional GPU memory may be needed for multi-modal encoding,
1235 - # which needs to be accounted for when calculating the GPU blocks
1236 - # for vLLM blocker manager.
1237 - # To exercise the worst scenario for GPU memory consumption,
1238 - # the number of seqs (batch_size) is chosen to maximize the number
1239 - # of images processed.
1240 -
1241 - max_mm_tokens = self.mm_registry.get_max_multimodal_tokens(
1242 - self.model_config)
1243 - if max_mm_tokens > 0:
1244 - max_num_seqs_orig = max_num_seqs
1245 - max_num_seqs = min(max_num_seqs,
1246 - max_num_batched_tokens // max_mm_tokens)
1247 - if max_num_seqs < 1:
1248 - expr = (f"min({max_num_seqs_orig}, "
1249 - f"{max_num_batched_tokens} // {max_mm_tokens})")
1250 - logger.warning(
1251 - "Computed max_num_seqs (%s) to be less than 1. "
1252 - "Setting it to the minimum value of 1.", expr)
1253 - max_num_seqs = 1
1254 -
1255 - batch_size = 0
1256 - for group_id in range(max_num_seqs):
1257 - seq_len = (max_num_batched_tokens // max_num_seqs +
1258 - (group_id < max_num_batched_tokens % max_num_seqs))
1259 - batch_size += seq_len
1260 -
1261 - dummy_data = self.input_registry \
1262 - .dummy_data_for_profiling(self.model_config,
1263 - seq_len,
1264 - self.mm_registry)
1265 -
1266 - seq = SequenceGroupMetadata(
1267 - request_id=str(group_id),
1268 - is_prompt=True,
1269 - seq_data={group_id: dummy_data.seq_data},
1270 - sampling_params=sampling_params,
1271 - block_tables=None,
1272 - lora_request=dummy_lora_requests_per_seq[group_id]
1273 - if dummy_lora_requests_per_seq else None,
1274 - multi_modal_data=dummy_data.multi_modal_data,
1275 - multi_modal_placeholders=dummy_data.
1276 - multi_modal_placeholders,
1277 - )
1278 - seqs.append(seq)
1279 -
1280 - # Run the model with the dummy inputs.
1281 - num_layers = self.model_config.get_num_layers(self.parallel_config)
1282 - # use an empty tensor instead of `None`` to force Dynamo to pass
1283 - # it by reference, rather by specializing on the value ``None``.
1284 - # the `dtype` argument does not matter, and we use `float32` as
1285 - # a placeholder (it has wide hardware support).
1286 - # it is important to create tensors inside the loop, rather than
1287 - # multiplying the list, to avoid Dynamo from treating them as
1288 - # tensor aliasing.
1289 - kv_caches = [
1290 - torch.tensor([], dtype=torch.float32, device=self.device)
1291 - for _ in range(num_layers)
1292 - ]
1293 - finished_requests_ids = [seq.request_id for seq in seqs]
1294 - model_input = self.prepare_model_input(
1295 - seqs, finished_requests_ids=finished_requests_ids)
1296 - intermediate_tensors = None
1297 - if not get_pp_group().is_first_rank:
1298 - intermediate_tensors = \
1299 - self.model.make_empty_intermediate_tensors(
1300 - batch_size=batch_size,
1301 - dtype=self.model_config.dtype,
1302 - device=self.device)
1303 -
1304 - # Disable KV Scale Calculation for dummy data during profile run
1305 - if model_input.attn_metadata is not None:
1306 - model_input.attn_metadata.enable_kv_scales_calculation = False
1307 -
1308 - self.execute_model(model_input, kv_caches, intermediate_tensors)
1309 - torch.cuda.synchronize()
1310 - if self.lora_config:
1311 - self._remove_dummy_loras()
1312 -
1313 - return
1314 -
1315 - def remove_all_loras(self):
1316 - if not self.lora_manager:
1317 - raise RuntimeError("LoRA is not enabled.")
1318 - self.lora_manager.remove_all_adapters()
1319 -
1320 - def set_active_loras(self, lora_requests: Set[LoRARequest],
1321 - lora_mapping: LoRAMapping) -> None:
1322 - if not self.lora_manager:
1323 - raise RuntimeError("LoRA is not enabled.")
1324 - self.lora_manager.set_active_adapters(lora_requests, lora_mapping)
1325 -
1326 - def add_lora(self, lora_request: LoRARequest) -> bool:
1327 - if not self.lora_manager:
1328 - raise RuntimeError("LoRA is not enabled.")
1329 - return self.lora_manager.add_adapter(lora_request)
1330 -
1331 - def remove_lora(self, lora_id: int) -> bool:
1332 - if not self.lora_manager:
1333 - raise RuntimeError("LoRA is not enabled.")
1334 - return self.lora_manager.remove_adapter(lora_id)
1335 -
1336 - def pin_lora(self, lora_id: int) -> bool:
1337 - if not self.lora_manager:
1338 - raise RuntimeError("LoRA is not enabled.")
1339 - return self.lora_manager.pin_adapter(lora_id)
1340 -
1341 - def list_loras(self) -> Set[int]:
1342 - if not self.lora_manager:
1343 - raise RuntimeError("LoRA is not enabled.")
1344 - return self.lora_manager.list_adapters()
1345 -
1346 - @torch.inference_mode()
1347 - def capture_model(self, kv_caches: List[List[torch.Tensor]]) -> int:
1348 - """Cuda graph capture a model and return cudagraph memory
1349 - consumption in bytes.
1350 -
1351 - Note that CUDA graph's performance gain is negligible if number
1352 - of batched tokens are larger than 200. And since CUDA graph
1353 - requires fixed sized tensors, supporting large/variable batch
1354 - size requires high GPU memory overhead. Thus, vLLM only captures
1355 - decoding requests. Mixed batch (chunked prefill + decoding) or
1356 - prefill requests are not captured.
1357 -
1358 - Since it is used for decoding-only, it assumes there's only 1 token
1359 - per sequence in the batch.
1360 - """
1361 - assert not self.model_config.enforce_eager
1362 - logger.info("Capturing cudagraphs for decoding. This may lead to "
1363 - "unexpected consequences if the model is not static. To "
1364 - "run the model in eager mode, set 'enforce_eager=True' or "
1365 - "use '--enforce-eager' in the CLI. "
1366 - "If out-of-memory error occurs during cudagraph capture,"
1367 - " consider decreasing `gpu_memory_utilization` or "
1368 - "switching to eager mode. You can also reduce the "
1369 - "`max_num_seqs` as needed to decrease memory usage.")
1370 - start_time = time.perf_counter()
1371 - start_free_gpu_memory = torch.cuda.mem_get_info()[0]
1372 -
1373 - # Prepare dummy inputs. These will be reused for all batch sizes.
1374 - max_batch_size = self.max_batchsize_to_capture
1375 - input_tokens = torch.zeros(max_batch_size,
1376 - dtype=torch.long,
1377 - device=self.device)
1378 - input_positions = torch.zeros(max_batch_size,
1379 - dtype=torch.long,
1380 - device=self.device)
1381 - inputs_embeds = torch.zeros(
1382 - (max_batch_size, self.model_config.get_hidden_size()),
1383 - dtype=self.model_config.dtype,
1384 - device=self.device)
1385 - if self.model_config.uses_mrope:
1386 - input_positions = torch.tile(input_positions,
1387 - (3, 1)).cuda(device=self.device)
1388 - # Prepare dummy previous_hidden_states only if needed by the model.
1389 - # This is used by draft models such as EAGLE.
1390 - previous_hidden_states = None
1391 - if "previous_hidden_states" in inspect.signature(
1392 - self.model.forward).parameters:
1393 - previous_hidden_states = torch.empty(
1394 - [max_batch_size,
1395 - self.model_config.get_hidden_size()],
1396 - dtype=self.model_config.dtype,
1397 - device=self.device)
1398 -
1399 - intermediate_inputs = None
1400 - if not get_pp_group().is_first_rank:
1401 - intermediate_inputs = self.model.make_empty_intermediate_tensors(
1402 - batch_size=max_batch_size,
1403 - dtype=self.model_config.dtype,
1404 - device=self.device)
1405 -
1406 - dummy_lora_id: Optional[int] = None
1407 - dummy_lora_request: LoRARequest = []
1408 - if self.lora_config:
1409 - # The goal is to capture the LoRA kernels in cuda graphs.
1410 - # for this purpose, as single dummy lora is sufficient.
1411 - dummy_lora_requests = self._add_dummy_loras(num_loras=1)
1412 - assert len(dummy_lora_requests) == 1
1413 - dummy_lora_request = dummy_lora_requests[0]
1414 - dummy_lora_id = dummy_lora_request.lora_int_id
1415 -
1416 - with self.attn_state.graph_capture(max_batch_size), graph_capture(
1417 - self.device) as graph_capture_context:
1418 - # NOTE: Capturing the largest batch size first may help reduce the
1419 - # memory usage of CUDA graph.
1420 - for virtual_engine in range(
1421 - self.parallel_config.pipeline_parallel_size):
1422 - # We need to not only iterate over batch sizes, but also whether
1423 - # to use inputs_embeds or not, hence we use the cartesian
1424 - # product.
1425 - cudagraph_capture_sizes = self.vllm_config.compilation_config\
1426 - .cudagraph_capture_sizes
1427 - cudagraph_inputs_embeds = ((
1428 - True, False) if self.model_config.enable_prompt_embeds else
1429 - (False, ))
1430 - compilation_cases = itertools.product(
1431 - cudagraph_capture_sizes,
1432 - cudagraph_inputs_embeds,
1433 - )
1434 - # Only rank 0 should print progress bar during capture
1435 - if get_tensor_model_parallel_rank() == 0:
1436 - compilation_cases = tqdm(
1437 - list(compilation_cases),
1438 - disable=not self.load_config.use_tqdm_on_load,
1439 - desc="Capturing CUDA graph shapes")
1440 - for batch_size, use_inputs_embeds in compilation_cases:
1441 - attn_metadata = (
1442 - self.attn_state.graph_capture_get_metadata_for_batch(
1443 - batch_size,
1444 - is_encoder_decoder_model=self.model_config.
1445 - is_encoder_decoder))
1446 - # Disable KV Scale Calculation for graph capture
1447 - attn_metadata.enable_kv_scales_calculation = False
1448 - if self.lora_config:
1449 - lora_mapping = LoRAMapping(
1450 - **dict(index_mapping=[dummy_lora_id] * batch_size,
1451 - prompt_mapping=[dummy_lora_id] * batch_size,
1452 - is_prefill=False))
1453 - self.set_active_loras(set([dummy_lora_request]),
1454 - lora_mapping)
1455 -
1456 - graph_runner = CUDAGraphRunner(
1457 - self.model, self.attn_backend.get_name(),
1458 - self.attn_state.graph_clone(batch_size),
1459 - self.model_config.is_encoder_decoder)
1460 -
1461 - capture_inputs = {
1462 - "input_ids":
1463 - input_tokens[:batch_size],
1464 - "inputs_embeds":
1465 - inputs_embeds[:batch_size]
1466 - if use_inputs_embeds else None,
1467 - "positions":
1468 - input_positions[..., :batch_size],
1469 - "intermediate_inputs":
1470 - intermediate_inputs[:batch_size]
1471 - if intermediate_inputs is not None else None,
1472 - "kv_caches":
1473 - kv_caches[virtual_engine],
1474 - "attn_metadata":
1475 - attn_metadata,
1476 - "memory_pool":
1477 - self.graph_memory_pool,
1478 - "stream":
1479 - graph_capture_context.stream
1480 - }
1481 - if previous_hidden_states is not None:
1482 - capture_inputs[
1483 - "previous_hidden_states"] = previous_hidden_states[:
1484 - batch_size]
1485 -
1486 - if self.has_inner_state:
1487 - # Only used by Mamba-based models CUDA graph atm (Jamba)
1488 - capture_inputs.update({
1489 - "seqlen_agnostic_capture_inputs":
1490 - self.model.get_seqlen_agnostic_capture_inputs(
1491 - batch_size)
1492 - })
1493 - if self.model_config.is_encoder_decoder:
1494 - # add the additional inputs to capture for
1495 - # encoder-decoder models.
1496 - self._update_inputs_to_capture_for_enc_dec_model(
1497 - capture_inputs)
1498 -
1499 - with set_forward_context(attn_metadata, self.vllm_config,
1500 - virtual_engine):
1501 - graph_runner.capture(**capture_inputs)
1502 - self.graph_memory_pool = graph_runner.graph.pool()
1503 - self.graph_runners[virtual_engine][(
1504 - batch_size, use_inputs_embeds)] = graph_runner
1505 -
1506 - if self.lora_config:
1507 - self._remove_dummy_loras()
1508 -
1509 - end_time = time.perf_counter()
1510 - end_free_gpu_memory = torch.cuda.mem_get_info()[0]
1511 - elapsed_time = end_time - start_time
1512 - cuda_graph_size = start_free_gpu_memory - end_free_gpu_memory
1513 - # This usually takes < 10 seconds.
1514 - logger.info("Graph capturing finished in %.0f secs, took %.2f GiB",
1515 - elapsed_time, cuda_graph_size / GiB_bytes)
1516 - return cuda_graph_size
1517 -
1518 - def _update_inputs_to_capture_for_enc_dec_model(self,
1519 - capture_inputs: Dict[str,
1520 - Any]):
1521 - """
1522 - Updates the set of input tensors needed for CUDA graph capture in an
1523 - encoder-decoder model.
1524 -
1525 - This method modifies the provided `capture_inputs` dictionary by
1526 - adding tensors specific to encoder-decoder specific models that
1527 - need to be captured for CUDA Graph replay.
1528 - """
1529 - # During the decode phase encoder_input_ids and encoder_positions are
1530 - # unset. Do the same thing for graph capture.
1531 - capture_inputs["encoder_input_ids"] = torch.tensor([],
1532 - dtype=torch.long,
1533 - device=self.device)
1534 - capture_inputs["encoder_positions"] = torch.tensor([],
1535 - dtype=torch.long,
1536 - device=self.device)
1537 -
1538 - @property
1539 - def vocab_size(self) -> int:
1540 - return self.model_config.get_vocab_size()
1541 -
1542 -
1543 -class ModelRunner(GPUModelRunnerBase[ModelInputForGPUWithSamplingMetadata]):
1544 - """
1545 - GPU model runner with sampling step.
1546 - """
1547 - _model_input_cls: Type[ModelInputForGPUWithSamplingMetadata] = (
1548 - ModelInputForGPUWithSamplingMetadata)
1549 - _builder_cls: Type[ModelInputForGPUBuilder] = ModelInputForGPUBuilder
1550 -
1551 - def make_model_input_from_broadcasted_tensor_dict(
1552 - self,
1553 - tensor_dict: Dict[str, Any],
1554 - ) -> ModelInputForGPUWithSamplingMetadata:
1555 - model_input = \
1556 - ModelInputForGPUWithSamplingMetadata.from_broadcasted_tensor_dict(
1557 - tensor_dict,
1558 - attn_backend=self.attn_backend,
1559 - )
1560 - return model_input
1561 -
1562 - def prepare_model_input(
1563 - self,
1564 - seq_group_metadata_list: List[SequenceGroupMetadata],
1565 - virtual_engine: int = 0,
1566 - finished_requests_ids: Optional[List[str]] = None,
1567 - ) -> ModelInputForGPUWithSamplingMetadata:
1568 - """Prepare the model input based on a given sequence group, including
1569 - metadata for the sampling step.
1570 -
1571 - The API assumes seq_group_metadata_list is sorted by prefill -> decode.
1572 -
1573 - The result tensors and data structure also batches input in prefill
1574 - -> decode order. For example,
1575 -
1576 - - input_tokens[:num_prefill_tokens] contains prefill tokens.
1577 - - input_tokens[num_prefill_tokens:] contains decode tokens.
1578 -
1579 - If cuda graph is required, this API automatically pads inputs.
1580 - """
1581 - model_input = self._prepare_model_input_tensors(
1582 - seq_group_metadata_list, finished_requests_ids)
1583 - if get_pp_group().is_last_rank:
1584 - # Sampling metadata is only required for the final pp group
1585 - generators = self.get_generators(finished_requests_ids)
1586 - sampling_metadata = SamplingMetadata.prepare(
1587 - seq_group_metadata_list, model_input.seq_lens,
1588 - model_input.query_lens, self.device, self.pin_memory,
1589 - generators, self.sampling_metadata_cache)
1590 - else:
1591 - sampling_metadata = None
1592 - is_prompt = (seq_group_metadata_list[0].is_prompt
1593 - if seq_group_metadata_list else None)
1594 - return dataclasses.replace(model_input,
1595 - sampling_metadata=sampling_metadata,
1596 - is_prompt=is_prompt,
1597 - virtual_engine=virtual_engine)
1598 -
1599 - @torch.inference_mode()
1600 - def execute_model(
1601 - self,
1602 - model_input: ModelInputForGPUWithSamplingMetadata,
1603 - kv_caches: List[torch.Tensor],
1604 - intermediate_tensors: Optional[IntermediateTensors] = None,
1605 - num_steps: int = 1,
1606 - **kwargs,
1607 - ) -> Optional[Union[List[SamplerOutput], IntermediateTensors]]:
1608 - if num_steps > 1:
1609 - raise ValueError("num_steps > 1 is not supported in ModelRunner")
1610 -
1611 - if self.lora_config:
1612 - assert model_input.lora_requests is not None
1613 - assert model_input.lora_mapping is not None
1614 - self.set_active_loras(model_input.lora_requests,
1615 - model_input.lora_mapping)
1616 -
1617 - self.attn_state.begin_forward(model_input)
1618 -
1619 - # Currently cuda graph is only supported by the decode phase.
1620 - assert model_input.attn_metadata is not None
1621 - prefill_meta = model_input.attn_metadata.prefill_metadata
1622 - decode_meta = model_input.attn_metadata.decode_metadata
1623 - # TODO(andoorve): We can remove this once all
1624 - # virtual engines share the same kv cache.
1625 - virtual_engine = model_input.virtual_engine
1626 - previous_hidden_states = kwargs.get("previous_hidden_states")
1627 - if prefill_meta is None and decode_meta.use_cuda_graph:
1628 - assert model_input.input_tokens is not None
1629 - graph_batch_size = model_input.input_tokens.shape[0]
1630 - use_inputs_embeds = model_input.inputs_embeds is not None
1631 - model_executable = self.graph_runners[virtual_engine][(
1632 - graph_batch_size, use_inputs_embeds)]
1633 - if previous_hidden_states is not None:
1634 - previous_hidden_states = torch.cat([
1635 - previous_hidden_states,
1636 - torch.empty([
1637 - graph_batch_size - previous_hidden_states.shape[0],
1638 - *previous_hidden_states.shape[1:]
1639 - ],
1640 - dtype=previous_hidden_states.dtype,
1641 - device=previous_hidden_states.device)
1642 - ])
1643 - else:
1644 - model_executable = self.model
1645 -
1646 - # Receive KV cache in distributed KV cache transfer setting
1647 - # In disagg prefill setting, it will also recv hidden states and bypass
1648 - # model forwarding
1649 - # In KV cache database setting, it will change the model input so that
1650 - # we can skip prefilling on tokens that successfully received KV caches
1651 - # NOTE: The receive operation is blocking
1652 - bypass_model_exec = False
1653 - if self.need_recv_kv(model_input, kv_caches):
1654 - hidden_or_intermediate_states, bypass_model_exec, model_input = \
1655 - get_kv_transfer_group().recv_kv_caches_and_hidden_states(
1656 - # model is used to know which layer the current worker
1657 - # is working on, so that we can receive KV for only those
1658 - # layers.
1659 - model_executable,
1660 - model_input,
1661 - kv_caches=kv_caches
1662 - )
1663 -
1664 - multi_modal_kwargs = model_input.multi_modal_kwargs or {}
1665 - seqlen_agnostic_kwargs = {
1666 - "finished_requests_ids": model_input.finished_requests_ids,
1667 - "request_ids_to_seq_ids": model_input.request_ids_to_seq_ids,
1668 - } if self.has_inner_state else {}
1669 - model_kwargs = {}
1670 - if previous_hidden_states is not None:
1671 - model_kwargs["previous_hidden_states"] = previous_hidden_states
1672 - if (self.observability_config is not None
1673 - and self.observability_config.collect_model_forward_time):
1674 - model_forward_start = torch.cuda.Event(enable_timing=True)
1675 - model_forward_end = torch.cuda.Event(enable_timing=True)
1676 - model_forward_start.record()
1677 -
1678 - if not bypass_model_exec:
1679 - with set_forward_context(model_input.attn_metadata,
1680 - self.vllm_config, virtual_engine):
1681 - hidden_or_intermediate_states = model_executable(
1682 - input_ids=model_input.input_tokens,
1683 - inputs_embeds=model_input.inputs_embeds,
1684 - positions=model_input.input_positions,
1685 - intermediate_tensors=intermediate_tensors,
1686 - **MultiModalKwargs.as_kwargs(
1687 - multi_modal_kwargs,
1688 - device=self.device,
1689 - ),
1690 - **seqlen_agnostic_kwargs,
1691 - **model_kwargs,
1692 - )
1693 -
1694 - if (self.observability_config is not None
1695 - and self.observability_config.collect_model_forward_time):
1696 - model_forward_end.record()
1697 -
1698 - # Sending KV cache in distributed KV cache transfer setting
1699 - # NOTE: the send operation is non-blocking
1700 - if self.need_send_kv(model_input, kv_caches):
1701 - get_kv_transfer_group().send_kv_caches_and_hidden_states(
1702 - # model_executable is used to know which layer the current
1703 - # worker is working on, so that we can send KV for only those
1704 - # layers.
1705 - model_executable,
1706 - model_input,
1707 - kv_caches,
1708 - hidden_or_intermediate_states,
1709 - )
1710 -
1711 - # Compute the logits in the last pipeline stage.
1712 - if not get_pp_group().is_last_rank:
1713 - if (self.is_driver_worker
1714 - and hidden_or_intermediate_states is not None
1715 - and isinstance(hidden_or_intermediate_states,
1716 - IntermediateTensors)
1717 - and self.observability_config is not None
1718 - and self.observability_config.collect_model_forward_time):
1719 - model_forward_end.synchronize()
1720 - model_forward_time = model_forward_start.elapsed_time(
1721 - model_forward_end)
1722 - orig_model_forward_time = 0.0
1723 - if intermediate_tensors is not None:
1724 - orig_model_forward_time = intermediate_tensors.tensors.get(
1725 - "model_forward_time", torch.tensor(0.0)).item()
1726 - hidden_or_intermediate_states.tensors["model_forward_time"] = (
1727 - torch.tensor(model_forward_time + orig_model_forward_time))
1728 - return hidden_or_intermediate_states
1729 -
1730 - logits = self.model.compute_logits(hidden_or_intermediate_states,
1731 - model_input.sampling_metadata)
1732 -
1733 - if self.is_driver_worker:
1734 - if model_input.async_callback is not None:
1735 - model_input.async_callback()
1736 -
1737 - # Sample the next token.
1738 - assert isinstance(self.sampler, Sampler)
1739 - orig_include_gpu_probs = self.sampler.include_gpu_probs_tensor
1740 - if model_input.inputs_embeds is not None:
1741 - self.sampler.include_gpu_probs_tensor = True
1742 -
1743 - output: SamplerOutput = self.sampler(
1744 - logits=logits,
1745 - sampling_metadata=model_input.sampling_metadata,
1746 - )
1747 - if (self.observability_config is not None
1748 - and self.observability_config.collect_model_forward_time
1749 - and output is not None):
1750 - model_forward_end.synchronize()
1751 - model_forward_time = model_forward_start.elapsed_time(
1752 - model_forward_end)
1753 - orig_model_forward_time = 0.0
1754 - if intermediate_tensors is not None:
1755 - orig_model_forward_time = intermediate_tensors.tensors.get(
1756 - "model_forward_time", torch.tensor(0.0)).item()
1757 - # If there are multiple workers, we are still tracking the
1758 - # latency from the start time of the driver worker to the end
1759 - # time of the driver worker. The model forward time will then
1760 - # end up covering the communication time as well.
1761 - output.model_forward_time = (orig_model_forward_time +
1762 - model_forward_time)
1763 -
1764 - if model_input.inputs_embeds is not None:
1765 - if self.is_driver_worker:
1766 - sampled_token_ids = []
1767 - valid_outputs = []
1768 - for sequence_group_output in output.outputs:
1769 - if len(sequence_group_output.samples) == 0:
1770 - continue
1771 - assert len(sequence_group_output.samples) == 1
1772 - valid_outputs.append(sequence_group_output)
1773 - sampled_token_ids.append(
1774 - sequence_group_output.samples[0].output_token)
1775 - sampled_token_ids = torch.tensor(sampled_token_ids).to(
1776 - self.device)
1777 - sampled_token_ids = broadcast_tensor_dict(
1778 - {"sampled_token_ids":
1779 - sampled_token_ids})["sampled_token_ids"]
1780 - else:
1781 - sampled_token_ids = broadcast_tensor_dict(
1782 - )["sampled_token_ids"]
1783 - if len(sampled_token_ids) > 0:
1784 - sampled_token_embeds = \
1785 - self.model.get_input_embeddings(sampled_token_ids)
1786 - if self.is_driver_worker:
1787 - self.sampler.include_gpu_probs_tensor = \
1788 - orig_include_gpu_probs
1789 - for i, sequence_group_output in enumerate(valid_outputs):
1790 - sequence_group_output.samples[0].output_embed = \
1791 - sampled_token_embeds[i]
1792 -
1793 - if not self.is_driver_worker:
1794 - return []
1795 -
1796 - if self.return_hidden_states:
1797 - # we only need to pass hidden states of most recent token
1798 - assert model_input.sampling_metadata is not None
1799 - indices = model_input.sampling_metadata.selected_token_indices
1800 - if model_input.is_prompt:
1801 - hidden_states = hidden_or_intermediate_states.index_select(
1802 - 0, indices)
1803 - output.prefill_hidden_states = hidden_or_intermediate_states
1804 - elif decode_meta.use_cuda_graph:
1805 - hidden_states = hidden_or_intermediate_states[:len(indices)]
1806 - else:
1807 - hidden_states = hidden_or_intermediate_states
1808 -
1809 - output.hidden_states = hidden_states
1810 -
1811 - return [output]
1812 -
1813 - def need_recv_kv(self, model_input: ModelInputForGPUWithSamplingMetadata,
1814 - kv_caches: List[torch.Tensor]) -> bool:
1815 - """Check if we need to receive kv-cache from the other worker.
1816 - We need to receive KV when
1817 - 1. current vLLM instance is KV cache consumer/decode vLLM instance
1818 - 2. this batch is not a profiling run
1819 - 3. this batch is a prefill run
1820 -
1821 - Args:
1822 - model_input: input to the model executable
1823 - kv_caches: vLLM's paged memory
1824 - """
1825 -
1826 - if self.vllm_config.kv_transfer_config is None:
1827 - return False
1828 -
1829 - if model_input.attn_metadata is None:
1830 - raise ValueError("model_input.attn_metadata cannot be None")
1831 -
1832 - prefill_meta = model_input.attn_metadata.prefill_metadata
1833 -
1834 - # check if the current run is profiling
1835 - is_profile_run = (kv_caches[0].numel() == 0)
1836 - # check if the current run is prefill
1837 - is_prefill_run = prefill_meta is not None
1838 -
1839 - return self.vllm_config.kv_transfer_config.is_kv_consumer and (
1840 - not is_profile_run) and is_prefill_run
1841 -
1842 - def need_send_kv(self, model_input: ModelInputForGPUWithSamplingMetadata,
1843 - kv_caches: List[torch.Tensor]) -> bool:
1844 - """Check if we need to send kv-cache to the other worker.
1845 - We need to send KV when
1846 - 1. current vLLM instance is KV cache producer/prefill vLLM instance
1847 - 2. this batch is not a profiling run
1848 - 3. this batch is a prefill run
1849 -
1850 - Args:
1851 - model_input: input to the model executable
1852 - kv_caches: vLLM's paged memory
1853 - """
1854 -
1855 - if self.vllm_config.kv_transfer_config is None:
1856 - return False
1857 -
1858 - if model_input.attn_metadata is None:
1859 - raise ValueError("model_input.attn_metadata cannot be None")
1860 -
1861 - prefill_meta = model_input.attn_metadata.prefill_metadata
1862 -
1863 - # check if the current run is profiling
1864 - is_profile_run = (kv_caches[0].numel() == 0)
1865 - # check if the current run is prefill
1866 - is_prefill_run = prefill_meta is not None
1867 -
1868 - return self.vllm_config.kv_transfer_config.is_kv_producer and (
1869 - not is_profile_run) and is_prefill_run
1870 -
1871 -
1872 -# NOTE: this is nn.Module so the profiler can properly capture/group
1873 -# kernels calls made within the graph
1874 -class CUDAGraphRunner(nn.Module):
1875 -
1876 - def __init__(self, model: nn.Module, backend_name: str,
1877 - attn_state: AttentionState, is_encoder_decoder_model: bool):
1878 - super().__init__()
1879 - self.model = model
1880 - self.backend_name = backend_name
1881 - self.attn_state = attn_state
1882 -
1883 - self.input_buffers: Dict[str, torch.Tensor] = {}
1884 - self.output_buffers: Dict[str, torch.Tensor] = {}
1885 -
1886 - self._graph: Optional[torch.cuda.CUDAGraph] = None
1887 - self._is_encoder_decoder_model = is_encoder_decoder_model
1888 -
1889 - @property
1890 - def graph(self):
1891 - assert self._graph is not None
1892 - return self._graph
1893 -
1894 - def capture(
1895 - self,
1896 - input_ids: torch.Tensor,
1897 - inputs_embeds: Optional[torch.Tensor],
1898 - positions: torch.Tensor,
1899 - intermediate_inputs: Optional[IntermediateTensors],
1900 - kv_caches: List[torch.Tensor],
1901 - attn_metadata: AttentionMetadata,
1902 - memory_pool: Optional[Tuple[int, int]],
1903 - stream: torch.cuda.Stream,
1904 - **kwargs,
1905 - ):
1906 - assert self._graph is None
1907 - # Run the model a few times without capturing the graph.
1908 - # This is to make sure that the captured graph does not include the
1909 - # kernel launches for initial benchmarking (e.g., Triton autotune).
1910 - # Note one iteration is not enough for torch.compile
1911 - for _ in range(_NUM_WARMUP_ITERS):
1912 - self.model(
1913 - input_ids=input_ids,
1914 - inputs_embeds=inputs_embeds,
1915 - positions=positions,
1916 - intermediate_tensors=intermediate_inputs,
1917 - **kwargs,
1918 - )
1919 - # Wait for the warm up operations to finish before proceeding with
1920 - # Graph Capture.
1921 - torch.cuda.synchronize()
1922 - # Capture the graph.
1923 - self._graph = torch.cuda.CUDAGraph()
1924 - with torch.cuda.graph(self._graph, pool=memory_pool, stream=stream):
1925 - output_hidden_or_intermediate_states = self.model(
1926 - input_ids=input_ids,
1927 - **({
1928 - "inputs_embeds": inputs_embeds,
1929 - } if inputs_embeds is not None else {}),
1930 - positions=positions,
1931 - intermediate_tensors=intermediate_inputs,
1932 - **kwargs,
1933 - )
1934 -
1935 - if isinstance(output_hidden_or_intermediate_states, torch.Tensor):
1936 - hidden_or_intermediate_states = weak_ref_tensor(
1937 - output_hidden_or_intermediate_states)
1938 - elif isinstance(output_hidden_or_intermediate_states,
1939 - IntermediateTensors):
1940 - hidden_or_intermediate_states = IntermediateTensors(
1941 - tensors={
1942 - key: weak_ref_tensor(value)
1943 - for key, value in
1944 - output_hidden_or_intermediate_states.tensors.items()
1945 - })
1946 -
1947 - del output_hidden_or_intermediate_states
1948 - # make sure `output_hidden_or_intermediate_states` is deleted
1949 - # in the graph's memory pool
1950 - gc.collect()
1951 - torch.cuda.synchronize()
1952 -
1953 - # Save the input and output buffers.
1954 - self.input_buffers = {
1955 - "input_ids":
1956 - input_ids,
1957 - **({
1958 - "inputs_embeds": inputs_embeds,
1959 - } if inputs_embeds is not None else {}),
1960 - "positions":
1961 - positions,
1962 - "kv_caches":
1963 - kv_caches,
1964 - **self.attn_state.get_graph_input_buffers(
1965 - attn_metadata, self._is_encoder_decoder_model),
1966 - **kwargs,
1967 - }
1968 - if intermediate_inputs is not None:
1969 - self.input_buffers.update(intermediate_inputs.tensors)
1970 - if get_pp_group().is_last_rank:
1971 - self.output_buffers = {
1972 - "hidden_states": hidden_or_intermediate_states
1973 - }
1974 - else:
1975 - self.output_buffers = hidden_or_intermediate_states
1976 -
1977 - def forward(
1978 - self,
1979 - input_ids: torch.Tensor,
1980 - inputs_embeds: Optional[torch.Tensor],
1981 - positions: torch.Tensor,
1982 - intermediate_tensors: Optional[IntermediateTensors],
1983 - **kwargs,
1984 - ) -> torch.Tensor:
1985 - attn_metadata: AttentionMetadata = get_forward_context().attn_metadata
1986 -
1987 - # Copy the input tensors to the input buffers.
1988 - self.input_buffers["input_ids"].copy_(input_ids, non_blocking=True)
1989 - if positions is not None:
1990 - # in some case like MLA, it will reuse positions in metadata
1991 - # but truncate them to the original size
1992 - # so the shape is not padded, we need to copy partial only
1993 - self.input_buffers["positions"][:positions.shape[0]].copy_(
1994 - positions, non_blocking=True)
1995 - if inputs_embeds is not None:
1996 - self.input_buffers["inputs_embeds"][:inputs_embeds.shape[0]].copy_(
1997 - inputs_embeds, non_blocking=True)
1998 -
1999 - if self.backend_name != "NO_ATTENTION":
2000 - self.input_buffers["slot_mapping"].copy_(
2001 - attn_metadata.slot_mapping, non_blocking=True)
2002 -
2003 - self.attn_state.prepare_graph_input_buffers(
2004 - self.input_buffers, attn_metadata, self._is_encoder_decoder_model)
2005 -
2006 - if "seqlen_agnostic_capture_inputs" in self.input_buffers:
2007 - self.model.copy_inputs_before_cuda_graphs(self.input_buffers,
2008 - **kwargs)
2009 -
2010 - if "previous_hidden_states" in self.input_buffers:
2011 - self.input_buffers["previous_hidden_states"].copy_(
2012 - kwargs["previous_hidden_states"], non_blocking=True)
2013 -
2014 - if intermediate_tensors is not None:
2015 - for key in intermediate_tensors.tensors:
2016 - if key != "model_execute_time" and key != "model_forward_time":
2017 - self.input_buffers[key].copy_(intermediate_tensors[key],
2018 - non_blocking=True)
2019 - if self._is_encoder_decoder_model:
2020 - self.input_buffers["encoder_input_ids"].copy_(
2021 - kwargs['encoder_input_ids'], non_blocking=True)
2022 - self.input_buffers["encoder_positions"].copy_(
2023 - kwargs['encoder_positions'], non_blocking=True)
2024 -
2025 - # Run the graph.
2026 - self.graph.replay()
2027 - # Return the output tensor.
2028 - if get_pp_group().is_last_rank:
2029 - return self.output_buffers["hidden_states"]
2030 -
2031 - return self.output_buffers
vllm/worker/worker.py deleted
-666
@@ -1,666 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -"""A GPU worker class."""
4 -import gc
5 -import os
6 -from contextlib import nullcontext
7 -from typing import Dict, List, Optional, Set, Tuple, Type, Union
8 -
9 -import torch
10 -import torch.distributed
11 -
12 -import vllm.envs as envs
13 -from vllm.attention.layer import Attention
14 -from vllm.config import VllmConfig, get_layers_from_vllm_config
15 -from vllm.device_allocator.cumem import CuMemAllocator
16 -from vllm.distributed import (ensure_model_parallel_initialized,
17 - init_distributed_environment,
18 - set_custom_all_reduce)
19 -from vllm.distributed.kv_transfer import ensure_kv_transfer_initialized
20 -from vllm.logger import init_logger
21 -from vllm.lora.request import LoRARequest
22 -from vllm.model_executor import set_random_seed
23 -from vllm.model_executor.layers.sampler import SamplerOutput
24 -from vllm.model_executor.model_loader.tensorizer import TensorizerConfig
25 -from vllm.platforms import current_platform
26 -from vllm.sequence import (ExecuteModelRequest, IntermediateTensors,
27 - SequenceGroupMetadata, SequenceGroupMetadataDelta)
28 -from vllm.utils import (GiB_bytes, MemorySnapshot, bind_kv_cache,
29 - memory_profiling)
30 -from vllm.worker.cache_engine import CacheEngine
31 -from vllm.worker.model_runner import GPUModelRunnerBase, ModelRunner
32 -from vllm.worker.worker_base import (LocalOrDistributedWorkerBase, WorkerBase,
33 - WorkerInput)
34 -
35 -logger = init_logger(__name__)
36 -
37 -
38 -class Worker(LocalOrDistributedWorkerBase):
39 - """A worker class that executes (a partition of) the model on a GPU.
40 -
41 - Each worker is associated with a single GPU. The worker is responsible for
42 - maintaining the KV cache and executing the model on the GPU. In case of
43 - distributed inference, each worker is assigned a partition of the model.
44 - """
45 -
46 - def __init__(
47 - self,
48 - vllm_config: VllmConfig,
49 - local_rank: int,
50 - rank: int,
51 - distributed_init_method: str,
52 - is_driver_worker: bool = False,
53 - model_runner_cls: Optional[Type[GPUModelRunnerBase]] = None,
54 - ) -> None:
55 - WorkerBase.__init__(self, vllm_config)
56 - self.parallel_config.rank = rank
57 - self.local_rank = local_rank
58 - self.rank = rank
59 - self.distributed_init_method = distributed_init_method
60 - self.is_driver_worker = is_driver_worker
61 - if self.model_config.trust_remote_code:
62 - # note: lazy import to avoid importing torch before initializing
63 - from vllm.utils import init_cached_hf_modules
64 - init_cached_hf_modules()
65 -
66 - # Return hidden states from target model if the draft model is an
67 - # mlp_speculator
68 - speculative_config = self.speculative_config
69 - model_config = self.model_config
70 - speculative_args = {} if speculative_config is None \
71 - or (speculative_config.draft_model_config.hf_config.model_type ==
72 - model_config.hf_config.model_type) \
73 - or (speculative_config.draft_model_config.hf_config.model_type
74 - not in ("medusa",
75 - "mlp_speculator",
76 - "eagle",
77 - "deepseek_mtp",
78 - "glm4_moe_mtp",
79 - "mimo_mtp",
80 - "ernie_mtp",
81 - "qwen3_next_mtp")) \
82 - else {"return_hidden_states": True}
83 -
84 - self.model_runner: GPUModelRunnerBase = ModelRunner(
85 - vllm_config=self.vllm_config,
86 - kv_cache_dtype=self.cache_config.cache_dtype,
87 - is_driver_worker=is_driver_worker,
88 - **speculative_args,
89 - )
90 - if model_runner_cls is not None:
91 - self.model_runner = model_runner_cls(self.model_runner)
92 -
93 - # Uninitialized cache engine. Will be initialized by
94 - # initialize_cache.
95 - self.cache_engine: List[CacheEngine]
96 - self.gpu_cache: Optional[List[List[torch.Tensor]]] = None
97 - self._seq_group_metadata_cache: Dict[str, SequenceGroupMetadata] = {}
98 -
99 - # Buffers saved before sleep
100 - self._sleep_saved_buffers: Dict[str, torch.Tensor] = {}
101 -
102 - # Torch profiler. Enabled and configured through env vars:
103 - # VLLM_TORCH_PROFILER_DIR=/path/to/save/trace
104 - if envs.VLLM_TORCH_PROFILER_DIR:
105 - torch_profiler_trace_dir = envs.VLLM_TORCH_PROFILER_DIR
106 - logger.info("Profiling enabled. Traces will be saved to: %s",
107 - torch_profiler_trace_dir)
108 - self.profiler = torch.profiler.profile(
109 - activities=[
110 - torch.profiler.ProfilerActivity.CPU,
111 - torch.profiler.ProfilerActivity.CUDA,
112 - ],
113 - with_stack=True,
114 - on_trace_ready=torch.profiler.tensorboard_trace_handler(
115 - torch_profiler_trace_dir, use_gzip=True))
116 - else:
117 - self.profiler = None
118 -
119 - def start_profile(self):
120 - if self.profiler is None:
121 - raise RuntimeError("Profiler is not enabled.")
122 - self.profiler.start()
123 -
124 - def stop_profile(self):
125 - if self.profiler is None:
126 - raise RuntimeError("Profiler is not enabled.")
127 - self.profiler.stop()
128 - # only print profiler results on rank 0
129 - if self.local_rank == 0:
130 - print(self.profiler.key_averages().table(
131 - sort_by="self_cuda_time_total"))
132 -
133 - def sleep(self, level: int = 1) -> None:
134 - free_bytes_before_sleep = torch.cuda.mem_get_info()[0]
135 -
136 - # Save the buffers before level 2 sleep
137 - if level == 2:
138 - model = self.model_runner.model
139 - self._sleep_saved_buffers = {
140 - name: buffer.cpu().clone()
141 - for name, buffer in model.named_buffers()
142 - }
143 -
144 - allocator = CuMemAllocator.get_instance()
145 - allocator.sleep(offload_tags=("weights", ) if level == 1 else tuple())
146 - free_bytes_after_sleep, total = torch.cuda.mem_get_info()
147 - freed_bytes = free_bytes_after_sleep - free_bytes_before_sleep
148 - used_bytes = total - free_bytes_after_sleep
149 - assert freed_bytes >= 0, "Memory usage increased after sleeping."
150 - logger.info(
151 - "Sleep mode freed %.2f GiB memory, "
152 - "%.2f GiB memory is still in use.", freed_bytes / GiB_bytes,
153 - used_bytes / GiB_bytes)
154 -
155 - def wake_up(self, tags: Optional[list[str]] = None) -> None:
156 - allocator = CuMemAllocator.get_instance()
157 - allocator.wake_up(tags=tags)
158 -
159 - # Restore the buffers after level 2 sleep
160 - if len(self._sleep_saved_buffers):
161 - model = self.model_runner.model
162 - for name, buffer in model.named_buffers():
163 - if name in self._sleep_saved_buffers:
164 - buffer.data.copy_(self._sleep_saved_buffers[name].data)
165 - self._sleep_saved_buffers = {}
166 -
167 - def init_device(self) -> None:
168 - if self.device_config.device.type == "cuda":
169 - # torch.distributed.all_reduce does not free the input tensor until
170 - # the synchronization point. This causes the memory usage to grow
171 - # as the number of all_reduce calls increases. This env var disables
172 - # this behavior.
173 - # Related issue:
174 - # https://discuss.pytorch.org/t/cuda-allocation-lifetime-for-inputs-to-distributed-all-reduce/191573
175 - os.environ["TORCH_NCCL_AVOID_RECORD_STREAMS"] = "1"
176 -
177 - # This env var set by Ray causes exceptions with graph building.
178 - os.environ.pop("NCCL_ASYNC_ERROR_HANDLING", None)
179 - self.device = torch.device(f"cuda:{self.local_rank}")
180 - torch.cuda.set_device(self.device)
181 -
182 - _check_if_gpu_supports_dtype(self.model_config.dtype)
183 - gc.collect()
184 - torch.cuda.empty_cache()
185 - torch.cuda.reset_peak_memory_stats()
186 - self.baseline_snapshot = MemorySnapshot()
187 - else:
188 - raise RuntimeError(
189 - f"Not support device type: {self.device_config.device}")
190 - # Initialize the distributed environment.
191 - init_worker_distributed_environment(self.vllm_config, self.rank,
192 - self.distributed_init_method,
193 - self.local_rank)
194 - # Set random seed.
195 - set_random_seed(self.model_config.seed)
196 -
197 - def load_model(self):
198 - if self.vllm_config.model_config.enable_sleep_mode:
199 - allocator = CuMemAllocator.get_instance()
200 - assert allocator.get_current_usage() == 0, (
201 - "Sleep mode can only be "
202 - "used for one instance per process.")
203 - context = allocator.use_memory_pool(tag="weights")
204 - else:
205 - context = nullcontext()
206 - with context:
207 - self.model_runner.load_model()
208 -
209 - def save_sharded_state(
210 - self,
211 - path: str,
212 - pattern: Optional[str] = None,
213 - max_size: Optional[int] = None,
214 - ) -> None:
215 - self.model_runner.save_sharded_state(
216 - path,
217 - pattern=pattern,
218 - max_size=max_size,
219 - )
220 -
221 - def save_tensorized_model(
222 - self,
223 - tensorizer_config: TensorizerConfig,
224 - ) -> None:
225 - self.model_runner.save_tensorized_model(
226 - tensorizer_config=tensorizer_config, )
227 -
228 - @torch.inference_mode()
229 - def determine_available_kv_cache_memory(self,
230 - total_gpu_memory: int) -> float:
231 - if kv_cache_memory_bytes := self.cache_config.kv_cache_memory_bytes:
232 - # still need a profile run which compiles the model for
233 - # max_num_batched_tokens
234 - self.model_runner.profile_run()
235 -
236 - GiB = lambda b: b / GiB_bytes
237 - msg = (
238 - f"Initial free memory "
239 - f"{GiB(self.baseline_snapshot.free_memory):.2f} "
240 - f"GiB, reserved {GiB(kv_cache_memory_bytes):.2f}GiB memory for "
241 - "KV Cache as specified by kv_cache_memory_bytes config and "
242 - "skipped memory profiling. This does does not respect the "
243 - "gpu_memory_utilization config. Only use kv_cache_memory_bytes "
244 - "config when you want manual control of KV cache memory "
245 - "size. If OOM'ed, check the difference of initial free "
246 - "memory between the current run and the previous run "
247 - "where kv_cache_memory_bytes is suggested and update it "
248 - "correspondingly.")
249 - logger.info(msg)
250 - return self.cache_config.kv_cache_memory_bytes
251 -
252 - # Execute a forward pass with dummy inputs to profile the memory usage
253 - # of the model.
254 - with memory_profiling(
255 - self.baseline_snapshot,
256 - weights_memory=self.model_runner.model_memory_usage) as result:
257 - self.model_runner.profile_run()
258 -
259 - self.non_torch_memory = result.non_torch_increase
260 - self.peak_activation_memory = result.torch_peak_increase
261 -
262 - self._assert_memory_footprint_increased_during_profiling()
263 -
264 - self.requested_memory = total_gpu_memory * \
265 - self.cache_config.gpu_memory_utilization
266 -
267 - self.available_kv_cache_memory = (self.requested_memory -
268 - result.non_kv_cache_memory)
269 -
270 - msg = (f"Memory profiling takes {result.profile_time:.2f} seconds\n"
271 - "the current vLLM instance can use "
272 - "total_gpu_memory "
273 - f"({(total_gpu_memory / GiB_bytes):.2f}GiB)"
274 - " x gpu_memory_utilization "
275 - f"({self.cache_config.gpu_memory_utilization:.2f})"
276 - f" = {(self.requested_memory / GiB_bytes):.2f}GiB\n"
277 - "model weights take "
278 - f"{(result.weights_memory / GiB_bytes):.2f}GiB;"
279 - " non_torch_memory takes "
280 - f"{(result.non_torch_increase / GiB_bytes):.2f}GiB;"
281 - " PyTorch activation peak memory takes "
282 - f"{(result.torch_peak_increase / GiB_bytes):.2f}GiB;"
283 - " the rest of the memory reserved for KV Cache is "
284 - f"{(self.available_kv_cache_memory / GiB_bytes):.2f}GiB.")
285 -
286 - logger.info(msg)
287 - return self.available_kv_cache_memory
288 -
289 - @torch.inference_mode()
290 - def determine_num_available_blocks(self) -> Tuple[int, int]:
291 - """Profiles the peak memory usage of the model to determine how many
292 - KV blocks may be allocated without OOMs.
293 -
294 - The engine will first conduct a profiling of the existing memory usage.
295 - Then, it calculates the maximum possible number of GPU and CPU blocks
296 - that can be allocated with the remaining free memory.
297 -
298 - Tip:
299 - You may limit the usage of GPU memory
300 - by adjusting the `gpu_memory_utilization` parameter.
301 - """
302 - # Profile the memory usage of the model and get the maximum number of
303 - # cache blocks that can be allocated with the remaining free memory.
304 - torch.cuda.empty_cache()
305 - torch.cuda.reset_peak_memory_stats()
306 -
307 - free_memory_pre_profile, total_gpu_memory = torch.cuda.mem_get_info()
308 - available_kv_cache_memory = self.determine_available_kv_cache_memory(
309 - total_gpu_memory)
310 -
311 - # Calculate the number of blocks that can be allocated with the
312 - # profiled peak memory.
313 - cache_block_size = self.get_cache_block_size_bytes()
314 - if cache_block_size == 0:
315 - num_gpu_blocks = 0
316 - num_cpu_blocks = 0
317 - else:
318 - num_gpu_blocks = int(available_kv_cache_memory // cache_block_size)
319 - num_cpu_blocks = int(self.cache_config.swap_space_bytes //
320 - cache_block_size)
321 - num_gpu_blocks = max(num_gpu_blocks, 0)
322 - num_cpu_blocks = max(num_cpu_blocks, 0)
323 -
324 - # Final cleanup
325 - gc.collect()
326 -
327 - return num_gpu_blocks, num_cpu_blocks
328 -
329 - def _assert_memory_footprint_increased_during_profiling(self):
330 - # NOTE(woosuk): Here we assume that the other processes using the same
331 - # GPU did not change their memory usage during the profiling.
332 - free_gpu_memory, total = torch.cuda.mem_get_info()
333 - cuda_memory = total - free_gpu_memory
334 - assert self.baseline_snapshot.cuda_memory < cuda_memory, (
335 - "Error in memory profiling. "
336 - f"Initial used memory {self.baseline_snapshot.cuda_memory}, "
337 - f"currently used memory {cuda_memory}. "
338 - f"This happens when the GPU memory was "
339 - "not properly cleaned up before initializing the vLLM instance.")
340 -
341 - def initialize_cache(self, num_gpu_blocks: int,
342 - num_cpu_blocks: int) -> None:
343 - """Allocate GPU and CPU KV cache with the specified number of blocks.
344 -
345 - This also warms up the model, which may record CUDA graphs.
346 - """
347 - raise_if_cache_size_invalid(
348 - num_gpu_blocks, self.cache_config.block_size,
349 - self.cache_config.is_attention_free,
350 - self.model_config.max_model_len,
351 - self.parallel_config.pipeline_parallel_size)
352 -
353 - self.cache_config.num_gpu_blocks = num_gpu_blocks
354 - self.cache_config.num_cpu_blocks = num_cpu_blocks
355 -
356 - if self.vllm_config.model_config.enable_sleep_mode:
357 - allocator = CuMemAllocator.get_instance()
358 - context = allocator.use_memory_pool(tag="kv_cache")
359 - else:
360 - context = nullcontext()
361 - with context:
362 - self._init_cache_engine()
363 - self._warm_up_model()
364 -
365 - def _init_cache_engine(self):
366 - assert self.cache_config.num_gpu_blocks is not None
367 - self.cache_engine = [
368 - CacheEngine(self.cache_config, self.model_config,
369 - self.parallel_config, self.device_config)
370 - for _ in range(self.parallel_config.pipeline_parallel_size)
371 - ]
372 - self.gpu_cache = [
373 - self.cache_engine[ve].gpu_cache
374 - for ve in range(self.parallel_config.pipeline_parallel_size)
375 - ]
376 -
377 - # Layer pairings for cross-layer KV sharing.
378 - # If an Attention layer `layer_name` is in the keys of this dict, it
379 - # means this layer will perform attention using the keys and values
380 - # from the KV cache of `shared_kv_cache_layers[layer_name]`.
381 - shared_kv_cache_layers: dict[str, str] = {}
382 -
383 - attn_layers = get_layers_from_vllm_config(self.vllm_config, Attention)
384 -
385 - for layer_name, attn_module in attn_layers.items():
386 - if (kv_tgt_layer :=
387 - attn_module.kv_sharing_target_layer_name) is not None:
388 - # The layer doesn't need its own KV cache and will use that of
389 - # the target layer. We skip creating a KVCacheSpec for it, so
390 - # that KV cache management logic will act as this layer does
391 - # not exist, and doesn't allocate KV cache for the layer. This
392 - # enables the memory saving of cross-layer kv sharing, allowing
393 - # a given amount of memory to accommodate longer context lengths
394 - # or enable more requests to be processed simultaneously.
395 - shared_kv_cache_layers[layer_name] = kv_tgt_layer
396 -
397 - bind_kv_cache(self.compilation_config.static_forward_context,
398 - self.gpu_cache, shared_kv_cache_layers)
399 -
400 - def _warm_up_model(self) -> None:
401 - # warm up sizes that are not in cudagraph capture sizes,
402 - # but users still want to compile for better performance,
403 - # e.g. for the max-num-batched token size in chunked prefill.
404 - warmup_sizes = self.vllm_config.compilation_config.compile_sizes.copy()
405 - if not self.model_config.enforce_eager:
406 - warmup_sizes = [
407 - x for x in warmup_sizes if x not in
408 - self.vllm_config.compilation_config.cudagraph_capture_sizes
409 - ]
410 - for size in sorted(warmup_sizes, reverse=True):
411 - logger.info("Compile and warming up model for size %d", size)
412 - self.model_runner._dummy_run(size)
413 -
414 - cuda_graph_memory_bytes = 0
415 - if not self.model_config.enforce_eager:
416 - cuda_graph_memory_bytes = self.model_runner.capture_model(
417 - self.gpu_cache)
418 -
419 - if (self.cache_config.kv_cache_memory_bytes is None
420 - and hasattr(self, "peak_activation_memory")):
421 - # Suggests optimal kv cache memory size if we rely on
422 - # memory_profiling to guess the kv cache memory size which
423 - # provides peak_activation_memory and a few other memory
424 - # consumption. `memory_profiling` does not consider
425 - # CUDAGraph memory size and may not utilize all gpu memory.
426 - # Users may want fine-grained control to specify kv cache
427 - # memory size.
428 - GiB = lambda b: round(b / GiB_bytes, 2)
429 - non_kv_cache_memory = (self.model_runner.model_memory_usage +
430 - self.peak_activation_memory +
431 - self.non_torch_memory +
432 - cuda_graph_memory_bytes)
433 -
434 - # empirically observed that the memory profiling may
435 - # slightly underestimate the memory consumption.
436 - # So leave a small buffer (=150MiB) to avoid OOM.
437 - redundancy_buffer_memory = 150 * (1 << 20)
438 - kv_cache_memory_bytes_to_gpu_limit = (
439 - self.baseline_snapshot.free_memory - non_kv_cache_memory -
440 - redundancy_buffer_memory)
441 - kv_cache_memory_bytes_to_requested_limit = (
442 - int(self.requested_memory) - non_kv_cache_memory -
443 - redundancy_buffer_memory)
444 -
445 - msg = (
446 - f"Free memory on device "
447 - f"({GiB(self.baseline_snapshot.free_memory)}/"
448 - f"{GiB(self.baseline_snapshot.total_memory)} GiB) on startup. "
449 - f"Desired GPU memory utilization is "
450 - f"({self.cache_config.gpu_memory_utilization}, "
451 - f"{GiB(self.requested_memory)} GiB). "
452 - f"Actual usage is {GiB(self.model_runner.model_memory_usage)} "
453 - f"GiB for weight, {GiB(self.peak_activation_memory)} GiB "
454 - f"for peak activation, {GiB(self.non_torch_memory)} GiB "
455 - f"for non-torch memory, and {GiB(cuda_graph_memory_bytes)} "
456 - f"GiB for CUDAGraph memory. Replace gpu_memory_utilization "
457 - f"config with `--kv-cache-memory="
458 - f"{kv_cache_memory_bytes_to_requested_limit}` to fit into "
459 - f"requested memory, or `--kv-cache-memory="
460 - f"{kv_cache_memory_bytes_to_gpu_limit}` to fully "
461 - f"utilize gpu memory. Current kv cache memory in use is "
462 - f"{int(self.available_kv_cache_memory)} bytes.")
463 - logger.info(msg)
464 -
465 - # Reset the seed to ensure that the random state is not affected by
466 - # the model initialization and profiling.
467 - set_random_seed(self.model_config.seed)
468 -
469 - @property
470 - def do_metadata_broadcast(self) -> bool:
471 - return self.parallel_config.tensor_parallel_size > 1
472 -
473 - @property
474 - def kv_cache(self) -> Optional[List[List[torch.Tensor]]]:
475 - return self.gpu_cache
476 -
477 - @torch.inference_mode()
478 - def prepare_worker_input(
479 - self, execute_model_req: ExecuteModelRequest) -> WorkerInput:
480 - virtual_engine = execute_model_req.virtual_engine
481 - num_steps = execute_model_req.num_steps
482 - num_seq_groups = len(execute_model_req.seq_group_metadata_list)
483 - # `blocks_to_swap_in` and `blocks_to_swap_out` are cpu tensors.
484 - # they contain parameters to launch cudamemcpyasync.
485 - blocks_to_swap_in = torch.tensor(execute_model_req.blocks_to_swap_in,
486 - device="cpu",
487 - dtype=torch.int64).view(-1, 2)
488 - blocks_to_swap_out = torch.tensor(execute_model_req.blocks_to_swap_out,
489 - device="cpu",
490 - dtype=torch.int64).view(-1, 2)
491 - # `blocks_to_copy` is a gpu tensor. The src and tgt of
492 - # blocks to copy are in the same device, and `blocks_to_copy`
493 - # can be used directly within cuda kernels.
494 - blocks_to_copy = torch.tensor(execute_model_req.blocks_to_copy,
495 - device=self.device,
496 - dtype=torch.int64).view(-1, 2)
497 -
498 - return WorkerInput(
499 - num_seq_groups=num_seq_groups,
500 - blocks_to_swap_in=blocks_to_swap_in,
501 - blocks_to_swap_out=blocks_to_swap_out,
502 - blocks_to_copy=blocks_to_copy,
503 - virtual_engine=virtual_engine,
504 - num_steps=num_steps,
505 - )
506 -
507 - @torch.inference_mode()
508 - def execute_worker(self, worker_input: WorkerInput) -> None:
509 - virtual_engine = worker_input.virtual_engine
510 - # Issue cache operations.
511 - if (worker_input.blocks_to_swap_in is not None
512 - and worker_input.blocks_to_swap_in.numel() > 0):
513 - self.cache_engine[virtual_engine].swap_in(
514 - worker_input.blocks_to_swap_in)
515 - if (worker_input.blocks_to_swap_out is not None
516 - and worker_input.blocks_to_swap_out.numel() > 0):
517 - self.cache_engine[virtual_engine].swap_out(
518 - worker_input.blocks_to_swap_out)
519 - if (worker_input.blocks_to_copy is not None
520 - and worker_input.blocks_to_copy.numel() > 0):
521 - self.cache_engine[virtual_engine].copy(worker_input.blocks_to_copy)
522 -
523 - def _get_cached_seq_group_metadata(
524 - self,
525 - seq_group_metadata_list: List[Union[SequenceGroupMetadata,
526 - SequenceGroupMetadataDelta]],
527 - finished_request_ids: List[str]) -> List[SequenceGroupMetadata]:
528 - """Return a list of cached Sequence Group Metadata after updating its
529 - state.
530 -
531 - It is used because scheduler only sends delta to workers to reduce
532 - the data payload size. The function also cleans up cache based on
533 - a given `finished_request_ids`.
534 - """
535 - new_seq_group_metadata_list = []
536 - for metadata_or_delta in seq_group_metadata_list:
537 - request_id = metadata_or_delta.request_id
538 - if request_id not in self._seq_group_metadata_cache:
539 - # The first prefill.
540 - assert isinstance(metadata_or_delta, SequenceGroupMetadata)
541 - self._seq_group_metadata_cache[request_id] = metadata_or_delta
542 - else:
543 - # The first prefill is already cached.
544 - if isinstance(metadata_or_delta, SequenceGroupMetadataDelta):
545 - self._seq_group_metadata_cache[request_id].apply_delta(
546 - metadata_or_delta)
547 - else:
548 - # If metadata snapshot is sent again, it is
549 - # preempted. Reset the cache because we need to start
550 - # from scratch.
551 - assert isinstance(metadata_or_delta, SequenceGroupMetadata)
552 - self._seq_group_metadata_cache[
553 - request_id] = metadata_or_delta
554 -
555 - new_seq_group_metadata_list.append(
556 - self._seq_group_metadata_cache[request_id])
557 -
558 - # Clean up finished ids
559 - for finished_id in finished_request_ids:
560 - del self._seq_group_metadata_cache[finished_id]
561 -
562 - return new_seq_group_metadata_list
563 -
564 - def _execute_model_spmd(
565 - self,
566 - execute_model_req: ExecuteModelRequest,
567 - intermediate_tensors: Optional[IntermediateTensors] = None,
568 - ) -> Optional[List[SamplerOutput]]:
569 - if execute_model_req is not None:
570 - new_seq_group_metadata_list = self._get_cached_seq_group_metadata(
571 - execute_model_req.seq_group_metadata_list,
572 - execute_model_req.finished_requests_ids)
573 -
574 - execute_model_req.seq_group_metadata_list = (
575 - new_seq_group_metadata_list)
576 - output = super()._execute_model_spmd(execute_model_req,
577 - intermediate_tensors)
578 - return output
579 -
580 - def add_lora(self, lora_request: LoRARequest) -> bool:
581 - return self.model_runner.add_lora(lora_request)
582 -
583 - def remove_lora(self, lora_id: int) -> bool:
584 - return self.model_runner.remove_lora(lora_id)
585 -
586 - def pin_lora(self, lora_id: int) -> bool:
587 - return self.model_runner.pin_lora(lora_id)
588 -
589 - def list_loras(self) -> Set[int]:
590 - return self.model_runner.list_loras()
591 -
592 - @property
593 - def max_model_len(self) -> int:
594 - return self.model_config.max_model_len
595 -
596 - @property
597 - def vocab_size(self) -> int:
598 - return self.model_runner.vocab_size
599 -
600 - def get_cache_block_size_bytes(self) -> int:
601 - """Get the size of the KV cache block size in bytes.
602 - """
603 - return CacheEngine.get_cache_block_size(self.cache_config,
604 - self.model_config,
605 - self.parallel_config)
606 -
607 -
608 -def init_worker_distributed_environment(
609 - vllm_config: VllmConfig,
610 - rank: int,
611 - distributed_init_method: Optional[str] = None,
612 - local_rank: int = -1,
613 -) -> None:
614 - """Initialize the distributed environment."""
615 - parallel_config = vllm_config.parallel_config
616 - set_custom_all_reduce(not parallel_config.disable_custom_all_reduce)
617 -
618 - init_distributed_environment(parallel_config.world_size, rank,
619 - distributed_init_method, local_rank,
620 - current_platform.dist_backend)
621 - ensure_model_parallel_initialized(
622 - parallel_config.tensor_parallel_size,
623 - parallel_config.pipeline_parallel_size,
624 - parallel_config.decode_context_parallel_size)
625 -
626 - ensure_kv_transfer_initialized(vllm_config)
627 -
628 -
629 -def _check_if_gpu_supports_dtype(torch_dtype: torch.dtype):
630 - # Check if the GPU supports the dtype.
631 - if torch_dtype == torch.bfloat16: # noqa: SIM102
632 - if not current_platform.has_device_capability(80):
633 - capability = current_platform.get_device_capability()
634 - gpu_name = current_platform.get_device_name()
635 -
636 - if capability is None:
637 - compute_str = "does not have a compute capability"
638 - else:
639 - version_str = capability.as_version_str()
640 - compute_str = f"has compute capability {version_str}"
641 -
642 - raise ValueError(
643 - "Bfloat16 is only supported on GPUs with compute capability "
644 - f"of at least 8.0. Your {gpu_name} GPU {compute_str}. "
645 - "You can use float16 instead by explicitly setting the "
646 - "`dtype` flag in CLI, for example: --dtype=half.")
647 -
648 -
649 -def raise_if_cache_size_invalid(num_gpu_blocks, block_size, is_attention_free,
650 - max_model_len, pipeline_parallel_size) -> None:
651 - if is_attention_free and num_gpu_blocks != 0:
652 - raise ValueError("No memory should be allocated for the cache blocks "
653 - f"for an attention-free model, but {num_gpu_blocks} "
654 - "blocks are allocated.")
655 - if not is_attention_free and num_gpu_blocks <= 0:
656 - raise ValueError("No available memory for the cache blocks. "
657 - "Try increasing `gpu_memory_utilization` when "
658 - "initializing the engine.")
659 - max_seq_len = block_size * (num_gpu_blocks // pipeline_parallel_size)
660 - if not is_attention_free and max_model_len > max_seq_len:
661 - raise ValueError(
662 - f"The model's max seq len ({max_model_len}) "
663 - "is larger than the maximum number of tokens that can be "
664 - f"stored in KV cache ({max_seq_len}). Try increasing "
665 - "`gpu_memory_utilization` or decreasing `max_model_len` when "
666 - "initializing the engine.")