[Bugfix][V1][Multimodal] Ignore stale same-step encoder cache evictions (#52482)

Signed-off-by: Tianyu Guo <guoty@inferact.ai>

Tianyu Guo committed Aug 17, 2026 at 06:21 UTC eee538d5daa0c8c969f20d0c48c972155a6f1859
2 files changed +22 -1
tests/v1/core/test_encoder_cache_manager.py
+19
@@ -167,6 +167,25 @@ def test_get_freed_mm_hashes_clears_freed_list():
167 assert manager.get_freed_mm_hashes() == []
168
169
170 +def test_reallocated_hash_is_not_reported_as_freed():
171 + manager = EncoderCacheManager(cache_size=8)
172 + req_a = MockRequest("reqA", ["a"], [4])
173 + req_b = MockRequest("reqB", ["b"], [4])
174 + req_c = MockRequest("reqC", ["c"], [4])
175 +
176 + manager.allocate(req_a, 0)
177 + manager.allocate(req_b, 0)
178 + manager.free(req_a)
179 + manager.free(req_b)
180 +
181 + assert manager.can_allocate(req_c, 0, int(1e9), 0)
182 + manager.allocate(req_c, 0)
183 + assert manager.can_allocate(req_a, 0, int(1e9), 0)
184 + manager.allocate(req_a, 0)
185 +
186 + assert manager.get_freed_mm_hashes() == ["b"]
187 +
188 +
189 def test_schedule_request_multi_images_respect_space_limit():
190 manager = EncoderCacheManager(cache_size=10)
191 req = MockRequest("reqA", ["a", "b"], [5, 6])
vllm/v1/core/encoder_cache_manager.py
+3 -1
@@ -269,7 +269,9 @@ class EncoderCacheManager:
269 encoder outputs can be removed from their caches. The internal
270 list is cleared after this call.
271 """
272 - freed = self.freed
272 + # An entry evicted early in the scheduling pass can be allocated again
273 + # later in the same pass. Keep its worker-side tensor in that case.
274 + freed = [mm_hash for mm_hash in self.freed if mm_hash not in self.cached]
275 self.freed = []
276 return freed
277