randomllama's picture
Upload folder using huggingface_hub
3cf242b verified
Raw
History Blame Contribute Delete
9.52 kB
#!/usr/bin/env python3
"""Single-stream decode benchmark for an OpenAI-compatible endpoint (vLLM or SGLang).
Discipline, because casual decode benchmarks are usually wrong:
* waits for a genuinely idle engine and DISCARDS any run foreign traffic touched mid-flight
(a background keepalive will otherwise silently poison your numbers)
* forces generation with `ignore_eos` so the run actually decodes instead of stopping early
* reports the MEDIAN of N clean runs, never the best
* names prompt, token count and clock state on every result
NOTE: `ignore_eos` makes this a FLOOR, not real-world throughput -- it pushes generation past the
natural stopping point into degenerate text. Real generation on the same stack measured ~33% higher.
Engine metric namespaces differ (`vllm:*` vs `sglang:*`) and the names are NOT the same; both are
parsed BY NAME. If neither matches, in-flight reads as UNVERIFIED -- never as "idle".
Env: BENCH_HOST BENCH_MODEL BENCH_NS BENCH_RUNS BENCH_MAX_TOK BENCH_LABEL BENCH_CLOCK_CMD BENCH_DEPTH
"""
import json, os, re, subprocess, sys, threading, time, urllib.request
# Engine-agnostic 2026-08-27. DEFAULTS ARE UNCHANGED so a DeepSeek run is byte-identical to the
# runs that produced the recorded baseline (chat 40.41 / code 65.71). Override via env to point at
# another stack -- SAME code, SAME prompts, SAME protocol is the whole point of the comparison.
HOST = os.environ.get("BENCH_HOST", "http://127.0.0.1:8899")
MODEL = os.environ.get("BENCH_MODEL", "qwen38-flash-next")
# vLLM exports `vllm:*`, SGLang exports `sglang:*` and the in-flight metric NAMES DIFFER.
# Parsed BY NAME (never positionally). If neither namespace matches, inflight() returns None and
# contended() reports UNVERIFIED -- it must never silently read as "idle".
NS = os.environ.get("BENCH_NS", "sglang")
_RUNNING = {"vllm": "vllm:num_requests_running", "sglang": "sglang:num_running_reqs"}[NS]
_WAITING = {"vllm": "vllm:num_requests_waiting", "sglang": "sglang:num_queue_reqs"}[NS]
RUNS = int(os.environ.get("BENCH_RUNS", "5"))
MAX_TOK = int(os.environ.get("BENCH_MAX_TOK", "800"))
LABEL = os.environ.get("BENCH_LABEL", "unlabelled")
PROMPTS = {
"chat": "Explain how consensus works in a distributed database, with examples.",
"code": "Write a Python LRU cache with TTL expiry, thread-safe, with tests. Explain the design.",
}
# ── context depth ────────────────────────────────────────────────────────────
# BENCH_DEPTH pads the prompt to roughly this many tokens so decode is measured
# at a chosen context length. Motivation, observed on production traffic
# 2026-08-27: at ~170k context `sglang:spec_accept_length` read 1.00 -- the
# drafter contributing NOTHING -- against 2.75 on a 39-token prompt. If that
# holds, the ~3x speculative speedup this stack advertises is a SHORT-CONTEXT
# number. That needs a controlled sweep, which needs this knob.
#
# The filler is UNIQUE per run (a counter is embedded) so the prefix cache cannot
# serve it. A cached prefill would make deep runs decode against a warm cache and
# report a depth the engine never actually processed -- and `cached_tokens` is
# asserted below rather than assumed.
DEPTH = int(os.environ.get("BENCH_DEPTH", "0"))
_depth_seq = [0]
def pad(prompt):
"""Return (prompt, expected_prefill_tokens). No padding when DEPTH is 0."""
if DEPTH <= 0:
return prompt, 0
_depth_seq[0] += 1
# ~4 chars/token is close enough; the ACTUAL prompt_tokens is read back from
# the response and reported, so the label never depends on this estimate.
words = []
seed = f"run{_depth_seq[0]}x{int(time.time())}"
for i in range(DEPTH):
words.append(f"{seed}-{i:06d}")
filler = " ".join(words)[: DEPTH * 4]
return (f"Reference log (ignore it; answer only the question at the end):\n"
f"{filler}\n\nQuestion: {prompt}"), DEPTH
def inflight():
try:
b = urllib.request.urlopen(HOST + "/metrics", timeout=8).read().decode()
r = re.search(r"^" + re.escape(_RUNNING) + r"\{?[^}\s]*\}?\s+([0-9.]+)", b, re.M)
w = re.search(r"^" + re.escape(_WAITING) + r"\{?[^}\s]*\}?\s+([0-9.]+)", b, re.M)
if r is None or w is None:
return None
return float(r.group(1)) + float(w.group(1))
except Exception:
return None
def wait_idle(timeout=1800):
end = time.time() + timeout; streak = 0
while time.time() < end:
if inflight() == 0:
streak += 1
if streak >= 2: return True
else: streak = 0
time.sleep(15)
return False
def sample(stop, out):
while not stop.is_set():
f = inflight(); out.append(f if f is not None else -1.0); time.sleep(1.5)
def contended(samples):
if not samples: return "UNVERIFIED: no samples"
foreign = blind = 0
for f in samples:
if f < 0:
blind += 1; foreign = 0
if blind >= 2: return "UNVERIFIED: /metrics unreadable mid-run"
else:
blind = 0; foreign = foreign + 1 if f >= 2.0 else 0
if foreign >= 2: return "foreign traffic mid-run"
return ""
def one(prompt):
prompt, _ = pad(prompt)
body = json.dumps({"model": MODEL, "messages": [{"role": "user", "content": prompt}],
"max_tokens": MAX_TOK, "min_tokens": MAX_TOK, "ignore_eos": True,
"temperature": 0, "stream": False}).encode()
s = []; stop = threading.Event()
th = threading.Thread(target=sample, args=(stop, s), daemon=True); th.start()
t0 = time.time()
try:
d = json.load(urllib.request.urlopen(
urllib.request.Request(HOST + "/v1/chat/completions", body,
{"Content-Type": "application/json"}), timeout=600))
finally:
stop.set(); th.join(timeout=5)
dt = time.time() - t0
u = d["usage"]
tok = u["completion_tokens"]
if tok < MAX_TOK * 0.9:
return None, f"only {tok}/{MAX_TOK} tok generated"
# A deep run served from the prefix cache did not measure that depth. Assert
# it rather than trusting the unique filler to have worked.
cached = (u.get("prompt_tokens_details") or {}).get("cached_tokens", 0) or 0
if DEPTH and cached > u["prompt_tokens"] * 0.2:
return None, f"prefix cache served {cached}/{u['prompt_tokens']} prompt tok — depth not real"
return (dt, tok, u["prompt_tokens"], cached, accept_length()), contended(s)
def accept_length():
"""sglang:spec_accept_length, BY NAME. None (never 0.0) when absent."""
try:
b = urllib.request.urlopen(HOST + "/metrics", timeout=8).read().decode()
m = re.search(r"^sglang:spec_accept_length\{[^}]*\}\s+([0-9.eE+-]+)", b, re.M)
return float(m.group(1)) if m else None
except Exception:
return None
def main():
# Clock state is REQUIRED context for a throughput number -- prompt, token count and clock
# state each move the result by more than the effects people try to measure with them.
# It is site-specific, so it comes from an env var. Unset -> print UNVERIFIED, never an
# empty string: a check that cannot verify must say so rather than look fine.
_cmd = os.environ.get("BENCH_CLOCK_CMD", "")
if _cmd:
_p = subprocess.run(_cmd, shell=True, capture_output=True, text=True)
clk = (_p.stdout.strip() or "UNVERIFIED") if _p.returncode == 0 else "UNVERIFIED"
else:
clk = "UNVERIFIED (set BENCH_CLOCK_CMD)"
print(f"=== decode bench :: {LABEL} ===")
print(f" prompts=chat,code max_tokens={MAX_TOK} (ignore_eos) runs={RUNS}/prompt clock={clk}")
if inflight() != 0:
print(" engine busy — waiting for idle...", flush=True)
if not wait_idle():
print(" ABORT: never idle. UNVERIFIED."); return 2
out = {}
for name, p in PROMPTS.items():
res = []; att = 0
while len(res) < RUNS and att < RUNS * 4:
att += 1
if inflight() != 0:
if not wait_idle(900): print(" gave up waiting"); break
r, why = one(p)
if why or r is None:
print(f" [{name}] attempt {att}: DISCARDED — {why}", flush=True); continue
dt, tok, ptok, cached, al = r
res.append((tok / dt, ptok, al))
print(f" [{name}] clean {len(res)}/{RUNS}: {tok/dt:6.2f} tok/s ({dt:.2f}s) "
f"prompt={ptok:,} cached={cached} accept_len={al if al is None else round(al,2)}",
flush=True)
if res:
res.sort(key=lambda x: x[0])
mid = res[len(res)//2]
als = [x[2] for x in res if x[2] is not None]
out[name] = {"tok_s": mid[0], "prompt_tokens": mid[1],
"accept_length_median": (sorted(als)[len(als)//2] if als else None)}
print()
for k, v in out.items():
al = v["accept_length_median"]
print(f" {k:5} median {v['tok_s']:.2f} tok/s prompt={v['prompt_tokens']:,} tok "
f"accept_len={'UNVERIFIED' if al is None else round(al,2)}")
if out:
print(f" MEAN of medians: {sum(v['tok_s'] for v in out.values())/len(out):.2f} tok/s")
print(json.dumps({"label": LABEL, "clock": clk, "max_tok": MAX_TOK,
"depth_requested": DEPTH, "results": out}))
return 0
if __name__ == "__main__":
sys.exit(main())