Signed-off-by: Sy03 <1370724210@qq.com> Signed-off-by: lishunyang12 <lishunyang03@gmail.com> Signed-off-by: Yueqian Lin <linyueqian@outlook.com> Co-authored-by: lishunyang12 <lishunyang03@gmail.com> Co-authored-by: Yueqian Lin <linyueqian@outlook.com> Co-authored-by: StormMelody <22371184@zju.edu.cn> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
162 lines
5.3 KiB
Python
162 lines
5.3 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
from __future__ import annotations
|
|
|
|
import queue
|
|
import threading
|
|
from typing import TypeAlias
|
|
|
|
from vllm.logger import init_logger
|
|
|
|
from vllm_omni.engine.messages import (
|
|
EngineQueueMessage,
|
|
ErrorMessage,
|
|
)
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
RpcCorrelationKey: TypeAlias = tuple[str, str]
|
|
RpcWaiter: TypeAlias = queue.Queue[EngineQueueMessage]
|
|
|
|
|
|
class RpcResultRouter:
|
|
"""Single consumer that dispatches shared RPC results by correlation ID."""
|
|
|
|
def __init__(self, source_queue) -> None:
|
|
self._source_queue = source_queue
|
|
self._pending: dict[RpcCorrelationKey, RpcWaiter] = {}
|
|
self._lock = threading.Lock()
|
|
self._stopped = threading.Event()
|
|
self._terminal_error: ErrorMessage | None = None
|
|
self._thread = threading.Thread(
|
|
target=self._run,
|
|
daemon=True,
|
|
name="omni-rpc-result-router",
|
|
)
|
|
self._thread.start()
|
|
|
|
def register(self, key: RpcCorrelationKey) -> RpcWaiter:
|
|
waiter: RpcWaiter = queue.Queue(maxsize=1)
|
|
with self._lock:
|
|
if self._stopped.is_set():
|
|
raise RuntimeError("RPC result router is closed")
|
|
if self._terminal_error is not None:
|
|
raise RuntimeError(self._terminal_error.error)
|
|
if key in self._pending:
|
|
raise RuntimeError(f"duplicate pending RPC correlation key: {key!r}")
|
|
self._pending[key] = waiter
|
|
return waiter
|
|
|
|
def unregister(self, key: RpcCorrelationKey, waiter: RpcWaiter) -> None:
|
|
with self._lock:
|
|
if self._pending.get(key) is waiter:
|
|
self._pending.pop(key, None)
|
|
|
|
def close(self) -> None:
|
|
if self._stopped.is_set():
|
|
return
|
|
self._stopped.set()
|
|
self._broadcast_error(ErrorMessage(error="RPC result router closed", fatal=True))
|
|
self._thread.join(timeout=1.0)
|
|
|
|
def _run(self) -> None:
|
|
while not self._stopped.is_set():
|
|
try:
|
|
message = self._source_queue.get(timeout=0.1)
|
|
except queue.Empty:
|
|
continue
|
|
except Exception as exc:
|
|
if not self._stopped.is_set():
|
|
logger.exception("RPC result router source queue failed")
|
|
self._broadcast_error(ErrorMessage(error=str(exc), fatal=True))
|
|
return
|
|
|
|
if isinstance(message, ErrorMessage):
|
|
if message.fatal:
|
|
self._broadcast_error(message)
|
|
else:
|
|
logger.warning(
|
|
"Dropping uncorrelated non-fatal RPC error request_id=%s stage_id=%s: %s",
|
|
message.request_id,
|
|
message.stage_id,
|
|
message.error,
|
|
)
|
|
continue
|
|
key = self._correlation_key(message)
|
|
if key is None:
|
|
logger.warning(
|
|
"Dropping unexpected RPC result message type=%s",
|
|
getattr(message, "type", type(message).__name__),
|
|
)
|
|
continue
|
|
with self._lock:
|
|
waiter = self._pending.pop(key, None)
|
|
if waiter is None:
|
|
logger.warning("Dropping late or unknown RPC result correlation_key=%s", key)
|
|
continue
|
|
waiter.put_nowait(message)
|
|
|
|
def _broadcast_error(self, message: ErrorMessage) -> None:
|
|
with self._lock:
|
|
if message.fatal:
|
|
self._terminal_error = message
|
|
waiters = list(self._pending.values())
|
|
self._pending.clear()
|
|
for waiter in waiters:
|
|
try:
|
|
waiter.put_nowait(message)
|
|
except queue.Full:
|
|
pass
|
|
|
|
@staticmethod
|
|
def _correlation_key(message: EngineQueueMessage) -> RpcCorrelationKey | None:
|
|
key = getattr(message, "rpc_correlation_key", None)
|
|
if not isinstance(key, tuple) or len(key) != 2 or not all(isinstance(part, str) and part for part in key):
|
|
return None
|
|
return key
|
|
|
|
|
|
class CorrelatedRpcClient:
|
|
"""Own request submission and correlated result waiting as one lifecycle."""
|
|
|
|
def __init__(self, request_queue, result_queue) -> None:
|
|
self._request_queue = request_queue
|
|
self._router = RpcResultRouter(result_queue)
|
|
|
|
def execute(
|
|
self,
|
|
key: RpcCorrelationKey,
|
|
message: EngineQueueMessage,
|
|
*,
|
|
timeout: float | None,
|
|
timeout_message: str,
|
|
block_on_submit: bool = False,
|
|
) -> EngineQueueMessage:
|
|
waiter = self._router.register(key)
|
|
try:
|
|
if block_on_submit:
|
|
self._request_queue.put(message)
|
|
else:
|
|
self._request_queue.put_nowait(message)
|
|
try:
|
|
result = waiter.get(timeout=timeout)
|
|
except queue.Empty as exc:
|
|
raise TimeoutError(timeout_message) from exc
|
|
if isinstance(result, ErrorMessage):
|
|
raise RuntimeError(result.error)
|
|
return result
|
|
finally:
|
|
self._router.unregister(key, waiter)
|
|
|
|
def close(self) -> None:
|
|
self._router.close()
|
|
|
|
|
|
__all__ = [
|
|
"CorrelatedRpcClient",
|
|
"RpcCorrelationKey",
|
|
"RpcResultRouter",
|
|
"RpcWaiter",
|
|
]
|