Skip to content

[Bugfix][KV Offload][P2P] Drop a timed-out store job's blocks from the round's parked supply - #59329

Open
sohom-cs wants to merge 4 commits into
vllm-project:mainfrom
sohom-cs:fix/p2p-store-timeout-supply
Open

sohom-cs wants to merge 4 commits into
vllm-project:mainfrom
sohom-cs:fix/p2p-store-timeout-supply

Conversation

@sohom-cs

@sohom-cs sohom-cs commented Sep 30, 2026 •

Copy link
Copy Markdown

Purpose

In the symmetric P2P KV offload tier, a decode instance asks a peer whether it holds a prompt's KV, and the peer pins the matching blocks in its own cache until the fetch arrives. If the fetch is late, the pin times out and the peer's engine frees those cache slots for reuse, but the round still advertises the blocks as available. A fetch that arrives after the timeout then RDMA-reads slots that may already hold another request's KV, and reports success. The decode instance would continue generating from someone else's cache. This PR withdraws the advertised blocks when their pin times out, so the late fetch fails fast and the request prefills locally.

Concretely, in vllm/v1/kv_offload/tiering/p2p/session/server.py:

  • Answering a lookup, the server pins the blocks through a store job and parks them in the fetch round: add_stored_blocks (:94) records them in available and the job in pending_job_ids.
  • _timeout_pending_store_jobs (:1121) fails a job older than _STORE_TIMEOUT_S (:40, 30 s) with StoreResult(success=False). The tiering manager maps that to a failed JobResult (p2p/manager.py:859), and the engine unpins the slots.
  • The round's available entries and pending_job_ids are left as they were. A late FetchMsg matches them in on_fetch and submits the transfer.

Fix: when store jobs time out, remove their entries from every round's available map and pending_job_ids. The late fetch then has unmatched demand and takes the existing fail-fast path for symmetric rounds (server.py:372): TransferDoneMsg(success=False), and the consumer prefills locally. The PD case where the prefiller finishes first has the same problem when its store stays parked longer than _STORE_TIMEOUT_S before the fetch arrives, and this fix covers it too: finish() has already set finishing, so on_fetch fails the round (thanks @liranschour for pointing this out and for reproducing the bug).

Not a duplicate: #53453 added a one-round-trip rejection of a late fetch for stores the manager reaps while still unbound (_reaped_stores). This PR covers the other case: a store job already bound to a session that _timeout_pending_store_jobs fails. #58168 (draft) edits server.py elsewhere and does not reference these identifiers. No open PR touches _timeout_pending_store_jobs.

Test Plan

python -m pytest tests/v1/kv_offload/tiering/p2p/test_sessions.py -q -k symmetric_supply_timed_out
python -m pytest tests/v1/kv_offload/tiering/ -q
pre-commit run --from-ref origin/main --to-ref HEAD
pre-commit run mypy-3.12 --hook-stage manual --from-ref origin/main --to-ref HEAD

New test, TestServerFlows::test_fetch_after_symmetric_supply_timed_out_reads_nothing: a lookup pins block hA (slot 7); the store job is aged past _STORE_TIMEOUT_S and reported failed; then a FetchMsg for hA arrives. It checks that no transfer is submitted and the round ends with TransferDoneMsg(success=False).

Test Result

With the fix (CPU, macOS arm64, on main 4e0a414c4):

-k symmetric_supply_timed_out ... 1 passed
tests/v1/kv_offload/tiering/ ... 434 passed, 1 skipped

On main (fix reverted, test kept):

E   AssertionError: assert {0: ('peer:8000', [7], [20])} == {}
    (the late fetch RDMA-reads the unpinned slot 7)
1 failed

pre-commit (--from-ref origin/main --to-ref HEAD) and mypy-3.12 (manual stage): clean.

This only changes a failure path (a fetch after its pin timed out now fails instead of reading a possibly reused slot); no evals are needed.

Related: one of a few independent fixes from an audit of the KV transfer paths (CPU offload, NIXL, P2P): #59096, #59099, #59102, #59325. None depends on another; they can be reviewed and merged in any order.

AI assistance

I used an AI coding assistant (Claude) to audit this code path, write the fix and write the test. I reviewed every changed line and ran the tests above myself. The commit carries a Co-authored-by trailer, as AGENTS.md asks.

