Skip to content

[Core][KV Connector] Release transfer-held KV before a cache reset - #87

Draft
aoshen02 wants to merge 1 commit into
pd-rl/mooncake-transfersfrom
pd-rl/pause-drops-kv
Draft

aoshen02 wants to merge 1 commit into
pd-rl/mooncake-transfersfrom
pd-rl/pause-drops-kv

Conversation

@aoshen02

@aoshen02 aoshen02 commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Purpose

For RL with prefill/decode disaggregation, the trainer pauses P and D, updates the weights, and resumes. With PD, every operation that resets the KV cache failed on D with "Failed to reset KV cache even when all the running requests are preempted…". That covers:

  • pause(wait|keep, clear_cache=True);
  • sleep(level>=1);
  • release_kv_cache_memory.

The reset failed because remote KV transfers still held blocks:

  • D requests were still waiting for remote KV.
  • P's finished requests were holding KV that D had not pulled yet.

A pause that keeps the KV had the opposite problem: P waited for those exports, up to 480 s when D was paused too and would not pull them.

The fix starts from two facts:

  • A pause only stops compute.
  • A cache reset needs every block back, including blocks held by remote transfers.

So:

  • Release, then reset. Once the engine is idle, an operation that resets the cache calls Scheduler.release_transfer_kv() once. That call:

    • asks the connector to end its transfers, through the new KVConnectorBase_V1.abort_transfers() hook: P stops holding finished requests' KV for remote readers, and D asks P to drop loads still in flight. MultiConnector fans it out; this PR implements it for Mooncake.
    • keeps every request whose load is cut short. It is marked as a failed load with 0 computed tokens, so it recomputes its KV after resume through the existing failed-load recovery, under either kv_load_failure_policy. Nothing is aborted (like SGLang's retract, with D recomputing).

    The engine then waits for the transfers to give their blocks back, and resets. All of this lives in one place, EngineCoreProc._when_idle. It covers pause with clear_cache, sleep and release_kv_cache_memory. A resume fails any such operation still pending.

  • Keep pauses do not wait. A paused engine does not wait for KV that finished requests hold for remote readers, unless it is releasing it. So a pause that keeps the KV returns at once, and the exports finish after resume.

  • Waiting requests recompute. The reset recomputes waiting requests that hold KV, including remote loads that arrived but were not scheduled yet. It shares the preemption path, and the queue order is kept.

  • No raise on held blocks. reset_prefix_cache(reset_running_requests=True) returns False while transfers hold blocks, as documented, instead of raising.

  • Docs. The PD note in async_rl.md: pause both P and D, and use kv_load_failure_policy="recompute".

Not covered:

  • NIXL keeps the default no-op hook. D reads P's blocks one-sided and cannot see a revoked lease, so P waits out its 30 s lease TTL. That wait is bounded and safe.
  • The in-process engine cannot wait for transfers. Its cache reset still fails while transfers hold blocks, as on main.

Stacked on vllm-project#59625 and the Mooncake transfer PR (this fork PR's base branch).

Test Plan

  • Unit: scheduler, async scheduler, engine core, Mooncake, MultiConnector and NIXL suites.

    • test_release_transfer_kv_frees_blocks_for_reset checks:
      • loads in flight are retracted, not aborted, and recompute after the load ends (written, failed blocks, or a failed request), also when a resume cancels the reset first;
      • held sends are dropped;
      • loaded and arrived KV is recomputed in order;
      • a streaming session keeps its KV;
      • a retracted load keeps its blocks until its transfer ends.
    • test_cache_reset_waits_for_transfer_kv covers every pause mode and release_kv_cache_memory, each idle or draining, with or without held transfers. It drives the real input loop, which must never block on input while work is pending.
    • test_resume_cancels_a_pending_cache_reset.
  • Mutation: each key line removed fails its test (31 of 31 across both PRs).

  • Hardware: GB300, 1P1D over Mooncake RDMA. The lifecycle matrix covers:

    • pause abort/wait/keep on P, D and both, including keep with clear_cache;
    • sleep L0/L1/L2 in abort and wait;
    • abort_requests;
    • reset_prefix_cache;
    • release_kv_cache_memory;
    • in-place weight reload under abort/wait/keep;
    • client disconnects.

    All cases run under load.

Test Result

main main + vllm-project#59625 + both PRs
lifecycle matrix (Mooncake, 27 cases) 6/22 pass most abort and clear cases fail 27/27 on every revision (v20–v26)
NIXL matrix (19 cases, no sleep) – – 19/19; P pause ≤ 30 s (lease)

🤖 Generated with Claude Code

@aoshen02
aoshen02 force-pushed the pd-rl/mooncake-transfers branch from c72b167 to 636a6c3 Compare October 4, 2026 17:04
@aoshen02
aoshen02 force-pushed the pd-rl/pause-drops-kv branch from 20f35c9 to 3efaa1d Compare October 4, 2026 17:04
@aoshen02 aoshen02 changed the title [Core][KV Connector] Release transfer-held KV when a pause drops the KV [Core][KV Connector] Release transfer-held KV before a cache reset Oct 4, 2026
With prefill/decode disaggregation, a pause that clears the cache (wait
or keep with clear_cache, sleep at level 1 or above) and
release_kv_cache_memory failed with "Failed to reset KV cache": D
requests waiting for remote KV, and P's finished requests whose KV D had
not pulled yet, still held blocks. A pause that keeps the KV instead
waited for those exports, up to VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT
when D was paused too.

A pause only stops compute; a cache reset needs every block back. So:

- Once the engine is idle, an operation that resets the cache first
  calls Scheduler.release_transfer_kv() and waits until the transfers
  give back their blocks, then resets. A resume fails any such
  operation still pending.
- release_transfer_kv() asks the connector to end its transfers (new
  KVConnectorBase_V1 hook abort_transfers, fanned out by MultiConnector,
  implemented for Mooncake): P stops holding finished requests' KV for
  remote readers, and D asks P to drop loads still in flight. A request
  whose load is cut short is not aborted: it recomputes its KV after
  resume, through the existing failed-load recovery and whatever the
  kv_load_failure_policy.
- A paused engine does not wait for KV held by remote transfers unless
  it is releasing it, so a pause that keeps the KV returns at once.
- The reset also recomputes waiting requests that hold KV, including
  remote loads that arrived but were not scheduled, sharing the
  preemption path. reset_prefix_cache(reset_running_requests=True)
  returns False while transfers hold blocks, as documented, instead of
  raising.

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aoshen02
aoshen02 force-pushed the pd-rl/mooncake-transfers branch from 636a6c3 to 3c48419 Compare October 5, 2026 02:53
@aoshen02
aoshen02 force-pushed the pd-rl/pause-drops-kv branch from 3efaa1d to 0242476 Compare October 5, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant