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
362 lines
13 KiB
Python
362 lines
13 KiB
Python
import glob
|
|
import json
|
|
import os
|
|
import random
|
|
import re
|
|
import subprocess
|
|
import uuid
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
import requests
|
|
from PIL import Image
|
|
|
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class RequestFuncInput:
|
|
prompt: str
|
|
api_url: str = ""
|
|
model: str = ""
|
|
num_outputs_per_prompt: int = 1
|
|
width: Optional[int] = None
|
|
height: Optional[int] = None
|
|
num_frames: Optional[int] = None
|
|
fps: Optional[int] = None
|
|
extra_body: Dict[str, Any] = field(default_factory=dict)
|
|
image_paths: Optional[List[str]] = None
|
|
request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
slo_ms: Optional[float] = None
|
|
num_inference_steps: Optional[int] = None
|
|
|
|
|
|
@dataclass
|
|
class RequestFuncOutput:
|
|
success: bool = False
|
|
latency: float = 0.0
|
|
error: str = ""
|
|
start_time: float = 0.0
|
|
response_body: Dict[str, Any] = field(default_factory=dict)
|
|
peak_memory_mb: float = 0.0
|
|
slo_achieved: Optional[bool] = None
|
|
output_count: int = 0
|
|
|
|
|
|
def is_dir_not_empty(path: str) -> bool:
|
|
return os.path.isdir(path) and bool(os.listdir(path))
|
|
|
|
|
|
class BaseDataset(ABC):
|
|
def __init__(self, args, api_url: str = "", model: str = ""):
|
|
self.args = args
|
|
self.api_url = api_url
|
|
self.model = model
|
|
self.items: List[Dict[str, Any]] = []
|
|
|
|
@abstractmethod
|
|
def __len__(self) -> int:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def __getitem__(self, idx: int) -> RequestFuncInput:
|
|
pass
|
|
|
|
def get_requests(self) -> List[RequestFuncInput]:
|
|
return [self[i] for i in range(len(self))]
|
|
|
|
|
|
class VBenchDataset(BaseDataset):
|
|
"""
|
|
Dataset loader for VBench prompts.
|
|
Supports t2v, i2v.
|
|
"""
|
|
|
|
T2V_PROMPT_URL = "https://raw.githubusercontent.com/Vchitect/VBench/master/prompts/prompts_per_dimension/subject_consistency.txt"
|
|
I2V_DOWNLOAD_SCRIPT_URL = "https://raw.githubusercontent.com/Vchitect/VBench/master/vbench2_beta_i2v/download_data.sh"
|
|
|
|
def __init__(self, args, api_url: str = "", model: str = ""):
|
|
super().__init__(args, api_url, model)
|
|
self.cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "sglang")
|
|
self.items = self._load_data()
|
|
|
|
@staticmethod
|
|
def _normalize_task_name(task_name: Any) -> Any:
|
|
"""Normalize enum-style task values to legacy benchmark task-name strings."""
|
|
enum_to_task_name = {
|
|
"T2V": "text-to-video",
|
|
"I2V": "image-to-video",
|
|
"TI2V": "image-to-video",
|
|
"T2I": "text-to-image",
|
|
"I2I": "image-to-image",
|
|
"TI2I": "image-to-image",
|
|
}
|
|
|
|
# Handle Enum-like objects, e.g., ModelTaskType.T2I
|
|
enum_name = getattr(task_name, "name", None)
|
|
if isinstance(enum_name, str):
|
|
return enum_to_task_name.get(enum_name, task_name)
|
|
|
|
# Handle direct string inputs or enum string repr
|
|
if isinstance(task_name, str):
|
|
if task_name in enum_to_task_name:
|
|
return enum_to_task_name[task_name]
|
|
if "." in task_name:
|
|
suffix = task_name.split(".")[-1]
|
|
return enum_to_task_name.get(suffix, task_name)
|
|
|
|
return task_name
|
|
|
|
def _load_data(self) -> List[Dict[str, Any]]:
|
|
task_name = self._normalize_task_name(self.args.task_name)
|
|
|
|
if task_name in ("text-to-video", "text-to-image", "video-to-video"):
|
|
return self._load_t2v_prompts()
|
|
elif task_name in ("image-to-video", "image-to-image"):
|
|
return self._load_i2v_data()
|
|
else:
|
|
raise ValueError(
|
|
f"Illegal task name is found in VBenchDataset {self.args.task_name}"
|
|
)
|
|
|
|
def _download_file(self, url: str, dest_path: str) -> None:
|
|
"""Download a file from URL to destination path."""
|
|
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
resp = requests.get(url)
|
|
resp.raise_for_status()
|
|
with open(dest_path, "w") as f:
|
|
f.write(resp.text)
|
|
|
|
def _load_t2v_prompts(self) -> List[Dict[str, Any]]:
|
|
path = self.args.dataset_path
|
|
|
|
if not path:
|
|
path = os.path.join(self.cache_dir, "vbench_subject_consistency.txt")
|
|
if not os.path.exists(path):
|
|
logger.info(f"Downloading VBench T2V prompts to {path}...")
|
|
try:
|
|
self._download_file(self.T2V_PROMPT_URL, path)
|
|
except Exception as e:
|
|
logger.info(f"Failed to download VBench prompts: {e}")
|
|
return [{"prompt": "A cat sitting on a bench"}] * 50
|
|
|
|
prompts = []
|
|
with open(path, "r") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line:
|
|
prompts.append({"prompt": line})
|
|
|
|
return self._resize_data(prompts)
|
|
|
|
def _auto_download_i2v_dataset(self) -> Optional[str]:
|
|
"""Auto-download VBench I2V dataset and return the dataset directory."""
|
|
vbench_i2v_dir = os.path.join(self.cache_dir, "vbench_i2v", "vbench2_beta_i2v")
|
|
info_json_path = os.path.join(vbench_i2v_dir, "data", "i2v-bench-info.json")
|
|
crop_dir = os.path.join(vbench_i2v_dir, "data", "crop")
|
|
origin_dir = os.path.join(vbench_i2v_dir, "data", "origin")
|
|
|
|
if (
|
|
os.path.exists(info_json_path)
|
|
and is_dir_not_empty(crop_dir)
|
|
and is_dir_not_empty(origin_dir)
|
|
):
|
|
return vbench_i2v_dir
|
|
|
|
logger.info(f"Downloading VBench I2V dataset to {vbench_i2v_dir}...")
|
|
try:
|
|
cache_root = os.path.join(self.cache_dir, "vbench_i2v")
|
|
script_path = os.path.join(cache_root, "download_data.sh")
|
|
|
|
self._download_file(self.I2V_DOWNLOAD_SCRIPT_URL, script_path)
|
|
os.chmod(script_path, 0o755)
|
|
|
|
logger.info("Executing download_data.sh (this may take a while)...")
|
|
|
|
result = subprocess.run(
|
|
["bash", script_path],
|
|
cwd=cache_root,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(f"Download script failed: {result.stderr}")
|
|
missing_packages = re.findall(r"(\S+): command not found", result.stderr)
|
|
if missing_packages:
|
|
missing_packages = list(set(missing_packages))
|
|
package_list = ", ".join(f"'{cmd}'" for cmd in missing_packages)
|
|
raise RuntimeError(
|
|
f"Download script failed because the following commands are not installed: {package_list}.\n"
|
|
"Please install them (e.g., on Ubuntu: `sudo apt install ...`) and try again."
|
|
)
|
|
logger.info(
|
|
f"Successfully downloaded VBench I2V dataset to {vbench_i2v_dir}"
|
|
)
|
|
except Exception as e:
|
|
logger.info(f"Failed to download VBench I2V dataset: {e}")
|
|
logger.info("Please manually download following instructions at:")
|
|
logger.info(
|
|
"https://github.com/Vchitect/VBench/tree/master/vbench2_beta_i2v#22-download"
|
|
)
|
|
return None
|
|
|
|
return vbench_i2v_dir if os.path.exists(info_json_path) else None
|
|
|
|
def _load_from_i2v_json(self, json_path: str) -> List[Dict[str, Any]]:
|
|
"""Load I2V data from i2v-bench-info.json format."""
|
|
with open(json_path, "r") as f:
|
|
items = json.load(f)
|
|
|
|
base_dir = os.path.dirname(
|
|
os.path.dirname(json_path)
|
|
) # Go up to vbench2_beta_i2v
|
|
origin_dir = os.path.join(base_dir, "data", "origin")
|
|
|
|
data = []
|
|
for item in items:
|
|
img_path = os.path.join(origin_dir, item.get("file_name", ""))
|
|
if os.path.exists(img_path):
|
|
data.append({"prompt": item.get("caption", ""), "image_path": img_path})
|
|
else:
|
|
logger.warning(f"Image not found: {img_path}")
|
|
|
|
logger.info(f"Loaded {len(data)} I2V samples from VBench I2V dataset")
|
|
return data
|
|
|
|
def _scan_directory_for_images(self, path: str) -> List[Dict[str, Any]]:
|
|
"""Scan directory for image files."""
|
|
exts = ["*.jpg", "*.jpeg", "*.png", "*.webp"]
|
|
files = []
|
|
|
|
for ext in exts:
|
|
files.extend(glob.glob(os.path.join(path, ext)))
|
|
files.extend(glob.glob(os.path.join(path, ext.upper())))
|
|
|
|
origin_dir = os.path.join(path, "data", "origin")
|
|
if os.path.exists(origin_dir):
|
|
files.extend(glob.glob(os.path.join(origin_dir, ext)))
|
|
files.extend(glob.glob(os.path.join(origin_dir, ext.upper())))
|
|
|
|
return [
|
|
{"prompt": os.path.splitext(os.path.basename(f))[0], "image_path": f}
|
|
for f in files
|
|
]
|
|
|
|
def _create_dummy_data(self) -> List[Dict[str, Any]]:
|
|
"""Create dummy data with a placeholder image in cache directory."""
|
|
logger.info("No I2V data found. Using dummy placeholders.")
|
|
|
|
dummy_image = os.path.join(self.cache_dir, "dummy_image.jpg")
|
|
if not os.path.exists(dummy_image):
|
|
os.makedirs(self.cache_dir, exist_ok=True)
|
|
img = Image.new("RGB", (100, 100), color="red")
|
|
img.save(dummy_image)
|
|
logger.info(f"Created dummy image at {dummy_image}")
|
|
|
|
return [{"prompt": "A moving cat", "image_path": dummy_image}] * 10
|
|
|
|
def _load_i2v_data(self) -> List[Dict[str, Any]]:
|
|
"""Load I2V data from VBench I2V dataset or user-provided path."""
|
|
path = self.args.dataset_path
|
|
if not path:
|
|
path = self._auto_download_i2v_dataset()
|
|
if not path:
|
|
return self._resize_data(self._create_dummy_data())
|
|
|
|
info_json_candidates = [
|
|
os.path.join(path, "data", "i2v-bench-info.json"),
|
|
path if path.endswith(".json") else None,
|
|
]
|
|
|
|
for json_path in info_json_candidates:
|
|
if json_path and os.path.exists(json_path):
|
|
try:
|
|
return self._resize_data(self._load_from_i2v_json(json_path))
|
|
except Exception as e:
|
|
logger.info(f"Failed to load {json_path}: {e}")
|
|
|
|
if os.path.isdir(path):
|
|
data = self._scan_directory_for_images(path)
|
|
if data:
|
|
return self._resize_data(data)
|
|
|
|
return self._resize_data(self._create_dummy_data())
|
|
|
|
def _resize_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
"""Resize data to match num_prompts."""
|
|
if not self.args.num_prompts:
|
|
return data
|
|
|
|
if len(data) < self.args.num_prompts:
|
|
factor = (self.args.num_prompts // len(data)) + 1
|
|
data = data * factor
|
|
|
|
return data[: self.args.num_prompts]
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.items)
|
|
|
|
def __getitem__(self, idx: int) -> RequestFuncInput:
|
|
item = self.items[idx]
|
|
return RequestFuncInput(
|
|
prompt=item.get("prompt", ""),
|
|
api_url=self.api_url,
|
|
model=self.model,
|
|
num_outputs_per_prompt=self.args.num_outputs_per_prompt,
|
|
width=self.args.width,
|
|
height=self.args.height,
|
|
num_frames=self.args.num_frames,
|
|
fps=self.args.fps,
|
|
num_inference_steps=self.args.num_inference_steps,
|
|
image_paths=[item["image_path"]] if "image_path" in item else None,
|
|
)
|
|
|
|
|
|
class RandomDataset(BaseDataset):
|
|
def __init__(self, args, api_url: str = "", model: str = ""):
|
|
super().__init__(args, api_url, model)
|
|
self.num_prompts = args.num_prompts or 100
|
|
|
|
self.random_request_config = args.random_request_config
|
|
if self.random_request_config:
|
|
self.random_request_config = json.loads(self.random_request_config)
|
|
weights = [p.pop("weight") for p in self.random_request_config]
|
|
seed = args.random_request_seed
|
|
rng = random.Random(seed)
|
|
self._sampled_requests = rng.choices(
|
|
self.random_request_config, weights=weights, k=self.num_prompts
|
|
)
|
|
else:
|
|
self._sampled_requests = None
|
|
|
|
def get_sampling_params(self, idx: int) -> dict:
|
|
"""Return the per-request sampling profile dict, or empty dict if not mix-diffusion."""
|
|
if self._sampled_requests:
|
|
return self._sampled_requests[idx]
|
|
return {}
|
|
|
|
def __len__(self) -> int:
|
|
return self.num_prompts
|
|
|
|
def __getitem__(self, idx: int) -> RequestFuncInput:
|
|
profile = self._sampled_requests[idx] if self._sampled_requests else {}
|
|
|
|
return RequestFuncInput(
|
|
prompt=f"Random prompt {idx} for benchmarking diffusion models",
|
|
api_url=self.api_url,
|
|
model=self.model,
|
|
num_outputs_per_prompt=profile.get(
|
|
"num_outputs_per_prompt", self.args.num_outputs_per_prompt
|
|
),
|
|
width=profile.get("width", self.args.width),
|
|
height=profile.get("height", self.args.height),
|
|
num_frames=profile.get("num_frames", self.args.num_frames),
|
|
num_inference_steps=profile.get(
|
|
"num_inference_steps", self.args.num_inference_steps
|
|
),
|
|
fps=profile.get("fps", self.args.fps),
|
|
)
|