…e round's parked supply

In symmetric P2P, a producer answering a consumer's lookup pins the
matching primary-tier slots through a store job and parks them in the
fetch round's available map until the consumer's FetchMsg arrives. If
the fetch comes later than _STORE_TIMEOUT_S, _timeout_pending_store_jobs
reports the job failed and the tiering manager releases the pins. The
parked entries stayed in the round, though, so the late fetch still
matched them, RDMA-read slots that may already hold another request's
KV, and reported TransferDoneMsg(success=True).

When store jobs time out, drop their entries from every round's
available map and pending set. The late fetch then finds no supply and
takes the existing fail-fast path for symmetric rounds, and the
consumer prefills locally.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Sohom Chakraborty <16609933+sohom-cs@users.noreply.github.com>

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

@github-actions

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.

🚀

@liranschour liranschour 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.

LGTM, thanks!

I confirmed the bug locally. With the fix reverted, a fetch that arrives after the pin times out still RDMA-writes the unpinned slot ({0: ('peer:8000', [7], [20])}) and reports success. That means the decoder could silently decode from another request's KV. With the fix, the round fails fast through the existing lookup_supplied path, and the full tests/v1/kv_offload/tiering/p2p/ suite passes (218 tests).

One note for the description: the PD prefiller-first case, where the store is parked for more than 30s before the fetch, has the same problem, and this fix covers it too. finish() sets finishing, so on_fetch fails the round.

@sohom-cs

sohom-cs commented Oct 3, 2026

Copy link
Copy Markdown
Author

Thanks @liranschour for reproducing it and running the full p2p suite. Good point on the prefiller-first PD case; I've updated the description: that case was covered by the fix but the description said PD rounds were unaffected.

@orozery, when you have a moment: it now has an approval from the P2P side; the change is confined to _timeout_pending_store_jobs in server.py plus one test.

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

Copy link
Copy Markdown

✅ @sohom-cs, 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.

@orozery

orozery commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94405 for commit aef88ff6d7bb.

@vllm-agent

Copy link
Copy Markdown
Contributor

CI selector (shadow): 1 test steps (1 jobs) instead of 62 (78 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 62 (78) 1 (1) 61 (77) 0 (0)
AMD mirrors 54 (65) 1 (1) 53 (64) 0 (0)
Selector would run (1)
  • v1-kv-offload
Would skip (today's rules run them) (61)
  • ascend-npu-test
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-test-other-cpu
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • cpu-language-generation-and-pooling-model-tests ×3
  • cpu-multimodal-config
  • cpu-params-env-tokenizers-parser
  • cpu-reasoning-renderers
  • cpu-tool-parsers
  • e2e-core-1-gpu
  • e2e-core-large-memory
  • e2e-scheduling-1-gpu
  • e2e-scheduling-accuracy-1-gpu
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • kv-offload-large
  • kv-offload-medium
  • kv-offload-small
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • metrics-tracing-2-gpus
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • rl-entrypoints-tests
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-others-cpu
  • v1-sample
  • v1-spec-decode
Would add (today's rules do not run them) (0)

none

AMD mirrors: would skip (53)
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • e2e-core-1-gpu
  • e2e-core-large-memory
  • e2e-scheduling-1-gpu
  • e2e-scheduling-accuracy-1-gpu
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • kv-offload-large
  • kv-offload-medium
  • kv-offload-small
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • metrics-tracing-2-gpus
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • platform-tests
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • rl-entrypoints-tests
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-sample
  • v1-spec-decode
AMD mirrors: would add (0)

none

2 changed files · base 4e4eab73ed · head aef88ff6d7 · Python record: build 93039 at 1e5d0ea888 · kernel record: table fe341a5 (build 94315), map fe341a5 · not counted: 11 build steps, 5 A100 steps the generator no longer emits, 7 optional steps the selector would also run

@orozery

orozery commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 40 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.

@orozery

orozery commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

/ci run

@orozery
orozery enabled auto-merge (squash) October 14, 2026 05:20
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94678 for commit ba2f2a700fb2.

@orozery

orozery commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

/ci retry

@github-actions

Copy link
Copy Markdown

✅ Queued 2 failed job(s) for retry in Buildkite CI #94678.

This branch has not been deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants