Files
auto-antislop/utils/vllm_manager.py
2025-10-17 03:44:24 +11:00

180 lines
7.4 KiB
Python

import os
import sys
import subprocess
import time
import requests
import logging
from pathlib import Path, PurePath
import tempfile, textwrap
from typing import Optional, List
logger = logging.getLogger(__name__)
def _show_tail(log_path: Path, *, n_lines: int = 300) -> None:
"""Dump the last *n_lines* of *log_path* to the logger."""
try:
if log_path.is_file():
tail = log_path.read_text(encoding="utf-8").splitlines()[-n_lines:]
logger.error(
"──── vLLM stdout/stderr (last %d lines) ────\n%s\n────────────────────────────────────────",
n_lines, "\n".join(tail),
)
except Exception as exc: # pragma: no cover
logger.error("Could not read vLLM log: %s", exc)
def is_vllm_server_alive(port: int, api_base_path: str = "/v1") -> bool:
"""Checks if the vLLM server is responsive."""
health_url = f"http://127.0.0.1:{port}/health" # Standard vLLM health endpoint
# Fallback for older vLLM or if /health is not available, try listing models
models_url = f"http://127.0.0.1:{port}{api_base_path.rstrip('/')}/models"
try:
response = requests.get(health_url, timeout=2)
if response.status_code == 200:
logger.debug(f"vLLM server on port {port} is healthy (via /health).")
return True
except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout):
logger.debug(f"vLLM /health endpoint on port {port} not responding. Trying /models.")
try:
response = requests.get(models_url, timeout=2)
# Expect 200 and a JSON response, typically with a 'data' list
if response.status_code == 200 and isinstance(response.json(), dict):
logger.debug(f"vLLM server on port {port} is alive (via /models).")
return True
except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout, requests.exceptions.JSONDecodeError):
logger.debug(f"vLLM /models endpoint on port {port} not responding or invalid response.")
return False
def start_vllm_server(
model_id: str,
port: int,
hf_token: Optional[str],
cuda_visible_devices: str,
gpu_memory_utilization: float,
max_model_len: int,
dtype: str,
vllm_extra_args: Optional[List[str]] = None,
extra_env: Optional[dict[str, str]] = None,
wait_timeout: int = 720,
uvicorn_log_level: str = "error",
quiet_stdout: bool = True,
log_to_file: bool | Path = True,
) -> Optional[subprocess.Popen]:
"""Starts the vLLM API server."""
if is_vllm_server_alive(port):
logger.info(f"vLLM server already running on port {port}.")
return None # Indicate it was already running
cmd = [
#sys.executable, "-m", "vllm.entrypoints.openai.api_server", # Corrected entrypoint
#"--model", model_id,
"vllm", "serve", model_id,
"--port", str(port),
"--host", "127.0.0.1",
"--gpu-memory-utilization", str(gpu_memory_utilization),
"--max-model-len", str(max_model_len),
"--dtype", dtype,
"--disable-log-requests", # Cleaner logs during generation
"--uvicorn-log-level", uvicorn_log_level.lower(),
]
if hf_token:
cmd.extend(["--hf-token", hf_token])
if vllm_extra_args:
cmd.extend(vllm_extra_args)
env = os.environ.copy()
env["CUDA_VISIBLE_DEVICES"] = cuda_visible_devices
env["HIP_VISIBLE_DEVICES"] = cuda_visible_devices
if extra_env:
# stringify to avoid type issues
env.update({k: str(v) for k, v in extra_env.items()})
logger.debug(f"vLLM extra env → {extra_env}")
logger.info("Starting vLLM server...")
logger.info(f"Command: {' '.join(cmd)}")
# ------------- stdout / stderr routing -----------------
if quiet_stdout:
if log_to_file is True:
tmp = Path(tempfile.gettempdir()) / f"vllm_{port}_{int(time.time())}.log"
elif log_to_file:
tmp = Path(PurePath(log_to_file)).expanduser().resolve()
tmp.parent.mkdir(parents=True, exist_ok=True)
else:
tmp = None # swallow completely
stdout_target = tmp.open("w") if tmp else subprocess.DEVNULL
stderr_target = stdout_target if tmp else subprocess.DEVNULL
if tmp:
logger.info("vLLM stdout/stderr → %s", tmp)
else:
stdout_target = None # inherit terminal
stderr_target = None
# --------------------------------------------------------
try:
server_proc = subprocess.Popen(
cmd,
env=env,
stdout=stdout_target,
stderr=stderr_target,
text=True,
)
except FileNotFoundError:
logger.error("vLLM not found. Is it installed (pip install vllm)?")
return None
except Exception as e:
logger.error("Failed to start vLLM: %s", e)
return None
logger.info(f"Waiting for vLLM server to become ready on port {port} (timeout: {wait_timeout}s)...")
start_time = time.time()
while time.time() - start_time < wait_timeout:
if server_proc.poll() is not None: # Process terminated
logger.error(f"vLLM server process terminated prematurely with code {server_proc.returncode}.")
if quiet_stdout and tmp:
_show_tail(tmp)
# Try to get some output if possible (might not work well without pipes)
# stdout, stderr = server_proc.communicate()
# if stdout: logger.error(f"vLLM stdout: {stdout.decode(errors='ignore')}")
# if stderr: logger.error(f"vLLM stderr: {stderr.decode(errors='ignore')}")
return None
if is_vllm_server_alive(port):
logger.info(f"🚀 vLLM server ready at http://127.0.0.1:{port}")
return server_proc
time.sleep(5) # Check every 5 seconds
logger.error(f"vLLM server failed to start or become healthy within {wait_timeout} seconds.")
if server_proc.poll() is None: # If still running, terminate it
logger.info("Terminating unresponsive vLLM server process...")
server_proc.terminate()
try:
server_proc.wait(timeout=10)
except subprocess.TimeoutExpired:
logger.warning("vLLM server did not terminate gracefully, killing.")
server_proc.kill()
return None
def stop_vllm_server(server_proc: Optional[subprocess.Popen]):
"""Stops the vLLM server process if it was started by this script."""
if server_proc and server_proc.poll() is None: # Check if process exists and is running
logger.info("Stopping managed vLLM server...")
server_proc.terminate()
try:
server_proc.wait(timeout=30) # Wait for graceful shutdown
logger.info("vLLM server stopped.")
except subprocess.TimeoutExpired:
logger.warning("vLLM server did not terminate gracefully after 30s, killing.")
server_proc.kill()
logger.info("vLLM server killed.")
except Exception as e:
logger.error(f"Error while stopping vLLM server: {e}")
elif server_proc and server_proc.poll() is not None:
logger.debug("Managed vLLM server was already stopped.")
else:
logger.debug("No managed vLLM server process to stop.")