166 lines
4.5 KiB
Python
166 lines
4.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Standard
|
|
from typing import Literal, Optional
|
|
import os
|
|
import socket
|
|
|
|
# Third Party
|
|
import zmq
|
|
import zmq.asyncio
|
|
|
|
# First Party
|
|
from lmcache.logging import init_logger
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
ServiceKind = Literal["lookup", "offload", "lookup_worker", "lookup_scheduler"]
|
|
|
|
# Default timeout constants for socket operations (in milliseconds)
|
|
DEFAULT_SOCKET_RECV_TIMEOUT_MS = 30000
|
|
DEFAULT_SOCKET_SEND_TIMEOUT_MS = 10000
|
|
|
|
|
|
def get_zmq_context(use_asyncio: bool = True):
|
|
if use_asyncio:
|
|
return zmq.asyncio.Context.instance()
|
|
else:
|
|
return zmq.Context.instance()
|
|
|
|
|
|
def get_zmq_socket(
|
|
context, socket_path: str, protocol: str, role, bind_or_connect: str
|
|
):
|
|
"""
|
|
Create a ZeroMQ socket with the specified protocol and role.
|
|
"""
|
|
socket_addr = f"{protocol}://{socket_path}"
|
|
socket = context.socket(role)
|
|
if bind_or_connect == "bind":
|
|
socket.bind(socket_addr)
|
|
elif bind_or_connect == "connect":
|
|
socket.connect(socket_addr)
|
|
else:
|
|
raise ValueError(f"Invalid bind_or_connect: {bind_or_connect}")
|
|
|
|
return socket
|
|
|
|
|
|
def get_zmq_socket_with_timeout(
|
|
context,
|
|
socket_path: str,
|
|
protocol: str,
|
|
role,
|
|
bind_or_connect: str,
|
|
recv_timeout_ms: int,
|
|
send_timeout_ms: int,
|
|
) -> zmq.asyncio.Socket:
|
|
"""
|
|
Create a ZeroMQ socket with timeout settings.
|
|
"""
|
|
socket = get_zmq_socket(
|
|
context,
|
|
socket_path,
|
|
protocol,
|
|
role,
|
|
bind_or_connect,
|
|
)
|
|
# Only set RCVTIMEO for client role connect sockets
|
|
if bind_or_connect == "connect":
|
|
socket.setsockopt(zmq.RCVTIMEO, recv_timeout_ms)
|
|
socket.setsockopt(zmq.SNDTIMEO, send_timeout_ms)
|
|
return socket
|
|
|
|
|
|
def close_zmq_socket(socket: zmq.asyncio.Socket, linger: int = 0) -> None:
|
|
"""
|
|
Close a ZeroMQ socket cleanly.
|
|
|
|
:param socket: The zmq.Socket to be closed.
|
|
:param linger: LINGER period (in milliseconds).
|
|
Default is 0 (drop immediately).
|
|
"""
|
|
try:
|
|
socket.setsockopt(zmq.LINGER, linger) # type: ignore[attr-defined]
|
|
socket.close()
|
|
except Exception as e:
|
|
logger.error("Warning: Failed to close socket cleanly: %s", e)
|
|
|
|
|
|
def get_ip():
|
|
"""
|
|
Get the local IP address of the machine.
|
|
"""
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
try:
|
|
# "Connect" to a public IP — just to determine local IP
|
|
s.connect(("8.8.8.8", 80))
|
|
return s.getsockname()[0]
|
|
except Exception:
|
|
logger.warning(
|
|
"Failed to get local IP address. Falling back to loopback address."
|
|
)
|
|
return "127.0.0.1" # Fallback to loopback
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
def get_zmq_rpc_path_lmcache(
|
|
engine_id: str,
|
|
service_name: ServiceKind = "lookup",
|
|
rpc_port: int = 0,
|
|
rank: int = 0,
|
|
base_url: Optional[str] = None,
|
|
) -> str:
|
|
"""Get the ZMQ RPC path for LMCache lookup and offload communication.
|
|
|
|
Args:
|
|
engine_id: The engine ID for the RPC path.
|
|
service_name: The service name, one of 'lookup', 'offload',
|
|
'lookup_worker', 'lookup_scheduler'.
|
|
rpc_port: The RPC port number.
|
|
rank: The rank of the worker.
|
|
base_url: Optional base URL for the socket path. If not provided,
|
|
will try to use VLLM_RPC_BASE_PATH or fallback to /tmp/vllm_rpc.
|
|
|
|
Returns:
|
|
The ZMQ socket path string.
|
|
"""
|
|
if base_url is None:
|
|
# Try to import vllm.envs, fallback to default if not available
|
|
try:
|
|
# Third Party
|
|
import vllm.envs as envs
|
|
|
|
base_url = envs.VLLM_RPC_BASE_PATH
|
|
except (ImportError, ModuleNotFoundError):
|
|
# Fallback for testing environments without vllm
|
|
base_url = "/tmp/vllm_rpc"
|
|
logger.debug("vllm not available, using default base_url: %s", base_url)
|
|
# Ensure the directory exists for IPC socket
|
|
os.makedirs(base_url, exist_ok=True)
|
|
|
|
if service_name not in {"lookup", "offload", "lookup_worker", "lookup_scheduler"}:
|
|
raise ValueError(
|
|
f"service_name must be 'lookup' or 'offload', got {service_name!r}"
|
|
)
|
|
|
|
if isinstance(rpc_port, str):
|
|
rpc_port = rpc_port + str(rank)
|
|
else:
|
|
rpc_port += rank
|
|
|
|
logger.debug(
|
|
"Base URL: %s, Engine: %s, Service Name: %s, RPC Port: %s",
|
|
base_url,
|
|
engine_id,
|
|
service_name,
|
|
rpc_port,
|
|
)
|
|
|
|
socket_path = (
|
|
f"{base_url}/engine_{engine_id}_service_{service_name}_"
|
|
f"lmcache_rpc_port_{rpc_port}"
|
|
)
|
|
|
|
return socket_path
|