analysis: summarize workload simulator regimes
This commit is contained in:
@@ -0,0 +1,274 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Summarize workload-regime Frontier metrics and closed-loop batch state."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
METRICS = (
|
||||||
|
"ttft_mean_ms",
|
||||||
|
"ttft_p90_ms",
|
||||||
|
"tpot_mean_ms",
|
||||||
|
"tpot_p90_ms",
|
||||||
|
"e2e_mean_ms",
|
||||||
|
"e2e_p90_ms",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--sim-root", type=Path, required=True)
|
||||||
|
parser.add_argument("--workload-manifest", type=Path, required=True)
|
||||||
|
parser.add_argument("--json-output", type=Path, required=True)
|
||||||
|
parser.add_argument("--markdown-output", type=Path, required=True)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_ledger(path: Path) -> dict[str, float | int | None]:
|
||||||
|
total_time = 0.0
|
||||||
|
weighted_batch = 0.0
|
||||||
|
decode_time = 0.0
|
||||||
|
decode_weighted_batch = 0.0
|
||||||
|
prefill_time = 0.0
|
||||||
|
prefill_weighted_batch = 0.0
|
||||||
|
max_batch = 0
|
||||||
|
batches = 0
|
||||||
|
moe_time = 0.0
|
||||||
|
collective_time = 0.0
|
||||||
|
for line in path.open():
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
row = json.loads(line)
|
||||||
|
request_tokens = [int(value) for value in row["request_num_tokens"]]
|
||||||
|
batch = len(request_tokens)
|
||||||
|
execution = row["execution_time"]
|
||||||
|
duration = float(execution["total_time_ms"])
|
||||||
|
components = execution["component_ledger_ms"]
|
||||||
|
total_time += duration
|
||||||
|
weighted_batch += batch * duration
|
||||||
|
max_batch = max(max_batch, batch)
|
||||||
|
batches += 1
|
||||||
|
if request_tokens and all(value == 1 for value in request_tokens):
|
||||||
|
decode_time += duration
|
||||||
|
decode_weighted_batch += batch * duration
|
||||||
|
else:
|
||||||
|
prefill_time += duration
|
||||||
|
prefill_weighted_batch += batch * duration
|
||||||
|
moe_time += sum(
|
||||||
|
float(components.get(name, 0.0))
|
||||||
|
for name in (
|
||||||
|
"moe_gating_linear_time",
|
||||||
|
"moe_gating_routing_topk_time",
|
||||||
|
"moe_grouped_gemm_time",
|
||||||
|
"moe_shuffling_time",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
collective_time += sum(
|
||||||
|
float(components.get(name, 0.0))
|
||||||
|
for name in (
|
||||||
|
"attention_all_reduce_time",
|
||||||
|
"mlp_all_reduce_time",
|
||||||
|
"moe_tensor_parallel_allgather_time",
|
||||||
|
"expert_parallel_communication_time",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"batches": batches,
|
||||||
|
"max_batch_size": max_batch,
|
||||||
|
"time_weighted_batch_size": weighted_batch / total_time if total_time else None,
|
||||||
|
"decode_time_weighted_batch_size": (
|
||||||
|
decode_weighted_batch / decode_time if decode_time else None
|
||||||
|
),
|
||||||
|
"prefill_mixed_time_weighted_batch_size": (
|
||||||
|
prefill_weighted_batch / prefill_time if prefill_time else None
|
||||||
|
),
|
||||||
|
"decode_time_fraction": decode_time / total_time if total_time else None,
|
||||||
|
"moe_time_fraction": moe_time / total_time if total_time else None,
|
||||||
|
"collective_time_fraction": (
|
||||||
|
collective_time / total_time if total_time else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def find_one(root: Path, pattern: str) -> Path:
|
||||||
|
matches = list(root.glob(pattern))
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise ValueError(f"expected one {pattern} under {root}, got {matches}")
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
def load_rows(sim_root: Path, workload_manifest: Path) -> list[dict[str, Any]]:
|
||||||
|
suite = json.loads(workload_manifest.read_text())
|
||||||
|
cases = {case["public_csv_sha256"]: case for case in suite["cases"]}
|
||||||
|
rows = []
|
||||||
|
for result_path in sorted(sim_root.glob("tp*-prefix-*/runs/*/*/result.json")):
|
||||||
|
result = json.loads(result_path.read_text())
|
||||||
|
case = cases.get(result.get("trace_sha256"))
|
||||||
|
if case is None:
|
||||||
|
raise ValueError(f"unknown trace hash in {result_path}")
|
||||||
|
row: dict[str, Any] = {
|
||||||
|
"family": case["family"],
|
||||||
|
"rho": case["rho"],
|
||||||
|
"prefix_caching": case["prefix_caching"],
|
||||||
|
"tp": result["config"]["tp"],
|
||||||
|
"mns": result["config"]["mns"],
|
||||||
|
"config": result["config"]["name"],
|
||||||
|
"status": result["status"],
|
||||||
|
"failure_kind": result.get("failure_kind"),
|
||||||
|
"result_path": str(result_path),
|
||||||
|
}
|
||||||
|
if result["status"] == "completed":
|
||||||
|
row.update({name: result["score"].get(name) for name in METRICS})
|
||||||
|
ledger = find_one(
|
||||||
|
result_path.parent,
|
||||||
|
"metrics/**/frontier_stage_batch_ledger.jsonl",
|
||||||
|
)
|
||||||
|
row["ledger_path"] = str(ledger)
|
||||||
|
row.update(summarize_ledger(ledger))
|
||||||
|
rows.append(row)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def best_configs(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
groups: dict[tuple[str, float], list[dict[str, Any]]] = defaultdict(list)
|
||||||
|
for row in rows:
|
||||||
|
if row["status"] == "completed":
|
||||||
|
groups[(row["family"], row["rho"])].append(row)
|
||||||
|
best = []
|
||||||
|
for (family, rho), records in sorted(groups.items()):
|
||||||
|
output: dict[str, Any] = {"family": family, "rho": rho}
|
||||||
|
for metric in METRICS:
|
||||||
|
valid = [row for row in records if row.get(metric) is not None]
|
||||||
|
if valid:
|
||||||
|
winner = min(valid, key=lambda row: (row[metric], row["config"]))
|
||||||
|
output[metric] = {
|
||||||
|
"config": winner["config"],
|
||||||
|
"value_ms": winner[metric],
|
||||||
|
}
|
||||||
|
best.append(output)
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def state_knees(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
groups: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list)
|
||||||
|
for row in rows:
|
||||||
|
value = row.get("decode_time_weighted_batch_size")
|
||||||
|
if row["status"] == "completed" and value is not None:
|
||||||
|
groups[(row["family"], row["config"])].append(row)
|
||||||
|
knees = []
|
||||||
|
for (family, config), records in sorted(groups.items()):
|
||||||
|
records.sort(key=lambda row: row["rho"])
|
||||||
|
candidates = []
|
||||||
|
for left, right in zip(records, records[1:]):
|
||||||
|
delta = (
|
||||||
|
right["decode_time_weighted_batch_size"]
|
||||||
|
- left["decode_time_weighted_batch_size"]
|
||||||
|
)
|
||||||
|
candidates.append((abs(delta), delta, left, right))
|
||||||
|
if candidates:
|
||||||
|
_, delta, left, right = max(candidates, key=lambda item: item[0])
|
||||||
|
knees.append(
|
||||||
|
{
|
||||||
|
"family": family,
|
||||||
|
"config": config,
|
||||||
|
"rho_interval": [left["rho"], right["rho"]],
|
||||||
|
"decode_batch_before": left[
|
||||||
|
"decode_time_weighted_batch_size"
|
||||||
|
],
|
||||||
|
"decode_batch_after": right[
|
||||||
|
"decode_time_weighted_batch_size"
|
||||||
|
],
|
||||||
|
"decode_batch_delta": delta,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return knees
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(summary: dict[str, Any]) -> str:
|
||||||
|
lines = [
|
||||||
|
"# Frontier simulator workload-regime discovery",
|
||||||
|
"",
|
||||||
|
f"- Expected cells: {summary['coverage']['expected']}",
|
||||||
|
f"- Observed cells: {summary['coverage']['observed']}",
|
||||||
|
f"- Completed: {summary['coverage']['completed']}",
|
||||||
|
f"- Failed: {summary['coverage']['failed']}",
|
||||||
|
"",
|
||||||
|
"This table reports simulator predictions only. It does not classify a workload as reliable without matched real-system evidence.",
|
||||||
|
"",
|
||||||
|
"## Simulator winners",
|
||||||
|
"",
|
||||||
|
"| Family | rho | TTFT mean | TTFT p90 | TPOT mean | TPOT p90 |",
|
||||||
|
"|---|---:|---|---|---|---|",
|
||||||
|
]
|
||||||
|
for row in summary["best_configs"]:
|
||||||
|
values = []
|
||||||
|
for metric in (
|
||||||
|
"ttft_mean_ms",
|
||||||
|
"ttft_p90_ms",
|
||||||
|
"tpot_mean_ms",
|
||||||
|
"tpot_p90_ms",
|
||||||
|
):
|
||||||
|
winner = row.get(metric)
|
||||||
|
values.append(
|
||||||
|
"—"
|
||||||
|
if winner is None
|
||||||
|
else f"{winner['config']} ({winner['value_ms']:.2f} ms)"
|
||||||
|
)
|
||||||
|
lines.append(
|
||||||
|
f"| {row['family']} | {row['rho']:.2f} | " + " | ".join(values) + " |"
|
||||||
|
)
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
"",
|
||||||
|
"## Largest decode-batch transition per config",
|
||||||
|
"",
|
||||||
|
"| Family | Config | rho interval | Batch before | Batch after | Delta |",
|
||||||
|
"|---|---|---|---:|---:|---:|",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for row in summary["state_knees"]:
|
||||||
|
lines.append(
|
||||||
|
"| {family} | {config} | {left:.2f}–{right:.2f} | {before:.2f} | {after:.2f} | {delta:+.2f} |".format(
|
||||||
|
family=row["family"],
|
||||||
|
config=row["config"],
|
||||||
|
left=row["rho_interval"][0],
|
||||||
|
right=row["rho_interval"][1],
|
||||||
|
before=row["decode_batch_before"],
|
||||||
|
after=row["decode_batch_after"],
|
||||||
|
delta=row["decode_batch_delta"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
rows = load_rows(args.sim_root, args.workload_manifest)
|
||||||
|
completed = sum(row["status"] == "completed" for row in rows)
|
||||||
|
summary = {
|
||||||
|
"schema": "frontier-workload-regime-simulator-analysis-v1",
|
||||||
|
"coverage": {
|
||||||
|
"expected": 7 * 5 * 12,
|
||||||
|
"observed": len(rows),
|
||||||
|
"completed": completed,
|
||||||
|
"failed": len(rows) - completed,
|
||||||
|
},
|
||||||
|
"rows": rows,
|
||||||
|
"best_configs": best_configs(rows),
|
||||||
|
"state_knees": state_knees(rows),
|
||||||
|
}
|
||||||
|
for output in (args.json_output, args.markdown_output):
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.json_output.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n")
|
||||||
|
args.markdown_output.write_text(render_markdown(summary))
|
||||||
|
print(args.markdown_output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user