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
471 lines
16 KiB
Python
471 lines
16 KiB
Python
import glob
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
from sglang.srt.utils import kill_process_tree
|
|
from sglang.srt.utils.common import temp_set_env
|
|
from sglang.test.test_utils import (
|
|
DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
|
|
DEFAULT_URL_FOR_TEST,
|
|
CustomTestCase,
|
|
dump_metric,
|
|
popen_launch_server,
|
|
)
|
|
|
|
# Set default mem_fraction_static to 0.8
|
|
DEFAULT_MEM_FRACTION_STATIC = 0.8
|
|
|
|
|
|
def _is_mmmu_parquet_corruption(error_output: str) -> bool:
|
|
"""Check if error is due to MMMU parquet file corruption."""
|
|
return (
|
|
"ArrowInvalid" in error_output
|
|
and "Parquet magic bytes not found" in error_output
|
|
and ("MMMU" in error_output or "lmms-lab--MMMU" in error_output)
|
|
)
|
|
|
|
|
|
def _cleanup_mmmu_dataset_cache():
|
|
"""Clean up corrupted MMMU dataset cache to allow fresh download."""
|
|
# Priority 1: Check CI convention path /hf_home first (used in Docker containers)
|
|
ci_hf_home = Path("/hf_home/hub/datasets--lmms-lab--MMMU")
|
|
if ci_hf_home.exists():
|
|
mmmu_cache_path = ci_hf_home
|
|
else:
|
|
# Priority 2: Use HF_HOME env var or default user cache
|
|
hf_home = os.environ.get("HF_HOME", os.path.expanduser("~/.cache/huggingface"))
|
|
mmmu_cache_path = Path(hf_home) / "hub" / "datasets--lmms-lab--MMMU"
|
|
|
|
if mmmu_cache_path.exists():
|
|
print(f"Detected corrupted MMMU parquet cache. Cleaning up: {mmmu_cache_path}")
|
|
try:
|
|
shutil.rmtree(mmmu_cache_path)
|
|
print(f"Successfully removed corrupted cache: {mmmu_cache_path}")
|
|
return True
|
|
except OSError as e:
|
|
print(f"Warning: Failed to remove cache {mmmu_cache_path}: {e}")
|
|
return False
|
|
else:
|
|
print(f"MMMU cache not found at {mmmu_cache_path}, skipping cleanup")
|
|
return False
|
|
|
|
|
|
def _run_lmms_eval_with_retry(cmd: list[str], timeout: int = 3600) -> None:
|
|
"""Run lmms_eval command with automatic retry on MMMU parquet corruption."""
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
check=True,
|
|
timeout=timeout,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
# Check for errors in output even if exit code is 0
|
|
# lmms_eval sometimes returns 0 even when errors occur
|
|
combined_output = result.stdout + result.stderr
|
|
if _is_mmmu_parquet_corruption(combined_output):
|
|
print(
|
|
"Detected MMMU parquet corruption error in output. Attempting recovery..."
|
|
)
|
|
if _cleanup_mmmu_dataset_cache():
|
|
print("Retrying lmms_eval with fresh download...")
|
|
with temp_set_env(
|
|
HF_HUB_OFFLINE="0",
|
|
HF_DATASETS_DOWNLOAD_MODE="force_redownload",
|
|
):
|
|
retry_result = subprocess.run(
|
|
cmd, check=True, timeout=timeout, capture_output=True, text=True
|
|
)
|
|
# Print retry output
|
|
if retry_result.stdout:
|
|
print(retry_result.stdout, end="")
|
|
if retry_result.stderr:
|
|
print(retry_result.stderr, end="")
|
|
else:
|
|
print(
|
|
f"Failed to cleanup corrupted MMMU cache. Output from lmms_eval:\nStdout:\n{result.stdout}\nStderr:\n{result.stderr}"
|
|
)
|
|
raise RuntimeError("Failed to cleanup corrupted MMMU cache")
|
|
else:
|
|
# Print captured output to maintain visibility of successful runs
|
|
if result.stdout:
|
|
print(result.stdout, end="")
|
|
if result.stderr:
|
|
print(result.stderr, end="")
|
|
except subprocess.CalledProcessError as e:
|
|
error_output = e.stderr + e.stdout
|
|
if _is_mmmu_parquet_corruption(error_output):
|
|
print("Detected MMMU parquet corruption error. Attempting recovery...")
|
|
if _cleanup_mmmu_dataset_cache():
|
|
print("Retrying lmms_eval with fresh download...")
|
|
with temp_set_env(
|
|
HF_HUB_OFFLINE="0",
|
|
HF_DATASETS_DOWNLOAD_MODE="force_redownload",
|
|
):
|
|
retry_result = subprocess.run(
|
|
cmd, check=True, timeout=timeout, capture_output=True, text=True
|
|
)
|
|
# Print retry output
|
|
if retry_result.stdout:
|
|
print(retry_result.stdout, end="")
|
|
if retry_result.stderr:
|
|
print(retry_result.stderr, end="")
|
|
else:
|
|
print(
|
|
f"Failed to cleanup corrupted MMMU cache. Error from lmms_eval:\nStdout:\n{e.stdout}\nStderr:\n{e.stderr}"
|
|
)
|
|
raise
|
|
else:
|
|
print(
|
|
f"lmms_eval failed with an unhandled error.\nStdout:\n{e.stdout}\nStderr:\n{e.stderr}"
|
|
)
|
|
raise
|
|
|
|
|
|
class MMMUMixin:
|
|
"""Mixin for MMMU evaluation.
|
|
|
|
Use with MMMUServerBase for single-model tests:
|
|
class TestMyModel(MMMUMixin, MMMUServerBase):
|
|
model = "my/model"
|
|
accuracy = 0.4
|
|
"""
|
|
|
|
accuracy: float
|
|
mmmu_args: list[str] = []
|
|
|
|
# For OpenAI API settings
|
|
api_key = "sk-123456"
|
|
|
|
def run_mmmu_eval(
|
|
self: CustomTestCase,
|
|
model_version: str,
|
|
output_path: str,
|
|
):
|
|
"""
|
|
Evaluate a VLM on the MMMU validation set with lmms-eval.
|
|
Only `model_version` (checkpoint) and `chat_template` vary;
|
|
We are focusing only on the validation set due to resource constraints.
|
|
"""
|
|
# -------- fixed settings --------
|
|
model = "openai_compatible"
|
|
tp = 1
|
|
tasks = "mmmu_val"
|
|
batch_size = 64
|
|
log_suffix = "openai_compatible"
|
|
os.makedirs(output_path, exist_ok=True)
|
|
|
|
# -------- compose --model_args --------
|
|
model_args = f'model_version="{model_version}",' f"tp={tp}"
|
|
|
|
# -------- build command list --------
|
|
cmd = [
|
|
"python3",
|
|
"-m",
|
|
"lmms_eval",
|
|
"--model",
|
|
model,
|
|
"--model_args",
|
|
model_args,
|
|
"--tasks",
|
|
tasks,
|
|
"--batch_size",
|
|
str(batch_size),
|
|
"--log_samples",
|
|
"--log_samples_suffix",
|
|
log_suffix,
|
|
"--output_path",
|
|
str(output_path),
|
|
*self.mmmu_args,
|
|
]
|
|
|
|
# Set OpenAI API key and base URL environment variables.
|
|
# Needed for lmms-eval to work.
|
|
with temp_set_env(
|
|
OPENAI_API_KEY=self.api_key,
|
|
OPENAI_API_BASE=f"{self.base_url}/v1",
|
|
):
|
|
_run_lmms_eval_with_retry(cmd)
|
|
|
|
def test_mmmu(self: CustomTestCase):
|
|
"""Run MMMU evaluation test."""
|
|
with tempfile.TemporaryDirectory() as output_path:
|
|
# Run evaluation
|
|
self.run_mmmu_eval(self.model, output_path)
|
|
|
|
result_files = glob.glob(f"{output_path}/**/*.json", recursive=True)
|
|
|
|
if not result_files:
|
|
raise FileNotFoundError(f"No JSON result files found in {output_path}")
|
|
|
|
result_file_path = result_files[0]
|
|
|
|
with open(result_file_path, "r") as f:
|
|
result = json.load(f)
|
|
print(f"Result: {result}")
|
|
|
|
# Process the result
|
|
mmmu_accuracy = result["results"]["mmmu_val"]["mmmu_acc,none"]
|
|
print(f"Model {self.model} achieved accuracy: {mmmu_accuracy:.4f}")
|
|
|
|
dump_metric(
|
|
"mmmu_score",
|
|
mmmu_accuracy,
|
|
labels={"model": self.model, "eval": "mmmu", "api": "lmms-eval"},
|
|
)
|
|
|
|
# Assert performance meets expected threshold
|
|
self.assertGreaterEqual(
|
|
mmmu_accuracy,
|
|
self.accuracy,
|
|
f"Model {self.model} accuracy ({mmmu_accuracy:.4f}) below expected threshold ({self.accuracy:.4f})",
|
|
)
|
|
|
|
|
|
class MMMUMultiModelTestBase(CustomTestCase):
|
|
"""Base class for multi-model MMMU tests.
|
|
|
|
This class is for tests that need to evaluate multiple models,
|
|
starting and stopping a server for each model within the test method.
|
|
For single-model tests, use MMMUMixin with MMMUServerBase instead.
|
|
"""
|
|
|
|
parsed_args = None # Class variable to store args
|
|
other_args = []
|
|
mmmu_args = []
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# Removed argument parsing from here
|
|
cls.base_url = DEFAULT_URL_FOR_TEST
|
|
cls.api_key = "sk-123456"
|
|
cls.time_out = DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH
|
|
|
|
if cls.parsed_args is None:
|
|
cls.parsed_args = SimpleNamespace(
|
|
mem_fraction_static=DEFAULT_MEM_FRACTION_STATIC
|
|
)
|
|
|
|
# Save original environment variables for restoration in tearDownClass
|
|
cls._original_openai_api_key = os.environ.get("OPENAI_API_KEY")
|
|
cls._original_openai_api_base = os.environ.get("OPENAI_API_BASE")
|
|
|
|
# Set OpenAI API key and base URL environment variables. Needed for lmm-evals to work.
|
|
os.environ["OPENAI_API_KEY"] = cls.api_key
|
|
os.environ["OPENAI_API_BASE"] = f"{cls.base_url}/v1"
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
# Restore original environment variables
|
|
if cls._original_openai_api_key is not None:
|
|
os.environ["OPENAI_API_KEY"] = cls._original_openai_api_key
|
|
elif "OPENAI_API_KEY" in os.environ:
|
|
del os.environ["OPENAI_API_KEY"]
|
|
|
|
if cls._original_openai_api_base is not None:
|
|
os.environ["OPENAI_API_BASE"] = cls._original_openai_api_base
|
|
elif "OPENAI_API_BASE" in os.environ:
|
|
del os.environ["OPENAI_API_BASE"]
|
|
|
|
def run_mmmu_eval(
|
|
self,
|
|
model_version: str,
|
|
output_path: str,
|
|
*,
|
|
env: dict | None = None,
|
|
):
|
|
"""
|
|
Evaluate a VLM on the MMMU validation set with lmms-eval.
|
|
Only `model_version` (checkpoint) and `chat_template` vary;
|
|
We are focusing only on the validation set due to resource constraints.
|
|
"""
|
|
# -------- fixed settings --------
|
|
model = "openai_compatible"
|
|
tp = 1
|
|
tasks = "mmmu_val"
|
|
batch_size = 64
|
|
log_suffix = "openai_compatible"
|
|
os.makedirs(output_path, exist_ok=True)
|
|
|
|
# -------- compose --model_args --------
|
|
model_args = f'model_version="{model_version}",' f"tp={tp}"
|
|
|
|
# -------- build command list --------
|
|
cmd = [
|
|
"python3",
|
|
"-m",
|
|
"lmms_eval",
|
|
"--model",
|
|
model,
|
|
"--model_args",
|
|
model_args,
|
|
"--tasks",
|
|
tasks,
|
|
"--batch_size",
|
|
str(batch_size),
|
|
"--log_samples",
|
|
"--log_samples_suffix",
|
|
log_suffix,
|
|
"--output_path",
|
|
str(output_path),
|
|
*self.mmmu_args,
|
|
]
|
|
|
|
_run_lmms_eval_with_retry(cmd)
|
|
|
|
def _run_vlm_mmmu_test(
|
|
self,
|
|
model,
|
|
output_path,
|
|
test_name="",
|
|
custom_env=None,
|
|
log_level="info",
|
|
capture_output=False,
|
|
):
|
|
"""
|
|
Common method to run VLM MMMU benchmark test.
|
|
|
|
Args:
|
|
model: Model to test
|
|
output_path: Path for output logs
|
|
test_name: Optional test name for logging
|
|
custom_env: Optional custom environment variables
|
|
log_level: Log level for server (default: "info")
|
|
capture_output: Whether to capture server stdout/stderr
|
|
"""
|
|
print(f"\nTesting model: {model.model}{test_name}")
|
|
|
|
process = None
|
|
mmmu_accuracy = 0 # Initialize to handle potential exceptions
|
|
server_output = ""
|
|
|
|
try:
|
|
# Prepare environment variables
|
|
process_env = os.environ.copy()
|
|
if custom_env:
|
|
process_env.update(custom_env)
|
|
# if test vlm with cuda_ipc feature, open this env_var
|
|
process_env["SGLANG_USE_CUDA_IPC_TRANSPORT"] = "1"
|
|
|
|
# Prepare stdout/stderr redirection if needed
|
|
stdout_file = None
|
|
stderr_file = None
|
|
if capture_output:
|
|
stdout_file = open("/tmp/server_stdout.log", "w")
|
|
stderr_file = open("/tmp/server_stderr.log", "w")
|
|
|
|
# Launch server for testing
|
|
process = popen_launch_server(
|
|
model.model,
|
|
base_url=self.base_url,
|
|
timeout=self.time_out,
|
|
api_key=self.api_key,
|
|
other_args=[
|
|
"--trust-remote-code",
|
|
"--cuda-graph-max-bs-decode",
|
|
"64",
|
|
"--enable-multimodal",
|
|
"--mem-fraction-static",
|
|
str(self.parsed_args.mem_fraction_static), # Use class variable
|
|
"--log-level",
|
|
log_level,
|
|
*self.other_args,
|
|
],
|
|
env=process_env,
|
|
return_stdout_stderr=(
|
|
(stdout_file, stderr_file) if capture_output else None
|
|
),
|
|
)
|
|
|
|
# Run evaluation
|
|
self.run_mmmu_eval(model.model, output_path)
|
|
|
|
result_files = glob.glob(f"{output_path}/**/*.json", recursive=True)
|
|
|
|
if not result_files:
|
|
raise FileNotFoundError(f"No JSON result files found in {output_path}")
|
|
|
|
result_file_path = result_files[0]
|
|
|
|
with open(result_file_path, "r") as f:
|
|
result = json.load(f)
|
|
print(f"Result{test_name}\n: {result}")
|
|
|
|
# Process the result
|
|
mmmu_accuracy = result["results"]["mmmu_val"]["mmmu_acc,none"]
|
|
print(
|
|
f"Model {model.model} achieved accuracy{test_name}: {mmmu_accuracy:.4f}"
|
|
)
|
|
|
|
dump_metric(
|
|
"mmmu_score",
|
|
mmmu_accuracy,
|
|
labels={"model": model.model, "eval": "mmmu", "api": "lmms-eval"},
|
|
)
|
|
|
|
# Capture server output if requested
|
|
if capture_output and process:
|
|
server_output = self._read_output_from_files()
|
|
|
|
# Assert performance meets expected threshold
|
|
self.assertGreaterEqual(
|
|
mmmu_accuracy,
|
|
model.mmmu_accuracy,
|
|
f"Model {model.model} accuracy ({mmmu_accuracy:.4f}) below expected threshold ({model.mmmu_accuracy:.4f}){test_name}",
|
|
)
|
|
|
|
return server_output
|
|
|
|
except Exception as e:
|
|
print(f"Error testing {model.model}{test_name}: {e}")
|
|
self.fail(f"Test failed for {model.model}{test_name}: {e}")
|
|
|
|
finally:
|
|
# Ensure process cleanup happens regardless of success/failure
|
|
if process is not None and process.poll() is None:
|
|
print(f"Cleaning up process {process.pid}")
|
|
try:
|
|
kill_process_tree(process.pid)
|
|
except Exception as e:
|
|
print(f"Error killing process: {e}")
|
|
|
|
# clean up temporary files
|
|
if capture_output:
|
|
if stdout_file:
|
|
stdout_file.close()
|
|
if stderr_file:
|
|
stderr_file.close()
|
|
for filename in ["/tmp/server_stdout.log", "/tmp/server_stderr.log"]:
|
|
try:
|
|
if os.path.exists(filename):
|
|
os.remove(filename)
|
|
except Exception as e:
|
|
print(f"Error removing {filename}: {e}")
|
|
|
|
def _read_output_from_files(self):
|
|
output_lines = []
|
|
|
|
log_files = [
|
|
("/tmp/server_stdout.log", "[STDOUT]"),
|
|
("/tmp/server_stderr.log", "[STDERR]"),
|
|
]
|
|
for filename, tag in log_files:
|
|
try:
|
|
if os.path.exists(filename):
|
|
with open(filename, "r") as f:
|
|
for line in f:
|
|
output_lines.append(f"{tag} {line.rstrip()}")
|
|
except Exception as e:
|
|
print(f"Error reading {tag.lower()} file: {e}")
|
|
|
|
return "\n".join(output_lines)
|
|
|
|
|
|
# Backward compatibility alias
|
|
MMMUVLMTestBase = MMMUMultiModelTestBase
|