@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 8c9da6be22
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

[Core] Simplify mm processing cache (#22457)

Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>

Cyrus Leung committed Aug 8, 2025 at 00:47 UTC 8c9da6be229336a769d9c904415daaa250824c89
4 files changed +97 -206
vllm/model_executor/models/qwen2_5_omni_thinker.py
+6 -6
@@ -431,7 +431,7 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
431 tokenization_kwargs: Mapping[str, object],
432 *,
433 enable_hf_prompt_update: bool,
434 - ) -> tuple[list[int], MultiModalKwargs, bool]:
434 + ) -> tuple[list[int], BatchFeature, bool]:
435 """
436 Qwen2.5-Omni reimplements this function to handle text only.
437 """
@@ -448,20 +448,20 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
448 else:
449 prompt_ids = self._apply_hf_processor_tokens_only(prompt)
450
451 - mm_kwargs = self._apply_hf_processor_mm_only(
451 + mm_processed_data = self._apply_hf_processor_mm_only(
452 mm_items=mm_items,
453 hf_processor_mm_kwargs=hf_processor_mm_kwargs,
454 tokenization_kwargs=tokenization_kwargs,
455 )
456
457 - return prompt_ids, mm_kwargs, False
457 + return prompt_ids, mm_processed_data, False
458
459 def _apply_hf_processor_mm_only(
460 self,
461 mm_items: MultiModalDataItems,
462 hf_processor_mm_kwargs: Mapping[str, object],
463 tokenization_kwargs: Mapping[str, object],
464 - ) -> MultiModalKwargs:
464 + ) -> BatchFeature:
465 """
466 Qwen2.5-Omni reimplements this function to handle `use_audio_in_video`.
467 """
@@ -473,14 +473,14 @@ class Qwen2_5OmniThinkerMultiModalProcessor(
473 assert "audio" in mm_counts
474 mm_counts["audio"] -= mm_counts["video"]
475
476 - _, mm_kwargs, _ = self._apply_hf_processor_text_mm(
476 + _, mm_processed_data, _ = self._apply_hf_processor_text_mm(
477 prompt_text=self.dummy_inputs.get_dummy_text(mm_counts),
478 mm_items=mm_items,
479 hf_processor_mm_kwargs=hf_processor_mm_kwargs,
480 tokenization_kwargs=tokenization_kwargs,
481 )
482
483 - return mm_kwargs
483 + return mm_processed_data
484
485 def _validate_mm_placeholders(
486 self,
vllm/model_executor/models/transformers.py
+3 -2
@@ -22,7 +22,8 @@ from typing import Literal, Optional, Union
22 import regex as re
23 import torch
24 from torch import nn
25 -from transformers import AutoModel, PretrainedConfig, PreTrainedModel
25 +from transformers import (AutoModel, BatchFeature, PretrainedConfig,
26 + PreTrainedModel)
27 from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
28
29 from vllm.attention import Attention
@@ -269,7 +270,7 @@ class MultiModalProcessor(BaseMultiModalProcessor[MultiModalProcessingInfo]):
270 mm_items: MultiModalDataItems,
271 hf_processor_mm_kwargs: Mapping[str, object],
272 tokenization_kwargs: Mapping[str, object],
272 - ):
273 + ) -> tuple[list[int], BatchFeature, bool]:
274 """
275 Apply the HF processor on the prompt text and multi-modal data
276 together.
vllm/multimodal/processing.py
+69 -179
@@ -18,7 +18,7 @@ from vllm.inputs import InputProcessingContext
18 from vllm.logger import init_logger
19 from vllm.transformers_utils.tokenizer import (AnyTokenizer, decode_tokens,
20 encode_tokens)
21 -from vllm.utils import GiB_bytes, flatten_2d_lists, full_groupby
21 +from vllm.utils import flatten_2d_lists, full_groupby
22
23 from .cache import MultiModalCache
24 from .hasher import MultiModalHasher
@@ -887,120 +887,19 @@ def find_mm_placeholders(
887 return dict(full_groupby_modality(it))
888
889
890 -class ProcessingCacheOptionalItem(NamedTuple):
891 - key: str
892 - value: Optional[MultiModalKwargsItem]
893 -
894 -
895 -class ProcessingCacheItem(NamedTuple):
896 - key: str
897 - value: MultiModalKwargsItem
898 -
899 -
890 class ProcessingCache(MultiModalCache):
891
902 - def __init__(
903 - self,
904 - capacity_gb: float,
905 - *,
906 - debug_cache_hit_ratio_steps: Optional[int] = None,
907 - ) -> None:
892 + def __init__(self, capacity_gb: float) -> None:
893 super().__init__()
894
910 - self.debug_cache_hit_ratio_steps = debug_cache_hit_ratio_steps
911 - self.debug_cache_hits = 0
912 - self.debug_cache_total = 0
913 -
914 - self._cache = self.get_lru_cache(
915 - capacity_gb,
916 - MultiModalKwargsItem,
917 - debug=bool(debug_cache_hit_ratio_steps),
918 - )
919 -
920 - def _maybe_log_cache_stats(self) -> None:
921 - steps = self.debug_cache_hit_ratio_steps
922 - if not steps:
923 - return
924 -
925 - total = self.debug_cache_total
926 - if total > 0 and total % steps == 0:
927 - logger.debug("ProcessingCache: hit_ratio = %.2f",
928 - self.debug_cache_hits / total)
929 - logger.debug("ProcessingCache: size = %.2f / %.2f GiB",
930 - self._cache.currsize / GiB_bytes,
931 - self._cache.maxsize / GiB_bytes)
932 -
933 - def get(
934 - self,
935 - model_id: str,
936 - modality: str,
937 - input_item: object,
938 - input_kwargs: Mapping[str, object],
939 - ) -> Optional[MultiModalKwargsItem]:
940 - """
941 - Get a processed multi-modal item from the cache
942 - according to its dependencies, including:
943 -
944 - - The model ID
945 - - The modality of the item
946 - - The original data item passed to the HF processor
947 - - The configuration options of the HF processor
948 - """
949 - self._maybe_log_cache_stats()
895 + self._cache = self.get_lru_cache(capacity_gb, MultiModalKwargsItem)
896
951 - cache_key = MultiModalHasher.hash_kwargs(model_id=model_id,
952 - **{modality: input_item},
953 - **input_kwargs)
897 + self.get = self._cache.get
898 + self.put = self._cache.put
899 + self.reset = self._cache.clear
900
955 - if self.debug_cache_hit_ratio_steps:
956 - if cache_key in self._cache:
957 - self.debug_cache_hits += 1
901
959 - self.debug_cache_total += 1
960 -
961 - return self._cache.get(cache_key)
962 -
963 - def get_item(
964 - self,
965 - model_id: str,
966 - modality: str,
967 - input_item: object,
968 - input_kwargs: Mapping[str, object],
969 - ) -> ProcessingCacheOptionalItem:
970 - cache_key = MultiModalHasher.hash_kwargs(model_id=model_id,
971 - **{modality: input_item},
972 - **input_kwargs)
973 -
974 - return ProcessingCacheOptionalItem(
975 - key=cache_key,
976 - value=self._cache.get(cache_key),
977 - )
978 -
979 - def put(
980 - self,
981 - model_id: str,
982 - modality: str,
983 - input_item: object,
984 - input_kwargs: Mapping[str, object],
985 - output_kwargs: MultiModalKwargsItem,
986 - ) -> None:
987 - """
988 - Put a processed multi-modal item into the cache
989 - according to its dependencies
990 - (see [`get`][vllm.multimodal.processing.ProcessingCache.get]).
991 - """
992 - cache_key = MultiModalHasher.hash_kwargs(model_id=model_id,
993 - **{modality: input_item},
994 - **input_kwargs)
995 - self._cache[cache_key] = output_kwargs
996 -
997 - def put_item(self, item: ProcessingCacheItem) -> None:
998 - self._cache[item.key] = item.value
999 -
1000 - def reset(self) -> bool:
1001 - self._cache.clear()
1002 -
1003 - return True
902 +_CacheItemOrHash = Union[MultiModalKwargsItem, str]
903
904
905 class BaseProcessingInfo:
@@ -1279,7 +1178,7 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1178 mm_items: MultiModalDataItems,
1179 hf_processor_mm_kwargs: Mapping[str, object],
1180 tokenization_kwargs: Mapping[str, object],
1282 - ) -> tuple[list[int], MultiModalKwargs, bool]:
1181 + ) -> tuple[list[int], "BatchFeature", bool]:
1182 """
1183 Apply the HF processor on the prompt text and multi-modal data
1184 together.
@@ -1298,11 +1197,6 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1197
1198 prompt_ids, = processed_data.pop("input_ids").tolist()
1199
1301 - mm_kwargs = MultiModalKwargs.from_hf_inputs(
1302 - processed_data,
1303 - self._get_mm_fields_config(processed_data, hf_processor_mm_kwargs),
1304 - )
1305 -
1200 is_update_applied = self._hf_processor_applies_updates(
1201 prompt_text=prompt_text,
1202 mm_items=mm_items,
@@ -1310,11 +1204,13 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1204 tokenization_kwargs=tokenization_kwargs,
1205 )
1206
1313 - return prompt_ids, mm_kwargs, is_update_applied
1207 + return prompt_ids, processed_data, is_update_applied
1208
1209 def _apply_hf_processor_text_only(
1316 - self, prompt_text: str,
1317 - tokenization_kwargs: Mapping[str, object]) -> list[int]:
1210 + self,
1211 + prompt_text: str,
1212 + tokenization_kwargs: Mapping[str, object],
1213 + ) -> list[int]:
1214 """
1215 Apply the HF processor on the prompt text only.
1216
@@ -1353,7 +1249,7 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1249 mm_items: MultiModalDataItems,
1250 hf_processor_mm_kwargs: Mapping[str, object],
1251 tokenization_kwargs: Mapping[str, object],
1356 - ) -> MultiModalKwargs:
1252 + ) -> "BatchFeature":
1253 """
1254 Apply the HF processor on the multi-modal data only.
1255
@@ -1364,14 +1260,14 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1260 """
1261 mm_counts = mm_items.get_all_counts()
1262
1367 - _, mm_kwargs, _ = self._apply_hf_processor_text_mm(
1263 + _, mm_processed_data, _ = self._apply_hf_processor_text_mm(
1264 prompt_text=self.dummy_inputs.get_dummy_text(mm_counts),
1265 mm_items=mm_items,
1266 hf_processor_mm_kwargs=hf_processor_mm_kwargs,
1267 tokenization_kwargs=tokenization_kwargs,
1268 )
1269
1374 - return mm_kwargs
1270 + return mm_processed_data
1271
1272 def _apply_hf_processor_main(
1273 self,
@@ -1381,7 +1277,7 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1277 tokenization_kwargs: Mapping[str, object],
1278 *,
1279 enable_hf_prompt_update: bool,
1384 - ) -> tuple[list[int], MultiModalKwargs, bool]:
1280 + ) -> tuple[list[int], "BatchFeature", bool]:
1281 """
1282 Apply the HF processor on the prompt text and multi-modal data.
1283
@@ -1407,52 +1303,46 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1303 else:
1304 prompt_ids = self._apply_hf_processor_tokens_only(prompt)
1305
1410 - mm_kwargs = self._apply_hf_processor_mm_only(
1306 + mm_processed_data = self._apply_hf_processor_mm_only(
1307 mm_items=mm_items,
1308 hf_processor_mm_kwargs=hf_processor_mm_kwargs,
1309 tokenization_kwargs=tokenization_kwargs,
1310 )
1311
1416 - return prompt_ids, mm_kwargs, False
1312 + return prompt_ids, mm_processed_data, False
1313
1314 def _get_cache_missing_items(
1315 self,
1316 cache: ProcessingCache,
1317 mm_data_items: MultiModalDataItems,
1422 - hf_processor_mm_kwargs: Mapping[str, object],
1423 - tokenization_kwargs: Mapping[str, object],
1424 - ) -> tuple[dict[str, list[ProcessingCacheOptionalItem]], dict[
1425 - str, list[object]]]:
1426 - model_id = self.info.model_id
1427 -
1428 - mm_cache_items = {
1429 - modality: [
1430 - cache.get_item(
1431 - model_id, modality, item,
1432 - dict(**hf_processor_mm_kwargs, **tokenization_kwargs))
1433 - for item in items
1434 - ]
1435 - for modality, items in mm_data_items.items()
1318 + mm_hashes: MultiModalHashes,
1319 + ) -> tuple[dict[str, list[_CacheItemOrHash]], MultiModalDataItems]:
1320 + mm_cache_items_or_hashes: dict[str, list[_CacheItemOrHash]] = {
1321 + modality: [(h if (v := cache.get(h)) is None else v)
1322 + for h in hashes]
1323 + for modality, hashes in mm_hashes.items()
1324 }
1325
1326 mm_missing_idxs = {
1327 modality: [
1440 - idx for idx, item in enumerate(cache_items)
1441 - if item.value is None
1328 + idx for idx, item_or_hash in enumerate(items_or_hashes)
1329 + if isinstance(item_or_hash, str)
1330 ]
1443 - for modality, cache_items in mm_cache_items.items()
1331 + for modality, items_or_hashes in mm_cache_items_or_hashes.items()
1332 }
1333 mm_missing_data = {
1334 modality: [mm_data_items[modality][idx] for idx in idxs]
1335 for modality, idxs in mm_missing_idxs.items()
1336 }
1337
1450 - return mm_cache_items, mm_missing_data
1338 + return mm_cache_items_or_hashes, self._to_mm_items(mm_missing_data)
1339
1340 def _hash_mm_items(
1453 - self, mm_items: MultiModalDataItems,
1454 - hf_processor_mm_kwargs: Mapping[str, object],
1455 - tokenization_kwargs: Mapping[str, object]) -> MultiModalHashes:
1341 + self,
1342 + mm_items: MultiModalDataItems,
1343 + hf_processor_mm_kwargs: Mapping[str, object],
1344 + tokenization_kwargs: Mapping[str, object],
1345 + ) -> MultiModalHashes:
1346 """Create MM hashes to be returned (only used in V1)."""
1347 model_id = self.info.model_id
1348
@@ -1470,34 +1360,25 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1360 def _merge_mm_kwargs(
1361 self,
1362 cache: ProcessingCache,
1473 - mm_cache_items: dict[str, list[ProcessingCacheOptionalItem]],
1474 - mm_missing_data: dict[str, list[object]],
1363 + mm_cache_items_or_hashes: dict[str, list[_CacheItemOrHash]],
1364 mm_missing_kwargs: MultiModalKwargs,
1476 - ) -> dict[str, list[ProcessingCacheItem]]:
1477 - mm_missing_next_idx = {modality: 0 for modality in mm_missing_data}
1365 + ) -> dict[str, list[MultiModalKwargsItem]]:
1366 + mm_missing_next_idx = defaultdict[str, int](lambda: 0)
1367
1479 - merged_items = defaultdict[str, list[ProcessingCacheItem]](list)
1480 - for modality, cache_items in mm_cache_items.items():
1481 - for cache_item in cache_items:
1482 - if cache_item.value is None:
1368 + merged_items = defaultdict[str, list[MultiModalKwargsItem]](list)
1369 + for modality, items_or_hashes in mm_cache_items_or_hashes.items():
1370 + for item_or_hash in items_or_hashes:
1371 + if isinstance(item_or_hash, str):
1372 kw_item = mm_missing_kwargs.get_item(
1373 modality,
1374 mm_missing_next_idx[modality],
1375 )
1487 - cache_item_new = ProcessingCacheItem(
1488 - key=cache_item.key,
1489 - value=kw_item,
1490 - )
1491 -
1492 - cache.put_item(cache_item_new)
1376 + cache.put(item_or_hash, kw_item)
1377 mm_missing_next_idx[modality] += 1
1378 else:
1495 - cache_item_new = ProcessingCacheItem(
1496 - key=cache_item.key,
1497 - value=cache_item.value,
1498 - )
1379 + kw_item = item_or_hash
1380
1500 - merged_items[modality].append(cache_item_new)
1381 + merged_items[modality].append(kw_item)
1382
1383 return dict(merged_items)
1384
@@ -1512,7 +1393,7 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1393 ) -> tuple[list[int], MultiModalKwargs, Optional[MultiModalHashes], bool]:
1394 (
1395 prompt_ids,
1515 - mm_kwargs,
1396 + mm_processed_data,
1397 is_update_applied,
1398 ) = self._apply_hf_processor_main(
1399 prompt=prompt,
@@ -1522,6 +1403,12 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1403 enable_hf_prompt_update=True,
1404 )
1405
1406 + mm_kwargs = MultiModalKwargs.from_hf_inputs(
1407 + mm_processed_data,
1408 + self._get_mm_fields_config(mm_processed_data,
1409 + hf_processor_mm_kwargs),
1410 + )
1411 +
1412 mm_hashes = (self._hash_mm_items(mm_data_items, hf_processor_mm_kwargs,
1413 tokenization_kwargs)
1414 if return_mm_hashes else None)
@@ -1553,49 +1440,52 @@ class BaseMultiModalProcessor(ABC, Generic[_I]):
1440 return_mm_hashes=return_mm_hashes,
1441 )
1442
1443 + mm_hashes = self._hash_mm_items(mm_data_items, hf_processor_mm_kwargs,
1444 + tokenization_kwargs)
1445 (
1557 - mm_cache_items,
1558 - mm_missing_data,
1446 + mm_cache_items_or_hashes,
1447 + mm_missing_data_items,
1448 ) = self._get_cache_missing_items(
1449 cache=cache,
1450 mm_data_items=mm_data_items,
1562 - hf_processor_mm_kwargs=hf_processor_mm_kwargs,
1563 - tokenization_kwargs=tokenization_kwargs,
1451 + mm_hashes=mm_hashes,
1452 )
1453
1454 + mm_hashes_to_return = mm_hashes if return_mm_hashes else None
1455 +
1456 # NOTE: `prompt` does not correspond to `mm_missing_data_items`,
1457 # so we can't apply prompt updates until the new multimodal
1458 # items are combined with the cached multimodal items
1459 (
1460 prompt_ids,
1571 - mm_missing_kwargs,
1461 + mm_missing_processed_data,
1462 is_update_applied,
1463 ) = self._apply_hf_processor_main(
1464 prompt=prompt,
1575 - mm_items=self._to_mm_items(mm_missing_data),
1465 + mm_items=mm_missing_data_items,
1466 hf_processor_mm_kwargs=hf_processor_mm_kwargs,
1467 tokenization_kwargs=tokenization_kwargs,
1468 enable_hf_prompt_update=False,
1469 )
1470
1471 + mm_missing_kwargs = MultiModalKwargs.from_hf_inputs(
1472 + mm_missing_processed_data,
1473 + self._get_mm_fields_config(mm_missing_processed_data,
1474 + hf_processor_mm_kwargs),
1475 + )
1476 +
1477 mm_cache_items_merged = self._merge_mm_kwargs(
1478 cache,
1583 - mm_cache_items=mm_cache_items,
1584 - mm_missing_data=mm_missing_data,
1479 + mm_cache_items_or_hashes=mm_cache_items_or_hashes,
1480 mm_missing_kwargs=mm_missing_kwargs,
1481 )
1482
1483 mm_kwargs = MultiModalKwargs.from_items([
1589 - item.value for cache_items in mm_cache_items_merged.values()
1484 + item for cache_items in mm_cache_items_merged.values()
1485 for item in cache_items
1486 ])
1487
1593 - mm_hashes = {
1594 - modality: [item.key for item in cache_items]
1595 - for modality, cache_items in mm_cache_items_merged.items()
1596 - } if return_mm_hashes else None
1597 -
1598 - return prompt_ids, mm_kwargs, mm_hashes, is_update_applied
1488 + return prompt_ids, mm_kwargs, mm_hashes_to_return, is_update_applied
1489
1490 def _bind_and_group_updates(
1491 self,
vllm/v1/serial_utils.py
+19 -19
@@ -312,25 +312,25 @@ class MsgpackDecoder:
312 return arr.view(torch_dtype).view(shape)
313
314 def _decode_mm_items(self, obj: list) -> list[MultiModalKwargsItem]:
315 - decoded_items = []
316 - for item in obj:
317 - elems = []
318 - for v in item:
319 - v["data"] = self._decode_nested_tensors(v["data"])
320 - # Reconstruct the field processor using MultiModalFieldConfig
321 - factory_meth_name, *field_args = v["field"]
322 - factory_meth = getattr(MultiModalFieldConfig,
323 - factory_meth_name)
324 -
325 - # Special case: decode the union "slices" field of
326 - # MultiModalFlatField
327 - if factory_meth_name == "flat":
328 - field_args[0] = self._decode_nested_slices(field_args[0])
329 -
330 - v["field"] = factory_meth(None, *field_args).field
331 - elems.append(MultiModalFieldElem(**v))
332 - decoded_items.append(MultiModalKwargsItem.from_elems(elems))
333 - return decoded_items
315 + return [self._decode_mm_item(v) for v in obj]
316 +
317 + def _decode_mm_item(self, obj: list) -> MultiModalKwargsItem:
318 + return MultiModalKwargsItem.from_elems(
319 + [self._decode_mm_field_elem(v) for v in obj])
320 +
321 + def _decode_mm_field_elem(self, obj: dict) -> MultiModalFieldElem:
322 + obj["data"] = self._decode_nested_tensors(obj["data"])
323 + # Reconstruct the field processor using MultiModalFieldConfig
324 + factory_meth_name, *field_args = obj["field"]
325 + factory_meth = getattr(MultiModalFieldConfig, factory_meth_name)
326 +
327 + # Special case: decode the union "slices" field of
328 + # MultiModalFlatField
329 + if factory_meth_name == "flat":
330 + field_args[0] = self._decode_nested_slices(field_args[0])
331 +
332 + obj["field"] = factory_meth(None, *field_args).field
333 + return MultiModalFieldElem(**obj)
334
335 def _decode_nested_tensors(self, obj: Any) -> NestedTensors:
336 if isinstance(obj, (int, float)):