Repository navigation
Conversation
Contributor
|
Documentation preview: https://vllm--59635.org.readthedocs.build/en/59635/ |
5 of 20 tasks
aoshen02
force-pushed
the
nixl-sleep-registration
branch
from
October 1, 2026 16:29
98c9e92 to
ce2066b
Compare
aoshen02
force-pushed
the
nixl-sleep-registration
branch
from
October 1, 2026 17:37
ce2066b to
39660ee
Compare
aoshen02
force-pushed
the
nixl-sleep-registration
branch
5 times, most recently
from
October 2, 2026 02:11
6736e2f to
478060e
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
aoshen02
force-pushed
the
nixl-sleep-registration
branch
2 times, most recently
from
October 2, 2026 03:35
ebd4f83 to
b56d906
Compare
aoshen02
force-pushed
the
nixl-sleep-registration
branch
6 times, most recently
from
October 3, 2026 13:38
6e96db7 to
8214269
Compare
Sleep mode maps new physical pages for the KV cache at the same addresses, so a transport registration of that memory goes stale. The worker now releases the KV connector before the KV cache is unmapped and restores it after it is mapped again; Mooncake implements both over RDMA. A connector that does not support sleep mode is refused at startup with sleep mode. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Connectors support sleep mode by default; NIXL and Mooncake over a protocol other than RDMA are refused, as they hand the KV cache to a peer and cannot re-register it. - Mooncake release: an expired send no longer holds the release, only pulls that write local blocks are waited for, and a wedged transfer loop fails at the deadline instead of hanging. - Drop the EngineCore handshake re-publish; Mooncake publishes none. - Tests: the expandable-segments sleep test uses a supported connector, the config gate gets a model-free test, and the NIXL MNNVL doc no longer suggests sleep mode. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MooncakeStoreConnector registers the KV cache with RDMA once. After a sleep/wake cycle the cache lives in new physical pages, but the store keeps the old registration, so transfers silently return wrong outputs. Report supports_sleep_mode() = False so the config check refuses the combination up front; re-registering on wake is left for a follow-up. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Refuse sleep mode with MoRIIOConnector: like NIXL, its peers keep the RDMA registration of the KV cache, which a sleep makes stale. - A timed-out Mooncake release now always raises the builtin TimeoutError with its message, also when the future wait times out (Python 3.10 too). - Name the configured connector in the sleep mode refusal. - Tests: cover pull counting through the bootstrap path, pin the timeout message, and drop tests duplicated by the config table. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sleep mode keeps the KV cache virtual addresses but maps new physical pages, so NIXL's memory registration, local prepped dlists, published handshake payload and every peer's loaded remote agent go stale. On GB300 (dmabuf, no nvidia_peermem) the stale MR pins the old pages: peers read old KV without any error and sleep frees nothing. Implement the vllm-project#59625 hooks for NixlPullConnector: - release_kv_caches waits for own reads and pending handshakes, releases the local dlists, drops the remote engines and deregisters. - restore_kv_caches registers again and rebuilds the payload with the new agent metadata. - After a wake-up that restored the KV cache, EngineCore hands the workers' payloads to the scheduler, which bumps its registration epoch and stamps it on every rank's payload; request_finished advertises it as remote_registration_epoch. A reader whose loaded agent is older drops it and handshakes again, deferring while reads through it run. - Under sleep mode UCX_RCACHE_ENABLE=n and UCX_TLS=^cuda_ipc are set process-wide unless already set, and logged once: UCX's registration cache and peers' cuda_ipc imports keep released pages pinned. Only the UCX backend is accepted; NixlPushConnector stays refused. NIXL_CONNECTOR_VERSION 13 -> 14. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aoshen02
force-pushed
the
nixl-sleep-registration
branch
from
October 5, 2026 01:11
8214269 to
646a5b1
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
vllm-project#59625 landed on main squashed and reworked, so its files take main's version and only the NIXL commit is replayed on top. Adaptations: - The connector hook is unregister_kv_caches on main; NixlPullConnector now implements that name (the old release_kv_caches was never called). - main took NIXL_CONNECTOR_VERSION 14 (vllm-project#59997); the registration epoch is version 15. - A request whose peer re-registered handshakes directly from _start_read; other requests go through _read_blocks_for_req (vllm-project#55471). - test_config follows main's refusal-only parametrization. Signed-off-by: Dakai An <dakaian108@gmail.com>
vllm-project#59625 settled that the engine drains KV transfers before a sleep unregisters the KV cache (the prefix-cache reset needs every block free), so the connector no longer polls its own reads. A background handshake is not covered by the drain and can still prepare local handles on the memory, so unregister_kv_caches keeps the bounded wait for those only. Also test that NixlConnector over UCX is accepted under sleep mode. Signed-off-by: Dakai An <dakaian108@gmail.com>
Follow the shape of the Mooncake support (vllm-project#59625): the engine drains transfers before a sleep, so the connector only deregisters and registers again. What NIXL needs on top: - Drop every handle built on the registration, local and remote, and rebuild the local one on wake-up; peers handshake again on demand. - Publish the new agent metadata after a wake-up and bump a registration epoch in kv_transfer_params. A decoder whose loaded agent is older than the request's epoch handshakes again. - Under sleep mode, default UCX_RCACHE_ENABLE=n and UCX_TLS=^cuda_ipc, which would otherwise keep the slept KV cache resident. Removed: the handshake payload epoch and per-rank stamping, deferring reads while old transfers run, the handshake wait on unregister, and the tests for them. Signed-off-by: Dakai An <dakaian108@gmail.com>
As for Mooncake (vllm-project#59625): the connector refusals, one unregister and restore cycle, and a real P/D sleep and wake before the GSM8K eval (SLEEP_WAKE=1 in the NIXL accuracy sweep), which exercises the peer refresh end to end. Add one unit test for the NIXL-only part: a request from a re-registered peer makes the decoder handshake again. Signed-off-by: Dakai An <dakaian108@gmail.com>
The NIXL accuracy sweep runs in five NVIDIA jobs and their AMD mirrors, so a sweep row would repeat the check in each. Like the Mooncake one, run it once: a single command in the default NixlConnector PD job. Signed-off-by: Dakai An <dakaian108@gmail.com>
aoshen02
marked this pull request as ready for review
October 14, 2026 13:18
aoshen02
requested review from
ApostaC,
Harry-Chen,
NickLucche,
ivanium,
khluu,
njhill,
orozery,
wzhao18 and
xuechendi
as code owners
October 14, 2026 13:18
The fixture builds the pull worker without __init__ and lists its state by hand; _read_blocks_for_req now reads _remote_registration_epochs. Signed-off-by: Dakai An <dakaian108@gmail.com>
…oaded When both sides slept, the decoder's own sleep already dropped the prefill agent; a request with the newer registration epoch then cleaned up an engine that is not loaded, which asserts. Only drop a loaded agent. Found on GB300 with P/D sleep and wake. Signed-off-by: Dakai An <dakaian108@gmail.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Stacked on #59625; only the last commit is new. This replaces the earlier lease-based revision of this PR (and the
closed #59713): there is no lease and no new protocol between engines.
#59625 refuses sleep mode with NIXL because peers keep the stale KV cache registration. Sleep keeps the virtual
addresses and maps new physical pages, so everything NIXL bound to the old pages goes stale: the memory registration
(rkeys), the local prepped dlists, the handshake payload served to peers, and each peer's loaded remote agent.
Evidence on #59625 with the NIXL refusal forced off (GB300, cross-node 1P1D, Qwen3-8B, UCX RDMA over RoCE):
nvidia_peermem, so UCX registers GPU memory throughdmabuf, and the stale MR pins the old pages; peers read old data without any error.
(150787 MiB instead of 85101).
This PR implements the #59625 hooks for the NIXL pull connector (
NixlConnector/NixlPullConnector):release_kv_caches()(before the unmap): waits until no own read is in flight and no handshake is pending (bounded,TimeoutErrorkeeps the registration), then releases every local prepped dlist, drops every remote engine (theirsplit handles reference local memory; they are re-handshaked on demand), and deregisters the KV cache.
restore_kv_caches()(after the remap): registers the same descriptors again, re-preps the local dlist, andrebuilds the handshake payload with the new agent metadata.
tagsis None or containskv_cache),EngineCorehands theworkers' payloads to the scheduler (the startup code, extracted).
every rank's payload, so ranks cannot advertise different epochs, even after a partly failed release.
request_finishedaddsremote_registration_epochtokv_transfer_params.case it straddled the republish). A request naming a newer epoch drops that remote engine and handshakes again
through the existing path; while reads through the old registration are still in flight, the request is
re-queued instead. Older epochs never trigger a re-handshake.
_start_read), so a handshake that completesconcurrently cannot install an old agent between the epoch check and the read.
UCX_RCACHE_ENABLE=nandUCX_TLS=^cuda_ipcright before creating the agent,each only if the user did not set it, and logs once which defaults it applied (as
UCX_RCACHE_MAX_UNRELEASEDinnixl_utils.pydoes). They are process-wide: other UCX users in the worker process (the NIXL EPLB communicator,the EC NIXL connector, NVSHMEM with a UCX transport) and child processes inherit them.
pinned in about half of the sleeps, and
release_kv_cache_memoryfreed nothing. With the cache off, every sleepfreed the KV cache.
MNNVL the outputs stayed correct, but the prefiller's KV cache stayed resident asleep and was doubled after the
wake. Without cuda_ipc in the exporter's UCX context no peer can import it; reads use RDMA (on one node: RDMA
loopback through the NIC). Cost measured below: none on GB300.
NixlPullConnector.supports_sleep_modeis True only with the UCX backend(
backends == ["UCX"]), the only one validated.NixlPushConnectorstays refused: the producer writes through thedecoder's registration and is not refreshed.
NIXL_CONNECTOR_VERSION13 -> 14 (payload field andkv_transfer_paramskey). Routers must forwardkv_transfer_paramsunchanged, as they already must forremote_block_ids.Test Plan
test_decoder_reads_from_republished_prefiller[all-ranks|one-rank|straddled](scheduler level, real sidechannel): whichever ranks re-registered, a handshake after the republish loads the epoch the prefiller
advertises, so the decoder reads instead of handshaking again; a handshake that straddled the republish is redone
exactly once. The
one-rankrow (one rank's restore was a no-op) livelocks on the previous revision of this PR.test_sleep_cycle_releases_and_restores_registration: with a tracking agent, nothing built on the registrationsurvives a release (registrations, local and remote dlists, remote agents), and a restore registers the same memory
with exactly one local dlist and publishes the new agent metadata; both calls idempotent over two cycles.
test_release_waits_for_reads_and_handshakes: an in-flight read or a pending handshake holds the release; a wedgedone times out and keeps the registration; shutdown while released does not deregister again.
TestPeerReplacement::test_rehandshakes_re_registered_peer: same epoch reads through the loaded agent; a newerepoch drops it and handshakes again; with reads in flight the request is deferred until they finish.
test_sleep_mode_ucx_defaults: the UCX defaults under sleep mode only, and a user setting wins per variable.test_config.py: NIXL pull supported, NIXL with a non-UCX backend and NIXL push refused, MultiConnector rows.test_engine_core.py::test_wake_up_republishes_kv_connector_handshake_metadata[all|kv_cache|weights]: republishafter the executor woke up, and only when the KV cache was restored.
pytest tests/v1/kv_connector/unit/{test_nixl_connector,test_config,test_mooncake_connector,test_multi_connector}.py tests/v1/engine/test_engine_core.py tests/v1/worker/test_sleep_mode_backend.py; the NIXL and config files 5 times;pre-commit on the changed files.
aarch64 + this branch as per-file mounts with sha256 gates (all 12 mounted files equal to this commit). NIXL 1.4.1
(bundled UCX). Qwen3-8B, prefix caching off,
--gpu-memory-utilization 0.3, level-1 sleep, UCX settings left tothe branch defaults. Toy proxy from
tests/v1/kv_connector/nixl_integration. Each check sends 30 prompts of1.1k-1.7k tokens (greedy, 32 tokens) and compares them with the awake control; transfers are counted from NIXL's KV
transfer metrics and the logs are scanned for transfer failures and UCX errors. The UCX protocol in use is taken
from
UCX_PROTO_INFO=y.(30 sequential requests of ~1.3k and ~5k tokens, twice; 128 requests at concurrency 32), with transfer time and
bytes from D's
vllm:nixl_xfer_time_secondsandvllm:nixl_bytes_transferred.Test Result
Cross-node 1P1D TP1 (final commit; UCX picked
rc_mlx5zero-copy GPU-to-GPU reads). "Asleep" and "woken" are MiB onthe slept GPU; awake is 85101 MiB.
release_kv_cache_memoryon both (pause wait, release, wake kv_cache, resume)weights-only step no longer republishes); the first request after a wake takes 0.21-0.24 s versus 0.18 s.
noise); every post-wake check is 30/30.
Single node, P on GPUs 0-1, D on GPUs 2-3, TP2 each (sleep mode with the branch defaults, so reads use RDMA loopback):
release_kv_cache_memoryon bothSingle-node transfer cost of the
UCX_TLS=^cuda_ipcdefault (medians of two launches each; "transfer" is NIXL'sper-transfer time, one transfer per TP rank and request):
UCX_TLS=^cuda_ipccuda_ipc for these transfers (cuda_ipc spent 5-29 ms per transfer in posting). This is UCX 1.4.1-bundled cuda_ipc
between two instances with disjoint
CUDA_VISIBLE_DEVICES; a tuned cuda_ipc setup, or a node whose NIC is slowerrelative to NVLink, may differ. Users who want cuda_ipc can set
UCX_TLSthemselves; then a peer's import keeps theslept KV cache resident (measured over MNNVL: 67763 MiB asleep, 150787 MiB after the wake;
UCX_CUDA_IPC_CACHE=nonthe reader frees it at +140 ms per request).
0/30, 0 errors, KV cache pinned asleep (67763 MiB) and doubled after the wake (150787 MiB).
probes, 23,792 transfers, 0 failed; Qwen3-8B 100/100 cycles, 0 failed.
meta-llama/Llama-3.2-1B-Instruct, andtest_engine_core_tp,_concurrent_batchesand_invalid_request_id_type); run alone on [KV Connector] Support sleep mode with MooncakeConnector over RDMA #59625's head, the same 4 fail. The NIXL and config files: 5 repeats,208 passed each time. Mutation check: 37 mutants, all killed; every
new test kills at least one. The reviewer's fix probes pass (28/28).
Known limitations:
smaps_rollupRss at Qwen3-8B TP1,num_threads=4: 1.25-1.34 MiB/cycle, linear over 500 cycles; Mooncake and sleepwithout a connector are flat). Each re-handshake creates and closes UCX RC endpoints, and UCX does not return their
send-queue buffers (about 640 KiB pinned per side per re-handshake, plus allocator fragmentation). The cost scales
with NIXL threads and TP ranks (
num_threads=1cuts it to 2/5). This revision keeps exactly one re-handshake perwake. Follow-up: keep the remote agent across a wake and reload only the peer's memory sections (NIXL partial
metadata without connection info), which needs NIXL to replace the old sections of a loaded agent; that is not
verified for NIXL 1.4.1. The UCX endpoint leak itself is to be reported upstream.
by default; a user who sets
UCX_TLSwith cuda_ipc keeps the peers' imports pinned, as measured above).Executor.sleepreports the engine awake, so nothing restores it. Partial-failure handling belongs to the executorand is shared with [KV Connector] Support sleep mode with MooncakeConnector over RDMA #59625's other connectors; it is not changed here. The registration epoch no longer diverges
across ranks in that case, because the scheduler stamps it.
enforce_handshake_compat: false, a version-13 decoder ignores the epoch and never refreshes after aversion-14 prefiller wakes; with the rcache-off default that shows up as failed transfers.
This PR contains AI-assisted code (Claude Code); commits carry a
Co-authored-bytrailer.🤖 Generated with Claude Code