Skip to content

[CI][RL] Add E2E test suite for sleep/wake/pause/weight-update lifecycle - #45586

Closed
aoshen02 wants to merge 25 commits into
vllm-project:mainfrom
aoshen02:ci/rl-sleep-wake-matrix
Closed

aoshen02 wants to merge 25 commits into
vllm-project:mainfrom
aoshen02:ci/rl-sleep-wake-matrix

Conversation

@aoshen02

Copy link
Copy Markdown
Contributor

Purpose

Implements the test matrix proposed in RFC #45585.

Adds tests/entrypoints/serve/dev/rlhf/ with two files that cover the full
RL lifecycle through the production HTTP endpoint surface (VLLM_SERVER_DEV_MODE=1).
All tests require --enable-sleep-mode — without it sleep/wake_up are no-ops
and the underlying bugs cannot be exercised.

Files

test_sleep_wake.py — 6 classes, 26 tests

Class What it tests
TestSleepWakeFlags is_sleeping flag + Prometheus vllm:engine_sleep_state metrics (supersedes test_sleep.py)
TestPhysicalMemory GPU free-bytes actually change per sleep level/tag; Prometheus metrics cross-validated
TestSchedulerGate Scheduler must not dispatch during partial or full wake; direct regression for #44483
TestOutputCorrectness Golden-output roundtrip: sleep(1)→wake, staged wake, 3-cycle stability, prefix-cache flush
TestPauseResume /pause /resume /is_paused — all three PauseMode values (abort/wait/keep)
TestErrorPaths Idempotency (double-sleep, wake-while-awake), abort+sleep interaction

test_weight_update.py — 3 classes, 7 tests

Class What it tests
TestWeightTransferProtocol /start_weight_update → /finish_weight_update framing, prefix-cache flush after update
TestWeightUpdateWithTensors Output changes after pushing real tensors (skipped unless VLLM_TEST_MODEL_ALT is set)
TestCompoundRLStep Full RL training-step: sleep → update → wake(weights) → wake(kv_cache) → rollout

Bugs directly addressed

Bug Test
#44483 partial-wake IMA TestSchedulerGate::test_partial_wake_blocks_until_kv_resident
#45326 request hang during sleep TestSchedulerGate::test_sleep_abort_mode_blocks_new_requests
#45518 double-sleep idempotency TestErrorPaths::test_double_sleep_idempotent

Test plan

VLLM_TEST_MODEL=/path/to/Llama-3.2-1B-Instruct \
  pytest tests/entrypoints/serve/dev/rlhf/ -v

Verified on H200, Qwen3-0.6B, --enforce-eager.

Notes

  • test_sleep.py (existing) is left untouched; TestSleepWakeFlags supersedes it
    and it can be removed in a follow-up once this suite is stable in CI.
  • TestWeightUpdateWithTensors is skipped by default (requires VLLM_TEST_MODEL_ALT);
    intended to run nightly or manually.
  • AI assistance was used to draft these tests; all assertions were verified by
    running against both patched and un-patched vLLM on H200.

