Add TP server GPU process regression test (#27721)

This commit is contained in:
Lianmin Zheng
2026-06-09 16:25:27 -07:00
committed by GitHub
parent 53a4b51f8c
commit ca716f4734
@@ -0,0 +1,137 @@
import shutil
import subprocess
import time
import unittest
import psutil
from sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import (
DEFAULT_SMALL_MODEL_NAME_FOR_TEST,
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
DEFAULT_URL_FOR_TEST,
CustomTestCase,
popen_launch_server,
)
register_cuda_ci(est_time=120, stage="base-b", runner_config="2-gpu-large")
class TestTPServerGPUProcesses(CustomTestCase):
tp_size = 2
@classmethod
def setUpClass(cls):
cls.model = DEFAULT_SMALL_MODEL_NAME_FOR_TEST
cls.base_url = DEFAULT_URL_FOR_TEST
cls.process = popen_launch_server(
cls.model,
cls.base_url,
timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
other_args=[
"--tp-size",
str(cls.tp_size),
"--mem-fraction-static",
"0.70",
"--disable-cuda-graph",
"--disable-piecewise-cuda-graph",
],
)
@classmethod
def tearDownClass(cls):
if hasattr(cls, "process") and cls.process:
kill_process_tree(cls.process.pid)
def test_tp_server_has_only_worker_gpu_processes(self):
if shutil.which("nvidia-smi") is None:
self.skipTest("nvidia-smi is required for GPU process assertions")
rows = self._wait_for_server_gpu_processes()
gpu_pids = {row["pid"] for row in rows}
self.assertNotIn(
self.process.pid,
gpu_pids,
f"server parent process unexpectedly holds a GPU context: "
f"{self._format_rows(rows)}",
)
self.assertEqual(
len(gpu_pids),
self.tp_size,
f"TP={self.tp_size} server should have exactly {self.tp_size} "
f"GPU worker processes, got {len(gpu_pids)}: {self._format_rows(rows)}",
)
def _wait_for_server_gpu_processes(self):
deadline = time.monotonic() + 60
stable_since = None
last_rows = []
while time.monotonic() < deadline:
tree_pids = self._server_process_tree_pids()
rows = [
row for row in self._query_gpu_processes() if row["pid"] in tree_pids
]
last_rows = rows
if len({row["pid"] for row in rows}) >= self.tp_size:
if stable_since is None:
stable_since = time.monotonic()
elif time.monotonic() - stable_since >= 3:
return rows
else:
stable_since = None
time.sleep(0.5)
self.fail(
f"Timed out waiting for TP={self.tp_size} GPU worker processes. "
f"Last observed rows: {self._format_rows(last_rows)}"
)
def _server_process_tree_pids(self):
pids = {self.process.pid}
try:
parent = psutil.Process(self.process.pid)
pids.update(child.pid for child in parent.children(recursive=True))
except psutil.NoSuchProcess:
pass
return pids
def _query_gpu_processes(self):
result = subprocess.run(
[
"nvidia-smi",
"--query-compute-apps=gpu_uuid,pid,process_name",
"--format=csv,noheader,nounits",
],
check=True,
capture_output=True,
text=True,
)
rows = []
for line in result.stdout.splitlines():
fields = [field.strip() for field in line.split(",", maxsplit=2)]
if len(fields) != 3:
continue
gpu_uuid, pid, process_name = fields
rows.append(
{
"gpu_uuid": gpu_uuid,
"pid": int(pid),
"process_name": process_name,
}
)
return rows
def _format_rows(self, rows):
if not rows:
return "[]"
return "[" + ", ".join(str(row) for row in rows) + "]"
if __name__ == "__main__":
unittest.main()