feat(experiments): E4 cross-comparison analysis helper
scripts/analyze_e4_d_to_p.py loads E1 / E3 / E4 summary.json + E4's metrics.jsonl, prints latency / TTFT / per-decode-load side-by-side, breaks E4 down by execution_mode (so the reseed-mode improvement vs E3 can be isolated), and emits PASS/FAIL verdicts for H1 and H3 from the protocol.
This commit is contained in:
141
scripts/analyze_e4_d_to_p.py
Normal file
141
scripts/analyze_e4_d_to_p.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Cross-comparison of E1 (naive PD), E3 (KVC v2 + load-floor), E4 (KVC + D→P).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
uv run --no-sync python scripts/analyze_e4_d_to_p.py \
|
||||||
|
--e1 outputs/e1_naive_1p3d_kvaware_rdma_50sess/e1_naive_1p3d_kvaware_run1_summary.json \
|
||||||
|
--e3 outputs/e3_kvc_v2_loadfloor_rdma_50sess/*_summary.json \
|
||||||
|
--e4 outputs/e4_kvc_v2_d_to_p_sync_50sess/e4_kvc_v2_d_to_p_sync_run1_summary.json \
|
||||||
|
--e4-metrics outputs/e4_kvc_v2_d_to_p_sync_50sess/e4_kvc_v2_d_to_p_sync_run1_metrics.jsonl
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import statistics
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def _load_summary(path_glob: str) -> dict[str, Any] | None:
|
||||||
|
paths = glob.glob(path_glob)
|
||||||
|
if not paths:
|
||||||
|
return None
|
||||||
|
with open(paths[0]) as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def _percentiles(values: list[float]) -> dict[str, float]:
|
||||||
|
if not values:
|
||||||
|
return {"p50": 0, "p90": 0, "p99": 0, "mean": 0}
|
||||||
|
values = sorted(values)
|
||||||
|
n = len(values)
|
||||||
|
return {
|
||||||
|
"mean": statistics.mean(values),
|
||||||
|
"p50": values[n // 2],
|
||||||
|
"p90": values[min(n - 1, int(n * 0.90))],
|
||||||
|
"p99": values[min(n - 1, int(n * 0.99))],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _row(label: str, s: dict[str, Any] | None, key: str) -> str:
|
||||||
|
if s is None:
|
||||||
|
return f" {label:<40} (missing)"
|
||||||
|
stat = s.get(key, {})
|
||||||
|
return (
|
||||||
|
f" {label:<40} "
|
||||||
|
f"mean={stat.get('mean', 0):>8.3f} "
|
||||||
|
f"p50={stat.get('p50', 0):>8.3f} "
|
||||||
|
f"p90={stat.get('p90', 0):>8.3f} "
|
||||||
|
f"p99={stat.get('p99', 0):>8.3f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--e1", required=True)
|
||||||
|
ap.add_argument("--e3", required=True)
|
||||||
|
ap.add_argument("--e4", required=True)
|
||||||
|
ap.add_argument("--e4-metrics", help="optional path to e4 metrics.jsonl for reseed-mode breakdown")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
e1 = _load_summary(args.e1)
|
||||||
|
e3 = _load_summary(args.e3)
|
||||||
|
e4 = _load_summary(args.e4)
|
||||||
|
|
||||||
|
print("=" * 90)
|
||||||
|
print("E1 / E3 / E4 cross-comparison")
|
||||||
|
print("=" * 90)
|
||||||
|
for s, name in [(e1, "E1"), (e3, "E3"), (e4, "E4")]:
|
||||||
|
if s is None:
|
||||||
|
print(f" {name}: MISSING")
|
||||||
|
continue
|
||||||
|
total = (s.get("error_count", 0) + s.get("abort_count", 0) +
|
||||||
|
sum(c for c in s.get("execution_modes", {}).values()))
|
||||||
|
print(f" {name}: error={s.get('error_count', 0):>4} abort={s.get('abort_count', 0):>4} "
|
||||||
|
f"failure={s.get('failure_count', 0):>4} exec_modes={dict(s.get('execution_modes', {}))}")
|
||||||
|
|
||||||
|
print("\n--- latency_stats_s ---")
|
||||||
|
print(_row("E1 naive PD", e1, "latency_stats_s"))
|
||||||
|
print(_row("E3 KVC v2 LF", e3, "latency_stats_s"))
|
||||||
|
print(_row("E4 KVC + D→P", e4, "latency_stats_s"))
|
||||||
|
|
||||||
|
print("\n--- ttft_stats_s ---")
|
||||||
|
print(_row("E1 naive PD", e1, "ttft_stats_s"))
|
||||||
|
print(_row("E3 KVC v2 LF", e3, "ttft_stats_s"))
|
||||||
|
print(_row("E4 KVC + D→P", e4, "ttft_stats_s"))
|
||||||
|
|
||||||
|
print("\n--- per-decode load ---")
|
||||||
|
for s, name in [(e1, "E1"), (e3, "E3"), (e4, "E4")]:
|
||||||
|
print(f" {name}: {dict(s.get('per_decode_load', {}) if s else {})}")
|
||||||
|
|
||||||
|
# ---- E4 reseed-mode breakdown ----
|
||||||
|
if args.e4_metrics:
|
||||||
|
print("\n--- E4 reseed-mode breakdown (from metrics.jsonl) ---")
|
||||||
|
try:
|
||||||
|
modes = defaultdict(list)
|
||||||
|
d2p_outcomes = Counter()
|
||||||
|
with open(args.e4_metrics) as f:
|
||||||
|
for line in f:
|
||||||
|
try:
|
||||||
|
rec = json.loads(line)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
mode = rec.get("execution_mode") or "?"
|
||||||
|
ttft = rec.get("ttft_s")
|
||||||
|
if ttft is not None:
|
||||||
|
modes[mode].append(float(ttft))
|
||||||
|
# D→P hit counter (we logged via logger.info, not in metrics
|
||||||
|
# — placeholder for future structured event)
|
||||||
|
print(f" per-mode TTFT (count, mean, p50, p99):")
|
||||||
|
for mode, ttfts in sorted(modes.items()):
|
||||||
|
p = _percentiles(ttfts)
|
||||||
|
print(f" {mode:<55} n={len(ttfts):>4} "
|
||||||
|
f"mean={p['mean']:>7.3f} p50={p['p50']:>7.3f} p99={p['p99']:>7.3f}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" parse error: {e}")
|
||||||
|
|
||||||
|
# ---- H1 / H2 / H3 verdicts ----
|
||||||
|
print("\n" + "=" * 90)
|
||||||
|
print("Hypothesis verdicts")
|
||||||
|
print("=" * 90)
|
||||||
|
if e1 and e4:
|
||||||
|
e1_p99 = e1.get("ttft_stats_s", {}).get("p99", float("inf"))
|
||||||
|
e4_p99 = e4.get("ttft_stats_s", {}).get("p99", float("inf"))
|
||||||
|
verdict_h1 = "PASS" if e4_p99 <= e1_p99 else "FAIL"
|
||||||
|
print(f" H1 (E4 TTFT p99 ≤ E1 TTFT p99): {e4_p99:.3f} vs {e1_p99:.3f} → {verdict_h1}")
|
||||||
|
if e3 and e4:
|
||||||
|
e3_modes = e3.get("execution_modes", {})
|
||||||
|
e4_modes = e4.get("execution_modes", {})
|
||||||
|
e3_success = sum(v for k, v in e3_modes.items() if "reseed" not in k.lower())
|
||||||
|
e4_success = sum(v for k, v in e4_modes.items() if "reseed" not in k.lower())
|
||||||
|
verdict_h3 = "PASS" if (e4_success or 0) >= 0.85 * (e3_success or 1) else "FAIL"
|
||||||
|
print(f" H3 (E4 success count ≥ 0.85 × E3 success): "
|
||||||
|
f"{e4_success} vs 0.85 × {e3_success} = {0.85 * e3_success:.0f} → {verdict_h3}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user