Skip to content

[Bugfix][KV Connector] Fix speculative block reservations across KV cache groups - #59880

Merged
njhill merged 4 commits into
vllm-project:mainfrom
bzsuni:fix/kv-group-spec-reservation
Oct 11, 2026
Merged

njhill merged 4 commits into
vllm-project:mainfrom
bzsuni:fix/kv-group-spec-reservation

Conversation

@bzsuni

@bzsuni bzsuni commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Overview

Fix speculative decode block reservations for asynchronous KV loads with
multiple KV cache groups. Under KV memory pressure, under-reserving these
blocks can leave a request admitted for an asynchronous KV load but unable
to start decoding once the load completes.

Claims

  • Account for every KV cache group using its actual allocation block size.
  • Extend the existing promotion-margin regression test to cover single-group,
    multi-group, and mixed-block-size configurations.

Validation

.venv/bin/python -m pytest -q \
  tests/v1/kv_connector/unit/test_remote_prefill_lifecycle.py \
  tests/v1/kv_connector/unit/test_remote_decode_lifecycle.py \
  tests/v1/kv_connector/unit/test_kv_load_failure_recovery.py

32 passed. Pre-commit checks and manual Python 3.12 mypy passed.

Regression controls replace only the reservation method in the test process:

Implementation Single group Multiple groups Different block sizes
Original Pass Fail Fail
Original × group count Pass Pass Fail
This fix Pass Pass Pass

The broader scheduler/decode/recovery run showed no additional failures:
both the patched implementation and the original reservation method produced
171 passes and the same 46 failing test IDs. Observed environment limitations
included unavailable offline model configurations and insufficient visible
GPUs for parallel tests.

Validation exercises the scheduler and KV allocation paths with simulated
remote completion. Live NIXL P/D and model-level evaluations were not run.

Details

The existing calculation converts the speculative token margin to blocks
using the scheduler block size once. However, each KV cache group allocates
independently from the shared block pool, and groups may use different
allocation block sizes.

Sum the reservation across cache managers using each manager's block size.
Simply multiplying the old result by the group count is insufficient for
mixed-block-size configurations. The existing conservative reservation
approach is preserved.

The regression test checks admission and subsequent scheduling rather than
asserting the helper's arithmetic directly.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • 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.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added bug Something isn't working kv-connector scheduler labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: 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.

🚀

@snadampal snadampal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one suggestion :num_spec_tokens, num_lookahead_tokens, and each manager's block_size are constant for a given deployment, but the function _spec_decode_step_blocks is called once per in-flight prefill on every scheduling step which is unnecessary.
this can be fixed either in this PR or a new one.

@bzsuni

bzsuni commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

one suggestion :num_spec_tokens, num_lookahead_tokens, and each manager's block_size are constant for a given deployment, but the function _spec_decode_step_blocks is called once per in-flight prefill on every scheduling step which is unnecessary. this can be fixed either in this PR or a new one.

Thanks, updated to cache the value with cached_property

@njhill

njhill commented Oct 9, 2026

Copy link
Copy Markdown
Member

Thanks @bzsuni, the fix looks correct: summing per-group cdiv(N, B_g) across managers is a sound upper bound, and the parametrized test exercises the deadlock directly.

Over-reservation for Mamba groups. Summing over all single_type_managers also reserves cdiv(..., mamba.block_size) = 1 block per Mamba group, but Mamba never needs it: in non-align mode its spec blocks are already counted by MambaManager.get_num_blocks_to_allocate (and its block size is huge), and in align mode lookahead tokens are explicitly ignored. For hybrid GDN/Mamba + MTP models this over-reserves ~1 block per Mamba group for every in-flight async load, which adds up on a P/D decode instance.

Could we make this more precise by letting each manager account for its own lookahead need? E.g. in _request_remaining_blocks, pass num_tokens = full_num_tokens + 1 + num_spec_tokens + num_lookahead_tokens (with num_tokens_main_model = full_num_tokens) to coordinator.get_num_blocks_to_allocate rather than adding _spec_decode_step_blocks on top. That handles Mamba/SWA/etc. correctly per group, and the manual per-manager sum would then only be needed for the admitting request's own margin (where it could skip managers whose allocation doesn't depend on token count).

