94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
638 lines
21 KiB
Python
638 lines
21 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import threading
|
|
import time
|
|
from urllib.parse import urlparse
|
|
|
|
from sglang.srt.utils import kill_process_tree
|
|
from sglang.test.ascend.e2e.test_npu_multi_node_utils import (
|
|
SERVICE_PORT,
|
|
check_role,
|
|
launch_pd_mix_node,
|
|
launch_pd_separation_node,
|
|
launch_router,
|
|
wait_server_ready,
|
|
)
|
|
from sglang.test.test_utils import (
|
|
DEFAULT_URL_FOR_TEST,
|
|
CustomTestCase,
|
|
dump_metric,
|
|
popen_launch_server,
|
|
)
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
handlers=[logging.StreamHandler()],
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
EVALSCOPE = "evalscope"
|
|
BENCHMARK_TOOL_DEFAULT = EVALSCOPE
|
|
|
|
PYTHON_FOR_TEST_TOOL = "test_env_transformers_tool/bin/python"
|
|
if not os.path.exists(PYTHON_FOR_TEST_TOOL) or not os.access(
|
|
PYTHON_FOR_TEST_TOOL, os.X_OK
|
|
):
|
|
PYTHON_FOR_TEST_TOOL = "python3"
|
|
logger.info(f"PYTHON_FOR_TEST_TOOL: {PYTHON_FOR_TEST_TOOL}")
|
|
|
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH = 3600
|
|
MAX_SERVER_KEEP_ALIVE_TIME = 3600
|
|
|
|
ACCURACY_TOLERANCE = 0.99
|
|
|
|
# Dataset total question counts and allowed fluctuation (in questions)
|
|
DATASET_QUESTION_COUNTS = {
|
|
"aime25": 30,
|
|
"aime26": 30,
|
|
"gpqa_diamond": 198,
|
|
}
|
|
|
|
DATASET_FLUCTUATION = {
|
|
"aime25": 2,
|
|
"aime26": 2,
|
|
"gpqa_diamond": 5,
|
|
}
|
|
|
|
MAX_RETRY_COUNT = 3
|
|
|
|
SERVER_INITIALIZATION_DELAY = 120
|
|
|
|
if os.environ.get("ASCEND_RT_VISIBLE_DEVICES"):
|
|
DEFAULT_SERVER_PORT_FOR_TEST = (
|
|
20000 + int(os.environ.get("ASCEND_RT_VISIBLE_DEVICES", "0")[0]) * 100
|
|
)
|
|
else:
|
|
DEFAULT_SERVER_PORT_FOR_TEST = (
|
|
20000 + int(os.environ.get("ASCEND_VISIBLE_DEVICES", "0")[0]) * 100
|
|
)
|
|
DEFAULT_URL_FOR_TEST = f"http://127.0.0.1:{DEFAULT_SERVER_PORT_FOR_TEST + 66}"
|
|
|
|
|
|
def get_accuracy_threshold(datasets, baseline_accuracy):
|
|
"""Calculate accuracy threshold based on dataset fluctuation tolerance.
|
|
|
|
For datasets with defined fluctuation (aime*, gpqa_diamond), use absolute
|
|
question count tolerance. For others (e.g. mmmu), use percentage tolerance.
|
|
"""
|
|
dataset = datasets[0] if datasets else None
|
|
if dataset in DATASET_FLUCTUATION and dataset in DATASET_QUESTION_COUNTS:
|
|
fluctuation = DATASET_FLUCTUATION[dataset] / DATASET_QUESTION_COUNTS[dataset]
|
|
return baseline_accuracy - fluctuation
|
|
return baseline_accuracy * ACCURACY_TOLERANCE
|
|
|
|
|
|
def get_max_retries(datasets):
|
|
"""Return max retry count for accuracy tests.
|
|
|
|
gpqa and aime datasets support up to MAX_RETRY_COUNT retries.
|
|
mmmu and others use 1 attempt (no retry).
|
|
"""
|
|
dataset = datasets[0] if datasets else None
|
|
if dataset in DATASET_FLUCTUATION:
|
|
return MAX_RETRY_COUNT
|
|
return 1
|
|
|
|
|
|
def run_evalscope(
|
|
host,
|
|
port,
|
|
model,
|
|
datasets,
|
|
dataset_args=None,
|
|
eval_batch_size=16,
|
|
limit=100000,
|
|
generation_config=None,
|
|
dataset_dir=None,
|
|
timeout=60000,
|
|
stream=True,
|
|
eval_type="openai_api",
|
|
):
|
|
|
|
metrics_path = os.getenv("METRICS_DATA_FILE")
|
|
result_path = "./evalscope_result" if not metrics_path else metrics_path
|
|
logger.info(f"The metrics result file: {result_path}")
|
|
|
|
api_url = f"http://{host}:{port}/v1/chat/completions"
|
|
|
|
if generation_config is None:
|
|
generation_config = {"max_tokens": 512}
|
|
|
|
config_dict = {
|
|
"model": model,
|
|
"api_url": api_url,
|
|
"eval_type": eval_type,
|
|
"datasets": datasets,
|
|
"eval_batch_size": eval_batch_size,
|
|
"generation_config": generation_config,
|
|
"timeout": timeout,
|
|
"stream": stream,
|
|
"limit": limit,
|
|
"work_dir": result_path,
|
|
}
|
|
if dataset_args:
|
|
config_dict["dataset_args"] = dataset_args
|
|
if dataset_dir:
|
|
config_dict["dataset_dir"] = dataset_dir
|
|
|
|
config_json = json.dumps(config_dict, ensure_ascii=False, indent=2)
|
|
config_json_escaped = config_json.replace("\\", "\\\\").replace("'''", "\\'\\'\\'")
|
|
|
|
script_content = "import json\n"
|
|
script_content += "from evalscope import TaskConfig, run_task\n\n"
|
|
script_content += f"config = json.loads('''{config_json_escaped}''')\n"
|
|
script_content += "task_cfg = TaskConfig(**config)\n"
|
|
script_content += "run_task(task_cfg=task_cfg)\n"
|
|
|
|
script_path = f"/tmp/evalscope_run_{model}_{'_'.join(datasets)}.py"
|
|
with open(script_path, "w") as f:
|
|
f.write(script_content)
|
|
|
|
logger.info(f"Generated evalscope script: {script_path}")
|
|
|
|
install_cmd = (
|
|
"/bin/bash /root/sglang/python/sglang/test/ascend/e2e/run_evalscope.sh"
|
|
)
|
|
subprocess.run(install_cmd, shell=True, check=True)
|
|
|
|
python_bin = "test_env_evalscope/bin/python"
|
|
cmd = f"{python_bin} {script_path}"
|
|
|
|
logger.info(f"Command: {cmd}")
|
|
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
shell=True,
|
|
)
|
|
|
|
output_lines = []
|
|
try:
|
|
for line in iter(process.stdout.readline, ""):
|
|
if line.strip():
|
|
print(line, end="")
|
|
output_lines.append(line.strip())
|
|
|
|
process.wait()
|
|
|
|
if process.returncode != 0:
|
|
logger.error(f"Command failed with return code: {process.returncode}")
|
|
raise subprocess.CalledProcessError(process.returncode, cmd)
|
|
|
|
logger.info("Command executed successfully")
|
|
|
|
metrics = {}
|
|
full_output = "\n".join(output_lines)
|
|
|
|
report_match = re.search(r"Dump report to:\s*(\S+)", full_output)
|
|
if report_match:
|
|
report_path = report_match.group(1)
|
|
logger.info(f"Found evalscope report file: {report_path}")
|
|
try:
|
|
with open(report_path, "r") as rf:
|
|
report_data = json.load(rf)
|
|
for item in report_data:
|
|
score = item.get("score")
|
|
if score is not None:
|
|
metrics["accuracy"] = float(score)
|
|
logger.info(f"The Final Accuracy from report: {score}")
|
|
break
|
|
except Exception as e:
|
|
logger.warning(f"Failed to read report file {report_path}: {e}")
|
|
|
|
if "accuracy" not in metrics:
|
|
accuracy_patterns = [
|
|
r"mean_acc\s*.*?│\s*\d+\s*│\s*([\d.]+)\s*│",
|
|
r"│\s+([\d.]+)\s+│\s+\S+\s+│\s*$",
|
|
r"accuracy\s*[:=]?\s*([\d.]+)",
|
|
r"Accuracy\s*[:=]?\s*([\d.]+)",
|
|
r"score\s*[:=]?\s*([\d.]+)",
|
|
]
|
|
|
|
for pattern in accuracy_patterns:
|
|
matches = re.findall(pattern, full_output)
|
|
if matches:
|
|
final_accuracy = float(matches[-1])
|
|
metrics["accuracy"] = final_accuracy
|
|
logger.info(f"The Final Accuracy from output: {final_accuracy}")
|
|
break
|
|
|
|
if "accuracy" not in metrics:
|
|
logger.info("Can Not Find The Accuracy in evalscope output")
|
|
|
|
return metrics
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("Keyboard interrupt received, terminating process...")
|
|
process.terminate()
|
|
try:
|
|
process.wait(timeout=5)
|
|
logger.info("Process terminated")
|
|
except subprocess.TimeoutExpired:
|
|
logger.warning("Process did not terminate gracefully, killing it...")
|
|
process.kill()
|
|
logger.info("Process killed")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error executing command: {e}")
|
|
process.terminate()
|
|
process.wait(timeout=5)
|
|
raise
|
|
|
|
|
|
def assert_metrics(self, metrics):
|
|
if not metrics:
|
|
raise Exception("No metrics obtained from benchmark")
|
|
|
|
if self.accuracy is not None:
|
|
threshold = get_accuracy_threshold(self.datasets, self.accuracy)
|
|
dump_metric(
|
|
"accuracy",
|
|
float(metrics["accuracy"]),
|
|
labels={"test_case": self.__class__.__name__, "type": "accuracy"},
|
|
)
|
|
dump_metric(
|
|
"accuracy_baseline",
|
|
float(self.accuracy),
|
|
labels={"test_case": self.__class__.__name__, "type": "accuracy"},
|
|
)
|
|
self.assertGreaterEqual(
|
|
float(metrics["accuracy"]),
|
|
threshold,
|
|
f"Accuracy check failed. Expected >= {threshold}, Got: {metrics['accuracy']}",
|
|
)
|
|
|
|
|
|
class TestNpuAccuracyTestCaseBase(CustomTestCase):
|
|
model = None
|
|
benchmark_tool = BENCHMARK_TOOL_DEFAULT
|
|
backend = "sglang"
|
|
datasets = ["gsm8k"]
|
|
dataset_args = None
|
|
eval_batch_size = 16
|
|
limit = 100000
|
|
generation_config = None
|
|
dataset_dir = None
|
|
stream = True
|
|
timeout = 60000
|
|
eval_type = "openai_api"
|
|
other_args = None
|
|
server_timeout = DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH
|
|
envs = None
|
|
max_attempts = 2
|
|
n_runs = 3
|
|
accuracy = 0.1
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
|
env = os.environ.copy()
|
|
for key, value in env.items():
|
|
logger.info(f"ENV_VAR_SYS {key}:{value}")
|
|
if cls.envs:
|
|
for key, value in cls.envs.items():
|
|
logger.info(f"ENV_VAR_CASE {key}:{value}")
|
|
env[key] = value
|
|
|
|
other_args = list(cls.other_args)
|
|
|
|
cls.process = popen_launch_server(
|
|
cls.model,
|
|
cls.base_url,
|
|
timeout=cls.server_timeout,
|
|
other_args=other_args,
|
|
env=env,
|
|
)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if hasattr(cls, "process") and cls.process:
|
|
try:
|
|
kill_process_tree(cls.process.pid)
|
|
except Exception as e:
|
|
logger.error(f"Error during tearDown: {e}")
|
|
|
|
def run_accuracy(self):
|
|
parsed_url = urlparse(self.base_url)
|
|
host = parsed_url.hostname
|
|
port = parsed_url.port
|
|
if self.benchmark_tool == EVALSCOPE:
|
|
model_name = os.path.basename(self.model)
|
|
max_retries = get_max_retries(self.datasets)
|
|
best_metrics = None
|
|
for attempt in range(max_retries):
|
|
metrics = run_evalscope(
|
|
host=host,
|
|
port=port,
|
|
model=model_name,
|
|
datasets=self.datasets,
|
|
dataset_args=self.dataset_args,
|
|
eval_batch_size=self.eval_batch_size,
|
|
limit=self.limit,
|
|
generation_config=self.generation_config,
|
|
dataset_dir=self.dataset_dir,
|
|
stream=self.stream,
|
|
timeout=self.timeout,
|
|
eval_type=self.eval_type,
|
|
)
|
|
if best_metrics is None or float(metrics.get("accuracy", 0)) > float(
|
|
best_metrics.get("accuracy", 0)
|
|
):
|
|
best_metrics = metrics
|
|
threshold = get_accuracy_threshold(self.datasets, self.accuracy)
|
|
if float(best_metrics.get("accuracy", 0)) >= threshold:
|
|
break
|
|
if attempt < max_retries - 1:
|
|
logger.info(
|
|
f"Accuracy {best_metrics.get('accuracy')} below threshold "
|
|
f"{threshold}, retrying ({attempt + 1}/{max_retries - 1})..."
|
|
)
|
|
assert_metrics(self, best_metrics)
|
|
|
|
def run_accuracy_multiple(self, n_runs=None):
|
|
if n_runs is None:
|
|
n_runs = self.n_runs
|
|
|
|
parsed_url = urlparse(self.base_url)
|
|
host = parsed_url.hostname
|
|
port = parsed_url.port
|
|
|
|
if self.benchmark_tool != EVALSCOPE:
|
|
raise Exception(
|
|
"run_accuracy_multiple only supports evalscope benchmark tool"
|
|
)
|
|
|
|
model_name = os.path.basename(self.model)
|
|
all_metrics = []
|
|
|
|
for i in range(n_runs):
|
|
logger.info(f"=== Accuracy run {i + 1}/{n_runs} ===")
|
|
metrics = run_evalscope(
|
|
host=host,
|
|
port=port,
|
|
model=model_name,
|
|
datasets=self.datasets,
|
|
dataset_args=self.dataset_args,
|
|
eval_batch_size=self.eval_batch_size,
|
|
limit=self.limit,
|
|
generation_config=self.generation_config,
|
|
dataset_dir=self.dataset_dir,
|
|
stream=self.stream,
|
|
timeout=self.timeout,
|
|
eval_type=self.eval_type,
|
|
)
|
|
all_metrics.append(metrics)
|
|
if metrics and "accuracy" in metrics:
|
|
logger.info(f"Run {i + 1} accuracy: {metrics['accuracy']}")
|
|
else:
|
|
logger.warning(f"Run {i + 1} failed to get accuracy metric")
|
|
|
|
valid_metrics = [m for m in all_metrics if m and "accuracy" in m]
|
|
if not valid_metrics:
|
|
raise Exception("No valid accuracy metrics obtained from any run")
|
|
|
|
avg_accuracy = sum(float(m["accuracy"]) for m in valid_metrics) / len(
|
|
valid_metrics
|
|
)
|
|
|
|
logger.info("=" * 60)
|
|
logger.info("Multiple Run Accuracy Results:")
|
|
for i, m in enumerate(valid_metrics):
|
|
logger.info(f" Run {i + 1}: {m['accuracy']}")
|
|
logger.info(f" Average: {avg_accuracy}")
|
|
logger.info("=" * 60)
|
|
|
|
avg_metrics = {"accuracy": avg_accuracy}
|
|
dump_metric(
|
|
"accuracy_avg",
|
|
avg_accuracy,
|
|
labels={"test_case": self.__class__.__name__, "type": "accuracy"},
|
|
)
|
|
assert_metrics(self, avg_metrics)
|
|
|
|
|
|
class TestNpuAccuracyMultiNodePdMixTestCaseBase(CustomTestCase):
|
|
model_config = None
|
|
benchmark_tool = BENCHMARK_TOOL_DEFAULT
|
|
backend = "sglang"
|
|
datasets = ["gsm8k"]
|
|
dataset_args = None
|
|
eval_batch_size = 16
|
|
limit = 100000
|
|
generation_config = None
|
|
dataset_dir = None
|
|
stream = True
|
|
timeout = 60000
|
|
eval_type = "openai_api"
|
|
other_args = None
|
|
server_timeout = DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH
|
|
envs = None
|
|
max_attempts = 2
|
|
accuracy = 0.1
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.local_ip = "127.0.0.1"
|
|
cls.host = os.getenv("POD_IP")
|
|
cls.port = SERVICE_PORT
|
|
cls.base_url = f"http://{cls.host}:{cls.port}"
|
|
cls.hostname = os.getenv("HOSTNAME")
|
|
cls.role = "master" if cls.hostname.endswith("sglang-node-0") else "worker"
|
|
logger.info(f"Init {cls.host} {cls.role=}!")
|
|
|
|
cls.start_pd_mix_master_node()
|
|
cls.start_pd_mix_worker_node()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
pass
|
|
|
|
@classmethod
|
|
@check_role(allowed_roles=["master"])
|
|
def start_pd_mix_master_node(cls):
|
|
sglang_thread = threading.Thread(
|
|
target=launch_pd_mix_node, args=(cls.model_config,)
|
|
)
|
|
sglang_thread.start()
|
|
|
|
wait_server_ready(f"{cls.base_url}/health")
|
|
|
|
logger.info(
|
|
f"Wait {SERVER_INITIALIZATION_DELAY}s, starting run benchmark ......"
|
|
)
|
|
time.sleep(SERVER_INITIALIZATION_DELAY)
|
|
|
|
@classmethod
|
|
@check_role(allowed_roles=["worker"])
|
|
def start_pd_mix_worker_node(cls):
|
|
sglang_thread = threading.Thread(
|
|
target=launch_pd_mix_node, args=(cls.model_config,)
|
|
)
|
|
sglang_thread.start()
|
|
|
|
logger.info(
|
|
f"{cls.role} node started, keeping test alive for {MAX_SERVER_KEEP_ALIVE_TIME} seconds"
|
|
)
|
|
time.sleep(MAX_SERVER_KEEP_ALIVE_TIME)
|
|
|
|
@check_role(allowed_roles=["master", "worker"])
|
|
def run_accuracy(self):
|
|
parsed_url = urlparse(self.base_url)
|
|
host = parsed_url.hostname
|
|
port = parsed_url.port
|
|
if self.benchmark_tool == EVALSCOPE:
|
|
model_name = os.path.basename(self.model_config.get("model_path"))
|
|
max_retries = get_max_retries(self.datasets)
|
|
best_metrics = None
|
|
for attempt in range(max_retries):
|
|
metrics = run_evalscope(
|
|
host=self.host,
|
|
port=self.port,
|
|
model=model_name,
|
|
datasets=self.datasets,
|
|
dataset_args=self.dataset_args,
|
|
eval_batch_size=self.eval_batch_size,
|
|
limit=self.limit,
|
|
generation_config=self.generation_config,
|
|
dataset_dir=self.dataset_dir,
|
|
stream=self.stream,
|
|
timeout=self.timeout,
|
|
eval_type=self.eval_type,
|
|
)
|
|
if best_metrics is None or float(metrics.get("accuracy", 0)) > float(
|
|
best_metrics.get("accuracy", 0)
|
|
):
|
|
best_metrics = metrics
|
|
threshold = get_accuracy_threshold(self.datasets, self.accuracy)
|
|
if float(best_metrics.get("accuracy", 0)) >= threshold:
|
|
break
|
|
if attempt < max_retries - 1:
|
|
logger.info(
|
|
f"Accuracy {best_metrics.get('accuracy')} below threshold "
|
|
f"{threshold}, retrying ({attempt + 1}/{max_retries - 1})..."
|
|
)
|
|
assert_metrics(self, best_metrics)
|
|
|
|
|
|
class TestNpuAccuracyMultiNodePdSepTestCaseBase(CustomTestCase):
|
|
model_config = None
|
|
benchmark_tool = BENCHMARK_TOOL_DEFAULT
|
|
backend = "sglang"
|
|
datasets = ["gsm8k"]
|
|
dataset_args = None
|
|
eval_batch_size = 16
|
|
limit = 100000
|
|
generation_config = None
|
|
dataset_dir = None
|
|
stream = True
|
|
timeout = 60000
|
|
eval_type = "openai_api"
|
|
other_args = None
|
|
server_timeout = DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH
|
|
max_attempts = 2
|
|
accuracy = 0.1
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.process = None
|
|
cls.local_ip = "127.0.0.1"
|
|
cls.host = os.getenv("POD_IP")
|
|
cls.port = SERVICE_PORT
|
|
cls.base_url = f"http://{cls.host}:{cls.port}"
|
|
cls.hostname = os.getenv("HOSTNAME")
|
|
cls.role = (
|
|
"router"
|
|
if "router" in cls.hostname
|
|
else "prefill" if "prefill" in cls.hostname else "decode"
|
|
)
|
|
logger.info(f"Init {cls.host} {cls.role=}!")
|
|
|
|
cls.start_pd_server()
|
|
cls.start_router_server()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls.process:
|
|
try:
|
|
kill_process_tree(cls.process.pid)
|
|
except Exception as e:
|
|
logger.error(f"Error during tearDown: {e}")
|
|
|
|
@classmethod
|
|
@check_role(allowed_roles=["router"])
|
|
def start_router_server(cls):
|
|
logger.info(f"Starting router in thread...")
|
|
sglang_thread = threading.Thread(target=launch_router, args=(cls.model_config,))
|
|
sglang_thread.daemon = True
|
|
sglang_thread.start()
|
|
|
|
health_check_url = f"{cls.base_url}/health"
|
|
logger.info(f"Waiting for router to be ready at {health_check_url}")
|
|
wait_server_ready(health_check_url)
|
|
|
|
logger.info(
|
|
f"Waiting {SERVER_INITIALIZATION_DELAY} seconds for the server to fully initialize..."
|
|
)
|
|
time.sleep(SERVER_INITIALIZATION_DELAY)
|
|
|
|
@classmethod
|
|
@check_role(allowed_roles=["prefill", "decode"])
|
|
def start_pd_server(cls):
|
|
logger.info(f"Starting pd separation node...")
|
|
cls.process = launch_pd_separation_node(cls.model_config)
|
|
logger.info(f"Pd separation node started with PID: {cls.process.pid}")
|
|
|
|
while True:
|
|
if cls.process.poll() is None:
|
|
time.sleep(30)
|
|
else:
|
|
exit_code = cls.process.poll()
|
|
raise Exception(
|
|
f"Sglang process exited on node {cls.host} {cls.hostname} with exit code: {exit_code}"
|
|
)
|
|
|
|
@check_role(allowed_roles=["router"])
|
|
def run_accuracy(self):
|
|
parsed_url = urlparse(self.base_url)
|
|
host = parsed_url.hostname
|
|
port = parsed_url.port
|
|
if self.benchmark_tool == EVALSCOPE:
|
|
model_name = os.path.basename(self.model_config.get("model_path"))
|
|
max_retries = get_max_retries(self.datasets)
|
|
best_metrics = None
|
|
for attempt in range(max_retries):
|
|
metrics = run_evalscope(
|
|
host=host,
|
|
port=port,
|
|
model=model_name,
|
|
datasets=self.datasets,
|
|
dataset_args=self.dataset_args,
|
|
eval_batch_size=self.eval_batch_size,
|
|
limit=self.limit,
|
|
generation_config=self.generation_config,
|
|
dataset_dir=self.dataset_dir,
|
|
stream=self.stream,
|
|
timeout=self.timeout,
|
|
eval_type=self.eval_type,
|
|
)
|
|
if best_metrics is None or float(metrics.get("accuracy", 0)) > float(
|
|
best_metrics.get("accuracy", 0)
|
|
):
|
|
best_metrics = metrics
|
|
threshold = get_accuracy_threshold(self.datasets, self.accuracy)
|
|
if float(best_metrics.get("accuracy", 0)) >= threshold:
|
|
break
|
|
if attempt < max_retries - 1:
|
|
logger.info(
|
|
f"Accuracy {best_metrics.get('accuracy')} below threshold "
|
|
f"{threshold}, retrying ({attempt + 1}/{max_retries - 1})..."
|
|
)
|
|
assert_metrics(self, best_metrics)
|