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 15, 21:00 UTC
[V1][TPU] Integrate the new ragged paged attention kernel with vLLM v1 on TPU (#13379)
Signed-off-by: Xiongfei Wei <isaacwxf23@gmail.com> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: mgoin <mgoin64@gmail.com>
iefgnoix committed
Feb 28, 2025 at 10:01 UTC
c3b6559a1019c73f5b2436d2d50cbaa3663e5382
6 files changed
+353
-905
requirements-tpu.txt
+5
-6
@@ -17,9 +17,8 @@ ray[default]
17
--find-links https://storage.googleapis.com/libtpu-releases/index.html
18
--find-links https://storage.googleapis.com/jax-releases/jax_nightly_releases.html
19
--find-links https://storage.googleapis.com/jax-releases/jaxlib_nightly_releases.html
20
-torch @ https://download.pytorch.org/whl/nightly/cpu/torch-2.6.0.dev20241216%2Bcpu-cp39-cp39-linux_x86_64.whl ; python_version == "3.9"
21
-torch @ https://download.pytorch.org/whl/nightly/cpu/torch-2.6.0.dev20241216%2Bcpu-cp310-cp310-linux_x86_64.whl ; python_version == "3.10"
22
-torch @ https://download.pytorch.org/whl/nightly/cpu/torch-2.6.0.dev20241216%2Bcpu-cp311-cp311-linux_x86_64.whl ; python_version == "3.11"
23
-torch_xla[tpu, pallas] @ https://storage.googleapis.com/pytorch-xla-releases/wheels/tpuvm/torch_xla-2.7.0.dev20250124-cp39-cp39-linux_x86_64.whl ; python_version == "3.9"
24
-torch_xla[tpu, pallas] @ https://storage.googleapis.com/pytorch-xla-releases/wheels/tpuvm/torch_xla-2.7.0.dev20250124-cp310-cp310-linux_x86_64.whl ; python_version == "3.10"
25
-torch_xla[tpu, pallas] @ https://storage.googleapis.com/pytorch-xla-releases/wheels/tpuvm/torch_xla-2.7.0.dev20250124-cp311-cp311-linux_x86_64.whl ; python_version == "3.11"
20
+
21
+torch==2.7.0.dev20250226+cpu
22
+torch_xla[tpu, pallas] @ https://storage.googleapis.com/pytorch-xla-releases/wheels/tpuvm/torch_xla-2.7.0.dev20250226+cxx11-cp39-cp39-linux_x86_64.whl ; python_version == "3.9"
23
+torch_xla[tpu, pallas] @ https://storage.googleapis.com/pytorch-xla-releases/wheels/tpuvm/torch_xla-2.7.0.dev20250226+cxx11-cp310-cp310-linux_x86_64.whl ; python_version == "3.10"
24
+torch_xla[tpu, pallas] @ https://storage.googleapis.com/pytorch-xla-releases/wheels/tpuvm/torch_xla-2.7.0.dev20250226+cxx11-cp311-cp311-linux_x86_64.whl ; python_version == "3.11"
vllm/v1/attention/backends/pallas.py
+62
-218
@@ -4,13 +4,16 @@ from dataclasses import dataclass
4
from typing import Any, Dict, List, Optional, Tuple, Type
5
6
import torch
7
-import torch_xla.experimental.custom_kernel # Required to register custom ops.
7
+# Required to register custom ops.
8
+import torch_xla.experimental.custom_kernel # noqa: F401
9
10
from vllm.attention.backends.abstract import (AttentionBackend, AttentionImpl,
10
- AttentionLayer,
11
- AttentionMetadata, AttentionType)
11
+ AttentionLayer, AttentionType)
12
from vllm.attention.backends.utils import CommonAttentionState
13
14
+NUM_QUERIES_PER_BLOCK = 16
15
+NUM_KV_PAGES_PER_BLOCK = 128
16
+
17
18
class PallasAttentionBackend(AttentionBackend):
19
@@ -47,47 +50,23 @@ class PallasAttentionBackend(AttentionBackend):
50
) -> None:
51
raise RuntimeError("swap_blocks is not used for the TPU backend.")
52
50
- @torch.compile(backend="openxla")
51
- @staticmethod
52
- def copy_blocks(
53
- kv_caches: List[Tuple[torch.Tensor, torch.Tensor]],
54
- src_to_dists: Tuple[torch.Tensor, torch.Tensor],
55
- ) -> None:
56
- src_indices, dst_indices = src_to_dists
57
- for k_cache, v_cache in kv_caches:
58
- torch.ops.xla.dynamo_set_buffer_donor_(k_cache, True)
59
- k_cache[:, dst_indices] = k_cache[:, src_indices]
60
- torch.ops.xla.dynamo_set_buffer_donor_(v_cache, True)
61
- v_cache[:, dst_indices] = v_cache[:, src_indices]
62
-
53
54
@dataclass
65
-class PallasMetadata(AttentionMetadata):
66
-
67
- # Currently, input sequences can only contain all prefills
68
- # or all decoding.
69
- block_tables: Optional[torch.Tensor] = None
70
- context_lens: Optional[torch.Tensor] = None
71
- effective_query_lens: Optional[torch.Tensor] = None
72
-
73
- @property
74
- def prefill_metadata(self) -> Optional["PallasMetadata"]:
75
- if self.num_prefills == 0:
76
- return None
77
-
78
- assert self.num_decode_tokens == 0
79
- return self
80
-
81
- @property
82
- def decode_metadata(self) -> Optional["PallasMetadata"]:
83
- if self.num_decode_tokens == 0:
84
- return None
85
-
86
- assert self.num_prefills == 0
87
- assert self.num_prefill_tokens == 0
88
- assert self.block_tables is not None
89
- assert self.context_lens is not None
90
- return self
55
+class PallasMetadata:
56
+ # NOTE(sang): Definition of context_len, query_len, and seq_len.
57
+ # |---------- N-1 iteration --------|
58
+ # |---------------- N iteration ---------------------|
59
+ # |- tokenA -|......................|-- newTokens ---|
60
+ # |---------- context_len ----------|
61
+ # |-------------------- seq_len ---------------------|
62
+ # |-- query_len ---|
63
+
64
+ # Used in the PallasAttentionBackendImpl
65
+ slot_mapping: torch.Tensor
66
+ block_tables: torch.Tensor
67
+ context_lens: torch.Tensor
68
+ query_start_loc: torch.Tensor
69
+ num_seqs: int
70
71
72
class PallasAttentionBackendImpl(AttentionImpl):
@@ -105,10 +84,13 @@ class PallasAttentionBackendImpl(AttentionImpl):
84
logits_soft_cap: Optional[float] = None,
85
attn_type: str = AttentionType.DECODER,
86
) -> None:
87
+ if blocksparse_params is not None:
88
+ raise ValueError("Paged attention Pallas kernel does "
89
+ "not support block-sparse attention.")
90
self.num_heads = num_heads
91
self.head_size = head_size
92
self.scale = float(scale)
111
- self.num_kv_heads = num_heads if num_kv_heads is None else num_kv_heads
93
+ self.num_kv_heads = num_kv_heads
94
95
assert self.num_heads % self.num_kv_heads == 0
96
self.num_queries_per_kv = self.num_heads // self.num_kv_heads
@@ -126,25 +108,6 @@ class PallasAttentionBackendImpl(AttentionImpl):
108
raise NotImplementedError(
109
"Attention logits soft-capping is not supported.")
110
129
- if torch_xla.tpu.version() < 4:
130
- raise NotImplementedError("TPU version must be 4 or higher.")
131
-
132
- self.megacore_mode = None
133
- tpu_env = torch_xla.tpu.get_tpu_env()
134
- tpu_type = (tpu_env.get("ACCELERATOR_TYPE", None)
135
- or tpu_env.get("TYPE", None)
136
- or tpu_env.get("TPU_ACCELERATOR_TYPE", None))
137
- assert tpu_type is not None
138
- tpu_type = tpu_type.lower()
139
-
140
- if (("lite" not in tpu_type) and ("v6" not in tpu_type)):
141
- if self.num_kv_heads % 2 == 0:
142
- self.megacore_mode = "kv_head"
143
- else:
144
- # NOTE(woosuk): If the batch size is not a multiple of 2, the
145
- # megacore mode will be None.
146
- self.megacore_mode = "batch"
147
-
111
if attn_type != AttentionType.DECODER:
112
raise NotImplementedError("Encoder self-attention and "
113
"encoder/decoder cross-attention "
@@ -164,135 +127,47 @@ class PallasAttentionBackendImpl(AttentionImpl):
127
"""Forward pass with Pallas attention.
128
129
Args:
167
- query: shape = [batch_size, seq_len, num_heads * head_size]
168
- key: shape = [batch_size, seq_len, num_kv_heads * head_size]
169
- value: shape = [batch_size, seq_len, num_kv_heads * head_size]
170
- kv_cache[0] = [num_kv_heads, num_blocks, block_size, head_size]
171
- kv_cache[1] = [num_kv_heads, num_blocks, block_size, head_size]
172
- NOTE: kv_cache[0] and kv_cache[1] will be an empty tensor
173
- with shape [0] for profiling run.
130
+ query: shape = [num_tokens, num_heads * head_size]
131
+ key: shape = [num_tokens, num_kv_heads * head_size]
132
+ value: shape = [num_tokens, num_kv_heads * head_size]
133
+ kv_cache = ([num_kv_heads, num_blocks, block_size, head_size],
134
+ [num_kv_heads, num_blocks, block_size, head_size])
135
attn_metadata: Metadata for attention.
136
Returns:
176
- shape = [batch_size, seq_len, num_heads * head_size]
137
+ shape = [num_tokens, num_heads * head_size]
138
"""
178
-
179
- if attn_metadata is None:
139
+ # For determine_available_memory case.
140
+ if kv_cache[0].numel() == 0:
141
if output is None:
142
output = torch.ones_like(query)
143
return output
144
145
assert layer._k_scale_float == 1.0 and layer._v_scale_float == 1.0
185
- batch_size, seq_len, hidden_size = query.shape
186
- query = query.view(batch_size, seq_len, self.num_heads, self.head_size)
187
- key = key.view(batch_size, seq_len, self.num_kv_heads, self.head_size)
188
- value = value.view(batch_size, seq_len, self.num_kv_heads,
189
- self.head_size)
146
+ num_tokens, hidden_size = query.shape
147
+ query = query.view(num_tokens, self.num_heads, self.head_size)
148
+ key = key.view(num_tokens, self.num_kv_heads, self.head_size)
149
+ value = value.view(num_tokens, self.num_kv_heads, self.head_size)
150
151
+ key_cache, value_cache = kv_cache
152
if kv_cache[0].numel() > 0:
153
slot_mapping = attn_metadata.slot_mapping
193
- key_cache, value_cache = kv_cache
154
write_to_kv_cache(key, value, key_cache, value_cache, slot_mapping)
155
156
query = query * self.scale
197
- if attn_metadata.num_prefills > 0:
198
- if attn_metadata.block_tables is None:
199
- # Prefill without paged KV cache.
200
- assert seq_len % 16 == 0, (
201
- "Pallas FlashAttention kernel requires seq_len to be a "
202
- f"multiple of 16 but got {seq_len}")
203
-
204
- # Handle GQA/MQA.
205
- if self.num_kv_heads != self.num_heads:
206
- key = key.repeat_interleave(self.num_queries_per_kv,
207
- dim=-2)
208
- key = key.view(batch_size, seq_len, self.num_heads,
209
- self.head_size)
210
- value = value.repeat_interleave(self.num_queries_per_kv,
211
- dim=-2)
212
- value = value.view(batch_size, seq_len, self.num_heads,
213
- self.head_size)
214
- # FlashAttention kernel requires the input shape to be
215
- # [batch_size, num_heads, seq_len, d_model]
216
- # while the input is [batch_size, seq_len, num_heads, d_model].
217
- # Permute the input to match the required format.
218
- output = torch.ops.xla.flash_attention(
219
- query.permute(0, 2, 1, 3),
220
- key.permute(0, 2, 1, 3),
221
- value.permute(0, 2, 1, 3),
222
- True,
223
- )
224
- output = output.permute(0, 2, 1, 3)
225
- else:
226
- # Prefill with paged KV cache.
227
- # TODO(woosuk): Tune the below knobs.
228
- num_kv_pages_per_compute_block = 16
229
- num_queries_per_compute_block = 16
230
- assert seq_len % num_queries_per_compute_block == 0
231
- output = torch.ops.xla.multi_queries_paged_attention(
232
- query,
233
- key_cache,
234
- value_cache,
235
- attn_metadata.context_lens,
236
- attn_metadata.block_tables,
237
- attn_metadata.effective_query_lens,
238
- num_kv_pages_per_compute_block,
239
- num_queries_per_compute_block,
240
- use_kernel=True,
241
- )
242
- else:
243
- # Decoding run.
244
- assert kv_cache[0].numel() > 0
245
- query = query.squeeze(dim=1)
246
- pages_per_compute_block = 16 # TODO(woosuk): Tune this value.
247
-
248
- assert attn_metadata.block_tables is not None
249
- assert attn_metadata.context_lens is not None
250
- # NOTE(woosuk): The PagedAttention Pallas kernel stores the entire
251
- # block table in SMEM. Therefore, if the block table is too large,
252
- # the kernel compilation will fail. To avoid this, we split the
253
- # batch dimension into smaller chunks and run the kernel multiple
254
- # times.
255
- MAX_SMEM_USAGE = 512 * 1024
256
- size_per_seq = 4 * attn_metadata.block_tables.shape[1]
257
- max_num_seq = MAX_SMEM_USAGE // size_per_seq
258
-
259
- if batch_size <= max_num_seq:
260
- output = paged_attention(
261
- query,
262
- key_cache,
263
- value_cache,
264
- attn_metadata.context_lens,
265
- attn_metadata.block_tables,
266
- pages_per_compute_block,
267
- self.megacore_mode,
268
- )
269
- else:
270
- chunk_size = max_num_seq
271
- # Make sure the chunk size is a multiple of 2.
272
- chunk_size = chunk_size // 2 * 2
273
- num_chunks = (batch_size + chunk_size - 1) // chunk_size
274
-
275
- output = torch.empty_like(query)
276
- for chunk_idx in range(num_chunks):
277
- chunk_start = chunk_idx * chunk_size
278
- chunk_end = chunk_start + chunk_size
279
- # NOTE(woosuk): We skip this line because it causes Dynamo
280
- # compilation error. Instead, we rely on the slice operation
281
- # to handle the out-of-bound case.
282
- # chunk_end = min(chunk_end, batch_size)
283
- chunk_output = paged_attention(
284
- query[chunk_start:chunk_end],
285
- key_cache,
286
- value_cache,
287
- attn_metadata.context_lens[chunk_start:chunk_end],
288
- attn_metadata.block_tables[chunk_start:chunk_end],
289
- pages_per_compute_block,
290
- self.megacore_mode,
291
- )
292
- output[chunk_start:chunk_end] = chunk_output
157
+ output = torch.ops.xla.ragged_paged_attention(
158
+ query,
159
+ key_cache,
160
+ value_cache,
161
+ attn_metadata.context_lens,
162
+ attn_metadata.block_tables,
163
+ attn_metadata.query_start_loc,
164
+ attn_metadata.num_seqs,
165
+ num_kv_pages_per_block=NUM_KV_PAGES_PER_BLOCK,
166
+ num_queries_per_block=NUM_QUERIES_PER_BLOCK,
167
+ use_kernel=False,
168
+ )
169
294
- # Reshape the output tensor.
295
- return output.reshape(batch_size, seq_len, hidden_size)
170
+ return output.reshape(num_tokens, hidden_size)
171
172
173
def write_to_kv_cache(
@@ -302,52 +177,21 @@ def write_to_kv_cache(
177
value_cache: torch.Tensor,
178
slot_mapping: torch.Tensor,
179
) -> None:
180
+ """ Write the key and values to the KV cache.
181
+
182
+ Args:
183
+ key: shape = [num_tokens, num_kv_heads, head_size]
184
+ value: shape = [num_tokens, num_kv_heads, head_size]
185
+ k_cache = [num_kv_heads, num_blocks, block_size, head_size]
186
+ v_cache = [num_kv_heads, num_blocks, block_size, head_size]
187
+
188
+ """
189
torch.ops.xla.dynamo_set_buffer_donor_(key_cache, True)
190
torch.ops.xla.dynamo_set_buffer_donor_(value_cache, True)
191
308
- key = key.flatten(0, 2)
309
- value = value.flatten(0, 2)
192
+ key = key.flatten(0, 1)
193
+ value = value.flatten(0, 1)
194
key_cache = key_cache.flatten(0, 2)
195
value_cache = value_cache.flatten(0, 2)
196
key_cache.index_copy_(0, slot_mapping, key)
197
value_cache.index_copy_(0, slot_mapping, value)
314
-
315
-
316
-def paged_attention(
317
- query: torch.Tensor,
318
- key_cache: torch.Tensor,
319
- value_cache: torch.Tensor,
320
- context_lens: torch.Tensor,
321
- block_tables: torch.Tensor,
322
- pages_per_compute_block: int,
323
- megacore_mode: Optional[str],
324
-) -> torch.Tensor:
325
- batch_size = query.shape[0]
326
- if megacore_mode == "batch" and batch_size % 2 != 0:
327
- megacore_mode = None
328
- else:
329
- megacore_mode = megacore_mode
330
-
331
- # NOTE(woosuk): A temporary workaround to avoid the error:
332
- # "xla::paged_attention() Expected a value of type 'str' for
333
- # argument 'megacore_mode' but instead found type 'NoneType'."
334
- if megacore_mode is not None:
335
- output = torch.ops.xla.paged_attention(
336
- query,
337
- key_cache,
338
- value_cache,
339
- context_lens,
340
- block_tables,
341
- pages_per_compute_block,
342
- megacore_mode=megacore_mode,
343
- )
344
- else:
345
- output = torch.ops.xla.paged_attention(
346
- query,
347
- key_cache,
348
- value_cache,
349
- context_lens,
350
- block_tables,
351
- pages_per_compute_block,
352
- )
353
- return output
vllm/v1/outputs.py
+1
-1
@@ -79,4 +79,4 @@ class ModelRunnerOutput:
79
# [prompt_len, num_prompt_logprobs]
80
# [prompt_len, num_prompt_logprobs]
81
# [prompt_len]
82
- prompt_logprobs_dict: Dict[str, LogprobsTensors]
82
+ prompt_logprobs_dict: Dict[str, Optional[LogprobsTensors]]
vllm/v1/worker/gpu_model_runner.py
+2
-2
@@ -1071,12 +1071,12 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1071
self,
1072
hidden_states: torch.Tensor,
1073
scheduler_output: "SchedulerOutput",
1074
- ) -> Dict[str, LogprobsTensors]:
1074
+ ) -> Dict[str, Optional[LogprobsTensors]]:
1075
num_prompt_logprobs_dict = self.input_batch.num_prompt_logprobs
1076
if not num_prompt_logprobs_dict:
1077
return {}
1078
1079
- prompt_logprobs_dict: Dict[str, LogprobsTensors] = {}
1079
+ prompt_logprobs_dict: Dict[str, Optional[LogprobsTensors]] = {}
1080
1081
# Since prompt logprobs are a rare feature, prioritize simple,
1082
# maintainable loop over optimal performance.
vllm/v1/worker/tpu_model_runner.py
+281
-674
@@ -1,8 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
-import enum
2
import time
4
-from dataclasses import dataclass
5
-from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
3
+from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, cast
4
from unittest.mock import patch
5
6
import numpy as np
@@ -21,7 +19,9 @@ from vllm.logger import init_logger
19
from vllm.model_executor.model_loader import get_model
20
from vllm.sampling_params import SamplingType
21
from vllm.utils import LayerBlockType, cdiv, is_pin_memory_available
24
-from vllm.v1.attention.backends.pallas import (PallasAttentionBackend,
22
+from vllm.v1.attention.backends.pallas import (NUM_KV_PAGES_PER_BLOCK,
23
+ NUM_QUERIES_PER_BLOCK,
24
+ PallasAttentionBackend,
25
PallasMetadata)
26
from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
27
KVCacheSpec)
@@ -37,36 +37,7 @@ logger = init_logger(__name__)
37
# Here we utilize the behavior that out-of-bound index is ignored.
38
# FIXME(woosuk): Find a more reliable way to prevent possible bugs.
39
_PAD_SLOT_ID = 1_000_000_000
40
-
41
-
42
-class ExecutionMode(enum.Enum):
43
- PREFILL = enum.auto()
44
- DECODE = enum.auto()
45
- PREFIX_PREFILL = enum.auto()
46
-
47
- def is_prefill(self) -> bool:
48
- return self in (ExecutionMode.PREFILL, ExecutionMode.PREFIX_PREFILL)
49
-
50
-
51
-@dataclass
52
-class PromptDecodeInfo:
53
- prompt_req_ids: List[str]
54
- decode_req_ids: List[str]
55
- prompt_scheduled_tokens: List[int]
56
-
57
-
58
-@dataclass
59
-class PromptData:
60
- input_tokens: torch.Tensor
61
- input_positions: torch.Tensor
62
- attn_metadata: PallasMetadata
63
-
64
-
65
-@dataclass
66
-class DecodeData:
67
- input_tokens: Optional[torch.Tensor] = None
68
- input_positions: Optional[torch.Tensor] = None
69
- attn_metadata: Optional[PallasMetadata] = None
40
+INVALID_TOKEN_ID = -1
41
42
43
class TPUModelRunner:
@@ -113,8 +84,6 @@ class TPUModelRunner:
84
self.head_size = model_config.get_head_size()
85
self.hidden_size = model_config.get_hidden_size()
86
116
- self.model: Optional[nn.Module] = None
117
-
87
# Persistent batch.
88
self.input_batch = InputBatch(
89
max_num_reqs=self.max_num_reqs,
@@ -134,50 +103,48 @@ class TPUModelRunner:
103
# KV caches for forward pass
104
self.kv_caches: List[Tuple[torch.Tensor, torch.Tensor]] = []
105
137
- # Cached torch/numpy tensors
138
- self.num_swaps = 2
139
- self.cur_swap_id = 0
140
- self.input_ids_cpu = []
141
- self.input_ids_np = []
142
- self.input_positions_cpu = []
143
- self.input_positions_np = []
144
- self.slot_mapping_cpu = []
145
- self.slot_mapping_np = []
146
- self.prompt_context_lens_cpu = []
147
- self.prompt_effective_query_lens_cpu = []
148
- self.decode_context_lens_cpu = []
149
- self.decode_context_lens_np = []
150
- for _ in range(self.num_swaps):
151
- self.input_ids_cpu.append(
152
- torch.empty(self.max_num_tokens,
153
- dtype=torch.int32,
154
- device="cpu"))
155
- self.input_ids_np.append(self.input_ids_cpu[-1].numpy())
156
-
157
- self.input_positions_cpu.append(
158
- torch.empty(self.max_num_tokens,
159
- dtype=torch.int32,
160
- device="cpu"))
161
- self.input_positions_np.append(
162
- self.input_positions_cpu[-1].numpy())
163
-
164
- self.slot_mapping_cpu.append(
165
- torch.empty(self.max_num_tokens,
166
- dtype=torch.int64,
167
- device="cpu"))
168
- self.slot_mapping_np.append(self.slot_mapping_cpu[-1].numpy())
169
-
170
- self.prompt_context_lens_cpu.append(
171
- torch.empty((1), dtype=torch.int32, device="cpu"))
172
- self.prompt_effective_query_lens_cpu.append(
173
- torch.empty((1), dtype=torch.int32, device="cpu"))
174
-
175
- self.decode_context_lens_cpu.append(
176
- torch.empty(self.max_num_tokens,
177
- dtype=torch.int32,
178
- device="cpu"))
179
- self.decode_context_lens_np.append(
180
- self.decode_context_lens_cpu[-1].numpy())
106
+ # Cached torch/numpy tensor
107
+ # The pytorch tensor and numpy array share the same buffer.
108
+ # Sometimes the numpy op is faster so we create both.
109
+ self.input_ids_cpu = torch.zeros(self.max_num_tokens,
110
+ dtype=torch.int32,
111
+ device="cpu")
112
+ self.input_ids_np = self.input_ids_cpu.numpy()
113
+
114
+ self.positions_cpu = torch.zeros(self.max_num_tokens,
115
+ dtype=torch.int32,
116
+ device="cpu")
117
+ self.positions_np = self.positions_cpu.numpy()
118
+
119
+ self.slot_mapping_cpu = torch.zeros(self.max_num_tokens,
120
+ dtype=torch.int64,
121
+ device="cpu")
122
+ self.slot_mapping_np = self.slot_mapping_cpu.numpy()
123
+
124
+ # self.input_batch.block_table has a shape of [max_num_reqs,
125
+ # max_num_blocks_per_req]. To reduce the number of recompilation,
126
+ # we want the block_table.shape[0] to be num_tokens.
127
+ # To make the block_table to be compatible with the paged attention
128
+ # kernel, we want the block_table[1] to be multiple of
129
+ # NUM_KV_PAGES_PER_BLOCK.
130
+ padded_max_num_blocks_per_req = _get_padded_number(
131
+ self.max_num_blocks_per_req, NUM_KV_PAGES_PER_BLOCK)
132
+ self.block_table_cpu = torch.zeros(
133
+ (self.max_num_tokens, padded_max_num_blocks_per_req),
134
+ dtype=self.input_batch.block_table.get_cpu_tensor().dtype,
135
+ device="cpu")
136
+
137
+ self.query_start_loc_cpu = torch.zeros(self.max_num_tokens + 1,
138
+ dtype=torch.int32,
139
+ device="cpu",
140
+ pin_memory=self.pin_memory)
141
+ self.query_start_loc_np = self.query_start_loc_cpu.numpy()
142
+
143
+ self.seq_lens_cpu = torch.zeros(self.max_num_tokens,
144
+ dtype=torch.int32,
145
+ device="cpu",
146
+ pin_memory=self.pin_memory)
147
+ self.seq_lens_np = self.seq_lens_cpu.numpy()
148
149
# Range tensor with values [0 .. self.max_num_tokens - 1].
150
# Used to initialize positions / context_lens / seq_lens
@@ -191,7 +158,7 @@ class TPUModelRunner:
158
the input GPU tensors for the model.
159
160
Returns:
194
- True if there is a new/resumed/paused/finished request in the batch.
161
+ True if there is a new/resumed/paused/finished request.
162
If False, we can skip copying SamplingMetadata to the GPU.
163
"""
164
# Remove finished requests from the cached states.
@@ -303,9 +270,6 @@ class TPUModelRunner:
270
self.input_batch.condense(removed_req_indices)
271
return len(unscheduled_req_ids) > 0 or len(req_ids_to_add) > 0
272
306
- def swap_step(self):
307
- self.cur_swap_id = (self.cur_swap_id + 1) % self.num_swaps
308
-
273
def get_model(self) -> nn.Module:
274
assert self.model is not None
275
return self.model
@@ -345,238 +309,124 @@ class TPUModelRunner:
309
310
return kv_cache_spec
311
348
- def _get_prompts_and_decodes(
349
- self,
350
- scheduler_output: "SchedulerOutput",
351
- ) -> PromptDecodeInfo:
312
+ def _prepare_inputs(self, scheduler_output: "SchedulerOutput"):
313
total_num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
314
assert total_num_scheduled_tokens > 0
315
num_reqs = self.input_batch.num_reqs
316
assert num_reqs > 0
317
357
- # Traverse decodes first
358
- decode_req_ids = []
359
- for i in range(num_reqs):
360
- req_id = self.input_batch.req_ids[i]
361
- assert req_id is not None
362
-
363
- num_computed_tokens = self.input_batch.num_computed_tokens_cpu[i]
364
- num_prompt_tokens = self.input_batch.num_prompt_tokens[i]
365
- num_scheduled_tokens = scheduler_output.num_scheduled_tokens[
366
- req_id]
367
-
368
- if num_computed_tokens < num_prompt_tokens:
369
- # This is prompt
370
- break
371
-
372
- # This is decode
373
- assert num_scheduled_tokens == 1
374
- decode_req_ids.append(req_id)
375
-
376
- # Traverse prompts
377
- prompt_req_ids = []
378
- prompt_scheduled_tokens = []
379
- for i in range(len(decode_req_ids), num_reqs):
380
- req_id = self.input_batch.req_ids[i]
318
+ # Get the number of scheduled tokens for each request.
319
+ num_scheduled_tokens_per_req = []
320
+ max_num_scheduled_tokens_all_reqs = 0
321
+ for req_id in self.input_batch.req_ids[:num_reqs]:
322
assert req_id is not None
382
-
383
- num_computed_tokens = self.input_batch.num_computed_tokens_cpu[i]
384
- num_prompt_tokens = self.input_batch.num_prompt_tokens[i]
385
- num_scheduled_tokens = scheduler_output.num_scheduled_tokens[
386
- req_id]
387
-
388
- # Must be prompt
389
- assert num_computed_tokens < num_prompt_tokens
390
-
391
- prompt_req_ids.append(req_id)
392
- prompt_scheduled_tokens.append(num_scheduled_tokens)
393
-
394
- return PromptDecodeInfo(prompt_req_ids, decode_req_ids,
395
- prompt_scheduled_tokens)
396
-
397
- def _prepare_prompt(self, req_index: int,
398
- num_scheduled_tokens: int) -> PromptData:
399
- num_computed_tokens = self.input_batch.num_computed_tokens_cpu[
400
- req_index]
401
- num_prompt_tokens = self.input_batch.num_prompt_tokens[req_index]
402
-
403
- # Must be prompt
404
- assert num_computed_tokens < num_prompt_tokens
405
-
406
- # Prompt len
407
- prompt_len = num_scheduled_tokens
408
- padded_prompt_len = _get_padded_prompt_len(prompt_len)
409
- assert padded_prompt_len <= self.max_model_len
410
-
411
- # Seq len
412
- seq_len = num_computed_tokens + prompt_len
413
- padded_seq_len = num_computed_tokens + padded_prompt_len
414
-
415
- # Input tokens
416
- input_tokens_cpu = self.input_batch.token_ids_cpu_tensor[
417
- req_index, num_computed_tokens:padded_seq_len]
418
- input_tokens_cpu[prompt_len:] = 0
419
-
420
- # Input positions
421
- input_positions_np = self.input_positions_np[
422
- self.cur_swap_id][:padded_prompt_len]
423
- np.add(num_computed_tokens,
424
- self.arange_np[:padded_prompt_len],
425
- out=input_positions_np)
426
- input_positions_np[prompt_len:] = 0
427
-
428
- # Slot mapping
429
- block_table_np = \
430
- self.input_batch.block_table.get_numpy_array()
431
- block_numbers_np = block_table_np[req_index, input_positions_np //
432
- self.block_size]
433
- block_offsets_np = input_positions_np % self.block_size
434
-
435
- slot_mapping_np = self.slot_mapping_np[
436
- self.cur_swap_id][:padded_prompt_len]
437
- np.add(block_numbers_np * self.block_size,
438
- block_offsets_np,
439
- out=slot_mapping_np)
440
- slot_mapping_np[prompt_len:] = _PAD_SLOT_ID
441
-
442
- # Block table
443
- block_table_cpu = None
444
- if num_computed_tokens > 0:
445
- block_table_cpu = self.input_batch.block_table.get_cpu_tensor()
446
- block_table_cpu = block_table_cpu[req_index]
447
-
448
- # Context len
449
- self.prompt_context_lens_cpu[self.cur_swap_id][0] = 0
450
- if num_computed_tokens > 0:
451
- self.prompt_context_lens_cpu[self.cur_swap_id][0] = seq_len
452
-
453
- # Effective query len
454
- self.prompt_effective_query_lens_cpu[self.cur_swap_id][0] = prompt_len
455
-
456
- # Get final tensors
457
- input_tokens = input_tokens_cpu.reshape(1, -1).to(self.device)
458
- input_positions = self.input_positions_cpu[
459
- self.cur_swap_id][:padded_prompt_len].reshape(1,
460
- -1).to(self.device)
461
- slot_mapping = self.slot_mapping_cpu[
462
- self.cur_swap_id][:padded_prompt_len].reshape(1,
463
- -1).to(self.device)
464
- block_table = block_table_cpu.reshape(1, -1).to(
465
- self.device) if block_table_cpu is not None else None
466
-
467
- context_lens = self.prompt_context_lens_cpu[self.cur_swap_id].to(
468
- self.device)
469
- effective_query_lens = self.prompt_effective_query_lens_cpu[
470
- self.cur_swap_id].to(self.device)
471
-
472
- self.swap_step()
473
-
474
- # Attn metadata
475
- attn_metadata = PallasMetadata(
476
- num_prefills=1,
477
- num_prefill_tokens=0, # NOTE: This is not used.
478
- num_decode_tokens=0,
479
- slot_mapping=slot_mapping,
480
- multi_modal_placeholder_index_maps=None,
481
- enable_kv_scales_calculation=True,
482
- block_tables=block_table,
483
- context_lens=context_lens,
484
- effective_query_lens=effective_query_lens,
485
- )
486
-
487
- return PromptData(input_tokens, input_positions, attn_metadata)
488
-
489
- def _prepare_decode(
490
- self,
491
- decode_req_ids: List[str],
492
- ) -> DecodeData:
493
- # Batch size
494
- batch_size = len(decode_req_ids)
495
- padded_batch_size = _get_padded_batch_size(batch_size)
496
- assert padded_batch_size <= self.max_model_len
497
-
498
- # Init [0 .. batch_size - 1]
499
- req_indices_np = self.arange_np[:padded_batch_size]
500
-
501
- # Input positions
502
- input_positions_np = self.input_positions_np[
503
- self.cur_swap_id][:padded_batch_size]
504
- np.add(self.input_batch.num_computed_tokens_cpu[:padded_batch_size],
505
- 0,
506
- out=input_positions_np)
507
- input_positions_np[batch_size:] = 0
508
- input_positions_cpu = self.input_positions_cpu[
509
- self.cur_swap_id][:padded_batch_size]
510
-
511
- # Input tokens
512
- token_indices_np = (
513
- input_positions_np +
514
- req_indices_np * self.input_batch.token_ids_cpu.shape[1])
515
- input_tokens_cpu = self.input_ids_cpu[
516
- self.cur_swap_id][:padded_batch_size]
323
+ num_tokens = scheduler_output.num_scheduled_tokens[req_id]
324
+ num_scheduled_tokens_per_req.append(num_tokens)
325
+ max_num_scheduled_tokens_all_reqs = max(
326
+ max_num_scheduled_tokens_all_reqs, num_tokens)
327
+ num_scheduled_tokens_per_req = np.array(num_scheduled_tokens_per_req,
328
+ dtype=np.int32)
329
+ assert max_num_scheduled_tokens_all_reqs > 0
330
+
331
+ # Get request indices.
332
+ # E.g., [2, 5, 3] -> [0, 0, 1, 1, 1, 1, 1, 2, 2, 2]
333
+ # For each scheduled token, what are the corresponding req index.
334
+ req_indices = np.repeat(self.arange_np[:num_reqs],
335
+ num_scheduled_tokens_per_req)
336
+
337
+ # Get batched arange.
338
+ # E.g., [2, 5, 3] -> [0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
339
+ # For each scheduled token, what is its position in corresponding req.
340
+ arange = np.concatenate(
341
+ [self.arange_np[:n] for n in num_scheduled_tokens_per_req])
342
+
343
+ # Get positions.
344
+ positions_np = self.positions_np[:total_num_scheduled_tokens]
345
+ np.add(self.input_batch.num_computed_tokens_cpu[req_indices],
346
+ arange,
347
+ out=positions_np)
348
+
349
+ # Get token indices.
350
+ # E.g., [0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
351
+ # -> [0, 1, M, M + 1, M + 2, M + 3, M + 4, 2 * M, 2 * M + 1, 2 * M + 2]
352
+ # where M is the max_model_len.
353
+ token_indices = (positions_np +
354
+ req_indices * self.input_batch.token_ids_cpu.shape[1])
355
+
356
+ # NOTE(woosuk): We use torch.index_select instead of np.take here
357
+ # because torch.index_select is much faster than np.take for large
358
+ # tensors.
359
torch.index_select(self.input_batch.token_ids_cpu_tensor.flatten(),
360
0,
519
- torch.from_numpy(token_indices_np),
520
- out=input_tokens_cpu)
521
- input_tokens_cpu[batch_size:] = 0
522
-
523
- # Slot mapping
524
- block_table_indices_np = (
525
- req_indices_np * self.max_num_blocks_per_req +
526
- input_positions_np // self.block_size)
527
-
361
+ torch.from_numpy(token_indices),
362
+ out=self.input_ids_cpu[:total_num_scheduled_tokens])
363
+
364
+ # Calculate the slot mapping.
365
+ # E.g., [0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
366
+ # -> [0, 0, K, K, K + 1, K + 1, K + 2, 2 * K, 2 * K, 2 * K + 1]
367
+ # where K is the max_num_blocks_per_req and the block size is 2.
368
+ # NOTE(woosuk): We can't simply use `token_indices // block_size` here
369
+ # because M (max_model_len) is not necessarily divisible by block_size.
370
+ # req_indices: # E.g., [2, 5, 3] -> [0, 0, 1, 1, 1, 1, 1, 2, 2, 2]
371
+ block_table_indices = (req_indices * self.max_num_blocks_per_req +
372
+ positions_np // self.block_size)
373
+ # NOTE(woosuk): We use torch.index_select instead of np.take here
374
+ # because torch.index_select is much faster than np.take for large
375
+ # tensors.
376
block_table_cpu = self.input_batch.block_table.get_cpu_tensor()
377
+ block_numbers = block_table_cpu.flatten()[block_table_indices].numpy()
378
+ block_offsets = positions_np % self.block_size
379
+ np.add(block_numbers * self.block_size,
380
+ block_offsets,
381
+ out=self.slot_mapping_np[:total_num_scheduled_tokens])
382
+
383
+ # Prepare the attention metadata.
384
+ self.query_start_loc_np[0] = 0
385
+ np.cumsum(num_scheduled_tokens_per_req,
386
+ out=self.query_start_loc_np[1:num_reqs + 1])
387
+
388
+ self.seq_lens_np[:num_reqs] = (
389
+ self.input_batch.num_computed_tokens_cpu[:num_reqs] +
390
+ num_scheduled_tokens_per_req)
391
+
392
+ # Do the padding and copy the tensors to the TPU.
393
+ padded_total_num_scheduled_tokens = _get_padded_number(
394
+ total_num_scheduled_tokens, NUM_QUERIES_PER_BLOCK)
395
+ self.input_ids = self.input_ids_cpu[:
396
+ padded_total_num_scheduled_tokens].to(
397
+ self.device)
398
+ self.position_ids = self.positions_cpu[:
399
+ padded_total_num_scheduled_tokens].to(
400
+ self.device)
401
+ self.slot_mapping_cpu[total_num_scheduled_tokens:] = _PAD_SLOT_ID
402
+ slot_mapping = self.slot_mapping_cpu[:
403
+ padded_total_num_scheduled_tokens].to(
404
+ self.device)
405
+ padded_block_table = self.block_table_cpu[:
406
+ padded_total_num_scheduled_tokens]
407
+ padded_block_table[:num_reqs, :self.max_num_blocks_per_req] = (
408
+ self.input_batch.block_table.get_cpu_tensor()[:num_reqs])
409
+ padded_block_table = padded_block_table.to(self.device)
410
+ query_start_loc = self.query_start_loc_cpu[:
411
+ padded_total_num_scheduled_tokens
412
+ + 1].to(self.device)
413
+ seq_lens = self.seq_lens_cpu[:padded_total_num_scheduled_tokens].to(
414
+ self.device)
415
530
- block_numbers_np = block_table_cpu.flatten(
531
- )[block_table_indices_np].numpy()
532
-
533
- block_offsets_np = input_positions_np % self.block_size
534
-
535
- slot_mapping_np = self.slot_mapping_np[
536
- self.cur_swap_id][:padded_batch_size]
537
- np.add(block_numbers_np * self.block_size,
538
- block_offsets_np,
539
- out=slot_mapping_np)
540
- slot_mapping_np[batch_size:] = _PAD_SLOT_ID
541
-
542
- block_table_cpu = block_table_cpu[:padded_batch_size]
543
-
544
- # Context lens
545
- context_lens_np = self.decode_context_lens_np[
546
- self.cur_swap_id][:padded_batch_size]
547
- np.add(self.input_batch.num_computed_tokens_cpu[:padded_batch_size],
548
- 1,
549
- out=context_lens_np)
550
- context_lens_np[batch_size:] = 0
551
-
552
- # Get final tensors
553
- input_tokens = input_tokens_cpu.reshape(-1, 1).to(self.device)
554
- input_positions = input_positions_cpu.reshape(-1, 1).to(self.device)
555
- slot_mapping = self.slot_mapping_cpu[
556
- self.cur_swap_id][:padded_batch_size].reshape(-1,
557
- 1).to(self.device)
558
- block_table = block_table_cpu.to(self.device)
559
- context_lens = self.decode_context_lens_cpu[
560
- self.cur_swap_id][:padded_batch_size].to(self.device)
561
-
562
- self.swap_step()
563
-
564
- # Attn metadata
416
attn_metadata = PallasMetadata(
566
- num_prefills=0,
567
- num_prefill_tokens=0,
568
- num_decode_tokens=padded_batch_size,
417
slot_mapping=slot_mapping,
570
- multi_modal_placeholder_index_maps=None,
571
- enable_kv_scales_calculation=True,
572
- block_tables=block_table,
573
- context_lens=context_lens,
574
- effective_query_lens=None,
418
+ block_tables=padded_block_table,
419
+ context_lens=seq_lens,
420
+ query_start_loc=query_start_loc,
421
+ num_seqs=num_reqs,
422
)
576
-
577
- return DecodeData(input_tokens=input_tokens,
578
- input_positions=input_positions,
579
- attn_metadata=attn_metadata)
423
+ # NOTE(woosuk): Due to chunked prefills, there can be at most 1 partial
424
+ # request in the batch. While we should not sample any token from this
425
+ # partial request, we do so for simplicity. We will ignore the sampled
426
+ # token from the partial request.
427
+ # TODO: Support prompt logprobs.
428
+ logits_indices = query_start_loc[1:] - 1
429
+ return attn_metadata, logits_indices
430
431
@torch.no_grad()
432
def execute_model(
@@ -586,118 +436,81 @@ class TPUModelRunner:
436
# Update cached state
437
self._update_states(scheduler_output)
438
589
- # If necessary, swap decodes/prompts to have all decodes on the start
590
- ensure_decodes_first(self.input_batch)
591
-
592
- # Prepare prompts/decodes info
593
- pd_info = self._get_prompts_and_decodes(scheduler_output)
594
-
595
- # Init
596
- num_prompts = len(pd_info.prompt_req_ids)
597
- num_decodes = len(pd_info.decode_req_ids)
598
- decode_data = None
599
- sampled_token_ids = [0] * self.input_batch.num_reqs
600
-
601
- # Run each prompt individually
602
- is_first = True
603
- for i in range(num_prompts):
604
- req_id = pd_info.prompt_req_ids[i]
605
- req_index = num_decodes + i
606
- assert req_index == self.input_batch.req_id_to_index[
607
- req_id] # TODO: Remove
608
- req_state = self.requests[req_id]
609
- num_scheduled_tokens = pd_info.prompt_scheduled_tokens[i]
610
- prompt_len = num_scheduled_tokens
611
- seq_len = req_state.num_computed_tokens + num_scheduled_tokens
612
-
613
- # Prepare first prompt
614
- if is_first:
615
- prompt_data = self._prepare_prompt(req_index,
616
- num_scheduled_tokens)
617
- is_first = False
618
-
619
- # Run forward pass
620
- with set_forward_context(prompt_data.attn_metadata,
621
- self.vllm_config):
622
- assert self.model is not None
623
- selected_token_ids = self.model(prompt_data.input_tokens,
624
- prompt_data.input_positions,
625
- self.kv_caches)
626
-
627
- # In parallel to TPU execution, prepare the next iteration
628
- if i < num_prompts - 1:
629
- # There is next prompt => prepare it
630
- prompt_data = self._prepare_prompt(
631
- req_index + 1, pd_info.prompt_scheduled_tokens[i + 1])
632
- elif i == num_prompts - 1 and num_decodes > 0:
633
- # There is next decode => prepare it
634
- decode_data = self._prepare_decode(pd_info.decode_req_ids)
635
-
636
- # Update cached state (if prompt is fully done)
637
- if seq_len >= len(req_state.prompt_token_ids):
638
- # Transfer sampled tokens from TPU to CPU
639
- selected_token_ids_cpu = selected_token_ids.cpu()
640
-
641
- # Get output token
642
- token_id = selected_token_ids_cpu[prompt_len - 1].item()
643
- sampled_token_ids[req_index] = token_id
644
-
645
- # Add output token to the request
646
- self.input_batch.token_ids_cpu[req_index, seq_len] = token_id
647
- self.input_batch.num_tokens[req_index] += 1
648
- req_state.output_token_ids.append(token_id)
439
+ # Prepare inputs
440
+ attn_metadata, logits_indices = self._prepare_inputs(scheduler_output)
441
+ total_num_scheduled_tokens = scheduler_output.total_num_scheduled_tokens
442
650
- # Run decodes (a single batch)
651
- if num_decodes > 0:
652
-
653
- # Prepare decode (if was not yet prepared)
654
- if decode_data is None:
655
- decode_data = self._prepare_decode(pd_info.decode_req_ids)
656
-
657
- # Run forward pass
658
- with set_forward_context(decode_data.attn_metadata,
659
- self.vllm_config):
660
- assert self.model is not None
661
- selected_token_ids = self.model(decode_data.input_tokens,
662
- decode_data.input_positions,
663
- self.kv_caches)
664
-
665
- # Transfer sampled tokens from TPU to CPU
666
- decode_token_ids_cpu = selected_token_ids.cpu()
667
- # Convert to list
668
- decode_token_ids_list = decode_token_ids_cpu.tolist()
669
-
670
- # Update cached state for each decode request
671
- for i in range(num_decodes):
672
- req_id = pd_info.decode_req_ids[i]
673
- req_index = i
674
- assert req_index == self.input_batch.req_id_to_index[
675
- req_id] # TODO: Remove
676
- req_state = self.requests[req_id]
677
- seq_len = req_state.num_computed_tokens + 1
678
-
679
- token_id = decode_token_ids_list[i]
680
- sampled_token_ids[req_index] = token_id
681
-
682
- self.input_batch.token_ids_cpu[req_index, seq_len] = token_id
683
- self.input_batch.num_tokens[req_index] += 1
684
- req_state.output_token_ids.append(token_id)
443
+ # Run the decoder
444
+ with set_forward_context(attn_metadata, self.vllm_config):
445
+ hidden_states = self.model(
446
+ token_ids=self.input_ids,
447
+ position_ids=self.position_ids,
448
+ kv_caches=self.kv_caches,
449
+ )
450
+ hidden_states = hidden_states[:total_num_scheduled_tokens]
451
+ num_reqs = self.input_batch.num_reqs
452
+ logits_indices = logits_indices[:num_reqs]
453
+ hidden_states = hidden_states[logits_indices]
454
+ logits = self.model.compute_logits(hidden_states, None)
455
+ selected_token_ids = torch.argmax(logits, dim=-1, keepdim=True)
456
+
457
+ # Then, let's update the cache state.
458
+ request_seq_lens: List[Tuple[int, CachedRequestState, int]] = []
459
+ for i, req_id in zip(range(num_reqs), self.input_batch.req_ids):
460
+ assert req_id is not None
461
+ req_state = self.requests[req_id]
462
+ seq_len = (req_state.num_computed_tokens +
463
+ scheduler_output.num_scheduled_tokens[req_id])
464
+ if seq_len >= req_state.num_tokens:
465
+ request_seq_lens.append((i, req_state, seq_len))
466
+ else:
467
+ # Ignore the sampled token from the partial request.
468
+ # Rewind the generator state as if the token was not sampled.
469
+ generator = self.input_batch.generators.get(i)
470
+ if generator is not None:
471
+ # This relies on cuda-specific torch-internal impl details
472
+ generator.set_offset(generator.get_offset() - 4)
473
+
474
+ # num_reqs entries should be non-None
475
+ assert all(
476
+ req_id is not None for req_id in
477
+ self.input_batch.req_ids[:num_reqs]), "req_ids contains None"
478
+ req_ids = cast(List[str], self.input_batch.req_ids[:num_reqs])
479
686
- # Create output.
687
- all_req_ids = pd_info.decode_req_ids + pd_info.prompt_req_ids
480
prompt_logprobs_dict: Dict[str, Optional[LogprobsTensors]] = {}
689
- for req_id in all_req_ids:
481
+ for req_id in self.input_batch.req_ids[:num_reqs]:
482
prompt_logprobs_dict[req_id] = None
483
484
+ max_gen_len = selected_token_ids.shape[-1]
485
+ if max_gen_len == 1:
486
+ valid_sampled_token_ids = selected_token_ids.tolist()
487
+ for i, req_state, seq_len in request_seq_lens:
488
+ token_id = valid_sampled_token_ids[i][0]
489
+ self.input_batch.token_ids_cpu[i, seq_len] = token_id
490
+ req_state.output_token_ids.append(token_id)
491
+ self.input_batch.num_tokens[i] += 1
492
+ else:
493
+ valid_mask = selected_token_ids != INVALID_TOKEN_ID
494
+ gen_lens = valid_mask.sum(dim=1).tolist()
495
+ valid_sampled_token_ids = [
496
+ seq.tolist()
497
+ for seq in selected_token_ids[valid_mask].split(gen_lens)
498
+ ]
499
+ self.input_batch.num_tokens[:num_reqs] += gen_lens
500
+ for i, req_state, seq_len in request_seq_lens:
501
+ target_slice = slice(seq_len - gen_lens[i] + 1, seq_len + 1)
502
+ self.input_batch.token_ids_cpu[
503
+ i, target_slice] = valid_sampled_token_ids[i]
504
+ req_state.output_token_ids.extend(valid_sampled_token_ids[i])
505
+
506
model_runner_output = ModelRunnerOutput(
693
- req_ids=all_req_ids,
507
+ req_ids=req_ids,
508
req_id_to_index=self.input_batch.req_id_to_index,
695
- sampled_token_ids=[[token_id] for token_id in sampled_token_ids],
509
+ sampled_token_ids=valid_sampled_token_ids,
510
spec_token_ids=None,
511
logprobs=None,
698
- prompt_logprobs_dict=prompt_logprobs_dict, # type: ignore[arg-type]
512
+ prompt_logprobs_dict=prompt_logprobs_dict,
513
)
700
-
514
return model_runner_output
515
516
def load_model(self) -> None:
@@ -731,185 +544,63 @@ class TPUModelRunner:
544
self,
545
kv_caches,
546
num_tokens: int,
734
- seq_len: Optional[int] = None,
735
- exec_mode: Optional[ExecutionMode] = None,
547
) -> None:
737
- assert seq_len is not None
738
- assert exec_mode is not None
739
-
740
- exec_mode = ExecutionMode(exec_mode)
741
- if exec_mode.is_prefill():
742
- seq_len = (seq_len + 15) // 16 * 16
743
- token_ids = torch.zeros((num_tokens, seq_len),
744
- dtype=torch.int32,
745
- device=self.device)
746
- position_ids = torch.zeros((num_tokens, seq_len),
747
- dtype=torch.int32,
748
- device=self.device)
749
- slot_mapping = torch.zeros((num_tokens, seq_len),
750
- dtype=torch.int64,
751
- device=self.device)
752
- if exec_mode == ExecutionMode.PREFILL:
753
- attn_metadata = PallasMetadata(
754
- num_prefills=num_tokens,
755
- num_prefill_tokens=num_tokens * seq_len,
756
- num_decode_tokens=0,
757
- slot_mapping=slot_mapping,
758
- multi_modal_placeholder_index_maps=None,
759
- enable_kv_scales_calculation=True,
760
- block_tables=None,
761
- context_lens=None,
762
- effective_query_lens=None,
763
- )
764
-
765
- else:
766
- context_lens = torch.ones((num_tokens, ),
767
- dtype=torch.int32,
768
- device=self.device)
769
-
770
- block_tables = torch.zeros(
771
- (num_tokens, self.max_num_blocks_per_req),
772
- dtype=torch.int32,
773
- device=self.device)
774
-
775
- effective_query_lens = torch.ones_like(context_lens)
776
-
777
- attn_metadata = PallasMetadata(
778
- num_prefills=num_tokens,
779
- num_prefill_tokens=num_tokens * seq_len,
780
- num_decode_tokens=0,
781
- slot_mapping=slot_mapping,
782
- multi_modal_placeholder_index_maps=None,
783
- enable_kv_scales_calculation=True,
784
- block_tables=block_tables,
785
- context_lens=context_lens,
786
- effective_query_lens=effective_query_lens,
787
- )
788
- else:
789
- assert seq_len == 1
790
- token_ids = torch.zeros((num_tokens, seq_len),
791
- dtype=torch.int32,
792
- device=self.device)
793
- position_ids = torch.zeros((num_tokens, seq_len),
794
- dtype=torch.int32,
795
- device=self.device)
796
- slot_mapping = torch.zeros((num_tokens, seq_len),
797
- dtype=torch.int64,
798
- device=self.device)
799
- block_tables = torch.zeros(
800
- (num_tokens, self.max_num_blocks_per_req),
801
- dtype=torch.int32,
802
- device=self.device)
803
- context_lens = torch.ones((num_tokens, ),
804
- dtype=torch.int32,
805
- device=self.device)
806
- attn_metadata = PallasMetadata(
807
- num_prefills=0,
808
- num_prefill_tokens=0,
809
- num_decode_tokens=num_tokens * seq_len,
810
- slot_mapping=slot_mapping,
811
- multi_modal_placeholder_index_maps=None,
812
- enable_kv_scales_calculation=True,
813
- block_tables=block_tables,
814
- context_lens=context_lens,
815
- )
548
+ input_ids = torch.zeros(num_tokens,
549
+ dtype=torch.int32,
550
+ device=self.device)
551
+ position_ids = torch.zeros(num_tokens,
552
+ dtype=torch.int32,
553
+ device=self.device)
554
+ slot_mapping = torch.zeros(num_tokens,
555
+ dtype=torch.int64,
556
+ device=self.device)
557
+ block_tables = torch.zeros((num_tokens, self.block_table_cpu.shape[1]),
558
+ dtype=torch.int32,
559
+ device=self.device)
560
+ query_lens = [1] * num_tokens
561
+ query_start_loc = torch.cumsum(torch.tensor([0] + query_lens,
562
+ dtype=torch.int32),
563
+ dim=0,
564
+ dtype=torch.int32).to(self.device)
565
+ context_lens = torch.ones((num_tokens, ),
566
+ dtype=torch.int32,
567
+ device=self.device)
568
+ attn_metadata = PallasMetadata(
569
+ slot_mapping=slot_mapping,
570
+ block_tables=block_tables,
571
+ context_lens=context_lens,
572
+ query_start_loc=query_start_loc,
573
+ num_seqs=num_tokens,
574
+ )
575
817
- # NOTE(woosuk): There are two stages of compilation: torch.compile and
818
- # XLA compilation. Using `mark_dynamic` can reduce the torch.compile
819
- # overhead by reusing the FX graph for different shapes.
820
- # However, the XLA graph will still require static shapes and needs to
821
- # be re-compiled for every different shapes. This overhead is inevitable
822
- # in the first run, but can be skipped afterwards as we cache the XLA
823
- # graphs in the disk (VLLM_XLA_CACHE_PATH).
824
- if exec_mode.is_prefill():
825
- # Prefll
826
- torch._dynamo.mark_dynamic(token_ids, 1)
827
- torch._dynamo.mark_dynamic(position_ids, 1)
828
- torch._dynamo.mark_dynamic(attn_metadata.slot_mapping, 1)
829
- else:
830
- # Decode
831
- torch._dynamo.mark_dynamic(token_ids, 0)
832
- torch._dynamo.mark_dynamic(position_ids, 0)
833
- torch._dynamo.mark_dynamic(attn_metadata.slot_mapping, 0)
834
- torch._dynamo.mark_dynamic(attn_metadata.context_lens, 0)
835
- torch._dynamo.mark_dynamic(attn_metadata.block_tables, 0)
576
+ torch._dynamo.mark_dynamic(input_ids, 0)
577
+ torch._dynamo.mark_dynamic(position_ids, 0)
578
+ torch._dynamo.mark_dynamic(attn_metadata.slot_mapping, 0)
579
+ torch._dynamo.mark_dynamic(attn_metadata.block_tables, 0)
580
+ torch._dynamo.mark_dynamic(attn_metadata.query_start_loc, 0)
581
+ torch._dynamo.mark_dynamic(attn_metadata.context_lens, 0)
582
583
with set_forward_context(attn_metadata, self.vllm_config, 0):
584
assert self.model is not None
839
- self.model(token_ids, position_ids, kv_caches)
585
+ self.model(input_ids, position_ids, kv_caches)
586
587
def capture_model(self) -> None:
588
"""Compile the model."""
589
844
- # Prefill
845
- logger.info(
846
- "Compiling the model with different input shapes for prefill:")
847
- start = time.time()
848
- for batch_size in [1]:
849
- seq_len = 16
850
- while seq_len <= self.model_config.max_model_len:
851
- self.dummy_run(self.kv_caches,
852
- batch_size,
853
- seq_len,
854
- exec_mode=ExecutionMode.PREFILL)
855
- xm.wait_device_ops()
856
- logger.info(" batch_size: %d, seq_len: %d", batch_size,
857
- seq_len)
858
- num_tokens = batch_size * seq_len
859
- if num_tokens >= self.scheduler_config.max_num_batched_tokens:
860
- break
861
- seq_len = seq_len * 2
862
-
863
- end = time.time()
864
- logger.info(" -- Compilation for prefill done in %.2f [secs].",
865
- end - start)
866
-
867
- # Prefix prefill
868
- if self.scheduler_config.enable_chunked_prefill:
869
- logger.info("Compiling the model with different input shapes for "
870
- "prefix prefill:")
871
- start = time.time()
872
- for batch_size in [1]:
873
- seq_len = 16
874
- while seq_len <= self.model_config.max_model_len:
875
- self.dummy_run(self.kv_caches,
876
- batch_size,
877
- seq_len,
878
- exec_mode=ExecutionMode.PREFIX_PREFILL)
879
- xm.wait_device_ops()
880
- logger.info(" batch_size: %d, seq_len: %d", batch_size,
881
- seq_len)
882
- num_tokens = batch_size * seq_len
883
- if (num_tokens
884
- >= self.scheduler_config.max_num_batched_tokens):
885
- break
886
- seq_len = seq_len * 2
887
- end = time.time()
888
- logger.info(
889
- " -- Compilation for prefix prefill done in %.2f [secs].",
890
- end - start)
891
-
892
- # Decode
893
- logger.info(
894
- "Compiling the model with different input shapes for decode:")
895
- start = time.time()
896
- seq_len = 1
897
- batch_size = 8 # Must be in sync with _get_padded_batch_size()
590
+ logger.info("Compiling the model with different input shapes.")
591
+
592
+ start = time.perf_counter()
593
+ num_tokens = 16
594
while True:
899
- self.dummy_run(self.kv_caches,
900
- batch_size,
901
- seq_len,
902
- exec_mode=ExecutionMode.DECODE)
595
+ self.dummy_run(self.kv_caches, num_tokens)
596
+ logger.info(" -- num_tokens: %d", num_tokens)
597
+ xm.mark_step()
598
xm.wait_device_ops()
904
- logger.info(" batch_size: %d, seq_len: %d", batch_size, seq_len)
905
-
906
- if batch_size >= self.scheduler_config.max_num_seqs:
599
+ if num_tokens >= self.scheduler_config.max_num_batched_tokens:
600
break
908
- batch_size = batch_size + 16 if batch_size >= 16 else batch_size * 2
909
-
910
- end = time.time()
911
- logger.info(" -- Compilation for decode done in %.2f [secs].",
912
- end - start)
601
+ num_tokens *= 2
602
+ end = time.perf_counter()
603
+ logger.info("Compilation finished in in %.2f [secs].", end - start)
604
605
def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
606
"""
@@ -965,12 +656,8 @@ class ModelWrapperV1(nn.Module):
656
"""Executes the forward pass of the model and samples the next token.
657
658
Args:
968
- token_ids: The input token IDs of shape [batch_size, seq_len].
969
- position_ids: The input position IDs of shape [batch_size, seq_len].
970
- input_lens: The actual input lengths of shape [batch_size].
971
- t: The sampling temperature of shape [batch_size].
972
- p: The top-p probability of shape [batch_size].
973
- num_samples: Number of samples to draw from each logits vector.
659
+ token_ids: The input token IDs of shape [num_tokens].
660
+ position_ids: The input position IDs of shape [num_tokens].
661
kv_caches: The key and value caches. They can be None during the
662
memory profiling at initialization.
663
"""
@@ -982,6 +669,7 @@ class ModelWrapperV1(nn.Module):
669
# [num_kv_heads, num_blocks, block_size, head_size]. To make it
670
# work, we need to flatten the first three dimensions and modify
671
# the slot_mapping accordingly.
672
+ # kv_caches: List[Tuple[torch.Tensor, torch.Tensor]]
673
num_kv_heads, num_blocks, block_size, _ = kv_caches[0][0].shape
674
slot_mapping = attn_metadata.slot_mapping
675
slot_mapping = slot_mapping.flatten()
@@ -997,103 +685,22 @@ class ModelWrapperV1(nn.Module):
685
attn_metadata.slot_mapping = slot_mapping
686
687
assert self.model is not None
1000
- hidden_states = self.model(token_ids, position_ids)
1001
-
1002
- hidden_states = hidden_states.flatten(0, 1)
1003
- logits = self.model.compute_logits(hidden_states, None)
1004
-
1005
- # Greedy sampling.
1006
- argmax_token_ids = torch.argmax(logits, dim=-1, keepdim=True)
1007
- argmax_token_ids = argmax_token_ids.squeeze(dim=-1)
1008
- return argmax_token_ids
1009
-
1010
-
1011
-def swap_positions(b: InputBatch, id_1, id_2):
1012
- assert id_1 != id_2
1013
- req_id_1 = b.req_ids[id_1]
1014
- req_id_2 = b.req_ids[id_2]
1015
- assert req_id_1 is not None
1016
- assert req_id_2 is not None
1017
- assert id_1 == b.req_id_to_index[req_id_1]
1018
- assert id_2 == b.req_id_to_index[req_id_2]
1019
-
1020
- b.req_ids[id_1], b.req_ids[id_2] = b.req_ids[id_2], b.req_ids[id_1]
1021
- b.req_id_to_index[req_id_1], b.req_id_to_index[
1022
- req_id_2] = b.req_id_to_index[req_id_2], b.req_id_to_index[req_id_1]
1023
-
1024
- ids = [id_1, id_2]
1025
- rev_ids = [id_2, id_1]
1026
- b.num_tokens[ids] = b.num_tokens[rev_ids]
1027
- b.token_ids_cpu[ids] = b.token_ids_cpu[rev_ids]
1028
- b.num_prompt_tokens[ids] = b.num_prompt_tokens[rev_ids]
1029
- b.num_computed_tokens_cpu[ids] = b.num_computed_tokens_cpu[rev_ids]
1030
-
1031
- b.block_table.swap_row(id_1, id_2)
1032
-
1033
- b.temperature_cpu[ids] = b.temperature_cpu[rev_ids]
1034
- b.top_p_cpu[ids] = b.top_p_cpu[rev_ids]
1035
- b.top_k_cpu[ids] = b.top_k_cpu[rev_ids]
1036
- b.frequency_penalties_cpu[ids] = b.frequency_penalties_cpu[rev_ids]
1037
- b.presence_penalties_cpu[ids] = b.presence_penalties_cpu[rev_ids]
1038
- b.repetition_penalties_cpu[ids] = b.repetition_penalties_cpu[rev_ids]
1039
-
1040
- b.min_tokens[id_1], b.min_tokens[id_2] = b.min_tokens[id_2], b.min_tokens[
1041
- id_1]
1042
-
1043
- gen_1 = b.generators.pop(id_1, None)
1044
- gen_2 = b.generators.pop(id_2, None)
1045
- if gen_1 is not None:
1046
- b.generators[id_2] = gen_1
1047
- if gen_2 is not None:
1048
- b.generators[id_1] = gen_2
1049
-
1050
-
1051
-def ensure_decodes_first(b: InputBatch):
1052
- num_reqs = b.num_reqs
1053
- while True:
1054
- # Find the first prompt index
1055
- first_prompt_index = None
1056
- for i in range(num_reqs):
1057
- if b.num_computed_tokens_cpu[i] < b.num_prompt_tokens[i]:
1058
- first_prompt_index = i
1059
- break
1060
- if first_prompt_index is None:
1061
- break
1062
-
1063
- # Find the last decode index
1064
- last_decode_index = None
1065
- for i in reversed(range(num_reqs)):
1066
- if b.num_computed_tokens_cpu[i] >= b.num_prompt_tokens[i]:
1067
- last_decode_index = i
1068
- break
1069
- if last_decode_index is None:
1070
- break
1071
-
1072
- # Sanity
1073
- assert first_prompt_index != last_decode_index
1074
-
1075
- # Check if done
1076
- if first_prompt_index > last_decode_index:
1077
- break
1078
-
1079
- # Swap
1080
- swap_positions(b, first_prompt_index, last_decode_index)
688
+ hidden_states = self.model(
689
+ token_ids,
690
+ position_ids,
691
+ kv_caches,
692
+ )
693
694
+ return hidden_states
695
1083
-def _get_padded_prompt_len(x: int) -> int:
1084
- # NOTE(woosuk): The pallas FlashAttention kernel requires the sequence
1085
- # length to be a multiple of 16. We pad the prompt length to the nearest
1086
- # multiple of 16. This is also good for performance.
1087
- if x <= 16:
1088
- return 16
1089
- return 1 << (x - 1).bit_length()
696
+ def compute_logits(
697
+ self,
698
+ hidden_states: torch.Tensor,
699
+ sampling_metadata,
700
+ ) -> Optional[torch.Tensor]:
701
+ logits = self.model.compute_logits(hidden_states, sampling_metadata)
702
+ return logits
703
704
1092
-def _get_padded_batch_size(batch_size: int) -> int:
1093
- # The GMM Pallas kernel requires num_tokens * topk to be a multiple of 16.
1094
- # To meet this requirement in the simplest way, we set the minimal batch
1095
- # size to 8.
1096
- if batch_size <= 8:
1097
- return 8
1098
- else:
1099
- return ((batch_size + 15) // 16) * 16
705
+def _get_padded_number(n: int, multiple: int) -> int:
706
+ return ((n + multiple - 1) // multiple) * multiple
vllm/v1/worker/tpu_worker.py
+2
-4
@@ -21,7 +21,7 @@ from vllm.v1.kv_cache_interface import (FullAttentionSpec, KVCacheConfig,
21
KVCacheSpec)
22
from vllm.v1.outputs import ModelRunnerOutput
23
from vllm.v1.utils import bind_kv_cache
24
-from vllm.v1.worker.tpu_model_runner import ExecutionMode, TPUModelRunner
24
+from vllm.v1.worker.tpu_model_runner import TPUModelRunner
25
26
logger = init_logger(__name__)
27
@@ -126,9 +126,7 @@ class TPUWorker:
126
127
self.model_runner.dummy_run(
128
runner_kv_caches,
129
- num_tokens=1,
130
- seq_len=self.scheduler_config.max_num_batched_tokens,
131
- exec_mode=ExecutionMode.PREFILL,
129
+ num_tokens=self.scheduler_config.max_num_batched_tokens,
130
)
131
132
# Synchronize before measuring the memory usage.