Skip to content

[Bugfix][DP] Synchronize the device on pause completion - #52914

Merged
njhill merged 5 commits into
vllm-project:mainfrom
aoshen02:fix/pause-device-barrier
Aug 26, 2026
Merged

njhill merged 5 commits into
vllm-project:mainfrom
aoshen02:fix/pause-device-barrier

Conversation

@aoshen02

@aoshen02 aoshen02 commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

Addresses the third problem in #51476: pause_generation() resolves before the device is idle — and in DP, the pause path is itself what makes the device busy. Companion PR #52957 (independent, no shared commits) removes the DP dummy-batch burst; this PR makes the pause completion contract correct regardless of how much work is in flight.

Purpose

1. Pause completion is a CPU-side predicate. The pause future resolves off has_work() and the DP consensus is a gloo CPU all-reduce; nothing on the pause path touches a CUDA stream. sleep() is safe only because GPUWorker.sleep synchronizes before unmapping — bare pause is sleep minus the synchronizing half. RL callers pause, run their own weight-sync kernels/NCCL, then sleep; that work races whatever the engine launched last. Reported from production, surviving today only on the incidental ~0.5s between pause and sleep.

2. The pause path launches unwaited GPU work. DPEngineCoreProc._pause_complete kick-starts engines_running = True so every rank reaches the consensus all-reduce in _has_global_unfinished_reqs — and the fixed every-32-step cadence then forces 32 dummy forwards per rank per pause call on an idle engine. The final dummy batch is launched in the consensus iteration itself, is not in batch_queue, and is invisible to has_work(); one iteration later the future resolves while it can still be executing. wait_for_requests_to_drain() cannot compensate: the coordinator only tracks request-driven waves, so during the burst it publishes "not running" and drain returns instantly.

Measured (DP=2 + EP, PowerMoE-3b, dummy batch inflated to ~110ms to model a large-MoE step): pause_generation() on an idle engine takes 3.40s and returns with the device still busy.

Change

A device barrier at pause completion. WorkerBase.synchronize_device() synchronizes through the device-generic torch.accelerator API whenever an accelerator is registered — the same barrier sleep() relies on — so CUDA, XPU and any other torch.accelerator backend get it without an override, and it stays a no-op only where there is genuinely no accelerator (CPU); backends outside torch.accelerator must override with their own wait. EngineCore._finish_pause(): collective_rpc("synchronize_device"), then cache reset — synchronizing first also stops clear_cache=True from resetting state the last launched batch may still be using. Called from all three completion paths (in-proc, fast path, deferred idle callback). collective_rpc waits for all workers, which also closes execute_dummy_batch waiting only for the output rank.

The barrier does not shrink the burst itself: an idle-engine DP pause still runs 32 dummy forwards, it just waits for them honestly. #52957 cuts the burst to a single step with a one-condition cadence change; the two PRs compose but neither depends on the other.

