InferScale-Sim / py /inferscale /metrics.py
ArchitSharma's picture
Release InferScale-Sim v0.2.0
20fb354
Raw
History Blame Contribute Delete
3.82 kB
from __future__ import annotations
import math
from statistics import mean
from .models import Request, RequestMetrics, SimulationConfig
def percentile(values: list[float], q: float) -> float:
if not values:
return 0.0
vals = sorted(values)
if len(vals) == 1:
return vals[0]
pos = (len(vals) - 1) * q
lo = math.floor(pos)
hi = math.ceil(pos)
if lo == hi:
return vals[lo]
frac = pos - lo
return vals[lo] * (1.0 - frac) + vals[hi] * frac
def request_metrics(req: Request, cfg: SimulationConfig) -> RequestMetrics:
if req.first_token_time is None or req.completion_time is None:
raise ValueError("Request is incomplete")
ttft_ms = (req.first_token_time - req.arrival_time) * 1000.0
e2e_ms = (req.completion_time - req.arrival_time) * 1000.0
if req.output_tokens <= 1:
tpot_ms = 0.0
else:
tpot_ms = (req.completion_time - req.first_token_time) * 1000.0 / (req.output_tokens - 1)
prefill_start = req.first_prefill_time if req.first_prefill_time is not None else req.arrival_time
queue_ms = max(0.0, (prefill_start - req.arrival_time) * 1000.0)
met_ttft = ttft_ms <= cfg.slo_ttft_ms
met_e2e = e2e_ms <= cfg.slo_e2e_ms
return RequestMetrics(
request_id=req.request_id,
arrival_time=req.arrival_time,
prompt_tokens=req.prompt_tokens,
output_tokens=req.output_tokens,
ttft_ms=ttft_ms,
e2e_ms=e2e_ms,
tpot_ms=tpot_ms,
queue_ms=queue_ms,
met_ttft_slo=met_ttft,
met_e2e_slo=met_e2e,
met_all_slos=met_ttft and met_e2e,
)
def summarize(completed: list[Request], cfg: SimulationConfig, makespan_s: float, busy_time_s: float) -> tuple[dict, dict]:
metrics = [request_metrics(r, cfg) for r in completed]
ttft = [m.ttft_ms for m in metrics]
e2e = [m.e2e_ms for m in metrics]
tpot = [m.tpot_ms for m in metrics]
queue = [m.queue_ms for m in metrics]
total_output = sum(r.output_tokens for r in completed)
met = sum(m.met_all_slos for m in metrics)
met_ttft = sum(m.met_ttft_slo for m in metrics)
met_e2e = sum(m.met_e2e_slo for m in metrics)
ttft_only_fail = sum((not m.met_ttft_slo) and m.met_e2e_slo for m in metrics)
e2e_only_fail = sum(m.met_ttft_slo and (not m.met_e2e_slo) for m in metrics)
both_fail = sum((not m.met_ttft_slo) and (not m.met_e2e_slo) for m in metrics)
duration = max(makespan_s, 1e-9)
count = len(metrics)
summary = {
"requests_completed": len(completed),
"request_throughput_rps": len(completed) / duration,
"output_throughput_tps": total_output / duration,
"goodput_rps": met / duration,
"slo_attainment": met / count if count else 0.0,
"ttft_slo_attainment": met_ttft / count if count else 0.0,
"e2e_slo_attainment": met_e2e / count if count else 0.0,
"slo_failure_breakdown": {"ttft_only": ttft_only_fail, "e2e_only": e2e_only_fail, "both": both_fail},
"simulated_makespan_s": makespan_s,
"busy_fraction": min(1.0, busy_time_s / duration),
"mean_prompt_tokens": mean([r.prompt_tokens for r in completed]) if completed else 0.0,
"mean_output_tokens": mean([r.output_tokens for r in completed]) if completed else 0.0,
}
latency = {
"ttft_ms": {"p50": percentile(ttft, 0.50), "p95": percentile(ttft, 0.95), "p99": percentile(ttft, 0.99)},
"e2e_ms": {"p50": percentile(e2e, 0.50), "p95": percentile(e2e, 0.95), "p99": percentile(e2e, 0.99)},
"tpot_ms": {"p50": percentile(tpot, 0.50), "p95": percentile(tpot, 0.95), "p99": percentile(tpot, 0.99)},
"queue_ms": {"p50": percentile(queue, 0.50), "p95": percentile(queue, 0.95), "p99": percentile(queue, 0.99)},
}
return summary, latency