xaguilar-amd and others added 5 commits June 11, 2026 16:24
…i-K2.5 (#42978)

Signed-off-by: Xavier Aguilar <xavier.aguilarfruto@amd.com>
Co-authored-by: Douglas Lehr <91553416+dllehr-amd@users.noreply.github.com>
Co-authored-by: Andreas Karatzas <akaratza@amd.com>
(cherry picked from commit baacbfc)
Signed-off-by: tjtanaa <tunjian.tan@embeddedllm.com>
(cherry picked from commit bfe1001)
…erts (#44679)

Signed-off-by: Rohan138 <rohanpotdar138@gmail.com>
Co-authored-by: Andreas Karatzas <akaratza@amd.com>
(cherry picked from commit 5b6b536)
Signed-off-by: Mohammad Miadh Angkad <176301910+mmangkad@users.noreply.github.com>
(cherry picked from commit 40e065e)
Signed-off-by: khluu <khluu000@gmail.com>
(cherry picked from commit e3e31e5)
aoshen02 added a commit to Meihan-chen/vllm that referenced this pull request Jun 14, 2026
…#45586

The behavioral sleep/wake tests from this file are fully superseded
by tests/entrypoints/serve/dev/rlhf/test_sleep_wake.py in PR vllm-project#45586,
which consolidates all RL CI into a single dedicated PR.

Signed-off-by: aoshen02 <aoshen@inferact.ai>
…strumentator crash on serve startup

A fresh pip install pulls fastapi 0.137, whose new _IncludedRouter route type
(a BaseRoute with no .path) breaks prometheus-fastapi-instrumentator's route
iteration -> 'vllm serve' dies at startup with:
  AttributeError: '_IncludedRouter' object has no attribute 'path'

requirements/common.txt pinned fastapi[standard]>=0.115.0 with no upper bound.
Cap to <0.137; lift once the instrumentator handles _IncludedRouter
(trallnag/prometheus-fastapi-instrumentator#370).

Signed-off-by: avesed <trevor.lijunlin@gmail.com>
@aoshen02
aoshen02 marked this pull request as draft June 15, 2026 23:31
@aoshen02
aoshen02 marked this pull request as ready for review June 20, 2026 14:13
aoshen02 and others added 6 commits June 21, 2026 07:42
Adds tests/entrypoints/serve/dev/rlhf/ with two test files covering the
full RL lifecycle via the VLLM_SERVER_DEV_MODE HTTP endpoint surface.

test_sleep_wake.py (6 classes, 26 tests)
  TestSleepWakeFlags     -- flag / Prometheus metrics smoke
  TestPhysicalMemory     -- GPU free-bytes quantification per sleep level/tag
  TestSchedulerGate      -- scheduler must not dispatch during partial/full wake
  TestOutputCorrectness  -- golden-output roundtrip, multi-cycle stability
  TestPauseResume        -- /pause /resume /is_paused (all 3 PauseMode values)
  TestErrorPaths         -- idempotency, wrong order, abort interaction

test_weight_update.py (3 classes, 7 tests)
  TestWeightTransferProtocol  -- /start /finish framing, prefix-cache flush
  TestWeightUpdateWithTensors -- output changes after real tensor push
  TestCompoundRLStep          -- full RL step: sleep -> update -> wake(weights)
                                -> wake(kv_cache) -> rollout (PR #44483 scenario)

Key properties:
- Tests drive the production HTTP surface (not internal Python API)
- --enable-sleep-mode required; without it sleep/wake are no-ops
- Self-contained: no tests.utils dependency
- VLLM_TEST_MODEL env var selects model

Directly addresses active bugs:
  #44483 -- partial wake IMA (TestSchedulerGate.test_partial_wake_blocks_*)
  #45326 -- request hang during sleep (test_sleep_abort_mode_blocks_*)
  #45518 -- double-sleep idempotency (TestErrorPaths.test_double_sleep_*)

RFC: #45585

Co-authored-by: Claude <noreply@anthropic.com>

Signed-off-by: aoshen02 <aoshen@inferact.ai>
TestSleepWakeFlags in test_sleep_wake.py covers all assertions from
test_sleep.py and more; keeping both would create duplicate coverage.

Signed-off-by: aoshen02 <aoshen@inferact.ai>
…rtions

0.55 was below the vLLM E2E test norm (0.75) and made the physical memory
assertions too weak (0.5 GiB threshold has too much noise from PyTorch cache
churn alone). With 0.75:
- KV cache is ~3x larger, making sleep/wake deltas unambiguous
- freed_gib threshold: 0.5 -> 1.5 GiB (1B bf16 weights ~2 GiB alone)
- recovered_gib threshold: 0.3 -> 1.0 GiB

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Add TestWeightReloadCodePaths to test_weight_update.py — exercises the
layerwise weight-reload path (initialize → finalize → process_weights_after_loading)
across model architecture × quantization combinations using vllm.LLM in-process.

Test matrix:
  - moe-bf16-tiny: TitanML/tiny-mixtral (MoE reload baseline)
  - moe-bf16-3b: ibm-research/PowerMoE-3b (real MoE kernel reconstruction)
  - moe-fp8: allenai/OLMoE-1B-7B-0924 with fp8 (#45989 crash path)
  - moe-mxfp8: allenai/OLMoE-1B-7B-0924 with mxfp8 (#44613 crash path, SM100+)

Each case triggers finalize_layerwise_reload which calls
process_weights_after_loading on all layers — the exact code path where
get_current_vllm_config() crashes during MoE kernel reconstruction if
the config context is missing.

Verified on GB200 (SM100): 4/4 passed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract duplicated _server(), _gen(), _ok(), sleep/wake/pause/resume helpers,
gpu_free_bytes(), sleep_metrics(), and weight-transfer helpers into a single
conftest.py shared by all RL lifecycle test modules.

Also adds:
- dummy_weights=True mode (--load-format dummy) for fast T0 state-machine tests
- poll_until() helper as a 200-lie workaround for tests that need to verify
  state after an operation where the HTTP 200 may precede completion
- gen_with_logprobs() for precision tests

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
17 HTTP-based tests across 6 classes covering the RL lifecycle
state-machine invariants. Uses --load-format dummy for fast startup
without downloading real weights.

Classes:
  TestSleepingTagsSemantics      - sleeping_tags partial/full wake semantics
  TestPauseStateOrthogonality    - pause and sleep are orthogonal dimensions
  TestWeightUpdateOrdering       - start/update/finish protocol ordering
  TestIdempotency                - double sleep/wake/pause engine survival
  TestInitialState               - server starts awake, unpaused, no active update
  TestConcurrentStateTransitions - concurrent sleep/wake doesn't corrupt state

All tests drive the full HTTP endpoint stack with VLLM_SERVER_DEV_MODE=1
and --load-format dummy for speed. References: sglang
test_pause_generation_tensor_consistency.py, AReaL
tests/test_staleness_manager.py, verl test_vllm_weight_update_utils_on_cpu.py.

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@aoshen02
aoshen02 force-pushed the ci/rl-sleep-wake-matrix branch from 64707bc to 6e662b4 Compare June 21, 2026 07:44
@mergify mergify Bot added ci/build rocm Related to AMD ROCm labels Jun 21, 2026
@mergify

mergify Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @aoshen02.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Jun 21, 2026
aoshen02 and others added 5 commits June 21, 2026 08:35
New test classes covering gaps identified in 12-framework survey:

TestConcurrentRace
  - test_concurrent_sleep_and_generate_no_deadlock
    10 generate threads racing against 1 sleep/wake thread must not
    deadlock. Ref: miles TestSessionConcurrencyContracts.

TestMemoryLeakCycle
  - test_no_host_rss_growth_over_10_cycles
    10 sleep/wake cycles; CPU RSS growth must be < 10% of baseline.
    Ref: ROLL test_vllm_mem_oom.py generate_memory().

TestSamplingNAbort
  - test_sampling_n4_abort_engine_survives
    sleep(abort) while sampling_n=4 is in-flight; engine must survive.
    Ref: ROLL _check_vllm_sampling_n.

TestLogprobsPrecision
  - test_logprobs_stable_after_sleep_wake
    logprobs before/after sleep/wake must match within 1e-3.
    Ref: ROLL tests/distributed/strategy/log_probs/.

TestSleepWakeLatency
  - test_sleep_wake_roundtrip_under_10s (single cycle < 10 s)
  - test_five_cycles_total_under_60s    (5 cycles < 60 s)
    Ref: sglang test_update_weights_from_distributed.py assert time < 3s.

TestCPUWeightBackup
  - test_weights_cpu_backup_5cycle_output_stable
    5× staged wake (weights-only then kv_cache); golden output stable.
    Ref: sglang test_release_and_resume_occupation_with_weights_cpu_backup.

All 7 pass in vllm/vllm-openai:v0.23.0 container.

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Codex review identified these real bugs and flakiness risks:

TestConcurrentRace:
  - Add is_alive() check after join(timeout=30) — a hanging thread would
    previously pass silently, hiding the deadlock the test targets.
  - Add `assert not errors` — exceptions in background threads were
    swallowed, allowing the test to pass vacuously.

TestMemoryLeakCycle:
  - RSS was measuring the pytest runner process, not the vLLM server
    subprocess. Switch to GPU free-bytes (via _gpu_free_bytes()) which
    directly reflects cumem-managed memory. Threshold: 50 MiB tolerance.

TestSamplingNAbort:
  - Assert `"err" not in result` so background thread exceptions are
    surfaced rather than silently ignored.

TestLogprobsPrecision:
  - Relax tolerance 1e-3 → 1e-2 (BF16 has ~3 significant decimal digits;
    1e-3 was tighter than the dtype can guarantee).
  - Assert `compared > 0` so an all-None response doesn't pass vacuously.

TestCPUWeightBackup:
  - Add --seed 42 to the server args for deterministic greedy decoding.
    Without a seed, temperature=0 is not bit-identical across wake cycles
    on all hardware; the text comparison would be a flakiness source.
  - Add assertion that each cycle's output is non-empty.

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…eview)

TestWeightUpdateProtocolErrors (3 tests):
  - test_start_without_init_returns_error_and_engine_survives
    In v0.23.0 /start_weight_update requires /init_weight_transfer_engine
    (NCCL setup) first; without it returns 500. Verifies informative error
    message and engine survival. Ref: AReaL TestConnect.
  - test_update_weights_without_start_returns_error
    /update_weights outside a start→finish session must error + engine survives.
    Ref: ROLL test_collective_rpc.py.
  - test_finish_without_start_returns_error_and_engine_survives
    Extends existing test_finish_without_start_handled with generate check.

TestCompoundRLStepExtended (2 tests):
  - test_five_colocate_rl_steps_output_stable
    5 staged sleep/wake cycles with --seed 42 golden output check.
    Tests the colocate RL pattern: sleep(1) → wake(weights) → wake(kv_cache).
  - test_sleep_staged_wake_latency
    Full staged wake cycle must complete in < 15 s.
    Ref: sglang test_update_weights_from_distributed.py latency gate.

Codex review fixes applied:
  - except (ValueError, KeyError) instead of bare except Exception
  - Added engine survival check to test_update_weights_without_start

All 5 tests pass in vllm/vllm-openai:v0.23.0 container.

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
4 tests across 3 classes; auto-skipped when < 2 GPUs.

TestTP2SleepWakeMemory:
  - test_tp2_sleep_frees_gpu_memory
    Verifies sleep(1) frees > 0.3 GiB on BOTH GPU-0 and GPU-1 (TP=2 shard),
    and wake restores > 0.2 GiB on both ranks. Ref: sglang
    test_multi_instance_release_memory_occupation.py.

TestTP2OutputCorrectness:
  - test_tp2_full_wake_restores_output
    sleep/wake roundtrip at TP=2 must not corrupt weight shards.
  - test_tp2_three_cycles_stable
    3 cycles stable; output matches golden on every cycle.

TestTP2StagedWake:
  - test_tp2_staged_wake_keeps_sleeping_between_tags
    wake(["weights"]) at TP=2 must leave is_sleeping=True until
    wake(["kv_cache"]) completes (#44483 regression at TP=2).

Codex review fixes applied:
  - Add _ok guard on golden capture in test_tp2_three_cycles_stable
  - Check both GPU-0 and GPU-1 memory in memory test

All 4 pass in vllm/vllm-openai:v0.23.0 container (2 GPU).

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The dev RLHF /sleep and /wake_up endpoints returned a bare Response(200)
with no body, inconsistent with the sibling /pause endpoint which returns
{"status": "paused"}. RL orchestrators (verl, SkyRL, trl) had no
machine-readable confirmation of which transition occurred, how long it
took, or — critically — whether a staged (partial-tag) wake left the engine
still sleeping.

Changes:
- /sleep   -> {"status": "sleeping", "level": int, "elapsed_ms": float}
- /wake_up -> {"status": "awake"|"sleeping", "tags_woken": list|None,
              "elapsed_ms": float}
  status="sleeping" after a partial wake (e.g. tags=["weights"]) because
  kv_cache is still unmapped; this lets orchestrators sequence staged wakes.
- Remove the stale FIXME comments. They referred to v0 frontend
  multiprocessing; in the v1 engine `await client.sleep()/wake_up()` resolves
  on the engine-core response, so the operation is already applied when the
  endpoint returns. elapsed_ms therefore measures the real operation time.

Backward compatible: status code stays 200 (callers that only check the
status code are unaffected); only a response body is added. This is the
serve/dev/ experimental namespace.

Tested in vllm/vllm-openai:v0.23.0 (tests/entrypoints/serve/dev/rlhf/
test_response_schema.py, 5 tests); 19 existing sleep/wake/state-machine
tests pass unchanged against the patched engine (no regression).

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aoshen02
aoshen02 requested a review from njhill as a code owner June 21, 2026 11:55
@mergify mergify Bot added the frontend label Jun 21, 2026
aoshen02 and others added 6 commits June 21, 2026 12:44
Add weight version tracking to the dev RLHF API router. This is a key
gap identified in the 12-framework survey (GAP-2 in RFC #45585):
downstream RL frameworks (AReaL, miles, slime, vime) need to know which
version of weights produced each rollout for off-policy detection.

Design (improvement over sglang's weight_version):

  weight_gen:   monotonic int, auto-incremented on /finish_weight_update.
                Cannot be forgotten, cannot go backwards. Enables staleness
                arithmetic: staleness = current_gen - sample_gen.

  weight_label: optional client-set string (e.g. "step-1500").
                Purely informational, decoupled from ordering. Set via
                /update_weight_label or via finish_weight_update body.

Both update AFTER the engine confirms finish_weight_update succeeded,
so there is no window where gen is bumped but weights are stale.

Endpoints:
  GET  /weight_info         → {"weight_gen": int, "weight_label": str}
  POST /update_weight_label → set label without bumping gen
  POST /finish_weight_update now returns weight_gen in response body

Implementation: _WeightVersionState on app.state (thread-safe, no engine
core plumbing needed). Initialized in attach_router().

8 tests pass in vllm/vllm-openai:v0.23.0 container.

RFC: vllm-project/vllm#45585

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… NCCL-free paths

test_sleep_wake.py:
- test_sleep_level1_frees_gpu_memory: fix re_allocated_gib direction
  (free_sleeping - free_awake2 > 0.4; after wake, free bytes decrease)
- test_pause_blocks_without_releasing_memory: fix is_sleeping assertion
  (v1 engine: is_sleeping = is_scheduler_paused OR executor.is_sleeping)

test_weight_update.py:
- test_start_finish_no_tensors_engine_survives: accept 200 or 500
  (start_weight_update needs init_weight_transfer_engine / NCCL)
- test_finish_without_start_handled: add 500 to allowed status codes
- TestCompoundRLStep: use sleep/wake only (remove start/finish that need NCCL)
- TestWeightReloadCodePaths: skip gracefully on read-only HF cache

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- _run_reload_test: use exc.errno == errno.EROFS instead of locale-sensitive
  string matching on OSError.strerror (Codex: str-match is locale-dependent)
- TestCompoundRLStep: update class docstring to reflect weight_update calls
  were removed; stale docstring listed the full RL lifecycle but tests only
  cover the staged-wake scheduler guard
- test_sleep_level1: consolidate 3 lines into 1 comment; trim redundant inline

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…us metrics + RL state machine

Phase 2.5 — Weight checksum (POST /weight_checker):
  actions: snapshot | compare | checksum | reset
  Worker computes SHA-256 per named parameter via collective_rpc.
  New files: rlhf/metrics.py, v1/worker/gpu_worker.compute_weight_checksums,
             v1/executor/abstract.compute_weight_checksums,
             v1/engine/core.compute_weight_checksums,
             v1/engine/core_client.compute_weight_checksums_async,
             v1/engine/async_llm.compute_weight_checksums

Phase 3a — Prometheus metrics (vllm:rl_weight_update_*):
  vllm:rl_weight_update_total        Counter  (increments on finish)
  vllm:rl_weight_update_duration_seconds Histogram (start→finish latency)
  vllm:rl_weight_gen                 Gauge    (mirrors weight_gen)
  vllm:rl_weight_update_active       Gauge    (1 during update, 0 at rest)
  New file: rlhf/metrics.py

Phase 3b — RL state machine enforcement (rlhf/rl_state_machine.py):
  Enforces ordering: start → update_weights → finish
  Double-start → 409 Conflict
  Finish-without-start → 409 Conflict
  update_weights-without-start → 409 Conflict
  New endpoint: GET /weight_update_active → {weight_update_active: bool}

New test files: test_weight_checker.py (13 tests)
                test_weight_update_metrics.py (7 tests)
                test_state_machine_enforcement.py (9 tests)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… at startup

test_weight_checker.py: sleep()/wake() from conftest return int (status_code),
not Response → use == 200 comparison, not .status_code

api_router.attach_router: initialize RL Prometheus metric label sets at startup
so vllm:rl_weight_* appear in /metrics before any weight update occurs
(prometheus_client labeled metrics only surface after first label-set creation)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…, compare

api_router.py:
- start/finish_weight_update: check SM BEFORE engine call (prevent stuck flag);
  mark SM AFTER engine succeeds; finish uses try/finally so active metric
  always clears even if bump raises
- update_weights: proper try/except around async on_update_weights()

rl_state_machine.py:
- Replace threading.Lock with asyncio.Lock (event-loop safe for async handlers)
- All transition methods are now async (await sm.on_*)
- Pre-check `is_updating` property for conflict detection before engine call
  (avoids double-acquire and allows early 409 before RPC cost)

gpu_worker.compute_weight_checksums:
- Include named_buffers() alongside named_parameters() (quantized/LoRA weights
  may be registered as buffers)
- Guard exotic dtypes to avoid view(torch.uint8) crash on bool tensors

_WeightCheckerState.compare:
- Surface tensors in snapshot but missing from current (partial transfer catch)

test_weight_update_metrics.py:
- Remove Counter from startup assertion: prometheus_client Counter labeled at
  value=0 may not appear in /metrics until first inc() — behavior is version-
  dependent. Document this in the test and check separately after updates.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@AlanFokCo

Copy link
Copy Markdown
Contributor

@aoshen02 Great test matrix — this kind of systematic lifecycle coverage is exactly what sleep mode needs.

A few edge cases from our multi-model hot-swap work that aren't in the current matrix:

  1. Discarded-then-slept double-unmap: if a tag's physical memory is released via unmap_and_release (e.g. stale KV cache freed to make room for a new model) and then a full sleep() is called, the current sleep() implementation iterates all pointer_to_data entries and calls unmap_and_release again on the already-released handles → CUDA_ERROR_INVALID_VALUE. We hit this in production when combining selective memory management with full sleep cycles.

  2. CUDA graph pool VMM slowdown: VMM operations (sleep/wake_up) take 3-5x longer when a CUDA graph capture pool is present on the device. Our measurements on 32B TP=8:

    • Without graph pool: ~2.0s
    • With graph pool: ~6-9s

    This isn't a bug per se, but it's a significant behavioral cliff that users will hit in production. Might be worth a test that asserts sleep/wake timing doesn't regress by more than Nx when enforce_eager=False.

  3. Cross-tag wake ordering: waking "weights" before "kv_cache" is safe, but waking "kv_cache" first can trigger flash_attn to access remapped-but-uninitialized weight memory if a forward pass is accidentally dispatched between the two wake calls. A test asserting that partial wake + forward → clean error (not segfault) would be valuable.

Happy to contribute these as additional test cases if they fit the scope.

@aoshen02

Copy link
Copy Markdown
Contributor Author

@aoshen02 Great test matrix — this kind of systematic lifecycle coverage is exactly what sleep mode needs.

A few edge cases from our multi-model hot-swap work that aren't in the current matrix:

  1. Discarded-then-slept double-unmap: if a tag's physical memory is released via unmap_and_release (e.g. stale KV cache freed to make room for a new model) and then a full sleep() is called, the current sleep() implementation iterates all pointer_to_data entries and calls unmap_and_release again on the already-released handles → CUDA_ERROR_INVALID_VALUE. We hit this in production when combining selective memory management with full sleep cycles.

  2. CUDA graph pool VMM slowdown: VMM operations (sleep/wake_up) take 3-5x longer when a CUDA graph capture pool is present on the device. Our measurements on 32B TP=8:

    • Without graph pool: ~2.0s
    • With graph pool: ~6-9s

    This isn't a bug per se, but it's a significant behavioral cliff that users will hit in production. Might be worth a test that asserts sleep/wake timing doesn't regress by more than Nx when enforce_eager=False.

  3. Cross-tag wake ordering: waking "weights" before "kv_cache" is safe, but waking "kv_cache" first can trigger flash_attn to access remapped-but-uninitialized weight memory if a forward pass is accidentally dispatched between the two wake calls. A test asserting that partial wake + forward → clean error (not segfault) would be valuable.

Happy to contribute these as additional test cases if they fit the scope.

I see, if needed please find me at slack via aoshen524@gmail.com or add my wechat a18102550020, thanks.

… wake ordering

Add test_cumem_physical.py covering allocator-level edge cases from
production multi-model hot-swap work:

Tier 2 (Physical — cumem state machine):
- Partial wake leaves KV unmapped (precondition for #44395)
- sleep() after partial wake skips already-unmapped allocations
  (prevents double unmap_and_release → CUDA_ERROR_INVALID_VALUE)
- Repeated wake of same tag is idempotent (no double create_and_map)
- sleep/wake correctness and timing regression with CUDA graph pool

Tier 3 (Protocol — cross-tag ordering):
- wake(weights) → generate: scheduler must block (KV still unmapped)
- wake(kv_cache) → generate: scheduler must block (weights unmapped)
- Concurrent wake + generate race does not crash engine

These tests exercise the CuMemAllocator VMM state transitions that are
not reachable through the HTTP flag-level tests in test_state_machine.py.

RFC: vllm-project/vllm#45585
Refs: #44395, #46438
)
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError):
pass # Both acceptable — blocked or crashed safely

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it seems like if r is normal, we need to fail the testcase, but it doesn't make it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the headsup!

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

Labels

ci/build cpu Related to CPU backends frontend needs-rebase rocm Related to AMD ROCm v1

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

9 participants