Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

404 lines
14 KiB
Python

# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import asyncio
import os
import statistics
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import imageio
import msgspec.msgpack
import numpy as np
import pytest
from openai import Client
from sglang.multimodal_gen.runtime.utils.realtime_video import (
RAW_RGB_CONTENT_TYPE,
RAW_RGB_DELTA_GZIP_CONTENT_TYPE,
RAW_RGBA_DELTA_GZIP_CONTENT_TYPE,
restore_delta_gzip_raw_rgb_payload,
)
from sglang.multimodal_gen.test.server.testcase_configs import DiffusionSamplingParams
from sglang.multimodal_gen.test.test_utils import is_image_url
_REALTIME_WS_TIMEOUT_SECS = float(
os.environ.get("SGLANG_TEST_REALTIME_WS_TIMEOUT_SECS", "1200")
)
@dataclass(frozen=True)
class RealtimeChunkStats:
chunk_index: int
request_id: str | None
content_type: str
num_frames: int
raw_bytes: int
ws_payload_bytes: int
request_prepare_ms: float
scheduler_forward_ms: float
raw_payload_build_ms: float
raw_write_ms: float
ws_write_ms: float
chunk_total_ms: float
@dataclass(frozen=True)
class RealtimeCollectionResult:
frames: list[np.ndarray]
chunk_stats: list[RealtimeChunkStats]
_REALTIME_CHUNK_STATS_BY_CASE: dict[str, list[RealtimeChunkStats]] = {}
_REALTIME_KEY_FRAMES_BY_CASE: dict[str, list[np.ndarray]] = {}
def realtime_ws_url(client: Client) -> str:
base_url = str(client.base_url).rstrip("/")
if base_url.startswith("https://"):
return "wss://" + base_url[len("https://") :] + "/realtime_video/generate"
if base_url.startswith("http://"):
return "ws://" + base_url[len("http://") :] + "/realtime_video/generate"
raise ValueError(f"Unsupported realtime client base_url: {base_url}")
def prepare_realtime_first_frame(
image_path: Path | str | list[Path | str] | None,
) -> bytes | str | None:
if image_path is None:
return None
if isinstance(image_path, list):
image_path = image_path[0]
if isinstance(image_path, str) and is_image_url(image_path):
return image_path
path = Path(image_path)
if not path.exists():
raise FileNotFoundError(f"Realtime first frame file missing: {path}")
return path.read_bytes()
def build_realtime_init_payload(
*,
model_path: str,
sampling_params: DiffusionSamplingParams,
output_size: str,
first_frame: bytes | str | None,
) -> dict[str, Any]:
payload: dict[str, Any] = {
"type": "init",
"model": model_path,
"prompt": sampling_params.prompt,
"size": output_size,
"seconds": sampling_params.seconds,
"first_frame": first_frame,
}
optional_fields = {
"fps": sampling_params.fps,
"num_frames": sampling_params.num_frames,
"realtime_output_format": sampling_params.realtime_output_format,
}
payload.update({k: v for k, v in optional_fields.items() if v is not None})
payload.update(dict(sampling_params.extras))
return {k: v for k, v in payload.items() if v is not None}
def build_realtime_event_payload(event: dict[str, Any]) -> dict[str, Any]:
payload = dict(event)
payload.pop("after_chunk", None)
payload.setdefault("type", "event")
if "kind" not in payload:
raise ValueError("realtime event config must include kind")
return payload
def parse_realtime_chunk_stats(header: dict[str, Any]) -> RealtimeChunkStats:
if header.get("type") != "chunk_stats":
raise ValueError(f"Unexpected realtime chunk stats message: {header}")
return RealtimeChunkStats(
chunk_index=int(header["chunk_index"]),
request_id=header.get("request_id"),
content_type=str(header.get("content_type", "")),
num_frames=int(header.get("num_frames", 0)),
raw_bytes=int(header.get("raw_bytes", 0)),
ws_payload_bytes=int(header.get("ws_payload_bytes", 0)),
request_prepare_ms=float(header.get("request_prepare_ms", 0.0)),
scheduler_forward_ms=float(header.get("scheduler_forward_ms", 0.0)),
raw_payload_build_ms=float(header.get("raw_payload_build_ms", 0.0)),
raw_write_ms=float(header.get("raw_write_ms", 0.0)),
ws_write_ms=float(header.get("ws_write_ms", 0.0)),
chunk_total_ms=float(header.get("chunk_total_ms", 0.0)),
)
def summarize_realtime_perf_stats(
chunk_stats: list[RealtimeChunkStats],
*,
ignore_initial_chunks: int = 0,
) -> dict[str, float]:
if not chunk_stats:
return {}
if ignore_initial_chunks < 0:
raise ValueError("ignore_initial_chunks must be non-negative")
if ignore_initial_chunks >= len(chunk_stats):
raise ValueError(
"ignore_initial_chunks must leave at least one realtime chunk to guard"
)
ignored_stats = chunk_stats[:ignore_initial_chunks]
guarded_stats = chunk_stats[ignore_initial_chunks:]
metrics = {
"request_prepare_ms": [s.request_prepare_ms for s in guarded_stats],
"scheduler_forward_ms": [s.scheduler_forward_ms for s in guarded_stats],
"raw_payload_build_ms": [s.raw_payload_build_ms for s in guarded_stats],
"raw_write_ms": [s.raw_write_ms for s in guarded_stats],
"ws_write_ms": [s.ws_write_ms for s in guarded_stats],
"chunk_total_ms": [s.chunk_total_ms for s in guarded_stats],
"ws_payload_mb": [s.ws_payload_bytes / (1024 * 1024) for s in guarded_stats],
}
summary: dict[str, float] = {
"num_chunks": float(len(chunk_stats)),
"total_frames": float(sum(s.num_frames for s in chunk_stats)),
"ignored_initial_chunks": float(ignore_initial_chunks),
"guarded_chunks": float(len(guarded_stats)),
}
if ignored_stats:
summary["ignored_max_chunk_total_ms"] = max(
s.chunk_total_ms for s in ignored_stats
)
summary["ignored_max_scheduler_forward_ms"] = max(
s.scheduler_forward_ms for s in ignored_stats
)
for name, values in metrics.items():
sorted_values = sorted(values)
p95_idx = min(len(sorted_values) - 1, int(len(sorted_values) * 0.95))
summary[f"avg_{name}"] = statistics.fmean(values)
summary[f"p95_{name}"] = sorted_values[p95_idx]
summary[f"max_{name}"] = max(values)
return summary
def validate_realtime_perf_stats(
case_id: str,
chunk_stats: list[RealtimeChunkStats],
thresholds: dict[str, float],
*,
ignore_initial_chunks: int = 0,
) -> None:
if not thresholds:
return
summary = summarize_realtime_perf_stats(
chunk_stats, ignore_initial_chunks=ignore_initial_chunks
)
if not summary:
pytest.fail(f"{case_id}: no realtime chunk stats were received")
failures = []
for metric_name, threshold in thresholds.items():
if metric_name not in summary:
raise ValueError(
f"{case_id}: unknown realtime perf metric {metric_name!r}; "
f"available metrics: {sorted(summary)}"
)
actual = summary[metric_name]
if actual > threshold:
failures.append(
f"{metric_name}: actual={actual:.2f}, limit={threshold:.2f}"
)
if failures:
pytest.fail(
f"Realtime performance guard failed for {case_id}:\n"
+ "\n".join(f" - {failure}" for failure in failures)
)
def record_realtime_perf_stats(
case_id: str, chunk_stats: list[RealtimeChunkStats]
) -> None:
_REALTIME_CHUNK_STATS_BY_CASE[case_id] = list(chunk_stats)
def pop_realtime_perf_stats(case_id: str) -> list[RealtimeChunkStats]:
return _REALTIME_CHUNK_STATS_BY_CASE.pop(case_id, [])
def select_realtime_key_frames(frames: list[np.ndarray]) -> list[np.ndarray]:
if not frames:
return []
key_indices = [0, len(frames) // 2, len(frames) - 1]
return [frames[idx].copy() for idx in key_indices]
def record_realtime_key_frames(case_id: str, frames: list[np.ndarray]) -> None:
_REALTIME_KEY_FRAMES_BY_CASE[case_id] = select_realtime_key_frames(frames)
def pop_realtime_key_frames(case_id: str) -> list[np.ndarray] | None:
return _REALTIME_KEY_FRAMES_BY_CASE.pop(case_id, None)
def decode_realtime_raw_rgb_frames(
header: dict[str, Any],
payload: bytes,
previous_frame: bytes | None = None,
) -> list[np.ndarray]:
content_type = header.get("content_type")
if content_type not in (
RAW_RGB_CONTENT_TYPE,
RAW_RGB_DELTA_GZIP_CONTENT_TYPE,
RAW_RGBA_DELTA_GZIP_CONTENT_TYPE,
):
raise ValueError(f"Unsupported realtime frame content type: {content_type}")
width = int(header["width"])
height = int(header["height"])
channels = int(header["channels"])
num_frames = int(header["num_frames"])
bytes_per_frame = int(header["bytes_per_frame"])
expected_size = num_frames * bytes_per_frame
if content_type in (
RAW_RGB_DELTA_GZIP_CONTENT_TYPE,
RAW_RGBA_DELTA_GZIP_CONTENT_TYPE,
):
if header.get("delta_reference") != "previous-frame":
previous_frame = None
payload = restore_delta_gzip_raw_rgb_payload(
payload,
bytes_per_frame=bytes_per_frame,
num_frames=num_frames,
reference_frame=previous_frame,
)
if len(payload) != expected_size:
raise ValueError(
f"Realtime payload size mismatch: expected {expected_size}, got {len(payload)}"
)
frames = []
for frame_idx in range(num_frames):
offset = frame_idx * bytes_per_frame
frame = np.frombuffer(
payload[offset : offset + bytes_per_frame], dtype=np.uint8
)
frame = frame.reshape(height, width, channels)
if channels > 3:
frame = frame[:, :, :3]
frames.append(frame.copy())
return frames
def encode_realtime_frames_to_mp4(frames: list[np.ndarray], fps: int) -> bytes:
if not frames:
raise ValueError("Cannot encode empty realtime frame list")
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
output_path = tmp.name
try:
imageio.mimsave(
output_path,
frames,
fps=fps,
format="mp4",
codec="libx264",
quality=5,
)
return Path(output_path).read_bytes()
finally:
try:
os.remove(output_path)
except OSError:
pass
async def collect_realtime_frames(
*,
ws_url: str,
init_payload: dict[str, Any],
events: list[dict[str, Any]],
num_chunks: int,
) -> list[np.ndarray]:
return (
await collect_realtime_output(
ws_url=ws_url,
init_payload=init_payload,
events=events,
num_chunks=num_chunks,
)
).frames
async def collect_realtime_output(
*,
ws_url: str,
init_payload: dict[str, Any],
events: list[dict[str, Any]],
num_chunks: int,
require_chunk_stats: bool = False,
) -> RealtimeCollectionResult:
try:
import websockets
except ImportError:
pytest.skip("websockets is required for realtime consistency checks")
frames: list[np.ndarray] = []
chunk_stats: list[RealtimeChunkStats] = []
sent_event_indices: set[int] = set()
async def send_events_for_boundary(ws, completed_chunk: int) -> None:
for event_idx, event in enumerate(events):
if event_idx in sent_event_indices:
continue
if int(event.get("after_chunk", 0)) != completed_chunk:
continue
await ws.send(msgspec.msgpack.encode(build_realtime_event_payload(event)))
sent_event_indices.add(event_idx)
async with websockets.connect(ws_url, max_size=None, ping_interval=None) as ws:
await ws.send(msgspec.msgpack.encode(init_payload))
await send_events_for_boundary(ws, -1)
received_chunks: set[int] = set()
previous_frame: bytes | None = None
while len(received_chunks) < num_chunks or (
require_chunk_stats and len(chunk_stats) < len(received_chunks)
):
header_payload = await asyncio.wait_for(
ws.recv(), timeout=_REALTIME_WS_TIMEOUT_SECS
)
header = msgspec.msgpack.decode(header_payload)
message_type = header.get("type")
if message_type == "error":
pytest.fail(f"Realtime generation failed: {header.get('content')}")
if message_type == "chunk_stats":
chunk_stats.append(parse_realtime_chunk_stats(header))
continue
if message_type == "frame_batch":
raw_payload = header.pop("payload", None)
header["type"] = "frame_batch_header"
elif message_type == "frame_batch_header":
raw_payload = await asyncio.wait_for(
ws.recv(), timeout=_REALTIME_WS_TIMEOUT_SECS
)
else:
raise ValueError(f"Unexpected realtime message: {header}")
if not isinstance(raw_payload, bytes):
raise ValueError("Realtime frame payload must be bytes")
chunk_frames = decode_realtime_raw_rgb_frames(
header,
raw_payload,
previous_frame,
)
frames.extend(chunk_frames)
if chunk_frames:
previous_frame = chunk_frames[-1].tobytes()
chunk_index = int(header["chunk_index"])
if header.get("is_final_frame_batch", True):
received_chunks.add(chunk_index)
await send_events_for_boundary(ws, chunk_index)
return RealtimeCollectionResult(frames=frames, chunk_stats=chunk_stats)