bertholomus's picture
Publish verified two-node DeepSeek V4 Graph-8 recipe v1.0.0
9322784 verified
Raw
History Blame Contribute Delete
5.5 kB
#!/usr/bin/env python3
"""Warm streaming TTFT, decode-rate, and DSpark acceptance benchmark."""
import argparse
import hashlib
import json
import os
import pathlib
import re
import statistics
import time
import urllib.request
CASES = [
{"name": "count300", "prompt": "Count from 1 to 300, separated by commas. Return only the numbers and commas.", "max_tokens": 700},
{"name": "dataclasses20", "prompt": "Write valid Python defining exactly 20 dataclasses named Record01 through Record20. Each has fields id: int, name: str, active: bool. Return only one Python code block and end it with the comment # END_DATACLASSES.", "max_tokens": 1400},
{"name": "prose500", "prompt": "Write a coherent 500-word technical explanation of failure isolation in a two-node tensor-parallel inference service. Use plain prose, no headings or bullets, and finish with the exact marker END_PROSE.", "max_tokens": 1100},
]
def request_json(url, body=None, timeout=600):
data = json.dumps(body).encode() if body is not None else None
request = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(request, timeout=timeout) as response:
return json.load(response)
def metrics(root):
text = urllib.request.urlopen(root + "/metrics", timeout=10).read().decode()
out = {}
for name in ("vllm:spec_decode_num_draft_tokens_total", "vllm:spec_decode_num_accepted_tokens_total"):
matches = re.findall(r"^" + re.escape(name) + r"\{[^\n]*\}\s+([0-9.eE+-]+)$", text, re.M)
out[name] = sum(float(value) for value in matches)
return out
def validate(name, content):
if name == "count300":
return content.strip() == ",".join(map(str, range(1, 301)))
if name == "dataclasses20":
return all(f"class Record{i:02d}:" in content for i in range(1, 21)) and content.rstrip("`\n ").endswith("# END_DATACLASSES")
if name == "prose500":
return content.rstrip().endswith("END_PROSE")
return False
def stream_one(root, model, case):
body = {"model": model, "messages": [{"role": "user", "content": case["prompt"]}], "temperature": 0, "max_tokens": case["max_tokens"], "stream": True, "stream_options": {"include_usage": True}}
request = urllib.request.Request(root + "/v1/chat/completions", data=json.dumps(body).encode(), headers={"Content-Type": "application/json"})
started = time.perf_counter()
first = None
usage = None
pieces = []
finish_reason = None
with urllib.request.urlopen(request, timeout=900) as response:
for raw in response:
line = raw.decode().strip()
if not line.startswith("data: ") or line == "data: [DONE]":
continue
event = json.loads(line[6:])
choices = event.get("choices") or []
if choices:
delta = choices[0].get("delta") or {}
piece = delta.get("content") or ""
if piece and first is None:
first = time.perf_counter()
pieces.append(piece)
finish_reason = choices[0].get("finish_reason") or finish_reason
if event.get("usage"):
usage = event["usage"]
finished = time.perf_counter()
content = "".join(pieces)
tokens = (usage or {}).get("completion_tokens", 0)
decode_elapsed = max(0.001, finished - (first or finished))
return {"ttft_s": (first or finished) - started, "elapsed_s": finished - started, "completion_tokens": tokens, "decode_tok_s": tokens / decode_elapsed, "finish_reason": finish_reason, "content_sha256": hashlib.sha256(content.encode()).hexdigest(), "output_valid": validate(case["name"], content), "usage": usage}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--base-url", default=os.environ.get("BASE_URL", "http://127.0.0.1:8000"))
parser.add_argument("--model", default=os.environ.get("MODEL", "deepseek-v4-flash-0731"))
parser.add_argument("--label", default="graph8-reproduction")
parser.add_argument("--output", default="benchmark.json")
parser.add_argument("--repetitions", type=int, default=3)
args = parser.parse_args()
root = args.base_url.rstrip("/")
report = {"schema_version": 1, "label": args.label, "model": args.model, "repetitions": args.repetitions, "cases": []}
for case in CASES:
stream_one(root, args.model, case)
before = metrics(root)
runs = [stream_one(root, args.model, case) for _ in range(args.repetitions)]
after = metrics(root)
draft = after["vllm:spec_decode_num_draft_tokens_total"] - before["vllm:spec_decode_num_draft_tokens_total"]
accepted = after["vllm:spec_decode_num_accepted_tokens_total"] - before["vllm:spec_decode_num_accepted_tokens_total"]
entry = {"name": case["name"], "median_decode_tok_s": statistics.median(r["decode_tok_s"] for r in runs), "median_ttft_s": statistics.median(r["ttft_s"] for r in runs), "draft_tokens_delta": draft, "accepted_tokens_delta": accepted, "acceptance_pct": 100 * accepted / draft if draft else None, "all_outputs_valid": all(r["output_valid"] for r in runs), "runs": runs}
report["cases"].append(entry)
pathlib.Path(args.output).write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
print(json.dumps({key: value for key, value in entry.items() if key != "runs"}, sort_keys=True), flush=True)
if __name__ == "__main__":
main()