A few nits:

  1. Since kv_cache_manager and the token limits are fixed at __init__, it'd be simpler to compute this once as a plain attribute in __init__ rather than turning the method into a @cached_property.
  2. Please keep a short form of the original test docstring explaining the deadlock (two loads fill the pool, the head can't be promoted, nothing is running so no block is ever freed). It's the reason the test exists.
  3. The docstring line "The token limits and allocation block sizes are fixed at initialization." can be dropped (it only justifies the caching).

@bzsuni
bzsuni force-pushed the fix/kv-group-spec-reservation branch from da1dd49 to 616f596 Compare October 10, 2026 02:19
@bzsuni

bzsuni commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @bzsuni, the fix looks correct: summing per-group cdiv(N, B_g) across managers is a sound upper bound, and the parametrized test exercises the deadlock directly.

Over-reservation for Mamba groups. Summing over all single_type_managers also reserves cdiv(..., mamba.block_size) = 1 block per Mamba group, but Mamba never needs it: in non-align mode its spec blocks are already counted by MambaManager.get_num_blocks_to_allocate (and its block size is huge), and in align mode lookahead tokens are explicitly ignored. For hybrid GDN/Mamba + MTP models this over-reserves ~1 block per Mamba group for every in-flight async load, which adds up on a P/D decode instance.

Could we make this more precise by letting each manager account for its own lookahead need? E.g. in _request_remaining_blocks, pass num_tokens = full_num_tokens + 1 + num_spec_tokens + num_lookahead_tokens (with num_tokens_main_model = full_num_tokens) to coordinator.get_num_blocks_to_allocate rather than adding _spec_decode_step_blocks on top. That handles Mamba/SWA/etc. correctly per group, and the manual per-manager sum would then only be needed for the admitting request's own margin (where it could skip managers whose allocation doesn't depend on token count).

A few nits:

  1. Since kv_cache_manager and the token limits are fixed at __init__, it'd be simpler to compute this once as a plain attribute in __init__ rather than turning the method into a @cached_property.
  2. Please keep a short form of the original test docstring explaining the deadlock (two loads fill the pool, the head can't be promoted, nothing is running so no block is ever freed). It's the reason the test exists.
  3. The docstring line "The token limits and allocation block sizes are fixed at initialization." can be dropped (it only justifies the caching).

Thanks for the detailed review! Addressed in 616f596b94:

  • In-flight reservations now account for speculative/lookahead tokens through the coordinator, keeping num_tokens_main_model unchanged. The admitting request's margin excludes Mamba groups.
  • Replaced cached_property with an attribute computed in __init__, restored the deadlock explanation in the test docstring, and removed the caching-only docstring.
  • Added admission and promotion regression tests for Mamba none and align modes. Both cases fail with the previous logic and pass with the fix.

@njhill

njhill commented Oct 10, 2026

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 1 commit behind upstream main. Your branch must contain every commit currently on upstream main. No new CI build was started. Merge or rebase onto the latest main, then rerun /ci run. To test this branch at your own risk, use /ci run --allow-stale.

@bzsuni
bzsuni force-pushed the fix/kv-group-spec-reservation branch from 616f596 to 37c6271 Compare October 10, 2026 02:44
Co-authored-by: Codex
Signed-off-by: bzsuni <bingzhe.sun@daocloud.io>
Signed-off-by: bzsuni <bingzhe.sun@daocloud.io>
Delegate in-flight lookahead accounting to cache managers and exclude Mamba from the admission margin. Compute the margin at initialization and cover Mamba admission and promotion in regression tests.

Signed-off-by: bzsuni <bingzhe.sun@daocloud.io>
@bzsuni
bzsuni force-pushed the fix/kv-group-spec-reservation branch from 37c6271 to c594a52 Compare October 10, 2026 02:53
@bzsuni

bzsuni commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

❌ @bzsuni, A reviewer with write access must run /ci run, approve the PR, or add the ready label first.

@njhill

njhill commented Oct 10, 2026

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 2 commits behind upstream main. Your branch must contain every commit currently on upstream main. No new CI build was started. Merge or rebase onto the latest main, then rerun /ci run. To test this branch at your own risk, use /ci run --allow-stale.

@njhill njhill added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 10, 2026
@github-actions

Copy link
Copy Markdown

✅ @bzsuni, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@njhill

njhill commented Oct 10, 2026

Copy link
Copy Markdown
Member

/ci run

@njhill
njhill enabled auto-merge (squash) October 10, 2026 03:23
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #93959 for commit f211aa933f3a.

@njhill njhill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bzsuni

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working kv-connector ready ONLY add when PR is ready to merge/full CI is needed scheduler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants