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
193 lines
6.2 KiB
Python
Executable File
193 lines
6.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Refresh est_time literals from sglang-ci-stats/model.json.
|
|
|
|
Usage:
|
|
python scripts/ci/update_est_time.py [--dry-run] \\
|
|
[--model-url URL] [--summary-file PATH]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
DEFAULT_MODEL_URL = (
|
|
"https://raw.githubusercontent.com/sgl-project/sglang-ci-stats/main/model.json"
|
|
)
|
|
|
|
# AMD / NPU live in separate workflows and are not scraped by sglang-ci-stats.
|
|
BACKENDS = ("cuda", "cpu")
|
|
|
|
# A change is "significant" if |delta| >= this many seconds AND the relative
|
|
# change is at least SIGNIFICANT_REL_DELTA. Dual threshold filters out both
|
|
# tiny absolute drifts on long tests and small-but-noisy relative swings on
|
|
# short tests.
|
|
SIGNIFICANT_ABS_DELTA = 30
|
|
SIGNIFICANT_REL_DELTA = 0.3
|
|
|
|
|
|
def fetch_model(url):
|
|
"""Curl model.json. Fail loudly on network or parse errors -- the
|
|
weekly workflow will surface the failure rather than silently making
|
|
a no-op PR."""
|
|
out = subprocess.run(
|
|
["curl", "--fail", "--silent", "--show-error", "--max-time", "30", url],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
return json.loads(out.stdout)
|
|
|
|
|
|
def make_patterns(suite):
|
|
"""Yield regex objects that match `register_{backend}_ci(est_time=N, ...)`
|
|
for the given suite, covering both registration styles:
|
|
|
|
legacy: register_X_ci(est_time=N, suite="<full-suite>")
|
|
new: register_X_ci(est_time=N, stage="<stage>", runner_config="<rc>")
|
|
"""
|
|
stage_rc = None
|
|
if "-test-" in suite:
|
|
stage, _, rc = suite.partition("-test-")
|
|
stage_rc = (stage, rc)
|
|
for backend in BACKENDS:
|
|
yield re.compile(
|
|
rf"(register_{backend}_ci\(est_time=)(\d+)"
|
|
rf'(,\s*suite="{re.escape(suite)}")'
|
|
)
|
|
if stage_rc is not None:
|
|
stage, rc = stage_rc
|
|
yield re.compile(
|
|
rf"(register_{backend}_ci\(est_time=)(\d+)"
|
|
rf'(,\s*stage="{re.escape(stage)}",\s*runner_config="{re.escape(rc)}")'
|
|
)
|
|
|
|
|
|
def update_files(model, dry_run=False):
|
|
"""Walk `model.est`, apply each p90 to the matching register call.
|
|
|
|
Returns list of (relpath, suite, old, new) for every changed entry.
|
|
"""
|
|
by_file = defaultdict(list)
|
|
for suite, files in model.get("est", {}).items():
|
|
for relpath, p90 in files.items():
|
|
by_file[relpath].append((suite, p90))
|
|
|
|
changes = []
|
|
for relpath, entries in sorted(by_file.items()):
|
|
filepath = REPO_ROOT / relpath
|
|
if not filepath.exists():
|
|
continue
|
|
content = filepath.read_text()
|
|
new_content = content
|
|
|
|
for suite, p90 in entries:
|
|
for pattern in make_patterns(suite):
|
|
match = pattern.search(new_content)
|
|
if match is None:
|
|
continue
|
|
old_val = int(match.group(2))
|
|
if old_val != p90:
|
|
new_content = pattern.sub(rf"\g<1>{p90}\3", new_content)
|
|
changes.append((relpath, suite, old_val, p90))
|
|
print(
|
|
f" {relpath}: suite={suite!r} " f"est_time {old_val} -> {p90}",
|
|
file=sys.stderr,
|
|
)
|
|
break # one (file, suite) -> at most one register call
|
|
|
|
if new_content != content and not dry_run:
|
|
filepath.write_text(new_content)
|
|
|
|
return changes
|
|
|
|
|
|
def is_significant(old, new):
|
|
delta = abs(new - old)
|
|
return (
|
|
delta >= SIGNIFICANT_ABS_DELTA and delta / max(old, 1) >= SIGNIFICANT_REL_DELTA
|
|
)
|
|
|
|
|
|
def write_summary(changes, summary_file):
|
|
"""Write a markdown summary of significant est_time changes."""
|
|
sig = [c for c in changes if is_significant(c[2], c[3])]
|
|
sig.sort(key=lambda c: abs(c[3] - c[2]), reverse=True)
|
|
|
|
lines = []
|
|
if sig:
|
|
lines.append(
|
|
f"### Significant est_time changes "
|
|
f"({len(sig)} of {len(changes)} updates)"
|
|
)
|
|
lines.append("")
|
|
lines.append("| File | Suite | Old (s) | New (s) | Δ |")
|
|
lines.append("| --- | --- | ---: | ---: | ---: |")
|
|
for relpath, suite, old, new in sig:
|
|
delta = new - old
|
|
sign = "+" if delta > 0 else ""
|
|
pct = round(delta / max(old, 1) * 100)
|
|
lines.append(
|
|
f"| `{Path(relpath).name}` | `{suite}` | "
|
|
f"{old} | {new} | {sign}{delta} ({sign}{pct}%) |"
|
|
)
|
|
else:
|
|
lines.append(
|
|
f"_{len(changes)} est_time update(s); none exceeded both "
|
|
f"±{SIGNIFICANT_ABS_DELTA}s and "
|
|
f"±{int(SIGNIFICANT_REL_DELTA * 100)}% thresholds._"
|
|
)
|
|
|
|
Path(summary_file).write_text("\n".join(lines) + "\n")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--model-url",
|
|
default=DEFAULT_MODEL_URL,
|
|
help="URL of model.json from sglang-ci-stats (file:// is OK for testing)",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Print changes without modifying files",
|
|
)
|
|
parser.add_argument(
|
|
"--summary-file",
|
|
default=None,
|
|
help="Write a markdown summary of significant changes to this path",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
print(f"Fetching {args.model_url}", file=sys.stderr)
|
|
model = fetch_model(args.model_url)
|
|
print(
|
|
f" model fit_window_start={model.get('fit_window_start')} "
|
|
f"fit_window_days={model.get('fit_window_days')} "
|
|
f"n_runs={model.get('n_runs')} "
|
|
f"n_suites={len(model.get('est', {}))}",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
changes = update_files(model, dry_run=args.dry_run)
|
|
|
|
n_files = len({c[0] for c in changes})
|
|
action = "Would update" if args.dry_run else "Updated"
|
|
print(
|
|
f"\n{action} {len(changes)} est_time entries across {n_files} files",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
if args.summary_file:
|
|
write_summary(changes, args.summary_file)
|
|
print(f"Wrote summary to {args.summary_file}", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|