main
py 142 lines 4.65 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Implementation of Key-Replicated Cache (see docs/configuration/optimization.md)."""
4
5 from typing_extensions import override
6
7 from vllm.config import ModelConfig
8 from vllm.utils.cache import CacheInfo
9
10 from ..inputs import MultiModalKwargsItem
11 from .base import (
12 BaseMultiModalProcessorCache,
13 BaseMultiModalReceiverCache,
14 MultiModalCache,
15 MultiModalCacheMissError,
16 MultiModalProcessorCacheInItem,
17 MultiModalProcessorCacheItemMetadata,
18 MultiModalProcessorCacheOutItem,
19 )
20
21
22 class LruKeyReplicatedSenderCache(BaseMultiModalProcessorCache):
23 """The cache which is used on P0 when LRU caching is enabled.
24
25 How to update each item:
26
27 - If the item is already in the cache, clear the input to avoid
28 unnecessary IPC.
29
30 - If the item is not in the cache, store the metadata of that item so
31 that the eviction policy remains the same as the cache on P1,
32 and return the input.
33 By only storing the metadata, we avoid keeping the data itself in
34 memory inside P0.
35 """
36
37 def __init__(self, model_config: ModelConfig) -> None:
38 super().__init__()
39
40 mm_config = model_config.get_multimodal_config()
41
42 self._cache = MultiModalCache.get_lru_cache(
43 mm_config.mm_processor_cache_gb,
44 MultiModalProcessorCacheItemMetadata,
45 )
46
47 @override
48 def is_cached_item(self, mm_hash: str) -> bool:
49 return mm_hash in self._cache
50
51 @override
52 def get_and_update_item(
53 self,
54 mm_item: MultiModalProcessorCacheInItem,
55 mm_hash: str,
56 ) -> MultiModalProcessorCacheOutItem:
57 if (cached_item := self._cache.get(mm_hash)) is not None:
58 return None, cached_item.prompt_updates
59
60 assert mm_item is not None, f"Expected a cached item for {mm_hash=}"
61
62 self.cache_if_fits(
63 self._cache, mm_hash, MultiModalProcessorCacheItemMetadata(*mm_item)
64 )
65 return mm_item
66
67 @override
68 def touch_sender_cache_item(self, mm_hash: str) -> None:
69 self._cache.touch(mm_hash)
70
71 @override
72 def clear_cache(self) -> None:
73 self._cache.clear()
74
75 @override
76 def make_stats(self, *, delta: bool = False) -> CacheInfo:
77 return self._cache.stat(delta=delta)
78
79 @override
80 def invalidate(self, mm_hash: str) -> None:
81 # Drop our stale shadow entry so the next request for this hash re-sends the
82 # data and repopulates P1 (see MultiModalCacheMissError).
83 self._cache.pop(mm_hash, None)
84
85
86 class LruKeyReplicatedReceiverCache(BaseMultiModalReceiverCache):
87 """The cache which is used on P1 when LRU caching is enabled.
88
89 How to update each item:
90
91 - If the caller sent tensor data, store it (replacing any cached item
92 under the same key) and return that data. P0 can miss after independent
93 LRU eviction and resend a different item for the same identity.
94 - If the caller sent no data and the item is cached, return the cached item.
95 - If the caller sent no data and the item is not cached, raise
96 `MultiModalCacheMissError`.
97 """
98
99 def __init__(self, model_config: ModelConfig) -> None:
100 super().__init__()
101
102 mm_config = model_config.get_multimodal_config()
103
104 self._cache = MultiModalCache.get_lru_cache(
105 mm_config.mm_processor_cache_gb,
106 MultiModalKwargsItem,
107 )
108
109 @override
110 def get_and_update_item(
111 self,
112 mm_item: MultiModalKwargsItem | None,
113 mm_hash: str,
114 ) -> MultiModalKwargsItem:
115 if mm_item is not None:
116 # P0 sent a payload. Never keep a stale cached tensor under this
117 # identity: a later P0 hit would pair new placeholders with the old
118 # item and crash the engine. Drop first so a too-large replacement
119 # cannot leave the previous entry in place.
120 self._cache.pop(mm_hash, None)
121 self.cache_if_fits(self._cache, mm_hash, mm_item)
122 return mm_item
123
124 if (cached_item := self._cache.get(mm_hash)) is not None:
125 return cached_item
126
127 # No data and not cached here: P0 sent data=None trusting its shadow, but
128 # the P0/P1 caches have drifted. Raise a retryable error (not assert) so the
129 # engine can have P0 drop the stale entry and the client resend the data.
130 raise MultiModalCacheMissError([mm_hash])
131
132 @override
133 def touch_receiver_cache_item(
134 self,
135 mm_hash: str,
136 mm_item: MultiModalKwargsItem | None = None,
137 ) -> None:
138 self._cache.touch(mm_hash)
139
140 @override
141 def clear_cache(self) -> None:
142 self._cache.clear()