Files
sgl-project--sglang/benchmark/bench_adaptive_speculative.py
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

264 lines
8.1 KiB
Python

"""Benchmark adaptive speculative decoding against static baselines.
Run the same workload against one adaptive server and one or more static
servers, then compare throughput, latency, and acceptance length.
Workloads:
- low: steady-state low-acceptance generation
- high: steady-state high-acceptance generation
- transition: alternating low/high acceptance shifts to stress runtime switching
"""
import argparse
import time
from concurrent.futures import ThreadPoolExecutor
import requests
HIGH_PROMPTS = [
"Output exactly 256 new lines. Every line must be 1. Do not add numbering, punctuation, or commentary.",
"Output exactly 256 new lines. Every line must be READY. Do not add numbering, punctuation, or commentary.",
]
LOW_PROMPTS = [
"Compose a poem in the style of Emily Dickinson about quantum entanglement. Make it emotionally resonant.",
"Write 100 two-sentence biographies of eccentric inventors with unique names, hometowns, and inventions.",
"Write a long travel diary from a botanist visiting a chain of floating islands. Every paragraph should introduce new flora, customs, weather, and political tensions.",
"Write 80 newspaper headlines and subheads from 80 different alternate-history worlds. Each headline must introduce a different place, conflict, and technology.",
]
WORKLOADS = {
"low": [
("low", LOW_PROMPTS),
],
"high": [
("high", HIGH_PROMPTS),
],
"transition": [
("low_1", LOW_PROMPTS),
("high_1", HIGH_PROMPTS),
("low_2", LOW_PROMPTS),
("high_2", HIGH_PROMPTS),
],
}
def build_phase_plan(workload: str, num_requests: int):
return [
(phase_name, prompts, num_requests)
for phase_name, prompts in WORKLOADS[workload]
]
def send_request(base_url: str, prompt: str, max_tokens: int = 256):
start = time.perf_counter()
try:
resp = requests.post(
f"{base_url}/generate",
json={
"text": prompt,
"sampling_params": {
"temperature": 0,
"max_new_tokens": max_tokens,
},
"return_logprob": False,
},
timeout=max(120, max_tokens),
)
resp.raise_for_status()
data = resp.json()
except Exception as e:
return {"error": str(e), "latency": time.perf_counter() - start}
latency = time.perf_counter() - start
meta = data.get("meta_info", {})
completion_tokens = meta.get("completion_tokens", 0)
spec_verify_ct = meta.get("spec_verify_ct", 0)
accept_len = (
completion_tokens / spec_verify_ct if spec_verify_ct > 0 else float("nan")
)
return {
"latency": latency,
"completion_tokens": completion_tokens,
"spec_verify_ct": spec_verify_ct,
"accept_length": accept_len,
}
def run_phase(
base_url: str,
prompts,
phase_name: str,
num_requests: int,
max_tokens: int,
concurrency: int,
):
expanded = (prompts * ((num_requests + len(prompts) - 1) // len(prompts)))[
:num_requests
]
print(
f"\n--- Phase: {phase_name} ({num_requests} requests, concurrency={concurrency}) ---"
)
start = time.perf_counter()
with ThreadPoolExecutor(max_workers=concurrency) as pool:
futures = [pool.submit(send_request, base_url, p, max_tokens) for p in expanded]
results = [f.result() for f in futures]
elapsed = time.perf_counter() - start
errors = [r for r in results if "error" in r]
ok = [r for r in results if "error" not in r]
if not ok:
print(f" All {len(errors)} requests failed!")
return {"phase": phase_name, "error": True}
total_tokens = sum(r["completion_tokens"] for r in ok)
total_verify = sum(r["spec_verify_ct"] for r in ok)
avg_latency = sum(r["latency"] for r in ok) / len(ok)
throughput = total_tokens / elapsed
avg_accept_len = total_tokens / total_verify if total_verify > 0 else float("nan")
stats = {
"phase": phase_name,
"num_requests": len(ok),
"num_errors": len(errors),
"total_tokens": total_tokens,
"elapsed_s": round(elapsed, 2),
"throughput_tok_s": round(throughput, 2),
"avg_latency_s": round(avg_latency, 3),
"avg_accept_length": round(avg_accept_len, 3),
}
print(
f" Throughput: {throughput:.1f} tok/s | "
f"Avg latency: {avg_latency:.3f}s | "
f"Avg accept_len: {avg_accept_len:.2f} | "
f"Errors: {len(errors)}"
)
return stats
def summarize_phases(phase_stats):
ok_stats = [s for s in phase_stats if not s.get("error")]
if not ok_stats:
return {"error": True}
total_tokens = sum(s["total_tokens"] for s in ok_stats)
total_elapsed = sum(s["elapsed_s"] for s in ok_stats)
total_requests = sum(s["num_requests"] for s in ok_stats)
weighted_latency = sum(s["avg_latency_s"] * s["num_requests"] for s in ok_stats)
weighted_accept = sum(s["avg_accept_length"] * s["num_requests"] for s in ok_stats)
return {
"num_requests": total_requests,
"total_tokens": total_tokens,
"elapsed_s": round(total_elapsed, 2),
"throughput_tok_s": round(total_tokens / total_elapsed, 2),
"avg_latency_s": round(weighted_latency / total_requests, 3),
"avg_accept_length": round(weighted_accept / total_requests, 3),
}
def main():
parser = argparse.ArgumentParser(
description="Benchmark one workload for adaptive-vs-static speculative decoding"
)
parser.add_argument("--host", type=str, default="127.0.0.1")
parser.add_argument("--port", type=int, default=30000)
parser.add_argument(
"--workload",
choices=sorted(WORKLOADS),
default="transition",
help="Workload preset to run.",
)
parser.add_argument(
"--requests",
type=int,
default=8,
help="Requests per phase.",
)
parser.add_argument("--max-tokens", type=int, default=256)
parser.add_argument(
"--concurrency",
type=int,
default=2,
help="Concurrent requests.",
)
parser.add_argument(
"--warmup", type=int, default=2, help="Warmup requests before the benchmark."
)
args = parser.parse_args()
if args.requests < 1:
parser.error("--requests must be >= 1")
if args.concurrency < 1:
parser.error("--concurrency must be >= 1")
if args.warmup < 0:
parser.error("--warmup must be >= 0")
base_url = f"http://{args.host}:{args.port}"
print(f"Server: {base_url}")
print(f"Workload: {args.workload}")
phase_plan = build_phase_plan(args.workload, args.requests)
if args.warmup > 0:
print(f"\nWarming up with {args.warmup} requests...")
warmup_prompts = phase_plan[0][1]
run_phase(
base_url,
warmup_prompts,
"warmup",
args.warmup,
args.max_tokens,
args.concurrency,
)
phase_stats = []
for phase_name, prompts, num_requests in phase_plan:
phase_stats.append(
run_phase(
base_url,
prompts,
phase_name,
num_requests,
args.max_tokens,
args.concurrency,
)
)
overall = summarize_phases(phase_stats)
print("\n" + "=" * 70)
print("SUMMARY")
print("=" * 70)
print(f"{'Phase':<10} {'Throughput':>12} {'Avg Latency':>12} {'Accept Len':>12}")
print("-" * 50)
for stats in phase_stats:
if stats.get("error"):
print(f"{stats['phase']:<10} {'ERROR':>12}")
continue
print(
f"{stats['phase']:<10} "
f"{stats['throughput_tok_s']:>10.1f}/s "
f"{stats['avg_latency_s']:>10.3f}s "
f"{stats['avg_accept_length']:>11.2f}"
)
if not overall.get("error"):
print("-" * 50)
print(
f"{'OVERALL':<10} "
f"{overall['throughput_tok_s']:>10.1f}/s "
f"{overall['avg_latency_s']:>10.3f}s "
f"{overall['avg_accept_length']:>11.2f}"
)
if __name__ == "__main__":
main()