[Enhancement] Engine runtime errors (#2426)

Signed-off-by: Daniel Huang <daniel1.huang@intel.com>
Signed-off-by: Daniel Huang <pilotflyer824@gmail.com>
Signed-off-by: bjf-frz <frz123db@gmail.com>
Co-authored-by: Chenguang Zheng <645327136@qq.com>
Co-authored-by: Zhou Taichang <tzhouam@connect.ust.hk>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
Co-authored-by: bjf-frz <frz123db@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-04-21 14:46:05 +08:00
1 parent c1ba86ab5b
commit 52b53367ef
23 files changed
+1597 -98

No files matched your search

@@ -1,14 +1,18 @@
from __future__ import annotations
import multiprocessing as mp
import multiprocessing.connection
import threading
import time
import weakref
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
import zmq
from vllm.distributed.device_communicators.shm_broadcast import MessageQueue
from vllm.logger import init_logger
from vllm.v1.engine.exceptions import EngineDeadError
from vllm_omni.diffusion.data import SHUTDOWN_MESSAGE, DiffusionOutput
from vllm_omni.diffusion.executor.abstract import DiffusionExecutor
@@ -22,6 +26,8 @@ if TYPE_CHECKING:
logger = init_logger(__name__)
_DEQUEUE_TIMEOUT_S = 5.0
@dataclass
class BackgroundResources:
@@ -67,6 +73,8 @@ class MultiprocDiffusionExecutor(DiffusionExecutor):
def _init_executor(self) -> None:
self._processes: list[mp.Process] = []
self._closed = False
self.is_failed = False
self._failure_callbacks: list[Callable[[], None]] = []
num_workers = self.od_config.num_gpus
self.wake_events = [mp.Event() for _ in range(num_workers)]
@@ -87,6 +95,8 @@ class MultiprocDiffusionExecutor(DiffusionExecutor):
)
self._finalizer = weakref.finalize(self, self.resources)
self.start_worker_monitor()
def _init_broadcast_queue(self, num_workers: int) -> MessageQueue:
return MessageQueue(
n_reader=num_workers,
@@ -106,6 +116,23 @@ class MultiprocDiffusionExecutor(DiffusionExecutor):
if self._result_mq is None:
raise RuntimeError("Result queue not initialized")
def _dequeue_one_with_failure_polling(self, deadline: float | None, method: str) -> Any:
"""Block until one result message, polling ``is_failed`` between chunk timeouts."""
while True:
if deadline is None:
chunk_timeout = _DEQUEUE_TIMEOUT_S
else:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError(f"RPC call to {method} timed out.")
chunk_timeout = min(_DEQUEUE_TIMEOUT_S, remaining)
try:
return self._result_mq.dequeue(timeout=chunk_timeout)
except (TimeoutError, zmq.error.Again):
if self.is_failed:
raise EngineDeadError()
continue
def _launch_workers(self, broadcast_handle, wake_events):
od_config = self.od_config
logger.info("Starting server...")
@@ -171,6 +198,49 @@ class MultiprocDiffusionExecutor(DiffusionExecutor):
return processes, result_handle
def start_worker_monitor(self) -> None:
# Monitors worker process liveness. If any die unexpectedly,
# logs an error, shuts down the executor and invokes the failure
# callback to inform the engine.
sentinels = [p.sentinel for p in self._processes]
if not sentinels:
return
def _monitor() -> None:
try:
finished = multiprocessing.connection.wait(sentinels)
except OSError:
return
if self._closed:
return
dead = [p.name for p in self._processes if p.sentinel in finished]
if dead:
logger.error(
"Diffusion worker(s) died unexpectedly: %s",
dead,
)
self.is_failed = True
self.shutdown()
for cb in self._failure_callbacks:
try:
cb()
except Exception:
logger.exception("failure_callback raised")
t = threading.Thread(target=_monitor, daemon=True, name="diffusion-worker-monitor")
t.start()
def register_failure_callback(
self,
callback: Callable[[], None],
) -> None:
"""Register a callback invoked when a worker process dies."""
self._failure_callbacks.append(callback)
def add_req(self, request: OmniDiffusionRequest) -> DiffusionOutput:
self._ensure_open()
rpc_request = {
@@ -293,27 +363,21 @@ class MultiprocDiffusionExecutor(DiffusionExecutor):
responses = []
for _ in range(num_responses):
dequeue_timeout = None if deadline is None else max(0, deadline - time.monotonic())
response = self._dequeue_one_with_failure_polling(deadline, method)
try:
response = self._result_mq.dequeue(timeout=dequeue_timeout)
unpack_diffusion_output_shm(response)
except Exception as e:
logger.warning("SHM unpack failed (data may already be inline): %s", e)
try:
unpack_diffusion_output_shm(response)
except Exception as e:
logger.warning("SHM unpack failed (data may already be inline): %s", e)
# Check if response indicates an error
if isinstance(response, dict) and response.get("status") == "error":
raise RuntimeError(
f"Worker failed with error '{response.get('error')}', "
"please check the stack trace above for the root cause"
)
# Check if response indicates an error
if isinstance(response, dict) and response.get("status") == "error":
raise RuntimeError(
f"Worker failed with error '{response.get('error')}', "
"please check the stack trace above for the root cause"
)
responses.append(response)
except zmq.error.Again as e:
raise TimeoutError(f"RPC call to {method} timed out.") from e
except TimeoutError as e:
raise TimeoutError(f"RPC call to {method} timed out.") from e
responses.append(response)
return responses[0] if unique_reply_rank is not None else responses
except Exception as e:
@@ -321,10 +385,13 @@ class MultiprocDiffusionExecutor(DiffusionExecutor):
raise
def check_health(self) -> None:
# Simple check if processes are alive
self._ensure_open()
if self.is_failed:
raise EngineDeadError()
for p in self._processes:
if not p.is_alive():
raise RuntimeError(f"Worker process {p.name} is dead")
self.is_failed = True
raise EngineDeadError(f"Worker process {p.name} is dead")
def shutdown(self) -> None:
self._closed = True