chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
View File
+33
View File
@@ -0,0 +1,33 @@
import argparse
from sglang.cli.utils import get_is_diffusion_model, get_model_path
def generate(args, extra_argv):
# If help is requested, show generate subcommand help without requiring --model-path
if any(h in extra_argv for h in ("-h", "--help")):
from sglang.multimodal_gen.runtime.entrypoints.cli.generate import (
add_multimodal_gen_generate_args,
)
parser = argparse.ArgumentParser(description="SGLang Multimodal Generation")
add_multimodal_gen_generate_args(parser)
parser.parse_args(extra_argv)
return
model_path = get_model_path(extra_argv)
is_diffusion_model = get_is_diffusion_model(model_path)
if is_diffusion_model:
from sglang.multimodal_gen.runtime.entrypoints.cli.generate import (
add_multimodal_gen_generate_args,
generate_cmd,
)
parser = argparse.ArgumentParser(description="SGLang Multimodal Generation")
add_multimodal_gen_generate_args(parser)
parsed_args, unknown_args = parser.parse_known_args(extra_argv)
generate_cmd(parsed_args, unknown_args)
else:
raise Exception(
f"Generate subcommand is not yet supported for model: {model_path}"
)
+457
View File
@@ -0,0 +1,457 @@
#!/usr/bin/env python3
"""Kill SGLang processes on CUDA_VISIBLE_DEVICES GPUs (CI mode only).
Called at the start of every CI job to clean up orphaned processes from
previous (possibly cancelled) runs. Requires SGLANG_IS_IN_CI=true.
For local/non-CI usage, use scripts/killall_sglang.sh instead.
Usage:
python killall.py
Exit codes:
0 - Clean: all target GPUs have <10% memory usage after cleanup
1 - Dirty: GPU memory still >10% after cleanup, indicating stuck processes
or orphaned CUDA contexts that need a container restart
"""
import os
import re
import signal
import subprocess
import sys
import time
from pathlib import Path
# Constants
MEMORY_THRESHOLD_PCT = 10
# Patterns matching SGLang process command lines (equivalent to pgrep -f in killall_sglang.sh)
_SGLANG_PROCESS_PATTERNS = re.compile(
r"sglang::|sglang\.launch_server|sglang\.bench|sglang\.data_parallel|sglang\.srt|sgl_diffusion::|sglang serve"
)
# Boxed output helpers
_LOG_LINES = []
def _log(msg=""):
"""Buffer a line for boxed output."""
_LOG_LINES.append(msg)
def _flush_box(title, status=""):
"""Print all buffered lines inside a box, then clear buffer."""
lines = _LOG_LINES.copy()
_LOG_LINES.clear()
all_text = [title] + ([status] if status else []) + lines
width = max((len(line) for line in all_text), default=40) + 4
width = max(width, 60)
h_bar = "" * (width - 2)
print(f"\n{h_bar}")
print(f"{title:<{width - 3}}")
print(f"{h_bar}")
for line in lines:
print(f"{line:<{width - 3}}")
if status:
print(f"{h_bar}")
print(f"{status:<{width - 3}}")
print(f"{h_bar}")
# nvidia-smi helpers
def _run_smi(query, query_type="gpu"):
"""Run nvidia-smi query and return raw CSV lines."""
flag = "--query-gpu" if query_type == "gpu" else "--query-compute-apps"
try:
out = subprocess.check_output(
["nvidia-smi", f"{flag}={query}", "--format=csv,noheader,nounits"],
text=True,
timeout=10,
)
return [line.strip() for line in out.strip().splitlines() if line.strip()]
except (subprocess.SubprocessError, FileNotFoundError):
return []
def _get_smi_version():
"""Return nvidia-smi driver version and GPU name, or None on failure."""
# Inline nvidia-smi query — killall.py runs before pip install, so sglang
# internals may not be importable.
try:
result = subprocess.run(
[
"nvidia-smi",
"--query-gpu=driver_version",
"--format=csv,noheader,nounits",
],
capture_output=True,
text=True,
check=True,
timeout=10,
)
driver = result.stdout.strip().split("\n")[0].strip() or None
except (subprocess.SubprocessError, FileNotFoundError):
driver = None
if driver is None:
return None
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
text=True,
timeout=10,
)
gpu_name = out.strip().splitlines()[0].strip() if out.strip() else "unknown"
except (subprocess.SubprocessError, FileNotFoundError, IndexError):
gpu_name = "unknown"
return f"driver {driver}, {gpu_name}"
def _get_target_gpus():
"""Return GPU indices from CUDA_VISIBLE_DEVICES, or all visible GPUs.
Note: only numeric indices are supported (e.g. "0,1,2").
UUID-style CUDA_VISIBLE_DEVICES values (e.g. "GPU-d4f1...") are not handled.
"""
cvd = os.environ.get("CUDA_VISIBLE_DEVICES")
if cvd is not None and cvd.strip():
return {int(g.strip()) for g in cvd.split(",") if g.strip().isdigit()}
return {int(line) for line in _run_smi("index") if line.isdigit()}
def _get_gpu_pids(gpu_indices):
"""Return PIDs using the specified GPUs (by index)."""
target_uuids = set()
for line in _run_smi("index,uuid"):
parts = line.split(",", 1)
if len(parts) == 2 and parts[0].strip().isdigit():
if int(parts[0].strip()) in gpu_indices:
target_uuids.add(parts[1].strip())
pids = set()
for line in _run_smi("gpu_uuid,pid", query_type="apps"):
parts = line.split(",", 1)
if len(parts) == 2 and parts[0].strip() in target_uuids:
pid = parts[1].strip()
if pid.isdigit():
pids.add(int(pid))
return pids
def _get_gpu_memory(gpu_indices):
"""Query memory usage for target GPUs.
Returns list of (idx, used_mib, total_mib, pct) tuples.
"""
result = []
for line in _run_smi("index,memory.used,memory.total"):
parts = line.split(",")
if len(parts) != 3 or not parts[0].strip().isdigit():
continue
idx = int(parts[0].strip())
if idx not in gpu_indices:
continue
try:
used, total = int(float(parts[1].strip())), int(float(parts[2].strip()))
except ValueError:
continue
pct = used / total * 100 if total > 0 else 0
result.append((idx, used, total, pct))
return result
def _get_dirty_gpus(gpu_indices):
"""Return list of dirty GPU description strings (memory >= threshold)."""
return [
f"GPU {idx} ({pct:.0f}%)"
for idx, _, _, pct in _get_gpu_memory(gpu_indices)
if pct >= MEMORY_THRESHOLD_PCT
]
def _log_gpu_memory(gpu_indices):
"""Log memory usage for all target GPUs and return dirty GPU descriptions."""
dirty = []
for idx, used, total, pct in _get_gpu_memory(gpu_indices):
_log(f" GPU {idx}: {used} MiB / {total} MiB ({pct:.0f}%)")
if pct >= MEMORY_THRESHOLD_PCT:
dirty.append(f"GPU {idx} ({pct:.0f}%)")
return dirty
# /proc helpers
def _read_proc_cmdline(pid):
"""Read /proc/{pid}/cmdline and return as decoded string, or None on failure."""
try:
raw = Path(f"/proc/{pid}/cmdline").read_bytes()
return raw.decode("utf-8", errors="replace").replace("\x00", " ")
except (FileNotFoundError, PermissionError):
return None
def _get_pid_cmdline(pid):
"""Get truncated command line for a PID."""
cmdline = _read_proc_cmdline(pid)
if cmdline is None:
return "<unknown>"
cmdline = cmdline.strip()
return cmdline[:120] + ("..." if len(cmdline) > 120 else "")
def _find_sglang_pids_by_name():
"""Find SGLang process PIDs by command-line pattern matching.
Scans /proc/*/cmdline for patterns matching known SGLang entry points.
Equivalent to: pgrep -f 'sglang::|sglang.launch_server|...'
Safe in shared-GPU containers: without --pid=host, /proc only exposes
processes in our own PID namespace, so this cannot kill other containers.
"""
my_pid = os.getpid()
pids = set()
for entry in Path("/proc").iterdir():
if not entry.name.isdigit():
continue
pid = int(entry.name)
if pid <= 1 or pid == my_pid:
continue
cmdline = _read_proc_cmdline(pid)
if cmdline and _SGLANG_PROCESS_PATTERNS.search(cmdline):
pids.add(pid)
return pids
def _check_pid_namespace(pid):
"""Check if a PID is in our PID namespace. Linux-only via /proc."""
try:
my_ns = os.readlink("/proc/self/ns/pid")
except OSError:
return "unknown (can't read self ns)"
try:
target_ns = os.readlink(f"/proc/{pid}/ns/pid")
except FileNotFoundError:
return f"NOT in our namespace (pid not in /proc, self={my_ns})"
except PermissionError:
return "unknown (no permission to read ns)"
if my_ns == target_ns:
return f"same namespace ({my_ns})"
return f"DIFFERENT namespace (self={my_ns}, target={target_ns})"
def _get_orchestrator_ancestors(pids):
"""Walk process tree upward from PIDs, return ancestors that are test orchestrators.
Linux-only: reads /proc filesystem. Returns empty set on other platforms.
"""
orchestrator_patterns = ["run_suite.py", "run_tests.py"]
ancestors, visited = set(), set()
for pid in pids:
current = pid
while current > 1 and current not in visited:
visited.add(current)
cmdline = _read_proc_cmdline(current)
if cmdline is None:
break
if any(p in cmdline for p in orchestrator_patterns):
ancestors.add(current)
try:
current = int(Path(f"/proc/{current}/stat").read_text().split()[3])
except (FileNotFoundError, PermissionError, IndexError, ValueError):
break
return ancestors
# Kill & diagnostic helpers
def _kill_pids(pids, label="", quiet=False):
"""Send SIGKILL to PIDs, skipping self and init.
Returns dict of {pid: exception_name} for PIDs that could not be killed.
When quiet=True, does not log individual kill results.
"""
my_pid = os.getpid()
pids = {p for p in pids if p != my_pid and p > 1}
if not pids:
return {}
if label and not quiet:
_log(f" Killing {label}:")
failed = {}
for pid in sorted(pids):
try:
os.kill(pid, signal.SIGKILL)
if not quiet:
_log(f" PID {pid}: killed ({_get_pid_cmdline(pid)})")
except (ProcessLookupError, PermissionError) as e:
failed[pid] = type(e).__name__
if not quiet:
_log(f" PID {pid}: failed ({type(e).__name__})")
return failed
def _get_ps_diagnostic():
"""Return ps auxf output filtered for GPU/sglang-related processes."""
try:
out = subprocess.run(["ps", "auxf"], capture_output=True, text=True, timeout=5)
return [
line.strip()[:140]
for line in out.stdout.splitlines()
if any(k in line.lower() for k in ["sglang", "python", "cuda", "gpu"])
][:20]
except (subprocess.SubprocessError, FileNotFoundError):
return []
def _print_diagnostics(unkillable_pids):
"""Print detailed diagnostics after the FAIL box (to stdout, outside box)."""
if unkillable_pids:
print("\n[killall] Diagnostic — unkillable PIDs:")
for pid in sorted(unkillable_pids):
ns_info = _check_pid_namespace(pid)
print(f" PID {pid}: ns: {ns_info}")
ps_lines = _get_ps_diagnostic()
if ps_lines:
print("\n[killall] Diagnostic — processes in this container (ps auxf):")
for line in ps_lines:
print(f" {line}")
else:
print(
"\n[killall] Diagnostic — no sglang/python/gpu processes "
"in this container"
)
# CI mode
def _kill_all_targets(gpu_indices, gpu_pids):
"""Kill all target processes: name-matched, orchestrator ancestors, GPU processes."""
# Kill name-matched SGLang processes (catches processes not visible to nvidia-smi)
name_only = _find_sglang_pids_by_name() - gpu_pids
if name_only:
_kill_pids(name_only, "name-matched SGLang processes")
time.sleep(1)
_log()
# Kill orchestrator ancestors first, then GPU processes (retry once)
if gpu_pids:
_kill_pids(_get_orchestrator_ancestors(gpu_pids), "orchestrator ancestors")
time.sleep(1)
for attempt in range(2):
current_pids = _get_gpu_pids(gpu_indices)
if not current_pids:
break
label = "GPU processes" if attempt == 0 else "stubborn GPU processes"
_kill_pids(current_pids, label)
time.sleep(3)
_log()
def _verify_gpu_clean(gpu_indices):
"""Retry loop: wait for GPUs to become clean.
Returns (dirty_list, unkillable_pids, elapsed_seconds).
"""
max_wait_secs = 100
retry_interval = 10
elapsed = 0
dirty = None
unkillable_pids = {}
while True:
dirty = _get_dirty_gpus(gpu_indices)
remaining_pids = _get_gpu_pids(gpu_indices)
if not dirty:
_log(f"Check at {elapsed}s: GPUs clean")
break
dirty_summary = ", ".join(dirty)
if elapsed >= max_wait_secs:
remaining_info = (
f", {len(remaining_pids)} processes remaining" if remaining_pids else ""
)
_log(f"Check at {elapsed}s: still dirty [{dirty_summary}]{remaining_info}")
break
# Kill remaining processes before waiting (silently for retries)
if remaining_pids:
failed = _kill_pids(remaining_pids, quiet=True)
unkillable_pids.update(failed)
print(
f"[killall] GPUs still dirty at {elapsed}s [{dirty_summary}], "
f"retrying in {retry_interval}s "
f"({elapsed + retry_interval}/{max_wait_secs}s)..."
)
time.sleep(retry_interval)
elapsed += retry_interval
if unkillable_pids:
parts = [f"{p} ({unkillable_pids[p]})" for p in sorted(unkillable_pids)]
_log(f" Unkillable PIDs: {', '.join(parts)}")
return dirty, unkillable_pids, elapsed
def _ci_mode():
"""GPU-scoped kill, abort if GPUs remain dirty."""
gpu_indices = _get_target_gpus()
if not gpu_indices:
_log("No GPUs detected, skipping cleanup")
_flush_box("killall_sglang", status="SKIP")
return 0
cvd = os.environ.get("CUDA_VISIBLE_DEVICES")
gpu_list = ", ".join(str(g) for g in sorted(gpu_indices))
smi_info = _get_smi_version()
if smi_info:
_log(f"nvidia-smi: {smi_info}")
if cvd is None or not cvd.strip():
_log(
"WARNING: CUDA_VISIBLE_DEVICES is not set. "
"Falling back to all visible GPUs."
)
_log("This may kill processes from other CI jobs on shared hosts.")
else:
_log(f"CUDA_VISIBLE_DEVICES={cvd}")
_log()
# Log pre-cleanup state
_log("Before cleanup:")
_log_gpu_memory(gpu_indices)
gpu_pids = _get_gpu_pids(gpu_indices)
if not gpu_pids:
_log(" No processes on target GPUs")
else:
_log(f" Processes ({len(gpu_pids)}):")
for pid in sorted(gpu_pids):
_log(f" PID {pid}: {_get_pid_cmdline(pid)}")
_log()
# Kill phase
_kill_all_targets(gpu_indices, gpu_pids)
# Verify phase
dirty, unkillable_pids, elapsed = _verify_gpu_clean(gpu_indices)
if dirty:
_log()
_log("Final GPU memory:")
_log_gpu_memory(gpu_indices)
_log(f"ERROR: memory >={MEMORY_THRESHOLD_PCT}%: {', '.join(dirty)}")
_log(f"Orphaned CUDA contexts after {elapsed}s — container needs restart.")
_flush_box(f"killall_sglang: GPUs [{gpu_list}]", status="FAIL — Aborting CI")
_print_diagnostics(unkillable_pids)
return 1
_flush_box(f"killall_sglang: GPUs [{gpu_list}]", status="PASS — GPUs clean")
return 0
# Entry point
def main():
return _ci_mode()
if __name__ == "__main__":
sys.exit(main())
+46
View File
@@ -0,0 +1,46 @@
import argparse
from sglang.cli.utils import get_git_commit_hash
from sglang.version import __version__
def version(args, extra_argv):
print(f"sglang version: {__version__}")
print(f"git revision: {get_git_commit_hash()[:7]}")
def main():
parser = argparse.ArgumentParser()
# complex sub commands
subparsers = parser.add_subparsers(dest="subcommand", required=True)
subparsers.add_parser(
"serve",
help="Launch an SGLang server.",
add_help=False,
)
subparsers.add_parser(
"generate",
help="Run inference on a multimodal model.",
add_help=False,
)
# simple commands
version_parser = subparsers.add_parser(
"version",
help="Show the version information.",
)
version_parser.set_defaults(func=version)
args, extra_argv = parser.parse_known_args()
if args.subcommand == "serve":
from sglang.cli.serve import serve
serve(args, extra_argv)
elif args.subcommand == "generate":
from sglang.cli.generate import generate
generate(args, extra_argv)
elif args.subcommand == "version":
version(args, extra_argv)
+143
View File
@@ -0,0 +1,143 @@
# SPDX-License-Identifier: Apache-2.0
import argparse
import logging
import os
from sglang.cli.utils import get_is_diffusion_model, get_model_path
from sglang.srt.utils import kill_process_tree
from sglang.srt.utils.common import suppress_noisy_warnings
suppress_noisy_warnings()
logger = logging.getLogger(__name__)
def _extract_model_type_override(extra_argv):
"""Extract and remove --model-type override from argv."""
model_type = "auto"
filtered_argv = []
i = 0
while i < len(extra_argv):
arg = extra_argv[i]
if arg == "--model-type":
if i + 1 >= len(extra_argv):
raise Exception(
"Error: --model-type requires a value. "
"Valid values are: auto, llm, diffusion."
)
model_type = extra_argv[i + 1]
i += 2
continue
if arg.startswith("--model-type="):
model_type = arg.split("=", 1)[1]
i += 1
continue
filtered_argv.append(arg)
i += 1
if model_type not in ("auto", "llm", "diffusion"):
raise Exception(
f"Error: invalid --model-type '{model_type}'. "
"Valid values are: auto, llm, diffusion."
)
return model_type, filtered_argv
def _normalize_positional_model_path(extra_argv):
"""Allow `sglang serve <model>` while preserving existing flag parsing."""
if extra_argv and not extra_argv[0].startswith("-"):
return ["--model-path", extra_argv[0], *extra_argv[1:]], True
return extra_argv, False
def serve(args, extra_argv):
if any(h in extra_argv for h in ("-h", "--help")):
# Since the server type is determined by the model, and we don't have a model path,
# we can't show the exact help. Instead, we show a general help message and then
# the help for both possible server types.
print(
"Usage: sglang serve <model-name-or-path> [additional-arguments]\n"
" or: sglang serve --model-path <model-name-or-path> [additional-arguments]\n\n"
"This command can launch either a standard language model server or a diffusion model server.\n"
"The server type is determined by the --model-path.\n"
"Optional override: --model-type {auto,llm,diffusion} "
"(default: auto, fallback to LLM on detection failure)."
)
print("\n--- Help for Standard Language Model Server ---")
from sglang.srt.server_args import prepare_server_args
try:
prepare_server_args(["--help"])
except SystemExit:
pass # argparse --help calls sys.exit
print("\n--- Help for Diffusion Model Server ---")
try:
from sglang.multimodal_gen.runtime.entrypoints.cli.serve import (
add_multimodal_gen_serve_args,
)
parser = argparse.ArgumentParser(
prog="sglang serve",
description="SGLang Diffusion Model Serving",
)
add_multimodal_gen_serve_args(parser)
parser.print_help()
except ImportError:
print(
"Diffusion model support is not available. "
'Install with: pip install "sglang[diffusion]"'
)
return
from sglang.srt.plugins import load_plugins
load_plugins()
model_type, dispatch_argv = _extract_model_type_override(extra_argv)
dispatch_argv, positional_model_path = _normalize_positional_model_path(
dispatch_argv
)
model_path = get_model_path(dispatch_argv)
try:
if model_type == "auto":
is_diffusion_model = get_is_diffusion_model(model_path)
if is_diffusion_model:
logger.info("Diffusion model detected")
else:
is_diffusion_model = model_type == "diffusion"
logger.info(
"Dispatch override enabled: --model-type=%s " "(skip auto detection)",
model_type,
)
if is_diffusion_model:
# Logic for Diffusion Models
from sglang.multimodal_gen.runtime.entrypoints.cli.serve import (
add_multimodal_gen_serve_args,
execute_serve_cmd,
)
parser = argparse.ArgumentParser(
description="SGLang Diffusion Model Serving"
)
add_multimodal_gen_serve_args(parser)
parsed_args, remaining_argv = parser.parse_known_args(dispatch_argv)
if positional_model_path:
parsed_args._sglang_explicit_arg_names = {"model_path"}
execute_serve_cmd(parsed_args, remaining_argv)
else:
# Logic for Standard Language Models
from sglang.launch_server import run_server
from sglang.srt.server_args import prepare_server_args
server_args = prepare_server_args(dispatch_argv)
run_server(server_args)
finally:
kill_process_tree(os.getpid(), include_parent=False)
+146
View File
@@ -0,0 +1,146 @@
import json
import logging
import os
import subprocess
from functools import lru_cache
from huggingface_hub import HfApi
from sglang.srt.environ import envs
from sglang.utils import (
has_diffusion_overlay_registry_match,
is_known_non_diffusers_diffusion_model,
load_diffusion_overlay_registry_from_env,
)
logger = logging.getLogger(__name__)
@lru_cache(maxsize=1)
def _load_overlay_registry() -> dict:
return load_diffusion_overlay_registry_from_env()
def _is_overlay_diffusion_model(model_path: str) -> bool:
return has_diffusion_overlay_registry_match(model_path, _load_overlay_registry())
def _is_registered_diffusion_model(model_path: str) -> bool:
try:
from sglang.multimodal_gen.registry import has_registered_diffusion_model_path
except ImportError:
# if diffusion dependencies are not installed
return False
return has_registered_diffusion_model_path(model_path)
def _is_diffusers_model_dir(model_dir: str) -> bool:
"""Check if a local directory contains a valid diffusers model_index.json."""
config_path = os.path.join(model_dir, "model_index.json")
if not os.path.exists(config_path):
return False
with open(config_path) as f:
config = json.load(f)
return "_diffusers_version" in config
def _is_gated_diffusion_repo(repo_id: str) -> bool:
"""Query HF model card metadata to check if a gated repo is a diffusers model."""
try:
info = HfApi().model_info(repo_id)
return getattr(info, "library_name", None) == "diffusers"
except Exception:
return False
def get_is_diffusion_model(model_path: str) -> bool:
"""Detect whether model_path points to a diffusion model.
For local directories, checks the filesystem directly.
For HF/ModelScope model IDs, attempts to fetch only model_index.json.
For gated repos where file download fails, falls back to HF model card
metadata (library_name == "diffusers").
Returns False on any failure (network error, 404, offline mode, etc.)
so that the caller falls through to the standard LLM server path.
"""
if _is_overlay_diffusion_model(model_path):
# short-circuit, if applicable for the overlay mechanism (diffusion-only)
return True
if os.path.isdir(model_path):
if _is_diffusers_model_dir(model_path):
return True
return is_known_non_diffusers_diffusion_model(model_path)
if is_known_non_diffusers_diffusion_model(model_path):
return True
if _is_registered_diffusion_model(model_path):
return True
try:
if envs.SGLANG_USE_MODELSCOPE.get():
from modelscope import model_file_download
file_path = model_file_download(
model_id=model_path, file_path="model_index.json"
)
else:
from huggingface_hub import hf_hub_download
file_path = hf_hub_download(repo_id=model_path, filename="model_index.json")
return _is_diffusers_model_dir(os.path.dirname(file_path))
except Exception as e:
logger.debug("Failed to auto-detect diffusion model for %s: %s", model_path, e)
return False
def get_model_path(extra_argv):
# Find the model_path argument
model_path = None
for i, arg in enumerate(extra_argv):
if arg in ("--model-path", "--model"):
if i + 1 < len(extra_argv):
model_path = extra_argv[i + 1]
break
elif arg.startswith("--model-path=") or arg.startswith("--model="):
model_path = arg.split("=", 1)[1]
break
if model_path is None:
# Fallback for --help or other cases where model-path is not provided
if any(h in extra_argv for h in ["-h", "--help"]):
raise Exception(
"Usage: sglang serve --model-path <model-name-or-path> [additional-arguments]\n\n"
"This command can launch either a standard language model server or a diffusion model server.\n"
"The server type is determined by the --model-path.\n"
)
else:
raise Exception(
"Error: --model-path is required. "
"Please provide the path to the model."
)
return model_path
@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"