The same contract is applied to the mirror transition: GPUWorker.wake_up now ends with torch.accelerator.synchronize(). sleep synchronizes on both sides of the memory transition, wake_up on neither — its tail work (buffer restores after level-2 sleep, post_kv_cache_wake_up's re-init kernels) was unwaited, so a caller could start same-device work (e.g. RLHF weight sync right after wake) racing it. Covered by the existing sleep/wake e2e cases in the regression suite below.

Reproduction (scripts included)

Requires 2 GPUs and vllm/vllm-openai:nightly. Save the three files below into a directory $W, then:

# baseline: the bug
docker run --rm --gpus '"device=0,1"' --ipc=host --shm-size=16g \
  -v ~/.cache/huggingface:/root/.cache/huggingface -v $W:/patch \
  --entrypoint bash vllm/vllm-openai:nightly /patch/gpurepro.sh instr

# with this PR: check out the branch, then
git diff main...HEAD -- vllm/ > $W/fix.patch
docker run --rm --gpus '"device=0,1"' --ipc=host --shm-size=16g \
  -v ~/.cache/huggingface:/root/.cache/huggingface -v $W:/patch \
  --entrypoint bash vllm/vllm-openai:nightly /patch/gpurepro.sh fixed

(If NCCL fails at init on a CUDA-13.1 driver, add -e NCCL_NVLS_ENABLE=0.)

gpupatch.py — instrumentation only: inflates the dummy batch to ~110ms to model a large-MoE step, and adds a device_idle() probe RPC
#!/usr/bin/env python3
"""Usage: gpupatch.py <site-packages> — instruments a vLLM install."""
import sys

site = sys.argv[1]

def sub(path, old, new):
    with open(path) as f:
        src = f.read()
    if new in src:
        return
    assert old in src and src.count(old) == 1, f"pattern not found/unique in {path}"
    with open(path, "w") as f:
        f.write(src.replace(old, new))

OLD = """\
    def execute_dummy_batch(self) -> None:
        num_tokens = getattr(self.model_runner, "uniform_decode_query_len", 1)
        self.model_runner._dummy_run(num_tokens, uniform_decode=True)
"""
NEW = """\
    def execute_dummy_batch(self) -> None:
        num_tokens = getattr(self.model_runner, "uniform_decode_query_len", 1)
        self.model_runner._dummy_run(num_tokens, uniform_decode=True)
        import os as _os
        _cycles = int(_os.environ.get("REPRO_SLEEP_CYCLES", "0"))
        if _cycles:
            # Simulate a large-MoE forward: keep the stream busy after launch.
            torch.cuda._sleep(_cycles)

    def device_idle(self) -> bool:
        return bool(torch.cuda.current_stream().query())
"""
sub(f"{site}/vllm/v1/worker/gpu_worker.py", OLD, NEW)
print("instrumented")
repro_gpu.py — the scenario: pause / drain / probe
import asyncio
import time

from vllm import SamplingParams
from vllm.engine.arg_utils import AsyncEngineArgs
from vllm.v1.engine.async_llm import AsyncLLM


async def main():
    engine = AsyncLLM.from_engine_args(
        AsyncEngineArgs(
            model="ibm-research/PowerMoE-3b",
            data_parallel_size=2,
            enable_expert_parallel=True,
            gpu_memory_utilization=0.5,
            max_model_len=512,
            enforce_eager=True,
        )
    )

    async def gen(rid):
        async for _ in engine.generate(
            "The capital of France is", SamplingParams(max_tokens=8), rid
        ):
            pass

    async def probe(tag):
        idle = await engine.collective_rpc("device_idle")
        print(f"PROBE {tag}: device_idle={idle}", flush=True)
        return all(idle)

    await gen("warmup")
    await asyncio.sleep(6)
    await probe("settled")  # sanity: True when quiet

    # scenario 1: pause on a quiet engine
    t0 = time.perf_counter()
    await engine.pause_generation(mode="abort")
    t1 = time.perf_counter()
    await probe("after_pause1_returned")
    print(f"S1 pause took {t1 - t0:.2f}s on an idle engine", flush=True)
    await engine.wait_for_requests_to_drain(60)
    await probe("after_drain1_returned")

    # scenario 2: second pause+drain right before sleep (the production recipe)
    t0 = time.perf_counter()
    await engine.pause_generation(mode="abort")
    t1 = time.perf_counter()
    await probe("after_pause2_returned")
    await engine.wait_for_requests_to_drain(60)
    await probe("after_drain2_returned")
    print(f"S2 second pause took {t1 - t0:.2f}s", flush=True)

    # scenario 3: drain called while the pause burst is mid-flight
    await engine.resume_generation()
    await gen("rearm")
    await asyncio.sleep(6)
    task = asyncio.create_task(engine.pause_generation(mode="abort"))
    await asyncio.sleep(0.8)
    await engine.wait_for_requests_to_drain(60)
    idle = await probe("drain_during_pause_burst")
    print(f"S3 drain returned while pause_done={task.done()}, idle={idle}", flush=True)
    await task

    engine.shutdown()


asyncio.run(main())
gpurepro.sh — container entrypoint
#!/bin/bash
# usage: gpurepro.sh instr|fixed
set -x
SITE=/usr/local/lib/python3.12/dist-packages
if [ "$1" = "fixed" ]; then
  (cd "$SITE" && patch -p1 --forward) < /patch/fix.patch || exit 89
fi
python3 /patch/gpupatch.py "$SITE" || exit 90
export REPRO_SLEEP_CYCLES=200000000   # ~110ms on H200
export VLLM_LOGGING_LEVEL=WARNING
python3 /patch/repro_gpu.py
Output on `0.26.1rc1.dev942` (H200):
probe baseline this PR
device idle when engine settled True True
device idle when pause_generation() returns False True
pause_generation() wall time on an idle engine 3.40s 3.40s (burst kept; #52957 cuts it to 0.17s)
device idle when drain returns False True
second pause+drain before sleep 3.40s, busy 3.40s, idle
drain mid-pause "Engines are idle" while pause_done=False, device busy pause already complete, device idle

Test Plan

Three unit tests in tests/v1/engine/test_engine_core.py (no GPU): the pause-ordering contracts — fast path and deferred path run synchronize → cache reset → future resolution, and overlapping pause calls each complete behind their own barrier.

Two adversarial DP tests for edges the pause path crosses that had no coverage: mode="wait" draining through the DP consensus path, and pausing a sleeping engine — whose ranks skip dummy batches yet must still reach consensus, with the barrier running against unmapped memory.

Regression: tests/v1/distributed/test_async_llm_dp.py -k "dp_pause or dp_sleep or prefill_schedule" — includes the anti-#38009 staggered-race and barrier-deadlock guards and the #51481 late-request pair.

python -m pytest tests/v1/engine/test_engine_core.py -k "pause_" -v
python -m pytest tests/v1/distributed/test_async_llm_dp.py -k "dp_pause or dp_sleep or prefill_schedule" -v

Test Result

vllm/vllm-openai:nightly (0.26.1rc1.dev942), A/B on H200s, before = unpatched image:

suite before after
test_engine_core.py unit tests (new) 3 failed 3 passed
test_async_llm_dp.py (14 cases incl. the 2 new adversarial) 14 passed 14 passed
GPU probe repro table above table above
offline LLM sleep/wake smoke, MPClient and in-proc — both pass

Essential Elements

@aoshen02
aoshen02 requested a review from njhill as a code owner August 19, 2026 09:04

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

@mergify mergify Bot added the bug Something isn't working label Aug 19, 2026
@aoshen02 aoshen02 changed the title [Bugfix] Synchronize the device before completing a scheduler pause [Bugfix][DP] Synchronize the device on pause completion and adapt the finish-sync cadence Aug 19, 2026
@aoshen02
aoshen02 force-pushed the fix/pause-device-barrier branch 2 times, most recently from 1a21733 to 868e5a5 Compare August 19, 2026 14:45
@aoshen02 aoshen02 changed the title [Bugfix][DP] Synchronize the device on pause completion and adapt the finish-sync cadence [Bugfix][DP] Synchronize the device on pause completion Aug 19, 2026
@aoshen02 aoshen02 added the ready ONLY add when PR is ready to merge/full CI is needed label Aug 20, 2026
@aoshen02

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #84723 for commit 6526c9cb4c64.

@aoshen02
aoshen02 force-pushed the fix/pause-device-barrier branch 2 times, most recently from 75e326c to d2ae48c Compare August 20, 2026 02:18
@aoshen02

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #84728 for commit d2ae48cd181c.

@njhill njhill self-assigned this Aug 20, 2026
A completed pause promises an idle device, not just an idle scheduler:
the pause future resolves off CPU-side state while the last dummy batch
an idle DP rank launched is still executing, with nothing waiting on it.
Callers that run their own kernels between pause_generation() and
sleep() race that in-flight work. The default synchronize_device goes
through the device-generic torch.accelerator API so any registered
backend gets the barrier without an override.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Ao Shen <aoshen524@gmail.com>

Signed-off-by: aoshen02 <aoshen@inferact.ai>
@aoshen02
aoshen02 force-pushed the fix/pause-device-barrier branch from d2ae48c to 7abd953 Compare August 20, 2026 06:53
Same contract as the pause barrier: wake_up's tail work (buffer
restores, post_kv_cache_wake_up's async re-init kernels) was
unwaited, so callers could launch same-device work (e.g. RLHF
weight sync) racing it.

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: aoshen <aoshen524@gmail.com>

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

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #84838 for commit c4b35e3af75e.

@aoshen02

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #84995 for commit 56e8f6e78673.

@aoshen02

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #85010 for commit 0c1bf2c2d7a2.

@njhill njhill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @aoshen02

@njhill
njhill merged commit 17da485 into vllm-project:main Aug 26, 2026
103 checks passed
@aoshen02
aoshen02 deleted the fix/pause-device-barrier branch August 26, 2026 23:53
pawel-olejniczak added a commit to pawel-olejniczak/vllm-gaudi that referenced this pull request Aug 28, 2026
…de synchronize_device for HPU

Root cause: vllm#52914 added WorkerBase.synchronize_device() defaulting to
torch.accelerator.synchronize() plus an EngineCore._finish_pause() that
collective_rpc's it on every pause, and HPU rejects a device-wide wait.
Upstream: vllm-project/vllm#52914
Fix: HPUWorker.synchronize_device() waits via torch.hpu.synchronize().

Signed-off-by: Paweł Olejniczak <pawelx.olejniczak@intel.com>
iboiko-habana pushed a commit to vllm-project/vllm-gaudi that referenced this pull request Aug 28, 2026
…PU HunYuan, Import cli_args from (+3 more) (#1759)

This PR consolidates 5 hourly-CI fixes against
vllm@`17da48596c98946d3e3e6896e2ebd341e809f3bd` per the
single-rolling-PR rule (invariant I9).

## Bug 1: Drop HPU HunYuan V1 overrides deleted upstream

- **State machine id**: hunyuan_v1_model_module_removed_plugin_reg
- **Commit**: c352fb7

### Root cause
vllm#53615 (48d7132) migrated HunYuan V1/VL to the Transformers
modeling backend and deleted vllm/model_executor/models/hunyuan_v1.py,
so the eager import in vllm_gaudi/models/__init__.py aborted plugin
registration and killed 48 of 51 jobs in CI run 32990520142 before any
test could run.

### Culprit
Regression introduced by [PR
#53615](vllm-project/vllm#53615).

### Fix
remove vllm_gaudi/models/hunyuan_v1.py and its two register_model calls;
upstream's registry now serves both architectures via the Transformers
backend and the HPU subclass only specialized the now-deleted
HunYuanAttention.

## Bug 2: Import cli_args from vllm.entrypoints.launchers

- **State machine id**: entrypoints_openai_cli_args_module_moved
- **Commit**: 935d821

### Root cause
upstream moved vllm/entrypoints/openai/cli_args.py to
vllm/entrypoints/launchers/cli_args.py with no compatibility re-export,
so multi_model_api_server.py raised ModuleNotFoundError at import time
and took down run_unit_tests plus e2e run_online_model_swap_test.

### Culprit
Regression introduced by [PR
#53659](vllm-project/vllm#53659).

### Fix
import make_arg_parser and validate_parsed_serve_args from the new
vllm.entrypoints.launchers.cli_args path; both symbols are unchanged
there.

## Bug 3: Rename group_mm_kwargs_by_modality to
group_and_batch_mm_kwargs

- **State machine id**: group_mm_kwargs_by_modality_import_removed
- **Commit**: 0988519

### Root cause
vllm#53559 (4f686e1) deleted the deprecated
group_mm_kwargs_by_modality alias, so importing HPUModelRunner raises
ImportError.

### Culprit
Regression introduced by [PR
#53559](vllm-project/vllm#53559).

### Fix
import and call group_and_batch_mm_kwargs, the signature- and
yield-identical replacement upstream renamed it to in vllm#36158.

## Bug 4: Index flattened LoRA _embeddings_indices

- **State machine id**: hpu_lora_embeddings_indices_zero_dim_indexerror
- **Commit**: fb5e4b0

### Root cause
vLLM PR #53843 flattened PunicaWrapperBase._embeddings_indices from [2,
max_num_batched_tokens] to 1-D, so the HPU override's [1] select yielded
a 0-dim tensor and slicing it raised IndexError.

### Culprit
Regression introduced by [PR
#53843](vllm-project/vllm#53843).

### Fix
drop the stale row select and slice the 1-D indices directly, mirroring
upstream's own forward update.

## Bug 5: Override synchronize_device for HPU

- **State machine id**: synchronize_device_torch_accelerator_unsupported
- **Commit**: e01570f

### Root cause
vllm#52914 added WorkerBase.synchronize_device() defaulting to
torch.accelerator.synchronize() plus an EngineCore._finish_pause() that
collective_rpc's it on every pause, and HPU rejects a device-wide wait.

### Culprit
Regression introduced by [PR
#52914](vllm-project/vllm#52914).

### Fix
HPUWorker.synchronize_device() waits via torch.hpu.synchronize().

---------

Signed-off-by: Paweł Olejniczak <pawelx.olejniczak@intel.com>
am-cohere pushed a commit to am-cohere/vllm that referenced this pull request Sep 1, 2026
mikeshawcode pushed a commit to mikeshawcode/vllm that referenced this pull request Sep 1, 2026
…#52914)

Signed-off-by: aoshen02 <aoshen@inferact.ai>
Signed-off-by: mikeshawcode <michaelwshaw2@gmail.com>
mylibrar pushed a commit to tanyuqian/vllm that referenced this pull request Sep 3, 2026
yfw added a commit to NVIDIA-NeMo/RL that referenced this pull request Sep 11, 2026
…e extension

vLLM 0.29 adds `WorkerBase.synchronize_device` (vllm-project/vllm#52914,
used by the pause-completion path) and `WorkerBase.init_worker` asserts
that a worker extension never carries an attribute the Worker already
has. `VllmInternalWorkerExtension.synchronize_device`, which the
sparse-delta refit invokes through `collective_rpc` to drain peers before
cleaning up staged batch files, therefore killed every vLLM engine at
init on the first 0.29 nightly:

    AssertionError: Worker class <class 'vllm.v1.worker.gpu_worker.Worker'>
    already has an attribute synchronize_device, which conflicts with the
    worker extension class <class '...VllmInternalWorkerExtension'>.

Rename the extension method to `synchronize_sparse_refit_device` (the
applier's own `synchronize_device` is untouched; it is not on the worker
class) and update the RPC caller and its test. Add a `vllm`-marked unit
test that mirrors vLLM's collision predicate over both extension classes
so the next upstream name clash fails in CI instead of on a GPU job.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
@yfw yfw mentioned this pull request Sep 11, 2026
4 tasks done
yfw added a commit to NVIDIA-NeMo/RL that referenced this pull request Sep 14, 2026
…e extension

vLLM 0.29 adds `WorkerBase.synchronize_device` (vllm-project/vllm#52914,
used by the pause-completion path) and `WorkerBase.init_worker` asserts
that a worker extension never carries an attribute the Worker already
has. `VllmInternalWorkerExtension.synchronize_device`, which the
sparse-delta refit invokes through `collective_rpc` to drain peers before
cleaning up staged batch files, therefore killed every vLLM engine at
init on the first 0.29 nightly:

    AssertionError: Worker class <class 'vllm.v1.worker.gpu_worker.Worker'>
    already has an attribute synchronize_device, which conflicts with the
    worker extension class <class '...VllmInternalWorkerExtension'>.

Rename the extension method to `synchronize_sparse_refit_device` (the
applier's own `synchronize_device` is untouched; it is not on the worker
class) and update the RPC caller and its test. Add a `vllm`-marked unit
test that mirrors vLLM's collision predicate over both extension classes
so the next upstream name clash fails in CI instead of on a GPU job.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Yi-Fu Wu <yifu.wu@gmail.com>
wangxiyuan pushed a commit to vllm-project/vllm-ascend that referenced this pull request Oct 13, 2026
…and wakeup (#17766)

### What this PR does / why we need it?
Ports the Ascend backend requirement from vLLM
vllm-project/vllm#52914: a resolved pause
promises an idle device, and backends outside `torch.accelerator` must
provide their own wait.

For main branch, there is also a PR
#17666 working on this
issue.

Without this synchronize, in RL scenario, if we sleep exactly when
dummy_batch executes, will cause the following issues:
```
[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987] Traceback (most recent call last):
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm/vllm/v1/executor/multiproc_executor.py", line 982, in worker_busy_loop
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     output = func(*args, **kwargs)
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]              ^^^^^^^^^^^^^^^^^^^^^
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/worker/worker.py", line 228, in sleep
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     allocator.sleep(offload_tags=("weights",) if level == 1 else tuple())
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/device_allocator/camem.py", line 220, in sleep
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     torch.npu.empty_cache()
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/usr/local/lib/python3.12/site-packages/torch_npu/npu/memory.py", line 180, in empty_cache
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     torch_npu._C._npu_emptyCache()
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987] RuntimeError: npuSynchronizeDevice:../torch_npu/csrc/core/npu/NPUStream.cpp:714 NPU function error: AclrtSynchronizeDeviceWithTimeout, error code is 507015
```

### Does this PR introduce _any_ user-facing change?
No 

### How was this patch tested?
Tested on A3 platform, using following Qwen3.5-397B script:
```sh
# master node:
export VLLM_SERVER_DEV_MODE=1
vllm serve /data/LLM/mindSpeed_mm/mm_qwen3_5/grpo/ckpt_hf/Qwen3.5-397B-A17B --tensor-parallel-size 8 --data-parallel-size 4 --data-parallel-size-local 2 --data-parallel-start-rank 0 --data-parallel-address 80.5.21.123 --data-parallel-rpc-port 12345 --max-num-seqs 96 --max-model-len 71680 --max-num-batched-tokens 16384 --enable_expert_parallel --port 20002 --gpu-memory-utilization 0.9  --compilation_config '{"cudagraph_capture_sizes":[4,8,16,32,64,128,192,256,384],"cudagraph_mode": "FULL_DECODE_ONLY"}' --enable-sleep-mode --additional-config '{"weight_nz_mode": 0}'

# slave node:
export VLLM_SERVER_DEV_MODE=1
vllm serve /data/LLM/mindSpeed_mm/mm_qwen3_5/grpo/ckpt_hf/Qwen3.5-397B-A17B  --tensor-parallel-size 8 --data-parallel-size 4 --data-parallel-size-local 2 --data-parallel-start-rank 2 --headless --data-parallel-address 80.5.21.123 --data-parallel-rpc-port 12345 --max-num-seqs 96 --max-model-len 71680 --max-num-batched-tokens 16384 --enable_expert_parallel --port 20002 --gpu-memory-utilization 0.9 --compilation_config '{"cudagraph_capture_sizes":[4,8,16,32,64,128,192,256,384],"cudagraph_mode": "FULL_DECODE_ONLY"}' --enable-sleep-mode --additional-config '{"weight_nz_mode": 0}'
```
and curl this sleep command:
```sh
curl -X POST "http://127.0.0.1:20002/sleep?level=1"
```
#### Before this fix
The testing case will reply these following error messages without this
patch:
```
(ApiServer_1 pid=779773) INFO:     Started server process [779773]
(ApiServer_1 pid=779773) INFO:     Waiting for application startup.
(ApiServer_0 pid=779772) INFO:     Application startup complete.
(ApiServer_1 pid=779773) INFO:     Application startup complete.
(EngineCore_DP0 pid=779770) INFO 09-29 19:59:07 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(EngineCore_DP1 pid=779771) INFO 09-29 19:59:07 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(Worker_DP1_TP0_EP8 pid=780969) INFO 09-29 19:59:07 [acl_graph.py:243] Replaying aclgraph
(Worker_DP0_TP0_EP0 pid=780976) INFO 09-29 19:59:07 [acl_graph.py:243] Replaying aclgraph
(EngineCore_DP0 pid=779770) INFO 09-29 19:59:08 [block_pool.py:490] Successfully reset prefix cache
(EngineCore_DP1 pid=779771) INFO 09-29 19:59:08 [block_pool.py:490] Successfully reset prefix cache
(Worker_DP0_TP7_EP7 pid=780984) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP1_EP1 pid=780977) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP5_EP5 pid=780981) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP4_EP4 pid=780980) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP3_EP3 pid=780979) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP0_EP0 pid=780976) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP2_EP2 pid=780978) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP4_EP12 pid=780975) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP7_EP15 pid=780986) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP3_EP11 pid=780974) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP0_EP8 pid=780969) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP1_EP9 pid=780970) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP5_EP13 pid=780982) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP6_EP6 pid=780983) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP6_EP14 pid=780985) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP2_EP10 pid=780972) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] WorkerProc hit an exception.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] Traceback (most recent call last):
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm/vllm/v1/executor/multiproc_executor.py", line 982, in worker_busy_loop
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     output = func(*args, **kwargs)
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]              ^^^^^^^^^^^^^^^^^^^^^
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/worker/worker.py", line 229, in sleep
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     allocator.sleep(offload_tags=("weights",) if level == 1 else tuple())
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/device_allocator/camem.py", line 220, in sleep
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     torch.npu.empty_cache()
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/usr/local/lib/python3.12/site-packages/torch_npu/npu/memory.py", line 178, in empty_cache
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     torch_npu._C._npu_emptyCache()
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] RuntimeError: npuSynchronizeDevice:../torch_npu/csrc/core/npu/NPUStream.cpp:714 NPU function error: AclrtSynchronizeDeviceWithTimeout, error code is 507011
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] [ERROR] 2026-09-29-19:59:10 (PID:780977, Device:1, RankID:-1) ERR00100 PTA call acl api failed
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] [Error]: Model execution failed. 
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         Rectify the fault based on the error information in the ascend log.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] [PID: 780977] 2026-09-29-19:59:10.232.584 Invalid_Argument(EL0003): The argument is invalid.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         Solution: Try again with a valid argument.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] TraceBack (most recent call last):
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         An error occurs on the device(chipId:0, dieId:1), the serial number is 8, the error is aivec error, core id is 5, error code = 0x800000, dump info: pc start: 0x1242001f8074, current: 0x1242001f8294, vec error info: 0x601d9073df, mte error info: 0xe003000077, ifu error info: 0x22da8102081c0, ccu error info: 0xde118eb01d540ea3, cube error info: 0, biu error info: 0, aic error mask: 0x6500020bd00028c, para base: 0x12c103cf5000, aic cond: 0.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] The extend info: errcode:(0x800000, 0, 0) errorStr: MTE accesses an invalid GM address or the cross-device memory access times out. fixp_error0 info: 0x3000077, fixp_error1 info: 0xe0, fsmId:0, tslot:7, thread:0, ctxid:0, blk:0, sublk:0, subErrType:4.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] For details, see the troubleshooting document on the Ascend official website. Search for the keyword "AI Core Error".[FUNC:PrintCoreInfo][FILE:device_error_core_proc.cc][LINE:1227]
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         The argument is invalid.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         rtMemGetInfoByType execution failed, reason=driver error:invalid handle[FUNC:FuncErrorReason][FILE:error_message_manage.cc][LINE:72]
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         rtDeviceSynchronizeWithTimeout execution failed, reason=the model stream execute failed[FUNC:FuncErrorReason][FILE:error_message_manage.cc][LINE:72]
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] 

```

#### After this fix
After adding this patch, server can sleep correctly:
```sh
(ApiServer_1 pid=987951) INFO:     Started server process [987951]
(ApiServer_1 pid=987951) INFO:     Waiting for application startup.
(ApiServer_1 pid=987951) INFO:     Application startup complete.
(EngineCore_DP0 pid=987948) INFO 09-29 20:14:51 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(EngineCore_DP1 pid=987949) INFO 09-29 20:14:51 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(Worker_DP1_TP0_EP8 pid=989150) INFO 09-29 20:14:51 [acl_graph.py:243] Replaying aclgraph
(Worker_DP0_TP0_EP0 pid=989149) INFO 09-29 20:14:51 [acl_graph.py:243] Replaying aclgraph
(EngineCore_DP0 pid=987948) INFO 09-29 20:14:52 [block_pool.py:490] Successfully reset prefix cache
(EngineCore_DP1 pid=987949) INFO 09-29 20:14:52 [block_pool.py:490] Successfully reset prefix cache
(Worker_DP1_TP1_EP9 pid=989151) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP0_EP8 pid=989150) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP7_EP15 pid=989164) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP3_EP3 pid=989157) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP2_EP2 pid=989156) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP2_EP10 pid=989153) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP7_EP7 pid=989161) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP1_EP1 pid=989152) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP3_EP11 pid=989154) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP6_EP14 pid=989163) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP6_EP6 pid=989160) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP4_EP12 pid=989155) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP5_EP5 pid=989159) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP0_EP0 pid=989149) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP5_EP13 pid=989162) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP4_EP4 pid=989158) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP0_EP8 pid=989150) INFO 09-29 20:14:57 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP0_TP5_EP5 pid=989159) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.96 GiB memory is still in use.
(Worker_DP1_TP4_EP12 pid=989155) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP1_TP1_EP9 pid=989151) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.97 GiB memory is still in use.
(Worker_DP0_TP1_EP1 pid=989152) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.97 GiB memory is still in use.
(Worker_DP0_TP3_EP3 pid=989157) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.97 GiB memory is still in use.
(Worker_DP1_TP6_EP14 pid=989163) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.19 GiB memory is still in use.
(Worker_DP0_TP6_EP6 pid=989160) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP0_TP7_EP7 pid=989161) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.95 GiB memory is still in use.
(Worker_DP1_TP5_EP13 pid=989162) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.95 GiB memory is still in use.
(Worker_DP0_TP2_EP2 pid=989156) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP0_TP4_EP4 pid=989158) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.19 GiB memory is still in use.
(Worker_DP1_TP7_EP15 pid=989164) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.96 GiB memory is still in use.
(Worker_DP0_TP0_EP0 pid=989149) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(EngineCore_DP0 pid=987948) INFO 09-29 20:14:59 [abstract.py:327] It took 6.552437 seconds to fall asleep.
(Worker_DP1_TP2_EP10 pid=989153) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.19 GiB memory is still in use.
(Worker_DP1_TP3_EP11 pid=989154) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.96 GiB memory is still in use.
(EngineCore_DP1 pid=987949) INFO 09-29 20:14:59 [abstract.py:327] It took 7.087198 seconds to fall asleep.
(ApiServer_1 pid=987951) INFO:     127.0.0.1:47916 - "POST /sleep?level=1 HTTP/1.1" 200 OK

```
- vLLM main:
vllm-project/vllm@ee0da84

Signed-off-by: YuhanBai <yuhan.bai0830@gmail.com>
ZT-AIA pushed a commit to vllm-project/vllm-ascend that referenced this pull request Oct 15, 2026
### What this PR does / why we need it?

Ports the Ascend backend requirement from vLLM
[#52914](vllm-project/vllm#52914): a resolved
pause
promises an idle device, and backends outside `torch.accelerator` must
provide
their own wait.

Ascend provided none. `pause_generation()` could return while the last
dummy
batch a DP rank launched was still executing, and `wake_up()` left its
tail work
(the level-2 buffer restores and the sleep-mode cleanup) unwaited -- so
a caller
starting a weight sync right after wake-up raced it.

This adds `NPUWorker.synchronize_device()`, which blocks on
`torch.npu.synchronize()`, and ends `wake_up()` with it. The method
stays on the
worker rather than relying on inheritance because `wake_up` calls it
unconditionally, while vLLM releases predating #52914 define
no `WorkerBase.synchronize_device`.

Verified against the vLLM tree (`vllm/v1/worker/worker_base.py`): the
method is
**absent in v0.28.0** and **present from v0.29.0 onward**. v0.28.0 is in
the
support matrix, so on that line the override is the only barrier that
exists --
there is nothing to inherit, and `wake_up()` would otherwise raise
`AttributeError`.

`sleep()` gets the same treatment, bracketing the memory transition with
a
barrier on each side: one before the baseline `mem_get_info()` snapshot
that the
freed-bytes assertion depends on, and one after the optional RL extra
cleanup --
whose `destroy_hccl()`/aclgraph work is the last thing that can touch
memory
before the allocator unmaps it. Without them, `sleep()` racing an
in-flight
dummy batch fails inside `CaMemAllocator.sleep()`'s `empty_cache()`
(`AclrtSynchronizeDeviceWithTimeout`, `507011`/`507015`) -- the same
failure the
`releases/v0.30.0` fix in #18338 addresses.

### Does this PR introduce _any_ user-facing change?

No. Control-plane only; no effect on model output.

### How was this patch tested?

Unit tests (`tests/ut/worker/test_worker_v1.py`): every `wake_up` test
now patches
the barrier and asserts it is reached, `synchronize_device()` delegates
to
`torch.npu.synchronize()`, and the method is asserted to live on
`NPUWorker`
itself.

Unit tests (`tests/ut/worker/test_kv_cache_allocation_scope.py`) and
`test_sleep_uses_rl_extra_cleanup`: the sleep tests now patch the
barrier and
assert both sleep barriers fire, covering the cleanup-enabled and
cleanup-disabled paths. They also stop executing a real
`torch.npu.synchronize()`
on NPU hosts, which previously made these specs behave differently on
hardware
versus CPU runners.

NPU system tests:

-
`tests/e2e/pull_request/one_card/rlhf/state_transitions/test_npu_device_synchronize.py`
-- on real hardware, `synchronize_device()` and `wake_up()` must leave
the NPU
  stream idle.
-
`tests/e2e/pull_request/two_card/rlhf/state_transitions/test_pause_device_idle.py`
-- the Ascend port of upstream's
`test_dp_pause_completion_implies_device_idle`.
In DPLB the pause path itself launches the dummy batch whose completion
the pause
promise covers, so the test inflates the dummy batches the pause
consensus
manufactures and requires an idle device the moment `pause_generation()`
returns.
It skips (rather than passing vacuously) on vLLM without the #52914
barrier.
-
`tests/e2e/pull_request/one_card/rlhf/state_transitions/npu_stream_busy.py`
  -- the device-busy helper both files share.

`torch.cuda._sleep(200_000_000)`, which the upstream test uses to keep a
CUDA
stream busy, cannot run on NPU: `torch.npu` exposes no `_sleep`, and
`torch.cuda._sleep` raises `AttributeError: module 'torch._C' has no
attribute
'_cuda_sleep'`. `enqueue_npu_busy_work()` queues real matmuls without
waiting
instead, and `torch.npu.current_stream().query()` reports the same "is
the stream
quiet" fact as `torch.cuda.current_stream().query()`.

Re-verified on Ascend 910B3 (2 chips) against the vLLM main lane
(`0.30.0`, the pinned `.github/vllm-main-verified.commit`), i.e. after
the sleep
barriers were added -- 84 passed:

| suite | result |
|---|---|
| `tests/ut/worker/test_worker_v1.py` +
`tests/ut/worker/test_kv_cache_allocation_scope.py` | 81 passed |
|
`tests/e2e/pull_request/one_card/rlhf/state_transitions/test_npu_device_synchronize.py`
| 2 passed |
|
`tests/e2e/pull_request/two_card/rlhf/state_transitions/test_pause_device_idle.py`
| 1 passed (DP=2 engine, not skipped) |

The DP pause test uses `Qwen/Qwen2.5-0.5B-Instruct`, a small text-only
checkpoint
the a3 runners already pre-download. CI runs with
`VLLM_USE_MODELSCOPE=1` and
`HF_HUB_OFFLINE=1`, so an unlisted checkpoint cannot be fetched on the
runners.

An earlier A/B check of the pause barrier itself, run on the vLLM 0.29.0
line
before the sleep barriers were added, kept a DP=2 engine busy for ~200
ms and then
called `pause_generation()`:

| | fixed | barrier neutralised |
|---|---|---|
| `pause_generation()` wall time | 0.229 s | 0.036 s |
| device idle when it returned | True | **False** |

### Upstream context

Ports the Ascend backend requirement from vLLM
[#52914](vllm-project/vllm#52914).

- vLLM main:
vllm-project/vllm@8cc9aa5

Signed-off-by: SeaAsters <keine214@qq.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
zzzzwwjj pushed a commit to vllm-project/vllm-ascend that referenced this pull request Oct 15, 2026
…18338)

### What this PR does / why we need it?
Ports the Ascend backend requirement from vLLM
vllm-project/vllm#52914: a resolved pause
promises an idle device, and backends outside `torch.accelerator` must
provide their own wait.

For main branch, there is also a PR
#17666 working on this
issue.

Without this synchronize, in RL scenario, if we sleep exactly when
dummy_batch executes, will cause the following issues:
```
[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987] Traceback (most recent call last):
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm/vllm/v1/executor/multiproc_executor.py", line 982, in worker_busy_loop
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     output = func(*args, **kwargs)
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]              ^^^^^^^^^^^^^^^^^^^^^
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/worker/worker.py", line 228, in sleep
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     allocator.sleep(offload_tags=("weights",) if level == 1 else tuple())
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/device_allocator/camem.py", line 220, in sleep
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     torch.npu.empty_cache()
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]   File "/usr/local/lib/python3.12/site-packages/torch_npu/npu/memory.py", line 180, in empty_cache
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987]     torch_npu._C._npu_emptyCache()
^[[36m(vLLMHttpServer pid=3455188, ip=80.5.21.123)^[[0m (Worker_DP6_TP11_EP107 pid=3455539) ERROR 09-29 15:05:09 [multiproc_executor.py:987] RuntimeError: npuSynchronizeDevice:../torch_npu/csrc/core/npu/NPUStream.cpp:714 NPU function error: AclrtSynchronizeDeviceWithTimeout, error code is 507015
```

### Does this PR introduce _any_ user-facing change?
No 

### How was this patch tested?
Tested on A3 platform, using following Qwen3.5-397B script:
```sh
# master node:
export VLLM_SERVER_DEV_MODE=1
vllm serve /data/LLM/mindSpeed_mm/mm_qwen3_5/grpo/ckpt_hf/Qwen3.5-397B-A17B --tensor-parallel-size 8 --data-parallel-size 4 --data-parallel-size-local 2 --data-parallel-start-rank 0 --data-parallel-address 80.5.21.123 --data-parallel-rpc-port 12345 --max-num-seqs 96 --max-model-len 71680 --max-num-batched-tokens 16384 --enable_expert_parallel --port 20002 --gpu-memory-utilization 0.9  --compilation_config '{"cudagraph_capture_sizes":[4,8,16,32,64,128,192,256,384],"cudagraph_mode": "FULL_DECODE_ONLY"}' --enable-sleep-mode --additional-config '{"weight_nz_mode": 0}'

# slave node:
export VLLM_SERVER_DEV_MODE=1
vllm serve /data/LLM/mindSpeed_mm/mm_qwen3_5/grpo/ckpt_hf/Qwen3.5-397B-A17B  --tensor-parallel-size 8 --data-parallel-size 4 --data-parallel-size-local 2 --data-parallel-start-rank 2 --headless --data-parallel-address 80.5.21.123 --data-parallel-rpc-port 12345 --max-num-seqs 96 --max-model-len 71680 --max-num-batched-tokens 16384 --enable_expert_parallel --port 20002 --gpu-memory-utilization 0.9 --compilation_config '{"cudagraph_capture_sizes":[4,8,16,32,64,128,192,256,384],"cudagraph_mode": "FULL_DECODE_ONLY"}' --enable-sleep-mode --additional-config '{"weight_nz_mode": 0}'
```
and curl this sleep command:
```sh
curl -X POST "http://127.0.0.1:20002/sleep?level=1"
```
#### Before this fix
The testing case will reply these following error messages without this
patch:
```
(ApiServer_1 pid=779773) INFO:     Started server process [779773]
(ApiServer_1 pid=779773) INFO:     Waiting for application startup.
(ApiServer_0 pid=779772) INFO:     Application startup complete.
(ApiServer_1 pid=779773) INFO:     Application startup complete.
(EngineCore_DP0 pid=779770) INFO 09-29 19:59:07 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(EngineCore_DP1 pid=779771) INFO 09-29 19:59:07 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(Worker_DP1_TP0_EP8 pid=780969) INFO 09-29 19:59:07 [acl_graph.py:243] Replaying aclgraph
(Worker_DP0_TP0_EP0 pid=780976) INFO 09-29 19:59:07 [acl_graph.py:243] Replaying aclgraph
(EngineCore_DP0 pid=779770) INFO 09-29 19:59:08 [block_pool.py:490] Successfully reset prefix cache
(EngineCore_DP1 pid=779771) INFO 09-29 19:59:08 [block_pool.py:490] Successfully reset prefix cache
(Worker_DP0_TP7_EP7 pid=780984) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP1_EP1 pid=780977) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP5_EP5 pid=780981) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP4_EP4 pid=780980) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP3_EP3 pid=780979) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP0_EP0 pid=780976) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP2_EP2 pid=780978) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP4_EP12 pid=780975) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP7_EP15 pid=780986) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP3_EP11 pid=780974) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP0_EP8 pid=780969) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP1_EP9 pid=780970) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP5_EP13 pid=780982) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP6_EP6 pid=780983) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP6_EP14 pid=780985) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP2_EP10 pid=780972) INFO 09-29 19:59:08 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] WorkerProc hit an exception.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] Traceback (most recent call last):
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm/vllm/v1/executor/multiproc_executor.py", line 982, in worker_busy_loop
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     output = func(*args, **kwargs)
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]              ^^^^^^^^^^^^^^^^^^^^^
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/worker/worker.py", line 229, in sleep
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     allocator.sleep(offload_tags=("weights",) if level == 1 else tuple())
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/autotest/pytorch/torch_autotest/model/run/Qwen35_VL_397B_GRPO/acc_/vllm-ascend/vllm_ascend/device_allocator/camem.py", line 220, in sleep
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     torch.npu.empty_cache()
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]   File "/usr/local/lib/python3.12/site-packages/torch_npu/npu/memory.py", line 178, in empty_cache
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]     torch_npu._C._npu_emptyCache()
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] RuntimeError: npuSynchronizeDevice:../torch_npu/csrc/core/npu/NPUStream.cpp:714 NPU function error: AclrtSynchronizeDeviceWithTimeout, error code is 507011
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] [ERROR] 2026-09-29-19:59:10 (PID:780977, Device:1, RankID:-1) ERR00100 PTA call acl api failed
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] [Error]: Model execution failed. 
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         Rectify the fault based on the error information in the ascend log.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] [PID: 780977] 2026-09-29-19:59:10.232.584 Invalid_Argument(EL0003): The argument is invalid.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         Solution: Try again with a valid argument.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] TraceBack (most recent call last):
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         An error occurs on the device(chipId:0, dieId:1), the serial number is 8, the error is aivec error, core id is 5, error code = 0x800000, dump info: pc start: 0x1242001f8074, current: 0x1242001f8294, vec error info: 0x601d9073df, mte error info: 0xe003000077, ifu error info: 0x22da8102081c0, ccu error info: 0xde118eb01d540ea3, cube error info: 0, biu error info: 0, aic error mask: 0x6500020bd00028c, para base: 0x12c103cf5000, aic cond: 0.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] The extend info: errcode:(0x800000, 0, 0) errorStr: MTE accesses an invalid GM address or the cross-device memory access times out. fixp_error0 info: 0x3000077, fixp_error1 info: 0xe0, fsmId:0, tslot:7, thread:0, ctxid:0, blk:0, sublk:0, subErrType:4.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] For details, see the troubleshooting document on the Ascend official website. Search for the keyword "AI Core Error".[FUNC:PrintCoreInfo][FILE:device_error_core_proc.cc][LINE:1227]
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         The argument is invalid.
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         rtMemGetInfoByType execution failed, reason=driver error:invalid handle[FUNC:FuncErrorReason][FILE:error_message_manage.cc][LINE:72]
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987]         rtDeviceSynchronizeWithTimeout execution failed, reason=the model stream execute failed[FUNC:FuncErrorReason][FILE:error_message_manage.cc][LINE:72]
(Worker_DP0_TP1_EP1 pid=780977) ERROR 09-29 19:59:10 [multiproc_executor.py:987] 

```

#### After this fix
After adding this patch, server can sleep correctly:
```sh
(ApiServer_1 pid=987951) INFO:     Started server process [987951]
(ApiServer_1 pid=987951) INFO:     Waiting for application startup.
(ApiServer_1 pid=987951) INFO:     Application startup complete.
(EngineCore_DP0 pid=987948) INFO 09-29 20:14:51 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(EngineCore_DP1 pid=987949) INFO 09-29 20:14:51 [core.py:761] Waiting for in-flight requests to complete before sleeping...
(Worker_DP1_TP0_EP8 pid=989150) INFO 09-29 20:14:51 [acl_graph.py:243] Replaying aclgraph
(Worker_DP0_TP0_EP0 pid=989149) INFO 09-29 20:14:51 [acl_graph.py:243] Replaying aclgraph
(EngineCore_DP0 pid=987948) INFO 09-29 20:14:52 [block_pool.py:490] Successfully reset prefix cache
(EngineCore_DP1 pid=987949) INFO 09-29 20:14:52 [block_pool.py:490] Successfully reset prefix cache
(Worker_DP1_TP1_EP9 pid=989151) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP0_EP8 pid=989150) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP7_EP15 pid=989164) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP3_EP3 pid=989157) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP2_EP2 pid=989156) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP2_EP10 pid=989153) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP7_EP7 pid=989161) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP1_EP1 pid=989152) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP3_EP11 pid=989154) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP6_EP14 pid=989163) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP6_EP6 pid=989160) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP4_EP12 pid=989155) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP5_EP5 pid=989159) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP0_EP0 pid=989149) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP5_EP13 pid=989162) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP0_TP4_EP4 pid=989158) INFO 09-29 20:14:52 [camem.py:198] CaMem sleep: offloading 138/154 allocations (tags=('weights',))
(Worker_DP1_TP0_EP8 pid=989150) INFO 09-29 20:14:57 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP0_TP5_EP5 pid=989159) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.96 GiB memory is still in use.
(Worker_DP1_TP4_EP12 pid=989155) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP1_TP1_EP9 pid=989151) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.97 GiB memory is still in use.
(Worker_DP0_TP1_EP1 pid=989152) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.97 GiB memory is still in use.
(Worker_DP0_TP3_EP3 pid=989157) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.97 GiB memory is still in use.
(Worker_DP1_TP6_EP14 pid=989163) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.19 GiB memory is still in use.
(Worker_DP0_TP6_EP6 pid=989160) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP0_TP7_EP7 pid=989161) INFO 09-29 20:14:58 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.95 GiB memory is still in use.
(Worker_DP1_TP5_EP13 pid=989162) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.95 GiB memory is still in use.
(Worker_DP0_TP2_EP2 pid=989156) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(Worker_DP0_TP4_EP4 pid=989158) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.19 GiB memory is still in use.
(Worker_DP1_TP7_EP15 pid=989164) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.96 GiB memory is still in use.
(Worker_DP0_TP0_EP0 pid=989149) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.20 GiB memory is still in use.
(EngineCore_DP0 pid=987948) INFO 09-29 20:14:59 [abstract.py:327] It took 6.552437 seconds to fall asleep.
(Worker_DP1_TP2_EP10 pid=989153) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 4.19 GiB memory is still in use.
(Worker_DP1_TP3_EP11 pid=989154) INFO 09-29 20:14:59 [worker.py:235] Sleep mode (level=1) freed 51.30 GiB memory, 3.96 GiB memory is still in use.
(EngineCore_DP1 pid=987949) INFO 09-29 20:14:59 [abstract.py:327] It took 7.087198 seconds to fall asleep.
(ApiServer_1 pid=987951) INFO:     127.0.0.1:47916 - "POST /sleep?level=1 HTTP/1.1" 200 OK

```

- vLLM main:
vllm-project/vllm@ced6857

---------

Signed-off-by: YuhanBai <yuhan.bai0830@gmail.com>
tangdafu pushed a commit to tangdafu/vllm-ascend that referenced this pull request Oct 16, 2026
### What this PR does / why we need it?

Ports the Ascend backend requirement from vLLM
[#52914](vllm-project/vllm#52914): a resolved
pause
promises an idle device, and backends outside `torch.accelerator` must
provide
their own wait.

Ascend provided none. `pause_generation()` could return while the last
dummy
batch a DP rank launched was still executing, and `wake_up()` left its
tail work
(the level-2 buffer restores and the sleep-mode cleanup) unwaited -- so
a caller
starting a weight sync right after wake-up raced it.

This adds `NPUWorker.synchronize_device()`, which blocks on
`torch.npu.synchronize()`, and ends `wake_up()` with it. The method
stays on the
worker rather than relying on inheritance because `wake_up` calls it
unconditionally, while vLLM releases predating #52914 define
no `WorkerBase.synchronize_device`.

Verified against the vLLM tree (`vllm/v1/worker/worker_base.py`): the
method is
**absent in v0.28.0** and **present from v0.29.0 onward**. v0.28.0 is in
the
support matrix, so on that line the override is the only barrier that
exists --
there is nothing to inherit, and `wake_up()` would otherwise raise
`AttributeError`.

`sleep()` gets the same treatment, bracketing the memory transition with
a
barrier on each side: one before the baseline `mem_get_info()` snapshot
that the
freed-bytes assertion depends on, and one after the optional RL extra
cleanup --
whose `destroy_hccl()`/aclgraph work is the last thing that can touch
memory
before the allocator unmaps it. Without them, `sleep()` racing an
in-flight
dummy batch fails inside `CaMemAllocator.sleep()`'s `empty_cache()`
(`AclrtSynchronizeDeviceWithTimeout`, `507011`/`507015`) -- the same
failure the
`releases/v0.30.0` fix in vllm-project#18338 addresses.

### Does this PR introduce _any_ user-facing change?

No. Control-plane only; no effect on model output.

### How was this patch tested?

Unit tests (`tests/ut/worker/test_worker_v1.py`): every `wake_up` test
now patches
the barrier and asserts it is reached, `synchronize_device()` delegates
to
`torch.npu.synchronize()`, and the method is asserted to live on
`NPUWorker`
itself.

Unit tests (`tests/ut/worker/test_kv_cache_allocation_scope.py`) and
`test_sleep_uses_rl_extra_cleanup`: the sleep tests now patch the
barrier and
assert both sleep barriers fire, covering the cleanup-enabled and
cleanup-disabled paths. They also stop executing a real
`torch.npu.synchronize()`
on NPU hosts, which previously made these specs behave differently on
hardware
versus CPU runners.

NPU system tests:

-
`tests/e2e/pull_request/one_card/rlhf/state_transitions/test_npu_device_synchronize.py`
-- on real hardware, `synchronize_device()` and `wake_up()` must leave
the NPU
  stream idle.
-
`tests/e2e/pull_request/two_card/rlhf/state_transitions/test_pause_device_idle.py`
-- the Ascend port of upstream's
`test_dp_pause_completion_implies_device_idle`.
In DPLB the pause path itself launches the dummy batch whose completion
the pause
promise covers, so the test inflates the dummy batches the pause
consensus
manufactures and requires an idle device the moment `pause_generation()`
returns.
It skips (rather than passing vacuously) on vLLM without the #52914
barrier.
-
`tests/e2e/pull_request/one_card/rlhf/state_transitions/npu_stream_busy.py`
  -- the device-busy helper both files share.

`torch.cuda._sleep(200_000_000)`, which the upstream test uses to keep a
CUDA
stream busy, cannot run on NPU: `torch.npu` exposes no `_sleep`, and
`torch.cuda._sleep` raises `AttributeError: module 'torch._C' has no
attribute
'_cuda_sleep'`. `enqueue_npu_busy_work()` queues real matmuls without
waiting
instead, and `torch.npu.current_stream().query()` reports the same "is
the stream
quiet" fact as `torch.cuda.current_stream().query()`.

Re-verified on Ascend 910B3 (2 chips) against the vLLM main lane
(`0.30.0`, the pinned `.github/vllm-main-verified.commit`), i.e. after
the sleep
barriers were added -- 84 passed:

| suite | result |
|---|---|
| `tests/ut/worker/test_worker_v1.py` +
`tests/ut/worker/test_kv_cache_allocation_scope.py` | 81 passed |
|
`tests/e2e/pull_request/one_card/rlhf/state_transitions/test_npu_device_synchronize.py`
| 2 passed |
|
`tests/e2e/pull_request/two_card/rlhf/state_transitions/test_pause_device_idle.py`
| 1 passed (DP=2 engine, not skipped) |

The DP pause test uses `Qwen/Qwen2.5-0.5B-Instruct`, a small text-only
checkpoint
the a3 runners already pre-download. CI runs with
`VLLM_USE_MODELSCOPE=1` and
`HF_HUB_OFFLINE=1`, so an unlisted checkpoint cannot be fetched on the
runners.

An earlier A/B check of the pause barrier itself, run on the vLLM 0.29.0
line
before the sleep barriers were added, kept a DP=2 engine busy for ~200
ms and then
called `pause_generation()`:

| | fixed | barrier neutralised |
|---|---|---|
| `pause_generation()` wall time | 0.229 s | 0.036 s |
| device idle when it returned | True | **False** |

### Upstream context

Ports the Ascend backend requirement from vLLM
[#52914](vllm-project/vllm#52914).

- vLLM main:
vllm-project/vllm@8cc9aa5

Signed-off-by: SeaAsters <keine214@qq.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
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.

2 participants