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
383 lines
12 KiB
Python
383 lines
12 KiB
Python
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
|
|
import dataclasses
|
|
import json
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from datetime import datetime
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
import torch
|
|
from dateutil.tz import UTC
|
|
|
|
import sglang
|
|
import sglang.multimodal_gen.envs as envs
|
|
from sglang.multimodal_gen.runtime.platforms import current_platform
|
|
from sglang.multimodal_gen.runtime.utils.logging_utils import (
|
|
CYAN,
|
|
RESET,
|
|
_SGLDiffusionLogger,
|
|
get_is_main_process,
|
|
init_logger,
|
|
)
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class MemorySnapshot:
|
|
allocated_mb: float # current allocated memory
|
|
reserved_mb: float # current reserved memory (actual VRAM)
|
|
peak_allocated_mb: float # peak allocated since last reset
|
|
peak_reserved_mb: float # peak reserved since last reset
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"allocated_mb": round(self.allocated_mb, 2),
|
|
"reserved_mb": round(self.reserved_mb, 2),
|
|
"peak_allocated_mb": round(self.peak_allocated_mb, 2),
|
|
"peak_reserved_mb": round(self.peak_reserved_mb, 2),
|
|
}
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class RequestMetrics:
|
|
"""Performance metrics for a single request, including timings and memory snapshots."""
|
|
|
|
def __init__(self, request_id: str):
|
|
self.request_id = request_id
|
|
self.stages: Dict[str, float] = {}
|
|
self.steps: list[float] = []
|
|
self.total_duration_ms: float = 0.0
|
|
self.suppress_stage_breakdown: bool = False
|
|
# memory tracking: {checkpoint_name: MemorySnapshot}
|
|
self.memory_snapshots: Dict[str, MemorySnapshot] = {}
|
|
|
|
@property
|
|
def total_duration_s(self) -> float:
|
|
return self.total_duration_ms / 1000.0
|
|
|
|
def record_stage(self, stage_name: str, duration_s: float):
|
|
"""Records the duration of a pipeline stage"""
|
|
if self.suppress_stage_breakdown:
|
|
return
|
|
self.stages[stage_name] = duration_s * 1000 # Store as milliseconds
|
|
|
|
def record_step(self, duration_s: float):
|
|
"""Records the duration of a denoising step in execution order."""
|
|
if self.suppress_stage_breakdown:
|
|
return
|
|
self.steps.append(duration_s * 1000)
|
|
|
|
def record_memory_snapshot(self, checkpoint_name: str, snapshot: MemorySnapshot):
|
|
if self.suppress_stage_breakdown:
|
|
return
|
|
self.memory_snapshots[checkpoint_name] = snapshot
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Serializes the metrics data to a dictionary."""
|
|
return {
|
|
"request_id": self.request_id,
|
|
"stages": self.stages,
|
|
"steps": self.steps,
|
|
"total_duration_ms": self.total_duration_ms,
|
|
"memory_snapshots": {
|
|
name: snapshot.to_dict()
|
|
for name, snapshot in self.memory_snapshots.items()
|
|
},
|
|
}
|
|
|
|
|
|
def get_diffusion_perf_log_dir() -> str:
|
|
"""
|
|
Determines the directory for performance logs.
|
|
"""
|
|
log_dir = os.environ.get("SGLANG_PERF_LOG_DIR")
|
|
if log_dir:
|
|
return os.path.abspath(log_dir)
|
|
if log_dir is None:
|
|
sglang_path = Path(sglang.__file__).resolve()
|
|
target_path = (sglang_path.parent / "../../.cache/logs").resolve()
|
|
return str(target_path)
|
|
return ""
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def get_git_commit_hash() -> str:
|
|
try:
|
|
commit_hash = os.environ.get("SGLANG_GIT_COMMIT")
|
|
if not commit_hash:
|
|
commit_hash = (
|
|
subprocess.check_output(
|
|
["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL
|
|
)
|
|
.strip()
|
|
.decode("utf-8")
|
|
)
|
|
_CACHED_COMMIT_HASH = commit_hash
|
|
return commit_hash
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
_CACHED_COMMIT_HASH = "N/A"
|
|
return "N/A"
|
|
|
|
|
|
def capture_memory_snapshot() -> MemorySnapshot:
|
|
if not torch.get_device_module().is_available():
|
|
return MemorySnapshot(
|
|
allocated_mb=0.0,
|
|
reserved_mb=0.0,
|
|
peak_allocated_mb=0.0,
|
|
peak_reserved_mb=0.0,
|
|
)
|
|
|
|
allocated = torch.get_device_module().memory_allocated()
|
|
reserved = torch.get_device_module().memory_reserved()
|
|
peak_allocated = torch.get_device_module().max_memory_allocated()
|
|
peak_reserved = torch.get_device_module().max_memory_reserved()
|
|
|
|
return MemorySnapshot(
|
|
allocated_mb=allocated / (1024**2),
|
|
reserved_mb=reserved / (1024**2),
|
|
peak_allocated_mb=peak_allocated / (1024**2),
|
|
peak_reserved_mb=peak_reserved / (1024**2),
|
|
)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class RequestPerfRecord:
|
|
request_id: str
|
|
|
|
timestamp: str
|
|
commit_hash: str
|
|
tag: str
|
|
|
|
stages: list[dict]
|
|
steps: list[float]
|
|
total_duration_ms: float
|
|
memory_snapshots: dict[str, dict] = dataclasses.field(default_factory=dict)
|
|
|
|
def __init__(
|
|
self,
|
|
request_id,
|
|
commit_hash,
|
|
tag,
|
|
stages,
|
|
steps,
|
|
total_duration_ms,
|
|
memory_snapshots=None,
|
|
timestamp=None,
|
|
):
|
|
self.request_id = request_id
|
|
if timestamp is not None:
|
|
self.timestamp = timestamp
|
|
else:
|
|
self.timestamp = datetime.now(UTC).isoformat()
|
|
|
|
self.commit_hash = commit_hash
|
|
self.tag = tag
|
|
self.stages = stages
|
|
self.steps = steps
|
|
self.total_duration_ms = total_duration_ms
|
|
self.memory_snapshots = memory_snapshots or {}
|
|
|
|
|
|
class StageProfiler:
|
|
"""
|
|
A unified context manager, records performance metrics (usually of a single Stage or a step) into a provided RequestMetrics object (usually from a Req).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
stage_name: str,
|
|
logger: _SGLDiffusionLogger,
|
|
metrics: Optional["RequestMetrics"],
|
|
log_stage_start_end: bool = False,
|
|
perf_dump_path_provided: bool = False,
|
|
capture_memory: bool = False,
|
|
record_as_step: bool = False,
|
|
):
|
|
self.stage_name = stage_name
|
|
self.metrics = metrics
|
|
self.logger = logger
|
|
self.start_time = 0.0
|
|
self.log_timing = perf_dump_path_provided or envs.SGLANG_DIFFUSION_STAGE_LOGGING
|
|
self.log_stage_start_end = log_stage_start_end
|
|
self.capture_memory = capture_memory
|
|
self.record_as_step = record_as_step
|
|
|
|
def _should_record_as_step(self) -> bool:
|
|
return self.record_as_step or self.stage_name.startswith("denoising_step_")
|
|
|
|
def __enter__(self):
|
|
if self.log_stage_start_end:
|
|
msg = f"[{self.stage_name}] started..."
|
|
if self.logger.isEnabledFor(logging.DEBUG):
|
|
# This debug-only memory log runs at every stage boundary in CI.
|
|
# Keep it observational; cache cleanup is handled at explicit
|
|
# failure and component-release points.
|
|
available_memory = current_platform.get_available_gpu_memory(
|
|
empty_cache=False
|
|
)
|
|
msg += f" ({round(available_memory, 2)} GB left)"
|
|
self.logger.info(msg)
|
|
|
|
if (self.log_timing and self.metrics) or self.log_stage_start_end:
|
|
if (
|
|
os.environ.get("SGLANG_DIFFUSION_SYNC_STAGE_PROFILING", "0") == "1"
|
|
and self._should_record_as_step()
|
|
and torch.get_device_module().is_available()
|
|
):
|
|
torch.get_device_module().synchronize()
|
|
self.start_time = time.perf_counter()
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if not ((self.log_timing and self.metrics) or self.log_stage_start_end):
|
|
return False
|
|
|
|
if (
|
|
os.environ.get("SGLANG_DIFFUSION_SYNC_STAGE_PROFILING", "0") == "1"
|
|
and self._should_record_as_step()
|
|
and torch.get_device_module().is_available()
|
|
):
|
|
torch.get_device_module().synchronize()
|
|
execution_time_s = time.perf_counter() - self.start_time
|
|
|
|
if exc_type:
|
|
self.logger.error(
|
|
"[%s] Error during execution after %.4f ms: %s",
|
|
self.stage_name,
|
|
execution_time_s * 1000,
|
|
exc_val,
|
|
exc_info=True,
|
|
)
|
|
return False
|
|
|
|
if self.log_stage_start_end:
|
|
self.logger.info(
|
|
f"[{self.stage_name}] finished in {execution_time_s:.4f} seconds",
|
|
)
|
|
|
|
if self.log_timing and self.metrics:
|
|
if self._should_record_as_step():
|
|
self.metrics.record_step(execution_time_s)
|
|
else:
|
|
self.metrics.record_stage(self.stage_name, execution_time_s)
|
|
|
|
# capture memory snapshot after stage if requested
|
|
if self.capture_memory and torch.get_device_module().is_available():
|
|
snapshot = capture_memory_snapshot()
|
|
self.metrics.record_memory_snapshot(
|
|
f"after_{self.stage_name}", snapshot
|
|
)
|
|
|
|
return False
|
|
|
|
|
|
class PerformanceLogger:
|
|
"""
|
|
A global utility class for logging performance metrics for all request, categorized by request-id.
|
|
|
|
Serves both as a runtime logger (stream to file) and a dump utility.
|
|
|
|
Notice that RequestMetrics stores the performance metrics of a single request
|
|
"""
|
|
|
|
@classmethod
|
|
def dump_benchmark_report(
|
|
cls,
|
|
file_path: str,
|
|
metrics: "RequestMetrics",
|
|
meta: Optional[Dict[str, Any]] = None,
|
|
tag: str = "benchmark_dump",
|
|
):
|
|
"""
|
|
Static method to dump a standardized benchmark report to a file.
|
|
Eliminates duplicate logic in CLI/Client code.
|
|
"""
|
|
formatted_steps = [
|
|
{"name": name, "duration_ms": duration_ms}
|
|
for name, duration_ms in metrics.stages.items()
|
|
]
|
|
|
|
denoise_steps_ms = [
|
|
{"step": idx, "duration_ms": duration_ms}
|
|
for idx, duration_ms in enumerate(metrics.steps)
|
|
]
|
|
|
|
memory_checkpoints = {
|
|
name: snapshot.to_dict()
|
|
for name, snapshot in metrics.memory_snapshots.items()
|
|
}
|
|
|
|
report = {
|
|
"timestamp": datetime.now(UTC).isoformat(),
|
|
"request_id": metrics.request_id,
|
|
"commit_hash": get_git_commit_hash(),
|
|
"tag": tag,
|
|
"total_duration_ms": metrics.total_duration_ms,
|
|
"steps": formatted_steps,
|
|
"denoise_steps_ms": denoise_steps_ms,
|
|
"memory_checkpoints": memory_checkpoints,
|
|
"meta": meta or {},
|
|
}
|
|
|
|
try:
|
|
abs_path = os.path.abspath(file_path)
|
|
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
|
|
with open(abs_path, "w", encoding="utf-8") as f:
|
|
json.dump(report, f, indent=2)
|
|
logger.info(f"Metrics dumped to: {CYAN}{abs_path}{RESET}")
|
|
except IOError as e:
|
|
logger.error(f"Failed to dump metrics to {abs_path}: {e}")
|
|
|
|
@classmethod
|
|
def log_request_summary(
|
|
cls,
|
|
metrics: "RequestMetrics",
|
|
tag: str = "total_inference_time",
|
|
):
|
|
"""logs the stage metrics and total duration for a completed request
|
|
to the performance_log file.
|
|
|
|
Note that this accords to the time spent internally in server, postprocess is not included
|
|
"""
|
|
formatted_stages = [
|
|
{"name": name, "execution_time_ms": duration_ms}
|
|
for name, duration_ms in metrics.stages.items()
|
|
]
|
|
|
|
memory_checkpoints = {
|
|
name: snapshot.to_dict()
|
|
for name, snapshot in metrics.memory_snapshots.items()
|
|
}
|
|
|
|
record = RequestPerfRecord(
|
|
metrics.request_id,
|
|
commit_hash=get_git_commit_hash(),
|
|
tag="pipeline_stage_metrics",
|
|
stages=formatted_stages,
|
|
steps=metrics.steps,
|
|
total_duration_ms=metrics.total_duration_ms,
|
|
memory_snapshots=memory_checkpoints,
|
|
)
|
|
|
|
try:
|
|
if get_is_main_process():
|
|
log_dir = get_diffusion_perf_log_dir()
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
|
|
log_file = os.path.join(log_dir, "performance.log")
|
|
|
|
with open(log_file, "a", encoding="utf-8") as f:
|
|
f.write(json.dumps(dataclasses.asdict(record)) + "\n")
|
|
|
|
except (OSError, PermissionError) as e:
|
|
print(f"WARNING: Failed to log performance record: {e}", file=sys.stderr)
|