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
[Core] Factor out common logic for MM budget calculation (#22228)
Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>
Cyrus Leung committed
Aug 5, 2025 at 14:54 UTC
811ac13d039648f2d78a636ce4366e70449380c8
3 files changed
+306
-223
vllm/v1/worker/gpu_model_runner.py
+108
-115
@@ -36,7 +36,8 @@ from vllm.model_executor.models.interfaces import (is_mixture_of_experts,
36
from vllm.model_executor.models.interfaces_base import (
37
VllmModelForPooling, is_pooling_model, is_text_generation_model)
38
from vllm.multimodal import MULTIMODAL_REGISTRY
39
-from vllm.multimodal.inputs import MultiModalKwargs, PlaceholderRange
39
+from vllm.multimodal.inputs import (BatchedTensorInputs, MultiModalKwargs,
40
+ PlaceholderRange)
41
from vllm.multimodal.utils import group_mm_inputs_by_modality
42
from vllm.pooling_params import PoolingParams
43
from vllm.sampling_params import SamplingType
@@ -51,7 +52,6 @@ from vllm.v1.attention.backends.utils import (
52
make_kv_sharing_fast_prefill_attention_metadata,
53
make_local_attention_virtual_batches,
54
reorder_batch_to_split_decodes_and_prefills)
54
-from vllm.v1.core.encoder_cache_manager import compute_encoder_budget
55
from vllm.v1.kv_cache_interface import (AttentionSpec,
56
ChunkedLocalAttentionSpec,
57
FullAttentionSpec, KVCacheConfig,
@@ -73,7 +73,7 @@ from vllm.v1.worker.kv_connector_model_runner_mixin import (
73
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
74
75
from ..sample.logits_processor import LogitsProcessorManager
76
-from .utils import (bind_kv_cache, gather_mm_placeholders,
76
+from .utils import (MultiModalBudget, bind_kv_cache, gather_mm_placeholders,
77
initialize_kv_cache_for_kv_sharing,
78
sanity_check_mm_encoder_outputs, scatter_mm_placeholders)
79
@@ -148,14 +148,6 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
148
self.mm_registry = MULTIMODAL_REGISTRY
149
self.uses_mrope = model_config.uses_mrope
150
151
- encoder_compute_budget, encoder_cache_size = compute_encoder_budget(
152
- model_config=model_config,
153
- scheduler_config=scheduler_config,
154
- mm_registry=self.mm_registry,
155
- )
156
- self.max_num_encoder_input_tokens = encoder_compute_budget
157
- self.encoder_cache_size = encoder_cache_size
158
-
151
# Sampler
152
self.sampler = Sampler(logprobs_mode=self.model_config.logprobs_mode)
153
@@ -330,6 +322,14 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
322
self.kv_sharing_fast_prefill_logits_indices = torch.zeros(
323
self.max_num_tokens, dtype=torch.int32, device=self.device)
324
325
+ self.mm_budget = (MultiModalBudget(
326
+ self.model_config,
327
+ self.scheduler_config,
328
+ self.mm_registry,
329
+ max_model_len=self.max_model_len,
330
+ max_num_reqs=self.max_num_reqs,
331
+ ) if self.is_multimodal_model else None)
332
+
333
self.reorder_batch_threshold: Optional[int] = None
334
335
def _may_reorder_batch(self, scheduler_output: "SchedulerOutput") -> None:
@@ -578,37 +578,33 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
578
# Refresh batch metadata with any pending updates.
579
self.input_batch.refresh_metadata()
580
581
- def _init_model_kwargs_for_multimodal_model(
581
+ def _extract_mm_kwargs(
582
self,
583
- scheduler_output: Optional["SchedulerOutput"] = None,
584
- num_reqs: int = -1,
585
- ) -> dict[str, Any]:
586
-
587
- model_kwargs: dict[str, Any] = {}
588
- if self.is_multimodal_raw_input_supported:
589
- # This model requires the raw multimodal data in input.
583
+ scheduler_output: "SchedulerOutput",
584
+ ) -> BatchedTensorInputs:
585
+ if self.is_multimodal_raw_input_supported: # noqa: SIM102
586
if scheduler_output:
591
- multi_modal_kwargs_list = []
587
+ multi_modal_kwargs_list = list[MultiModalKwargs]()
588
for req in scheduler_output.scheduled_new_reqs:
589
req_mm_inputs = req.mm_inputs
590
if not isinstance(req_mm_inputs, list):
591
req_mm_inputs = list(req_mm_inputs)
592
multi_modal_kwargs_list.extend(req_mm_inputs)
597
- multi_modal_kwargs = MultiModalKwargs.batch(
598
- multi_modal_kwargs_list)
599
- else:
600
- # The only case where SchedulerOutput is None is for
601
- # a dummy run let's get some dummy data.
602
- dummy_data = [
603
- self.mm_registry.get_decoder_dummy_data(
604
- model_config=self.model_config,
605
- seq_len=1).multi_modal_data for i in range(num_reqs)
606
- ]
607
- multi_modal_kwargs = MultiModalKwargs.batch(dummy_data)
593
609
- model_kwargs.update(multi_modal_kwargs)
594
+ return MultiModalKwargs.batch(multi_modal_kwargs_list)
595
611
- return model_kwargs
596
+ return {}
597
+
598
+ def _dummy_mm_kwargs(self, num_seqs: int) -> BatchedTensorInputs:
599
+ if self.is_multimodal_raw_input_supported:
600
+ mm_budget = self.mm_budget
601
+ assert mm_budget is not None
602
+
603
+ dummy_modality, _ = mm_budget.get_modality_with_max_tokens()
604
+
605
+ return self._get_mm_dummy_batch(dummy_modality, num_seqs)
606
+
607
+ return {}
608
609
def _get_cumsum_and_arange(
610
self,
@@ -1517,19 +1513,18 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1513
# NOTE(woosuk): To unify token ids and soft tokens (vision
1514
# embeddings), we always use embeddings (rather than token ids)
1515
# as input to the multimodal model, even when the input is text.
1520
- input_ids = self.input_ids[:num_scheduled_tokens]
1521
-
1522
- model_kwargs = self._init_model_kwargs_for_multimodal_model(
1523
- scheduler_output=scheduler_output)
1524
- inputs_embeds = self.model.get_input_embeddings(
1525
- input_ids=input_ids,
1516
+ inputs_embeds_scheduled = self.model.get_input_embeddings(
1517
+ input_ids=self.input_ids[:num_scheduled_tokens],
1518
multimodal_embeddings=mm_embeds or None,
1519
)
1520
1521
# TODO(woosuk): Avoid the copy. Optimize.
1530
- self.inputs_embeds[:num_scheduled_tokens].copy_(inputs_embeds)
1531
- inputs_embeds = self.inputs_embeds[:num_input_tokens]
1522
+ self.inputs_embeds[:num_scheduled_tokens].copy_(
1523
+ inputs_embeds_scheduled)
1524
+
1525
input_ids = None
1526
+ inputs_embeds = self.inputs_embeds[:num_input_tokens]
1527
+ model_mm_kwargs = self._extract_mm_kwargs(scheduler_output)
1528
else:
1529
# For text-only models, we use token ids as input.
1530
# While it is possible to use embeddings as input just like the
@@ -1537,7 +1532,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1532
# then the embedding layer is not included in the CUDA graph.
1533
input_ids = self.input_ids[:num_input_tokens]
1534
inputs_embeds = None
1540
- model_kwargs = {}
1535
+ model_mm_kwargs = {}
1536
if self.uses_mrope:
1537
positions = self.mrope_positions[:, :num_input_tokens]
1538
else:
@@ -1571,7 +1566,7 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1566
intermediate_tensors=intermediate_tensors,
1567
inputs_embeds=inputs_embeds,
1568
**MultiModalKwargs.as_kwargs(
1574
- model_kwargs,
1569
+ model_mm_kwargs,
1570
device=self.device,
1571
),
1572
)
@@ -2149,6 +2144,30 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2144
yield
2145
input_ids.fill_(0)
2146
2147
+ def _get_mm_dummy_batch(
2148
+ self,
2149
+ modality: str,
2150
+ max_items_per_batch: int,
2151
+ ) -> BatchedTensorInputs:
2152
+ """Dummy data for profiling and precompiling multimodal models."""
2153
+ dummy_decoder_data = self.mm_registry.get_decoder_dummy_data(
2154
+ model_config=self.model_config,
2155
+ seq_len=self.max_num_tokens,
2156
+ mm_counts={modality: 1},
2157
+ )
2158
+ dummy_mm_data = dummy_decoder_data.multi_modal_data
2159
+
2160
+ # Result in the maximum GPU consumption of the model
2161
+ dummy_mm_item = dummy_mm_data.get_item(modality=modality, item_index=0)
2162
+ dummy_mm_kwargs = MultiModalKwargs.from_items([dummy_mm_item])
2163
+
2164
+ batched_dummy_mm_inputs = MultiModalKwargs.batch([dummy_mm_kwargs] *
2165
+ max_items_per_batch)
2166
+ return MultiModalKwargs.as_kwargs(
2167
+ batched_dummy_mm_inputs,
2168
+ device=self.device,
2169
+ )
2170
+
2171
@torch.inference_mode()
2172
def _dummy_run(
2173
self,
@@ -2213,16 +2232,14 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2232
2233
with self.maybe_dummy_run_with_lora(self.lora_config,
2234
num_scheduled_tokens):
2216
- model = self.model
2235
if self.is_multimodal_model:
2218
- model_kwargs = self._init_model_kwargs_for_multimodal_model(
2219
- num_reqs=num_reqs)
2236
input_ids = None
2237
inputs_embeds = self.inputs_embeds[:num_tokens]
2238
+ model_mm_kwargs = self._dummy_mm_kwargs(num_reqs)
2239
else:
2240
input_ids = self.input_ids[:num_tokens]
2241
inputs_embeds = None
2225
- model_kwargs = {}
2242
+ model_mm_kwargs = {}
2243
2244
if self.uses_mrope:
2245
positions = self.mrope_positions[:, :num_tokens]
@@ -2247,13 +2264,13 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2264
self.vllm_config,
2265
num_tokens=num_tokens,
2266
num_tokens_across_dp=num_tokens_across_dp):
2250
- outputs = model(
2267
+ outputs = self.model(
2268
input_ids=input_ids,
2269
positions=positions,
2270
intermediate_tensors=intermediate_tensors,
2271
inputs_embeds=inputs_embeds,
2272
**MultiModalKwargs.as_kwargs(
2256
- model_kwargs,
2273
+ model_mm_kwargs,
2274
device=self.device,
2275
),
2276
)
@@ -2423,75 +2440,51 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
2440
2441
def profile_run(self) -> None:
2442
# Profile with multimodal encoder & encoder cache.
2426
- # TODO: handle encoder-decoder models once we support them.
2427
- if (self.is_multimodal_model and self.max_num_encoder_input_tokens > 0
2428
- and self.encoder_cache_size > 0):
2429
-
2430
- # NOTE: Currently model is profiled with a single non-text
2431
- # modality with the max possible input tokens even when
2432
- # it supports multiple.
2433
- max_tokens_by_modality_dict = self.mm_registry \
2434
- .get_max_tokens_per_item_by_nonzero_modality(self.model_config)
2435
- dummy_data_modality, max_tokens_per_mm_item = max(
2436
- max_tokens_by_modality_dict.items(), key=lambda item: item[1])
2437
-
2438
- # Check how many items of this modality can be supported by
2439
- # the encoder budget.
2440
- encoder_budget = min(self.max_num_encoder_input_tokens,
2441
- self.encoder_cache_size)
2442
-
2443
- max_num_mm_items_encoder_budget = encoder_budget // \
2444
- max_tokens_per_mm_item
2445
-
2446
- # Check how many items of this modality can be supported by
2447
- # the decoder budget.
2448
- max_mm_items_per_req = self.mm_registry.get_mm_limits_per_prompt(
2449
- self.model_config)[dummy_data_modality]
2450
-
2451
- # NOTE: We do not consider max_num_batched_tokens on purpose
2452
- # because the multimodal embeddings can be generated in advance
2453
- # and chunked prefilled.
2454
- max_num_mm_items_decoder_budget = self.max_num_reqs * \
2455
- max_mm_items_per_req
2456
-
2457
- max_num_mm_items = max(
2458
- 1,
2459
- min(max_num_mm_items_encoder_budget,
2460
- max_num_mm_items_decoder_budget))
2461
-
2462
- logger.info(
2463
- "Encoder cache will be initialized with a budget of %s tokens,"
2464
- " and profiled with %s %s items of the maximum feature size.",
2465
- encoder_budget, max_num_mm_items, dummy_data_modality)
2466
-
2467
- # Create dummy batch of multimodal inputs.
2468
- dummy_mm_kwargs = self.mm_registry.get_decoder_dummy_data(
2469
- model_config=self.model_config,
2470
- seq_len=max_tokens_per_mm_item,
2471
- mm_counts={
2472
- dummy_data_modality: 1
2473
- },
2474
- ).multi_modal_data
2475
-
2476
- batched_dummy_mm_inputs = MultiModalKwargs.batch(
2477
- [dummy_mm_kwargs] * max_num_mm_items,
2478
- pin_memory=self.pin_memory)
2479
- batched_dummy_mm_inputs = MultiModalKwargs.as_kwargs(
2480
- batched_dummy_mm_inputs,
2481
- device=self.device,
2482
- )
2443
+ if self.is_multimodal_model:
2444
+ mm_budget = self.mm_budget
2445
+ assert mm_budget is not None
2446
+
2447
+ # TODO: handle encoder-decoder models once we support them.
2448
+ if (encoder_budget := mm_budget.get_encoder_budget()) > 0:
2449
+ # NOTE: Currently model is profiled with a single non-text
2450
+ # modality with the max possible input tokens even when
2451
+ # it supports multiple.
2452
+ (
2453
+ dummy_modality,
2454
+ max_tokens,
2455
+ ) = mm_budget.get_modality_with_max_tokens()
2456
+ (
2457
+ max_mm_items_per_prompt,
2458
+ max_mm_items_per_batch,
2459
+ ) = mm_budget.get_max_items(dummy_modality, max_tokens)
2460
+
2461
+ logger.info(
2462
+ "Encoder cache will be initialized with a budget of "
2463
+ "%s tokens, and profiled with %s %s items of the maximum "
2464
+ "feature size.",
2465
+ encoder_budget,
2466
+ max_mm_items_per_batch,
2467
+ dummy_modality,
2468
+ )
2469
2484
- # Run multimodal encoder.
2485
- dummy_encoder_outputs = self.model.get_multimodal_embeddings(
2486
- **batched_dummy_mm_inputs)
2470
+ # Create dummy batch of multimodal inputs.
2471
+ batched_dummy_mm_inputs = self._get_mm_dummy_batch(
2472
+ dummy_modality,
2473
+ max_mm_items_per_batch,
2474
+ )
2475
2488
- sanity_check_mm_encoder_outputs(
2489
- dummy_encoder_outputs,
2490
- expected_num_items=max_num_mm_items,
2491
- )
2476
+ # Run multimodal encoder.
2477
+ dummy_encoder_outputs = self.model.get_multimodal_embeddings(
2478
+ **batched_dummy_mm_inputs)
2479
+
2480
+ sanity_check_mm_encoder_outputs(
2481
+ dummy_encoder_outputs,
2482
+ expected_num_items=max_mm_items_per_batch,
2483
+ )
2484
2493
- # Cache the dummy encoder outputs.
2494
- self.encoder_cache["tmp"] = dict(enumerate(dummy_encoder_outputs))
2485
+ # Cache the dummy encoder outputs.
2486
+ self.encoder_cache["tmp"] = dict(
2487
+ enumerate(dummy_encoder_outputs))
2488
2489
# Add `is_profile` here to pre-allocate communication buffers
2490
hidden_states, last_hidden_states \
vllm/v1/worker/tpu_model_runner.py
+89
-108
@@ -42,7 +42,6 @@ from vllm.v1.attention.backends.pallas import (TPU_STR_DTYPE_TO_TORCH_DTYPE,
42
PallasAttentionBackend,
43
PallasMetadata,
44
get_page_size_bytes)
45
-from vllm.v1.core.encoder_cache_manager import compute_encoder_budget
45
from vllm.v1.kv_cache_interface import (AttentionSpec, FullAttentionSpec,
46
KVCacheConfig, KVCacheSpec,
47
SlidingWindowSpec)
@@ -55,7 +54,8 @@ from vllm.v1.worker.kv_connector_model_runner_mixin import (
54
from vllm.v1.worker.lora_model_runner_mixin import LoRAModelRunnerMixin
55
from vllm.v1.worker.tpu_input_batch import CachedRequestState, InputBatch
56
58
-from .utils import (bind_kv_cache, initialize_kv_cache_for_kv_sharing,
57
+from .utils import (MultiModalBudget, bind_kv_cache,
58
+ initialize_kv_cache_for_kv_sharing,
59
sanity_check_mm_encoder_outputs)
60
61
if TYPE_CHECKING:
@@ -195,14 +195,6 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
195
# TODO: Support M-RoPE (e.g, Qwen2-VL)
196
assert not self.uses_mrope, "TPU does not support M-RoPE yet."
197
198
- encoder_compute_budget, encoder_cache_size = compute_encoder_budget(
199
- model_config=model_config,
200
- scheduler_config=scheduler_config,
201
- mm_registry=self.mm_registry,
202
- )
203
- self.max_num_encoder_input_tokens = encoder_compute_budget
204
- self.encoder_cache_size = encoder_cache_size
205
-
198
self._num_slices_per_kv_cache_update_block = \
199
_get_num_slices_per_kv_cache_update_block(get_page_size_bytes(
200
block_size=self.block_size,
@@ -294,36 +286,13 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
286
self.structured_decode_arange = torch.arange(
287
0, 32, device="cpu", pin_memory=self.pin_memory)
288
297
- # Get maximum number of mm items per modality (batch size).
298
- self.max_num_mm_items_by_modality = dict()
299
- if (self.is_multimodal_model and self.max_num_encoder_input_tokens > 0
300
- and self.encoder_cache_size > 0):
301
- max_tokens_by_modality_dict = (
302
- MULTIMODAL_REGISTRY.
303
- get_max_tokens_per_item_by_nonzero_modality(self.model_config))
304
- for modality, max_tokens in max_tokens_by_modality_dict.items():
305
- # Check how many items of this modality can be supported by
306
- # the encoder budget.
307
- encoder_budget = min(self.max_num_encoder_input_tokens,
308
- self.encoder_cache_size)
309
-
310
- max_num_mm_items_encoder_budget = cdiv(encoder_budget,
311
- max_tokens)
312
-
313
- # Check how many items of this modality can be supported by
314
- # the decoder budget.
315
- max_mm_items_per_req = self.mm_registry.\
316
- get_mm_limits_per_prompt(self.model_config)[modality]
317
-
318
- # NOTE: We do not consider max_num_batched_tokens on purpose
319
- # because the multimodal embeddings can be generated in advance
320
- # and chunked prefilled.
321
- max_num_mm_items_decoder_budget = self.max_num_reqs * \
322
- max_mm_items_per_req
323
-
324
- max_num_mm_items = min(max_num_mm_items_encoder_budget,
325
- max_num_mm_items_decoder_budget)
326
- self.max_num_mm_items_by_modality[modality] = max_num_mm_items
289
+ self.mm_budget = (MultiModalBudget(
290
+ self.model_config,
291
+ self.scheduler_config,
292
+ self.mm_registry,
293
+ max_model_len=self.max_model_len,
294
+ max_num_reqs=self.max_num_reqs,
295
+ ) if self.is_multimodal_model else None)
296
297
if not self.use_spmd:
298
self.sample_from_logits_func = torch.compile(
@@ -1335,23 +1304,33 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1304
xm.mark_step() # Captures metadata updates
1305
1306
def _precompile_mm_encoder(self) -> None:
1307
+ if not self.is_multimodal_model:
1308
+ return
1309
+
1310
# Pre-compile MM encoder for all supported data modalities.
1311
hf_config = self.vllm_config.model_config.hf_config
1340
- for mode, max_items_by_mode in \
1341
- self.max_num_mm_items_by_modality.items():
1312
+
1313
+ mm_budget = self.mm_budget
1314
+ assert mm_budget is not None
1315
+
1316
+ max_items_per_seq_by_modality = mm_budget.max_items_per_batch_by_modality # noqa: E501
1317
+
1318
+ for mode, max_items_per_seq in max_items_per_seq_by_modality.items():
1319
logger.info(
1320
"Compiling Multimodal %s Encoder with different input"
1321
" shapes.", mode)
1322
start = time.perf_counter()
1323
# No padding for MM encoder just yet.
1347
- for num_items in range(1, max_items_by_mode + 1):
1324
+ for num_items in range(1, max_items_per_seq + 1):
1325
logger.info(" -- mode: %s items: %d", mode, num_items)
1326
batched_dummy_mm_inputs = self._get_mm_dummy_batch(
1350
- mode, num_items)
1327
+ mode,
1328
+ num_items,
1329
+ )
1330
# Run multimodal encoder.
1331
xm.mark_step()
1353
- mm_embeds = self.model.\
1354
- get_multimodal_embeddings(**batched_dummy_mm_inputs)
1332
+ mm_embeds = self.model.get_multimodal_embeddings(
1333
+ **batched_dummy_mm_inputs)
1334
xm.mark_step()
1335
num_patches = mm_embeds[0].shape[0]
1336
items_size = num_patches * num_items
@@ -1547,51 +1526,61 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1526
num_tokens: int,
1527
) -> None:
1528
# Profile with multimodal encoder & encoder cache.
1550
- # TODO: handle encoder-decoder models once we support them.
1551
- if (self.is_multimodal_model and self.max_num_encoder_input_tokens > 0
1552
- and self.encoder_cache_size > 0):
1553
-
1554
- # NOTE: Currently model is profiled with a single non-text
1555
- # modality with the max possible input tokens even when
1556
- # it supports multiple.
1557
- dummy_data_modality, max_num_mm_items = max(
1558
- self.max_num_mm_items_by_modality.items(), key=lambda t: t[1])
1559
-
1560
- encoder_budget = min(self.max_num_encoder_input_tokens,
1561
- self.encoder_cache_size)
1562
-
1563
- logger.info(
1564
- "Encoder cache will be initialized with a budget of %d tokens,"
1565
- " and profiled with %s %s items of the maximum feature size.",
1566
- encoder_budget, max_num_mm_items, dummy_data_modality)
1567
-
1568
- # Create dummy batch of multimodal inputs.
1569
- batched_dummy_mm_inputs = self._get_mm_dummy_batch(
1570
- dummy_data_modality, max_num_mm_items)
1529
+ if self.is_multimodal_model:
1530
+ mm_budget = self.mm_budget
1531
+ assert mm_budget is not None
1532
+
1533
+ # TODO: handle encoder-decoder models once we support them.
1534
+ if (encoder_budget := mm_budget.get_encoder_budget()) > 0:
1535
+ # NOTE: Currently model is profiled with a single non-text
1536
+ # modality with the max possible input tokens even when
1537
+ # it supports multiple.
1538
+ (
1539
+ dummy_modality,
1540
+ max_tokens,
1541
+ ) = mm_budget.get_modality_with_max_tokens()
1542
+ (
1543
+ max_mm_items_per_prompt,
1544
+ max_mm_items_per_batch,
1545
+ ) = mm_budget.get_max_items(dummy_modality, max_tokens)
1546
+
1547
+ logger.info(
1548
+ "Encoder cache will be initialized with a budget of "
1549
+ "%s tokens, and profiled with %s %s items of the maximum "
1550
+ "feature size.",
1551
+ encoder_budget,
1552
+ max_mm_items_per_batch,
1553
+ dummy_modality,
1554
+ )
1555
1572
- # Run multimodal encoder.
1573
- # Isolate encoder graph from post-processing to minimize
1574
- # impact of recompilation until it's fixed.
1575
- start = time.perf_counter()
1576
- xm.mark_step()
1577
- dummy_encoder_outputs = self.model.get_multimodal_embeddings(
1578
- **batched_dummy_mm_inputs)
1579
- xm.mark_step()
1580
- xm.wait_device_ops()
1581
- end = time.perf_counter()
1582
- logger.info(
1583
- "Multimodal Encoder profiling finished in in %.2f [secs].",
1584
- end - start)
1556
+ # Create dummy batch of multimodal inputs.
1557
+ batched_dummy_mm_inputs = self._get_mm_dummy_batch(
1558
+ dummy_modality,
1559
+ max_mm_items_per_batch,
1560
+ )
1561
1586
- assert len(dummy_encoder_outputs) == max_num_mm_items, (
1587
- "Expected dimension 0 of encoder outputs to match the number "
1588
- f"of multimodal data items: {max_num_mm_items}, got "
1589
- f"{len(dummy_encoder_outputs)=} instead. This is most likely "
1590
- "due to the 'get_multimodal_embeddings' method of the model "
1591
- "not implemented correctly.")
1562
+ # Run multimodal encoder.
1563
+ # Isolate encoder graph from post-processing to minimize
1564
+ # impact of recompilation until it's fixed.
1565
+ start = time.perf_counter()
1566
+ xm.mark_step()
1567
+ dummy_encoder_outputs = self.model.get_multimodal_embeddings(
1568
+ **batched_dummy_mm_inputs)
1569
+ xm.mark_step()
1570
+ xm.wait_device_ops()
1571
+ end = time.perf_counter()
1572
+ logger.info(
1573
+ "Multimodal Encoder profiling finished in in %.2f [secs].",
1574
+ end - start)
1575
+
1576
+ sanity_check_mm_encoder_outputs(
1577
+ dummy_encoder_outputs,
1578
+ expected_num_items=max_mm_items_per_batch,
1579
+ )
1580
1593
- # Cache the dummy encoder outputs.
1594
- self.encoder_cache["tmp"] = dict(enumerate(dummy_encoder_outputs))
1581
+ # Cache the dummy encoder outputs.
1582
+ self.encoder_cache["tmp"] = dict(
1583
+ enumerate(dummy_encoder_outputs))
1584
1585
# Trigger compilation for general shape.
1586
self._dummy_run(num_tokens, self.num_reqs_max_model_len,
@@ -1809,33 +1798,25 @@ class TPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
1798
self.grammar_bitmask_cpu[:num_reqs].to(logits.device), \
1799
self.structured_decode_arange.to(logits.device)
1800
1812
- def _get_mm_dummy_batch(self, modality: str,
1813
- batch_size: int) -> BatchedTensorInputs:
1814
- # Dummy data for pre-compiling multimodal models.
1815
- dummy_request_data = self.mm_registry.get_decoder_dummy_data(
1801
+ def _get_mm_dummy_batch(
1802
+ self,
1803
+ modality: str,
1804
+ max_items_per_batch: int,
1805
+ ) -> BatchedTensorInputs:
1806
+ """Dummy data for profiling and precompiling multimodal models."""
1807
+ dummy_decoder_data = self.mm_registry.get_decoder_dummy_data(
1808
model_config=self.model_config,
1809
seq_len=self.max_num_tokens,
1810
+ mm_counts={modality: 1},
1811
)
1819
- dummy_mm_data = dummy_request_data.multi_modal_data
1820
-
1821
- # Dummy data definition in V0 may contain multiple multimodal items
1822
- # (e.g, multiple images) for a single request, therefore here we
1823
- # always replicate first item by max_num_mm_items times since in V1
1824
- # they are scheduled to be processed separately.
1825
- assert isinstance(dummy_mm_data, MultiModalKwargs), (
1826
- "Expected dummy multimodal data to be of type "
1827
- f"MultiModalKwargs, got {type(dummy_mm_data)=} instead. "
1828
- "This is most likely due to the model not having a merged "
1829
- "processor.")
1830
-
1831
- # When models have a merged processor, their dummy data is
1832
- # already batched `MultiModalKwargs`, therefore we take the first
1833
- # `MultiModalKwargsItem` from the desired modality to profile on.
1812
+ dummy_mm_data = dummy_decoder_data.multi_modal_data
1813
+
1814
+ # Result in the maximum GPU consumption of the model
1815
dummy_mm_item = dummy_mm_data.get_item(modality=modality, item_index=0)
1816
dummy_mm_kwargs = MultiModalKwargs.from_items([dummy_mm_item])
1817
1818
batched_dummy_mm_inputs = MultiModalKwargs.batch([dummy_mm_kwargs] *
1838
- batch_size)
1819
+ max_items_per_batch)
1820
return MultiModalKwargs.as_kwargs(
1821
batched_dummy_mm_inputs,
1822
device=self.device,
vllm/v1/worker/utils.py
+109
@@ -5,14 +5,123 @@ from typing import TYPE_CHECKING, Optional
5
6
import torch
7
8
+from vllm.config import ModelConfig, SchedulerConfig
9
from vllm.model_executor.models.interfaces import MultiModalEmbeddings
10
from vllm.model_executor.models.utils import extract_layer_index
11
+from vllm.multimodal.registry import MultiModalRegistry
12
+from vllm.v1.core.encoder_cache_manager import compute_encoder_budget
13
from vllm.v1.kv_cache_interface import KVCacheGroupSpec
14
15
if TYPE_CHECKING:
16
from vllm.attention.layer import Attention
17
18
19
+class MultiModalBudget:
20
+ """Helper class to calculate budget information for multi-modal models."""
21
+
22
+ def __init__(
23
+ self,
24
+ model_config: ModelConfig,
25
+ scheduler_config: SchedulerConfig,
26
+ mm_registry: MultiModalRegistry,
27
+ *,
28
+ max_model_len: int,
29
+ max_num_reqs: int,
30
+ ) -> None:
31
+ super().__init__()
32
+
33
+ self.model_config = model_config
34
+ self.scheduler_config = scheduler_config
35
+ self.mm_registry = mm_registry
36
+
37
+ encoder_compute_budget, encoder_cache_size = compute_encoder_budget(
38
+ model_config=model_config,
39
+ scheduler_config=scheduler_config,
40
+ mm_registry=mm_registry,
41
+ )
42
+
43
+ self.max_num_encoder_input_tokens = encoder_compute_budget
44
+ self.encoder_cache_size = encoder_cache_size
45
+ self.max_model_len = max_model_len
46
+ self.max_num_reqs = max_num_reqs
47
+
48
+ self.mm_limits = mm_registry.get_mm_limits_per_prompt(model_config)
49
+
50
+ max_items_per_prompt_by_modality = dict[str, int]()
51
+ max_items_per_batch_by_modality = dict[str, int]()
52
+
53
+ max_tokens_by_modality = mm_registry \
54
+ .get_max_tokens_per_item_by_nonzero_modality(model_config)
55
+
56
+ for modality, max_tokens in max_tokens_by_modality.items():
57
+ (
58
+ max_items_per_prompt,
59
+ max_items_per_batch,
60
+ ) = self.get_max_items(modality, max_tokens)
61
+
62
+ max_items_per_prompt_by_modality[modality] = max_items_per_prompt
63
+ max_items_per_batch_by_modality[modality] = max_items_per_batch
64
+
65
+ self.max_tokens_by_modality = max_tokens_by_modality
66
+ self.max_items_per_prompt_by_modality = max_items_per_prompt_by_modality
67
+ self.max_items_per_batch_by_modality = max_items_per_batch_by_modality
68
+
69
+ def get_modality_with_max_tokens(self) -> tuple[str, int]:
70
+ max_tokens_by_modality = self.max_tokens_by_modality
71
+ modality, max_tokens = max(max_tokens_by_modality.items(),
72
+ key=lambda item: item[1])
73
+
74
+ return modality, max_tokens
75
+
76
+ def get_encoder_budget(self) -> int:
77
+ return min(self.max_num_encoder_input_tokens, self.encoder_cache_size)
78
+
79
+ def get_max_items(
80
+ self,
81
+ modality: str,
82
+ max_tokens_per_item: int,
83
+ ) -> tuple[int, int]:
84
+ if max_tokens_per_item == 0:
85
+ return 0, 0
86
+
87
+ # Check how many items of this modality can be supported by
88
+ # the encoder budget.
89
+ encoder_budget = self.get_encoder_budget()
90
+
91
+ # TODO: handle encoder-decoder models once we support them.
92
+ if encoder_budget == 0:
93
+ return 0, 0
94
+
95
+ max_encoder_items_per_batch = encoder_budget // max_tokens_per_item
96
+
97
+ # Check how many items of this modality can be supported by
98
+ # the decoder budget.
99
+ mm_limit = self.mm_limits[modality]
100
+
101
+ max_items_per_prompt = max(
102
+ 1,
103
+ min(mm_limit, self.max_model_len // max_tokens_per_item),
104
+ )
105
+
106
+ scheduler_config = self.scheduler_config
107
+ max_num_reqs = self.max_num_reqs
108
+
109
+ if not scheduler_config.enable_chunked_prefill:
110
+ max_num_reqs = min(
111
+ max_num_reqs,
112
+ scheduler_config.max_num_batched_tokens // max_tokens_per_item,
113
+ )
114
+
115
+ max_decoder_items_per_batch = max_num_reqs * max_items_per_prompt
116
+
117
+ max_items_per_batch = max(
118
+ 1,
119
+ min(max_encoder_items_per_batch, max_decoder_items_per_batch),
120
+ )
121
+
122
+ return max_items_per_prompt, max_items_per_batch
123
+
124
+
125
def sanity_check_mm_encoder_outputs(
126
mm_embeddings: MultiModalEmbeddings,
127
expected_num_items: int,