#!/usr/bin/env python3 """Parse H200 vLLM baseline benchmark JSONL outputs. Reads the single-line summary JSON produced by `sglang.bench_serving --output-file --output-details` and generates: - results.json (appended scenarios) - report.md (human-readable summary) Usage: python3 parse_results.py """ import json import sys from pathlib import Path def parse_jsonl(path: Path) -> dict | None: """Read the first (and usually only) JSON object from the JSONL file.""" with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue try: return json.loads(line) except json.JSONDecodeError: continue return None def compute_metrics(data: dict) -> dict: completed = data.get("completed", 0) total = len(data.get("input_lens", [])) failed = total - completed if total > 0 else 0 duration_s = data.get("duration", 0.0) return { "success": completed, "failed": failed, "duration_s": duration_s, "request_throughput": data.get("request_throughput", 0.0), "input_token_throughput": data.get("input_throughput", 0.0), "output_token_throughput": data.get("output_throughput", 0.0), "total_token_throughput": data.get("total_throughput", 0.0), "total_input_tokens": data.get("total_input_tokens", 0), "total_output_tokens": data.get("total_output_tokens", 0), "e2e_ms": { "mean": data.get("mean_e2e_latency_ms", 0.0), "p50": data.get("median_e2e_latency_ms", 0.0), "p90": data.get("p90_e2e_latency_ms", 0.0), "p95": data.get("p95_e2e_latency_ms", 0.0), "p99": data.get("p99_e2e_latency_ms", 0.0), }, "ttft_ms": { "mean": data.get("mean_ttft_ms", 0.0), "p50": data.get("median_ttft_ms", 0.0), "p90": data.get("p90_ttft_ms", 0.0), "p95": data.get("p95_ttft_ms", 0.0), "p99": data.get("p99_ttft_ms", 0.0), }, "tpot_ms": { "mean": data.get("mean_tpot_ms", 0.0), "p50": data.get("median_tpot_ms", 0.0), "p90": data.get("p90_tpot_ms", 0.0), "p95": data.get("p95_tpot_ms", 0.0), "p99": data.get("p99_tpot_ms", 0.0), }, "itl_ms": { "mean": data.get("mean_itl_ms", 0.0), "p50": data.get("median_itl_ms", 0.0), "p90": data.get("p90_itl_ms", 0.0), "p95": data.get("p95_itl_ms", 0.0), "p99": data.get("p99_itl_ms", 0.0), }, } def scenario_name(concurrency: int, input_len: int, output_len: int) -> str: return f"c{concurrency}_i{input_len}_o{output_len}" def append_scenario(results_json: Path, scenario: dict) -> None: with open(results_json, "r", encoding="utf-8") as f: data = json.load(f) data["scenarios"].append(scenario) with open(results_json, "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) def generate_report(result_root: Path, scenarios: list[dict]) -> None: report_path = result_root / "report.md" with open(report_path, "w", encoding="utf-8") as f: f.write("# H200 vLLM Baseline Benchmark Report\n\n") f.write(f"- Result root: `{result_root}`\n") f.write("- Model: `/data/models/DeepSeek-V4-Flash`\n") f.write("- Backend: vLLM (TP=4, FP8 KV cache, no speculative decoding)\n") f.write("- Benchmark client: `sglang.bench_serving --backend vllm`\n\n") f.write("## Results\n\n") f.write("| Scenario | Concurrency | Input | Output | Duration(s) | Success | Req/s | In tok/s | Out tok/s | Total tok/s | Mean TTFT(ms) | P95 TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P95 TPOT(ms) | P99 TPOT(ms) | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) |\n") f.write("|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|\n") for s in scenarios: cfg = s["config"] m = s["metrics"] f.write( f"| {s['name']} | {cfg['concurrency']} | {cfg['input_len']} | {cfg['output_len']} | " f"{m['duration_s']:.2f} | {m['success']} | {m['request_throughput']:.2f} | " f"{m['input_token_throughput']:.2f} | {m['output_token_throughput']:.2f} | " f"{m['total_token_throughput']:.2f} | " f"{m['ttft_ms']['mean']:.2f} | {m['ttft_ms']['p95']:.2f} | {m['ttft_ms']['p99']:.2f} | " f"{m['tpot_ms']['mean']:.2f} | {m['tpot_ms']['p95']:.2f} | {m['tpot_ms']['p99']:.2f} | " f"{m['e2e_ms']['mean']:.2f} | {m['e2e_ms']['p95']:.2f} | {m['e2e_ms']['p99']:.2f} |\n" ) f.write("\n") def main() -> None: result_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("results") raw_dir = result_root / "raw_outputs" results_json = result_root / "results.json" if not raw_dir.exists(): raise SystemExit(f"raw_outputs directory not found: {raw_dir}") scenarios = [] for jsonl_path in sorted(raw_dir.glob("vllm_*.jsonl")): # Filename: vllm_MMDD_concurrency_inputlen_outputlen.jsonl parts = jsonl_path.stem.split("_") if len(parts) < 5: continue concurrency, input_len, output_len = int(parts[2]), int(parts[3]), int(parts[4]) data = parse_jsonl(jsonl_path) if data is None: continue metrics = compute_metrics(data) scenario = { "name": scenario_name(concurrency, input_len, output_len), "config": { "concurrency": concurrency, "input_len": input_len, "output_len": output_len, "dataset": "random", "num_prompts": metrics["success"] + metrics["failed"], }, "metrics": metrics, "raw_file": str(jsonl_path), } scenarios.append(scenario) if not scenarios: print("No benchmark outputs found to parse") return if results_json.exists(): for s in scenarios: append_scenario(results_json, s) generate_report(result_root, scenarios) print(f"Parsed {len(scenarios)} scenarios into {result_root}/report.md") if __name__ == "__main__": main()