Repository navigation
[Bugfix][kv-events] Making partial blocks emit all tail hashes - #62195
Lucas-Fernandes-Martins wants to merge 5 commits into
Conversation
Publish the complete partial-tail hash chain for external cache consumers while preserving local prefix lookup. Track published intermediate hashes with their physical block so removal events cover every advertised hash. Assisted-by: OpenAI Codex Signed-off-by: Lucas-Fernandes-Martins <lucasfmartins16@gmail.com> Co-authored-by: Andy Lo <andy@mistral.ai>
Assisted-by: OpenAI Codex Signed-off-by: Lucas-Fernandes-Martins <lucasfmartins16@gmail.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
|
This pull request has merge conflicts that must be resolved before it can be |
Preserve upstream event-key conversion and adapt the tail regression to the reusable prompt boundary. Assisted-by: OpenAI Codex Signed-off-by: Lucas-Fernandes-Martins <lucasfmartins16@gmail.com>
sagearc
left a comment
There was a problem hiding this comment.
Thanks for digging into this @Lucas-Fernandes-Martins, amazing work
| if report_all_hash_boundaries: | ||
| block_start = round_down(num_tokens, block_size) | ||
| first_hash_idx = block_start // self.hash_block_size | ||
| block_hashes = request.block_hashes[first_hash_idx:num_hash_blocks] | ||
| parent_hash = ( | ||
| request.block_hashes[first_hash_idx - 1] | ||
| if first_hash_idx > 0 | ||
| else None | ||
| ) | ||
| if len(block_hashes) > 1: | ||
| self.reported_block_hashes_by_block.setdefault( | ||
| block.block_id, set() | ||
| ).update( | ||
| make_block_hash_with_group_id(block_hash, kv_cache_group_id) | ||
| for block_hash in block_hashes[:-1] | ||
| ) |
There was a problem hiding this comment.
Is there a reason to handle a single tail hash separately from multiple tail hashes here?
There was a problem hiding this comment.
So I added this condition as the final tail hash is already tracked by the cache index. But we need extra reporting for the additional tail hashes. Basically, if we didn't have the if, the only consequence would be self.reported_block_hashes_by_block dict having an entry with an an empty set as value when len(block_hashes) == 1.
Basically, self.reported_block_hashes_by_block does the accouting for all the intermediate tail hashes of a partial block, so we can emit evict events for them later.
There was a problem hiding this comment.
right, so looking up an intermediate hash returns no hit even though its tokens are present in the physical block? maybe it is worth documenting that behavior in a comment
There was a problem hiding this comment.
Exactly, I did it this way to 'solve' the routing issue without changing the current lookup behaviour. I felt it would be slightly tricky to align with mamba events in the edge case where we the final hash block was evicted, and we lost the mamba checkpoint, but are left with a non-tail KV block.
Added the following comment block in block_pool.py before if len(block_hashes) > 1:
# Intermediate hashes provide ancestry for KV-event consumers.
# They are not registered as lookup aliases for this block, even when
# the corresponding KV is present. Only the final hash is registered
# for prefix lookup.
Happy to update other docs if necessary!
| num_tokens=boundary_tokens, | ||
| kv_cache_group_id=self.kv_cache_group_id, | ||
| block_size=self.block_size, | ||
| report_all_hash_boundaries=True, |
There was a problem hiding this comment.
in what scenario would we want report_all_hash_boundaries=False? isn't True always preferable?
There was a problem hiding this comment.
For mamba events my reasoning is that flushing those intermediate tail hashes could be misleading. I agree that, for both full attention and mamba, the cache lookup stays the same (the additional intermediate hashes reported for the partial block are not registered for prefix lookup). But in the case of mamba, since those non-tail hash states are not retained as cache checkpoints, it seemed weird to flush them.
Either way, if you believe mamba events should get the same treatment, happy to remove the flag.
|
Thanks for the review @sagearc, responded to the comments! If you feel mamba should have the same behaviour of full attention in what regards flushing tail blocks, I can remove |
Signed-off-by: Lucas-Fernandes-Martins <lucasfmartins16@gmail.com> Assisted-by: OpenAI Codex
|
This pull request has merge conflicts that must be resolved before it can be |
Resolve conflicts with vllm-project#61589 by generating lookahead tokens and multimodal extra keys for each published partial-tail hash. Retain upstream lookahead test configuration and extend regressions across lookahead lengths 0, 1, and 2 and DCP world sizes 1, 2, and 4. Assisted-by: OpenAI Codex Signed-off-by: Lucas-Fernandes-Martins <lucasfmartins16@gmail.com>
|
Hi @sagearc, do you think it makes sense to add the |
|
cc @Change72 |
|
✅ @Lucas-Fernandes-Martins, CI is now available for this PR.
|
|
@vMaroon, how should consumers handle these ancestry-only hashes? Dynamo supports only one block size and would either index every hash as reusable or drop the event on a size mismatch. llm-d seems to have the same issue. |
|
Hi @Change72 I don't believe this is an issue for llm-d, as it just needs the mapping between the engine hashes and token_ids. I've been using llm-d with this commit for many weeks now and it works well. |
|
This pull request has merge conflicts that must be resolved before it can be |
Overview
Issue: partial full-attention cache events can reference a parent hash that was never published, as described in #58119.
Solution: have BlockStored contain all completed tail hashes of a partial full-attention block.
Claims
We fix #58119, which is particularly important for llm-d's precise KV cache report mode, in models which use
prefix_match_unit, such as kimi-k3.Validation
Conflict-resolution validation (2026-10-26; AI-assisted)
Merged upstream
71294c2e9aindbf6ca50ed. Upstream #61589 added EAGLE lookahead-token metadata in the same event path. The resolution emits the corresponding lookahead tokens and multimodal extra keys for every published tail hash, retaining the existing local-lookup behavior and full-attention-only switch.Extended the existing tests for lookahead lengths 0, 1 and 2, including images overlapping lookahead windows. The manager regression covers DCP world sizes 1, 2 and 4.
Validation commands below use
VLLM_TEST_PYTHONto refer to the existing Python 3.12 test virtualenv. Run from the corresponding checkout:Upstream failure:
assert tail_event.parent_block_hash == full_event.block_hashes[-1](9 failed, 236 deselected). Local pre-commit, including Ruff, mypy and test tethering, and commit/sign-off hooks passed. Hosted pre-commit was skipped because the upstream pre-run eligibility gate requires a qualifying label or at least four previously merged PRs; the gate found zero. These are CPU metadata/cache-manager tests using--confcutdir; no GPU serving, model evaluation or end-to-end ZMQ validation was performed during conflict resolution.We add the following tests:
test_full_attention_tail_events_preserve_local_lookup: exercises cache manager with partial-prefix hits enabled. Verifies the core logic works and we emit all the hashes for all tail hashes.test_dense_partial_event_splits_multimodal_extra_keys: check the main logic with image data (accounted for inextra-keys)test_reported_partial_hashes_follow_block_lifecycle: checks eviction, movement, promotion to full block, replacement, and cache reset.We add some checks on:
test_cache_partial_block_kv_cache_events: now also runs withreport_all_hash_boundaries=True.Details
The main motivation is to ensure consumers of
kv-eventscan reconstruct KV cache chains. Previously, a partial block would emit an event stating a parent block that was never emitted. This is particularly consequential for precise KV cache routing on llm-d, as it relies on ZMQ kv-events to understand which pods have which KV blocks.Hence, the main design decision is to go from this:
to
We introduce the
report_all_hash_boundariesarg oncache_partial_block.Therefore, with the latter, the consumer of
kv-eventscan know the parent ofH14(H12). Before, we'd just receiveH14, andH12was never announced. Consequently,H14had a missing parent.Most importantly, this does not impact the hashes registered for local lookup, only the hashes published in
kv-eventsthrough ZMQ. Thus, there's no effect in how cache hits are computed.Thanks @andylolu2 for helping on iterating on the solution.
Pull Request Checklist
[x ] I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).[ x] AI assistance was used during the creation of this PR.
Code had AI assistince by was reviewed manually. PR description was human written.
[x ] Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
[ x] Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.