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

262 lines
7.6 KiB
Python

from __future__ import annotations
import gzip
import json
import logging
import os
import shutil
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Optional
import torch
from sglang.srt.managers.io_struct import ProfileReqOutput
from sglang.srt.utils.tensor_bridge import use_mlx
logger = logging.getLogger(__name__)
@dataclass
class MetalCaptureProfiler:
label: str
trace_path: Path
stop_capture: Callable[[], None]
standalone: bool
@classmethod
def start_mlx(cls, trace_path: Path):
trace_path.parent.mkdir(parents=True, exist_ok=True)
try:
import mlx.core as mx
mx.metal.start_capture(str(trace_path))
except RuntimeError as e:
return None, _capture_error("MLX", e)
return cls._started(
label="MLX",
trace_path=trace_path,
stop_capture=mx.metal.stop_capture,
standalone=True,
)
@classmethod
def start_mps(cls, trace_path: Path):
trace_path.parent.mkdir(parents=True, exist_ok=True)
try:
if not hasattr(torch, "mps") or not hasattr(torch.mps, "profiler"):
raise RuntimeError("torch.mps.profiler is not available")
context = torch.mps.profiler.metal_capture(str(trace_path))
context.__enter__()
except RuntimeError as e:
return None, _capture_error("MPS", e)
return cls._started(
label="MPS",
trace_path=trace_path,
stop_capture=lambda: context.__exit__(None, None, None),
standalone=False,
)
@classmethod
def _started(
cls,
*,
label: str,
trace_path: Path,
stop_capture: Callable[[], None],
standalone: bool,
):
profiler = cls(
label=label,
trace_path=trace_path,
stop_capture=stop_capture,
standalone=standalone,
)
logger.info("%s Metal capture started, saving to %s", label, trace_path)
return profiler, ProfileReqOutput(success=True, message="Succeeded")
def stop(self) -> str:
self.stop_capture()
logger.info(
"%s Metal capture stopped. Trace saved to: %s",
self.label,
self.trace_path,
)
return f" Metal trace: {self.trace_path}"
def _capture_error(label: str, error: RuntimeError) -> ProfileReqOutput:
return ProfileReqOutput(
success=False,
message=(
f"Failed to start {label} Metal capture: {error}. "
"Set MTL_CAPTURE_ENABLED=1 in the server's environment "
"before launching to enable GPU trace capture."
),
)
class MetalTorchProfiler:
def __init__(
self,
*,
start_metal_capture: Callable[[Path], tuple[Any, ProfileReqOutput]],
torch_profiler: Optional[Any] = None,
):
self.start_metal_capture = start_metal_capture
self.torch_profiler = torch_profiler
self.metal_profiler = None
def start(self):
trace_path = _new_temp_gputrace_path()
self.metal_profiler, result = self.start_metal_capture(trace_path)
if not result.success:
raise RuntimeError(result.message)
if self.torch_profiler is not None:
try:
self.torch_profiler.start()
except Exception:
self.metal_profiler.stop()
raise
def stop(self):
try:
if self.torch_profiler is not None:
self.torch_profiler.stop()
finally:
if self.metal_profiler is not None:
self.metal_profiler.stop()
def export_chrome_trace(self, path: str):
if self.torch_profiler is not None:
self.torch_profiler.export_chrome_trace(path)
else:
_write_empty_chrome_trace(path)
if self.metal_profiler is None:
return
final_path = _unique_gputrace_path_for_chrome_trace(path)
final_path.parent.mkdir(parents=True, exist_ok=True)
if self.metal_profiler.trace_path.exists():
shutil.move(str(self.metal_profiler.trace_path), str(final_path))
logger.info("Metal trace saved to: %s", final_path)
def apply_metal_profiler_patches() -> None:
if getattr(torch.profiler.profile, "_sglang_metal_patched", False):
return
original_profile = torch.profiler.profile
def profile(*args, **kwargs):
activities = _get_activities(args, kwargs)
if not _has_cuda_activity(activities):
return original_profile(*args, **kwargs)
if use_mlx():
return MetalTorchProfiler(
start_metal_capture=MetalCaptureProfiler.start_mlx
)
torch_activities = [
activity for activity in activities if not _is_cuda_activity(activity)
]
torch_profiler = None
if torch_activities:
patched_args, patched_kwargs = _replace_activities(
args, kwargs, torch_activities
)
torch_profiler = original_profile(*patched_args, **patched_kwargs)
return MetalTorchProfiler(
start_metal_capture=MetalCaptureProfiler.start_mps,
torch_profiler=torch_profiler,
)
profile._sglang_metal_patched = True
profile._sglang_original_profile = original_profile
torch.profiler.profile = profile
def _get_activities(args, kwargs):
if "activities" in kwargs:
return kwargs["activities"]
if args:
return args[0]
return None
def _replace_activities(args, kwargs, activities):
kwargs = dict(kwargs)
if "activities" in kwargs:
kwargs["activities"] = activities
return args, kwargs
if args:
args = list(args)
args[0] = activities
return tuple(args), kwargs
kwargs["activities"] = activities
return args, kwargs
def _has_cuda_activity(activities) -> bool:
if activities is None:
return False
return any(_is_cuda_activity(activity) for activity in activities)
def _is_cuda_activity(activity) -> bool:
return activity == torch.profiler.ProfilerActivity.CUDA
def _new_temp_gputrace_path() -> Path:
output_dir = Path(os.getenv("SGLANG_TORCH_PROFILER_DIR", "/tmp")).expanduser()
output_dir.mkdir(parents=True, exist_ok=True)
for i in range(100):
candidate = (
output_dir / f"sglang-metal-{os.getpid()}-{time.time_ns()}-{i}.gputrace"
)
if not candidate.exists():
return candidate
raise RuntimeError(f"Cannot find an unused Metal trace path in {output_dir}")
def _unique_gputrace_path_for_chrome_trace(path: str) -> Path:
chrome_path = Path(path).expanduser()
name = chrome_path.name
if name.endswith(".trace.json.gz"):
name = name[: -len(".trace.json.gz")] + ".gputrace"
else:
name = chrome_path.stem + ".gputrace"
base = chrome_path.with_name(name)
if not base.exists():
return base
stem = base.name[: -len(".gputrace")]
for i in range(100):
candidate = base.with_name(f"{stem}-{time.time_ns()}-{i}.gputrace")
if not candidate.exists():
return candidate
raise RuntimeError(f"Cannot find an unused Metal trace path for {base}")
def _write_empty_chrome_trace(path: str):
trace = {"traceEvents": []}
Path(path).expanduser().parent.mkdir(parents=True, exist_ok=True)
if str(path).endswith(".gz"):
with gzip.open(path, "wt") as f:
json.dump(trace, f)
else:
with open(path, "w") as f:
json.dump(trace, f)