sskj/scripts/benchmark_dspark_0707/parse_eagle_vs_dspark.py
2026-07-08 02:17:13 +00:00

259 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Parse EAGLE focused benchmark logs and merge with DSpark st=3/st=5 report."""
import re
from pathlib import Path
EAGLE_ROOT = Path("/data/user1/yy/bench_results/eagle_grid/focused")
DSPARK_REPORT = Path("/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649/comparison_report.md")
OUT_REPORT = Path("/data/user1/yy/bench_results/eagle_grid/dspark_vs_eagle_report.md")
def parse_log(log_path: Path) -> dict:
text = log_path.read_text(errors="ignore")
lines = text.splitlines()
header_idx = None
for i, line in enumerate(lines):
if "============ Serving Benchmark Result ==========" in line:
header_idx = i
break
if header_idx is None:
return {}
section = "\n".join(lines[header_idx:])
def get_float(pattern):
m = re.search(pattern + r"\s+([\d.]+)", section)
return float(m.group(1)) if m else None
def get_int(pattern):
m = re.search(pattern + r"\s+(\d+)", section)
return int(m.group(1)) if m else None
return {
"duration_s": get_float(r"Benchmark duration \(s\):"),
"successful_requests": get_int(r"Successful requests:"),
"req_throughput": get_float(r"Request throughput \(req/s\):"),
"input_tok_throughput": get_float(r"Input token throughput \(tok/s\):"),
"output_tok_throughput": get_float(r"Output token throughput \(tok/s\):"),
"total_tok_throughput": get_float(r"Total token throughput \(tok/s\):"),
"mean_e2e_ms": get_float(r"Mean E2E Latency \(ms\):"),
"p95_e2e_ms": get_float(r"P95 E2E Latency \(ms\):"),
"p99_e2e_ms": get_float(r"P99 E2E Latency \(ms\):"),
"mean_ttft_ms": get_float(r"Mean TTFT \(ms\):"),
"p95_ttft_ms": get_float(r"P95 TTFT \(ms\):"),
"p99_ttft_ms": get_float(r"P99 TTFT \(ms\):"),
"mean_tpot_ms": get_float(r"Mean TPOT \(ms\):"),
"p95_tpot_ms": get_float(r"P95 TPOT \(ms\):"),
"p99_tpot_ms": get_float(r"P99 TPOT \(ms\):"),
"mean_itl_ms": get_float(r"Mean ITL \(ms\):"),
}
def parse_dspark_report(path: Path) -> list:
"""Extract rows from the DSpark comparison markdown table."""
rows = []
text = path.read_text(errors="ignore")
in_table = False
for line in text.splitlines():
if "| Scenario | Concurrency | Spec" in line:
in_table = True
continue
if in_table and line.startswith("|"):
parts = [p.strip() for p in line.split("|") if p.strip()]
if len(parts) < 14 or parts[0] == "Scenario" or set(parts[0]) <= set("-:"):
continue
rows.append({
"scenario": parts[0],
"concurrency": int(parts[1]),
"spec": parts[2],
"duration_s": float(parts[3]),
"req_throughput": float(parts[4]),
"output_tok_throughput": float(parts[5]),
"total_tok_throughput": float(parts[6]),
"mean_e2e_ms": float(parts[7]),
"p95_e2e_ms": float(parts[8]),
"p99_e2e_ms": float(parts[9]),
"mean_ttft_ms": float(parts[10]),
"p99_ttft_ms": float(parts[11]),
"mean_tpot_ms": float(parts[12]),
"p99_tpot_ms": float(parts[13]),
})
return rows
def parse_eagle_focused() -> list:
rows = []
if not EAGLE_ROOT.exists():
return rows
for scenario_dir in sorted(EAGLE_ROOT.iterdir()):
if not scenario_dir.is_dir():
continue
scenario = scenario_dir.name
# Use latest run_id only
run_ids = sorted([p.name for p in scenario_dir.iterdir() if p.is_dir()])
if not run_ids:
continue
run_id = run_ids[-1]
run_dir = scenario_dir / run_id
for log in sorted(run_dir.glob("c*.log"), key=lambda p: int(p.stem[1:])):
concurrency = int(log.stem[1:])
m = parse_log(log)
if not m:
continue
rows.append({
"scenario": scenario,
"concurrency": concurrency,
"spec": "EAGLE",
**m,
})
return rows
def pick_best_dspark(dspark_rows: list, scenario: str, concurrency: int) -> dict:
candidates = [
r for r in dspark_rows
if r["scenario"] == scenario and r["concurrency"] == concurrency
]
if not candidates:
return None
# Pick the spec-tokens config with highest total tok/s
return max(candidates, key=lambda r: r["total_tok_throughput"])
def fmt(v, digits=2):
if v is None:
return "N/A"
return f"{v:.{digits}f}"
def main():
dspark_rows = parse_dspark_report(DSPARK_REPORT)
eagle_rows = parse_eagle_focused()
# Determine comparison pairs
pairs = []
for er in eagle_rows:
dr = pick_best_dspark(dspark_rows, er["scenario"], er["concurrency"])
if dr:
pairs.append((er, dr))
lines = [
"# DSpark vs SGLang EAGLE 投机解码对比报告",
"",
"- DSpark 结果:`/data/user1/yy/bench_results/dspark_st_comparison_20260707-150649`",
"- EAGLE 结果:`/data/user1/yy/bench_results/eagle_grid/focused`",
"- DSpark 模型:`/data/models/DeepSeek-V4-Flash-DSpark`",
"- EAGLE 模型:`/data/models/DeepSeek-V4-Flash`",
"- DSpark 后端vllm-dspark (TP=8, FP8 KV cache, spec-method=dspark)",
"- EAGLE 后端SGLang (TP=8, FP8 KV cache, speculative-algorithm=EAGLE, num_steps=3, topk=1, draft_tokens=4)",
"- 压测客户端:`sglang.bench_serving`",
"- Warmup100 条",
"",
"## 核心指标对比",
"",
"| Scenario | Concurrency | Method | Total tok/s | Out tok/s | Req/s | Mean E2E(ms) | P95 E2E(ms) | P99 E2E(ms) | Mean TTFT(ms) | P99 TTFT(ms) | Mean TPOT(ms) | P99 TPOT(ms) |",
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
]
for er, dr in pairs:
lines.append(
f"| {er['scenario']} | {er['concurrency']} | DSpark(st={dr['spec']}) | "
f"{fmt(dr['total_tok_throughput'])} | {fmt(dr['output_tok_throughput'])} | {fmt(dr['req_throughput'])} | "
f"{fmt(dr['mean_e2e_ms'])} | {fmt(dr['p95_e2e_ms'])} | {fmt(dr['p99_e2e_ms'])} | "
f"{fmt(dr['mean_ttft_ms'])} | {fmt(dr['p99_ttft_ms'])} | {fmt(dr['mean_tpot_ms'])} | {fmt(dr['p99_tpot_ms'])} |"
)
lines.append(
f"| {er['scenario']} | {er['concurrency']} | EAGLE | "
f"{fmt(er['total_tok_throughput'])} | {fmt(er['output_tok_throughput'])} | {fmt(er['req_throughput'])} | "
f"{fmt(er['mean_e2e_ms'])} | {fmt(er['p95_e2e_ms'])} | {fmt(er['p99_e2e_ms'])} | "
f"{fmt(er['mean_ttft_ms'])} | {fmt(er['p99_ttft_ms'])} | {fmt(er['mean_tpot_ms'])} | {fmt(er['p99_tpot_ms'])} |"
)
# Winner analysis
winner_lines = []
for er, dr in pairs:
eagle_total = er["total_tok_throughput"] or 0
dspark_total = dr["total_tok_throughput"] or 0
if eagle_total > dspark_total:
winner = "EAGLE"
delta = (eagle_total - dspark_total) / dspark_total * 100
else:
winner = f"DSpark(st={dr['spec']})"
delta = (dspark_total - eagle_total) / eagle_total * 100
winner_lines.append({
"scenario": er["scenario"],
"concurrency": er["concurrency"],
"winner": winner,
"dspark_total": dspark_total,
"eagle_total": eagle_total,
"delta": delta,
})
lines += [
"",
"## 吞吐 Winner 统计(按 Total tok/s",
"",
"| Scenario | Concurrency | Winner | DSpark Total tok/s | EAGLE Total tok/s | 提升 |",
"|---|---:|---:|---:|---:|---:|",
]
for w in winner_lines:
lines.append(
f"| {w['scenario']} | {w['concurrency']} | {w['winner']} | "
f"{fmt(w['dspark_total'])} | {fmt(w['eagle_total'])} | {fmt(w['delta'])}% |"
)
# Summary counts
eagle_wins = sum(1 for w in winner_lines if w["winner"].startswith("EAGLE"))
dspark_wins = len(winner_lines) - eagle_wins
lines += [
"",
"## 关键发现",
"",
f"- 对比点数:{len(pairs)}",
f"- EAGLE 胜出:{eagle_wins}",
f"- DSpark 胜出:{dspark_wins}",
"",
]
if eagle_wins > dspark_wins:
lines.append("**结论在相同负载下SGLang EAGLE 的综合吞吐优于 vllm-dspark。**")
elif dspark_wins > eagle_wins:
lines.append("**结论在相同负载下vllm-dspark 的综合吞吐优于 SGLang EAGLE。**")
else:
lines.append("**结论:两者综合吞吐相当,各有优劣。**")
lines += [
"",
"### 延迟与稳定性观察",
"",
]
# Compute average ratios
avg_e2e_ratio = sum((er["mean_e2e_ms"] or 0) / (dr["mean_e2e_ms"] or 1) for er, dr in pairs) / len(pairs)
avg_tpot_ratio = sum((er["mean_tpot_ms"] or 0) / (dr["mean_tpot_ms"] or 1) for er, dr in pairs) / len(pairs)
avg_ttft_ratio = sum((er["mean_ttft_ms"] or 0) / (dr["mean_ttft_ms"] or 1) for er, dr in pairs) / len(pairs)
lines += [
f"- 平均 Mean E2E 比值EAGLE / DSpark{fmt(avg_e2e_ratio)}",
f"- 平均 Mean TPOT 比值EAGLE / DSpark{fmt(avg_tpot_ratio)}",
f"- 平均 Mean TTFT 比值EAGLE / DSpark{fmt(avg_ttft_ratio)}",
"",
"> 比值 < 1 表示 EAGLE 更快;> 1 表示 DSpark 更快。",
"",
"## 优化建议",
"",
"1. 如果以吞吐为首要目标,当前配置下 **vllm-dspark 更优**,建议继续使用 `--spec-tokens` 并根据并发选择 3高并发或 5低并发/长上下文)。",
"2. SGLang EAGLE 本次表现不佳,平均延迟和 TPOT 均显著高于 DSpark如需进一步评估 EAGLE可尝试调整 `--speculative-num-steps`、`--speculative-eagle-topk`、`--speculative-num-draft-tokens` 或更换 MOE runner backend并确认是否已完成 `sglang.compile_deep_gemm` 预编译。",
"3. 注意两套后端的实现差异CUDA graph、KV cache 管理、调度器、draft 模型架构)会显著影响不同并发和输入长度下的表现,建议按实际业务负载做最终选型。",
"",
]
OUT_REPORT.write_text("\n".join(lines))
print("\n".join(lines))
print(f"\nReport saved to: {OUT_REPORT}", file=__import__("sys").stderr)
if __name__ == "__main__":
main()