main
py 142 lines 4.51 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import contextlib
4 import os
5 import signal
6 import subprocess
7 import time
8 from types import TracebackType
9
10 import requests
11 from typing_extensions import Self
12
13
14 class ServerProcess:
15 VLLM_RESET_CACHE_ENDPOINTS = [
16 "/reset_prefix_cache",
17 "/reset_mm_cache",
18 "/reset_encoder_cache",
19 ]
20
21 def __init__(
22 self,
23 server_cmd: list[str],
24 after_bench_cmd: list[str],
25 *,
26 show_stdout: bool,
27 ) -> None:
28 super().__init__()
29
30 self.server_cmd = server_cmd
31 self.after_bench_cmd = after_bench_cmd
32 self.show_stdout = show_stdout
33
34 def __enter__(self) -> Self:
35 self.start()
36 return self
37
38 def __exit__(
39 self,
40 exc_type: type[BaseException] | None,
41 exc_value: BaseException | None,
42 exc_traceback: TracebackType | None,
43 ) -> None:
44 self.stop()
45
46 def start(self):
47 # Create new process for clean termination
48 self._server_process = subprocess.Popen(
49 self.server_cmd,
50 start_new_session=True,
51 stdout=None if self.show_stdout else subprocess.DEVNULL,
52 # Need `VLLM_SERVER_DEV_MODE=1` for `_reset_caches`
53 env=os.environ | {"VLLM_SERVER_DEV_MODE": "1"},
54 )
55
56 def stop(self):
57 server_process = self._server_process
58
59 if server_process.poll() is None:
60 # In case only some processes have been terminated
61 with contextlib.suppress(ProcessLookupError):
62 # We need to kill both API Server and Engine processes
63 os.killpg(os.getpgid(server_process.pid), signal.SIGKILL)
64
65 def run_subcommand(self, cmd: list[str]):
66 return subprocess.run(
67 cmd,
68 stdout=None if self.show_stdout else subprocess.DEVNULL,
69 check=True,
70 )
71
72 def after_bench(self) -> None:
73 if not self.after_bench_cmd:
74 self.reset_caches()
75 return
76
77 self.run_subcommand(self.after_bench_cmd)
78
79 def _get_vllm_server_address(self) -> str:
80 server_cmd = self.server_cmd
81
82 for host_key in ("--host",):
83 if host_key in server_cmd:
84 host = server_cmd[server_cmd.index(host_key) + 1]
85 break
86 else:
87 host = "localhost"
88
89 for port_key in ("-p", "--port"):
90 if port_key in server_cmd:
91 port = int(server_cmd[server_cmd.index(port_key) + 1])
92 break
93 else:
94 port = 8000 # The default value in vllm serve
95
96 return f"http://{host}:{port}"
97
98 def is_server_ready(self) -> bool:
99 server_address = self._get_vllm_server_address()
100 try:
101 response = requests.get(f"{server_address}/health")
102 return response.status_code == 200
103 except requests.RequestException:
104 return False
105
106 def wait_until_ready(self, timeout: int) -> None:
107 start_time = time.monotonic()
108 while not self.is_server_ready():
109 # Check if server process has crashed
110 if self._server_process.poll() is not None:
111 returncode = self._server_process.returncode
112 raise RuntimeError(
113 f"Server process crashed with return code {returncode}"
114 )
115 if time.monotonic() - start_time > timeout:
116 raise TimeoutError(
117 f"Server failed to become ready within {timeout} seconds."
118 )
119 time.sleep(1)
120
121 def reset_caches(self) -> None:
122 server_cmd = self.server_cmd
123
124 # Use `.endswith()` to match `/bin/...`
125 if server_cmd[0].endswith("vllm"):
126 server_address = self._get_vllm_server_address()
127 print(f"Resetting caches at {server_address}")
128
129 for endpoint in self.VLLM_RESET_CACHE_ENDPOINTS:
130 res = requests.post(server_address + endpoint)
131 res.raise_for_status()
132 elif server_cmd[0].endswith("infinity_emb"):
133 if "--vector-disk-cache" in server_cmd:
134 raise NotImplementedError(
135 "Infinity server uses caching but does not expose a method "
136 "to reset the cache"
137 )
138 else:
139 raise NotImplementedError(
140 f"No implementation of `reset_caches` for `{server_cmd[0]}` server. "
141 "Please specify a custom command via `--after-bench-cmd`."
142 )