fix(ENG-MM-INPUT-PIPELINE): an encoder-cache entry goes evictable on its first free while the same request still needs a later occurrence
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 423
- Forks
- 53
- Avg merge
- 20h 26m
- Merged PRs (30d)
- 310
Description
Row: ENG-MM-INPUT-PIPELINE (.agents/engine-matrix.md:77, state READY, spec specs/multimodal-track.md §3), the row that owns EncoderCacheManager per include/vllm/v1/core/sched/scheduler.h:91 (#2379). Resolved by line number before filing.
Found by wave PORTQ-7 (#2717) re-deriving PORT-NOW entry [280], upstream 3958a420f0 vllm#54284. Nothing was executed.
The defect
An encoder-cache entry becomes evictable as soon as one occurrence of its mm_hash is freed, even while the same request still holds another cached occurrence of it.
EncoderCacheManager::FreeEncoderInput (src/vllm/v1/core/encoder_cache_manager.cpp:98-115):
auto cit = cached_.find(mm_hash);
if (cit == cached_.end() || cit->second.empty()) return;
cit->second.erase(request_id);
if (cit->second.empty()) {
FreeablePush(mm_hash, num_embeds);
num_freeable_slots_ += num_embeds;
}
The request's reference is dropped on the first free, with no check for another still-cached occurrence. Upstream now returns early in exactly that case.
This tree cannot express the check as written. The manager keys request_cached_ids_ as request_id -> set<int> of input_ids (include/vllm/v1/core/encoder_cache_manager.h:75) and holds no input_id -> mm_hash map; the caller supplies mm_hash per input_id. Both production call sites do have the request's features to hand — Scheduler::free_encoder_inputs (src/vllm/v1/core/sched/scheduler.cpp:589-590) and Scheduler::free_request_encoder_inputs (:607-609) both hold request.mm_features — so the fix is available either by giving the manager the map or by moving the predicate to the caller.
Reachability
The manager is constructed unconditionally at scheduler.cpp:292, and FreeEncoderInput is reached from update_from_output (:1242) and from the finish, preempt and admission-failure paths (:341, :360, :939, :1303).
Relation to [#2686]: independent, not a duplicate
#2686 is PORTQ-5's finding on GetFreedMmHashes (encoder_cache_manager.cpp:117-121), which returns freed_ unfiltered where upstream returns [h for h in self.freed if h not in self.cached].
Different function, different upstream commit, different mechanism. #2686 tells the worker to drop a tensor the manager re-allocated later in the same pass; this one makes an entry evictable while the same request still needs a later occurrence of it. Fixing either does not fix the other.
Checked upstream as well, to rule out one being the other's parent: at 5559679229:vllm/v1/core/encoder_cache_manager.py:255-267 get_freed_mm_hashes was still unfiltered (so #2686's fix is some other in-range commit), while free_encoder_input at :217-242 lacked this occurrence check. Two separate distances.
They share a row, so they can share a branch — but they need separate red-first tests, and a reviewer must not accept one test as covering both.
The commit is the whole distance
free_encoder_input existed at 5559679229:vllm/v1/core/encoder_cache_manager.py:217-242 in exactly the shape this tree ported. Not a pre-pin hole. git log 3958a420f0..e126687a9a -- vllm/v1/core/encoder_cache_manager.py is empty: no later in-range commit touches the file.
Size
~10-20 product lines across encoder_cache_manager.{h,cpp} and possibly the two scheduler call sites, plus one ported test — upstream's is 37 lines: one request, two input_ids, one shared mm_hash, asserting the entry is not freeable after the first free. Port its parameters and assertions rather than re-deriving them.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with EncoderCacheManager::FreeEncoderInput in src/vllm/v1/core/encoder_cache_manager.cpp and its state in include/vllm/v1/core/encoder_cache_manager.h. Then inspect Scheduler::free_encoder_inputs and Scheduler::free_request_encoder_inputs in scheduler.cpp, along with the upstream 37-line test. Done means a shared mm_hash is not freeable after the first occurrence is freed, with a separate red-first test for this behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend, machine-learning
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100