Repository navigation
[KV Connector][Mooncake] Start async lookup when a request arrives via on_new_request - #61292
Conversation
…n_preschedule_request Add KVConnectorBase_V1.on_preschedule_request(request), called on the engine-core input thread right after the request is built. It may run concurrently with schedule(), so a connector may only start self-contained, thread-safe work and return a handle. The engine stores the handle on request.kv_preschedule_handle, which hands it to the scheduler thread through the input queue; the connector adopts it in get_num_new_matched_tokens. Connector state stays scheduler-thread-only, so no lock is needed. MooncakeStore uses it to start its async prefix lookup when a request arrives, so the lookup overlaps the input-queue and waiting-queue wait. LookupKeyClient.prefetch() only submits to the lookup executor and takes a slot from a semaphore (lookup_prefetch_limit caps in-flight prefetches; off by default). adopt() registers the result, and LookupKeyClient now keeps async results until expiry (lookup_prefetch_ttl_s), a changed token/hash range, or discard on finish, preemption, reset or close. A handle not yet adopted is cancelled when its request finishes. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Dao Le <Dao007forever@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qGetiYhFjQ6b3gmBJeTDi
|
Documentation preview: https://vllm--61292.org.readthedocs.build/en/61292/ |
|
This pull request has merge conflicts that must be resolved before it can be |
…n_preschedule_request Add KVConnectorBase_V1.on_preschedule_request(request), called on the engine-core input thread right after the request is built. It may run concurrently with schedule(), so a connector may only start self-contained, thread-safe work and return a handle. The engine stores the handle on request.kv_preschedule_handle, which hands it to the scheduler thread through the input queue; the connector adopts it in get_num_new_matched_tokens. Connector state stays scheduler-thread-only, so no lock is needed. MooncakeStore uses it to start its async prefix lookup when a request arrives, so the lookup overlaps the input-queue and waiting-queue wait. LookupKeyClient.prefetch() only submits to the lookup executor and takes a slot from a semaphore (lookup_prefetch_limit caps in-flight prefetches; off by default). adopt() registers the result, and LookupKeyClient now keeps async results until expiry (lookup_prefetch_ttl_s), a changed token/hash range, or discard on finish, preemption, reset or close. A handle not yet adopted is cancelled when its request finishes. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Dao Le <Dao007forever@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qGetiYhFjQ6b3gmBJeTDi Rebased prerequisite from vllm-project#61292 at 83bc03e. Signed-off-by: Sy03 <1370724210@qq.com>
Keep input-thread work request-owned and isolate child handles during scheduler callbacks. Preserve child order and cancellation through nested connectors. Depends on the on_preschedule_request interface from vllm-project#61292. Co-authored-by: OpenAI Codex <noreply@openai.com> Signed-off-by: Sy03 <1370724210@qq.com>
Resolve the vllm/v1/request.py conflict with vllm-project#60960, which replaced Request.skip_reading_prefix_cache with prefix_cache_hit_limit, and move the admission lookup to request.skip_reading_external_kv like the other connectors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DrGRraHy6yNvfSeyS3usoB
|
Thanks for the prefetch work — it matches the head-of-line blocking we hit in production: the store lookup client is single-threaded with no RPC timeout, so one slow Upstream Would you be open to folding that two-line discard into this PR (with attribution), or should we close #63932 as low-value and keep the timeout work separate? The above information was written by me and polished with AI assistance. |
|
This pull request has merge conflicts that must be resolved before it can be |
|
Hey @Sy0307, I'll fold it in and let's do the RPC timeout as a separate PR. |
Resolve the vllm/v1/engine/core.py conflict with main's frontend block hashes: build the Request with frontend_hashes, then start the connector's preschedule work on it. Signed-off-by: Dao Le <Dao007forever@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwEq3d2Nyvagw8ujXUmfci
Fold vllm-project#63932 by @Sy0307 into the finish hook, as they suggested in review. request_finished now releases both lookup states: an unadopted arrival-time prefetch on the request and any adopted or scheduler-submitted lookup in LookupKeyClient. Previously the latter was only discarded by the next build_connector_meta, so if the single lookup executor freed up in between, the dead request's queued RPC started, could no longer be cancelled, and delayed the next real request's lookup. Co-authored-by: Sy0307 <24841857+Sy0307@users.noreply.github.com> Signed-off-by: Dao Le <Dao007forever@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwEq3d2Nyvagw8ujXUmfci
… paths Address review feedback: - Measure lookup_prefetch_ttl_s from when the lookup RPC returned (stamped in a done callback) and never expire an in-flight lookup. Expiring a queued prefetch by submission age resubmitted it behind the same backlog, doubling RPC load under the load prefetch is meant for. - Remove the finished-request discard in build_connector_meta; every finish already goes through request_finished -> release_lookup. - adopt() asserts the request has no registered lookup instead of handling an unreachable duplicate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5Xg465seG36GHKr3k29vq Signed-off-by: Dao Le <Dao007forever@gmail.com>
Alex-ai-future
left a comment
There was a problem hiding this comment.
Thanks for the careful input-thread → scheduler handoff. The bounded prefetch and scheduler-owned adoption make the concurrency boundary easy to follow.
One small suggestion for the planned serving/TTFT validation: report the fraction of eligible requests whose prefetch has completed by their first scheduler probe, alongside the request wait time. That would directly show whether the change hides queue wait as intended, and how often the cap falls back to a scheduler-time lookup.
|
@Dao007forever I have opened #65466, please take a look. I think if we merge that first, this PR can be simplified quite a bit. In particular we should not longer need the new connector hook and request field, and can just initiate the lookup in the existing |
|
This pull request has merge conflicts that must be resolved before it can be |
|
Documentation preview: https://vllm--61292.org.readthedocs.build/en/61292/ |
|
This pull request has merge conflicts that must be resolved before it can be |
Drop the on_preschedule_request hook, Request.kv_preschedule_handle, and the input-thread prefetch/adopt handoff. The store connector now starts its async lookup from the existing on_new_request hook, which runs on the scheduler thread, so LookupKeyClient.prefetch() registers the lookup directly and the client is used from a single thread. With requests handled while waiting for model output (vllm-project/vllm vllm-project#65466), on_new_request runs up to a step before the request is scheduled, giving the lookup the same head start without a new connector API or core changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwEq3d2Nyvagw8ujXUmfci Signed-off-by: Dao Le <Dao007forever@gmail.com>
- Discard the cached async lookup in update_state_after_alloc. A request rescheduled after a failed KV load otherwise reused the stale hit until the TTL expired and retried loading the same missing keys. - Restore the executor-shutdown guard in prefetch() so a request added during teardown neither raises out of add_request nor leaks a slot. - Describe arrival-time lookup without relying on the engine handling inputs during a step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TwEq3d2Nyvagw8ujXUmfci Signed-off-by: Dao Le <Dao007forever@gmail.com>
|
/ci run |
|
✅ Triggered Buildkite CI #100095 for commit |
Overview
Start MooncakeStore's async prefix lookup from
on_new_request(), when the scheduler adds a request, so the lookup can finish while the request waits in the scheduler's waiting queue. GPU KV allocation and transfer stay in the scheduler's existing path.This uses the existing connector hook
KVConnectorBase_V1.on_new_request(request). An earlier revision added a new hook that ran on the input thread. That needed a handle handoff to stay lock-free, and it bought little: the engine core thread and the input thread share the GIL, so the only thing the lookup needs to overlap is GPU execution.on_new_requestruns on the engine core thread, so all lookup state stays on one thread and needs no locking.on_new_requestruns when the core thread handles the ADD:_process_input_queuebetween engine steps. With async scheduling (the default with the mp executor), the next batch is already running on the GPU at that point.Design
MooncakeStoreConnector.on_new_requestcallsMooncakeStoreConnectorScheduler.start_lookup_prefetch. That callsLookupKeyClient.prefetch(), which submits the lookup to the existing single lookup executor and registers it in_lookups. The ZMQ socket is still used only by the executor thread.get_num_new_matched_tokensprobe goes through the existinglookup(), which reuses the registered lookup instead of submitting a second RPC.update_state_after_alloc(consumed; a reschedule after a failed load looks up again), inrequest_finished/request_finished_all_groups, on preemption inbuild_connector_meta, and onreset()andclose().skip_reading_external_kv, prompts shorter than the lookup alignment, or no block hashes are not prefetched.Claims
lookup_async=True,enable_lookup=True, and a positivelookup_prefetch_limit, eligible requests submit their lookup when they are added. Off by default.lookup_prefetch_limitcaps arrival-time lookups in flight. A slot is released when the RPC completes or is cancelled. Over the cap, the request is looked up when the scheduler first reaches it. Scheduler lookups are not capped.LoadSpecor GPU allocation. The scheduler still computes the external tokens to load from its current local-prefix hit count.lookup_prefetch_ttl_s(default 2 s, measured from when its RPC returned) is looked up again. So is a lookup whose token count or block-hash chain changed. A lookup still in flight never expires, so it is not resubmitted behind the backlog it is waiting on.MultiConnectoralready forwardson_new_request, so this also works when MooncakeStore is wrapped in one.Validation
141 passed (Linux aarch64, Python 3.12, at 86cc07f). New and updated cases cover:
on_new_requestand reused by the scheduler's probes with a single RPC, then dropped once allocatedNot run: GPU serving, real Mooncake transfers, model evaluation, TTFT/throughput benchmarks.
Trade-offs
Duplicate-work checks: searched open vllm-project/vllm PRs for Mooncake lookup, async/prefetch lookup, and
on_new_requestlookup. #48230 skips lookups for locally reused prefixes, which is a different change. #42321 (enqueue-time prefetch forLMCacheMPConnectorviaon_new_request) is the same idea for another connector; it was closed for inactivity, not rejected.AI assistance: Codex and Claude Code assisted with implementation, source analysis, and validation.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (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. CPU validation is complete; GPU serving/model evaluation and benchmarks remain pending.
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.