Groot-1 / groot1 /bench.py
marcyano's picture
Groot 1 v1.1: Folha correction (87.8% vs Jev 85.8%), S+M ensemble
17a55cc verified
Raw History Blame Contribute Delete
4.01 kB
"""Latency benchmark for Groot checkpoints (and, optionally, the Jev API).
groot-bench --model runs/groot-1-s/final [--device cpu --threads 8] [--jev]
Measures p50/p95 end-to-end latency of `GrootEngine.answer` for synthetic
requests at several state sizes and question counts. With --jev and a
TYPESAFE_API_KEY in the environment, the same requests are sent to
https://api.typesafe.ai/v1/systemone for a side-by-side table.
"""
from __future__ import annotations
import argparse
import json
import os
import statistics
import time
import torch
from .schema import SystemOneRequest
LOREM = (
"The customer reported that the invoice was charged twice on the corporate card and asked for a refund. "
"Support confirmed the duplicate transaction and escalated the case to billing with high priority. "
)
def synthetic_request(state_words: int, n_questions: int) -> SystemOneRequest:
words = (LOREM * (state_words // len(LOREM.split()) + 1)).split()[:state_words]
state = " ".join(words)
questions = {}
kinds = ["choice", "score", "noul"]
for i in range(n_questions):
k = kinds[i % 3]
if k == "choice":
questions[f"q{i}"] = {"type": "choice", "instructions": "Which team should handle this?", "criteria": {"billing": "payments", "shipping": "delivery", "access": "login", "other": "anything else"}}
elif k == "score":
questions[f"q{i}"] = {"type": "score", "instructions": "How urgent is it?", "criteria": ["can wait", "this week", "today"]}
else:
questions[f"q{i}"] = {"type": "noul", "instructions": "Is the customer asking for a refund?"}
return SystemOneRequest(state=state, questions=questions)
def time_fn(fn, warmup: int = 2, runs: int = 10) -> dict:
for _ in range(warmup):
fn()
times = []
for _ in range(runs):
t0 = time.perf_counter()
fn()
times.append((time.perf_counter() - t0) * 1000)
times.sort()
return {"p50_ms": round(statistics.median(times), 1), "p95_ms": round(times[int(0.95 * (len(times) - 1))], 1), "min_ms": round(times[0], 1)}
def main(argv: list[str] | None = None) -> None:
ap = argparse.ArgumentParser(prog="groot-bench")
ap.add_argument("--model", required=True)
ap.add_argument("--device", default=None)
ap.add_argument("--threads", type=int, default=None)
ap.add_argument("--runs", type=int, default=10)
ap.add_argument("--jev", action="store_true")
args = ap.parse_args(argv)
if args.threads:
torch.set_num_threads(args.threads)
from .serve import GrootEngine
engine = GrootEngine(args.model, args.device)
grid = [(64, 1), (256, 1), (256, 5), (1024, 5), (1024, 20), (4096, 5)]
rows = []
for words, nq in grid:
req = synthetic_request(words, nq)
enc = (engine.members[0] if engine.members else engine).ser.encode_request(req.state, req.questions)
res = time_fn(lambda: engine.answer(req.state, req.questions), runs=args.runs)
row = {"state_tokens": enc.state.tokens, "chunks": len(enc.state.chunks), "questions": nq, **{f"groot_{k}": v for k, v in res.items()}}
if args.jev and os.environ.get("TYPESAFE_API_KEY"):
import httpx
body = {"model": "jev-latest", "state": req.state, "questions": {k: v.model_dump(exclude_none=True) for k, v in req.questions.items()}}
headers = {"Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}"}
with httpx.Client(timeout=30) as client:
res_j = time_fn(lambda: client.post("https://api.typesafe.ai/v1/systemone", json=body, headers=headers).raise_for_status(), warmup=1, runs=max(3, args.runs // 2))
row.update({f"jev_{k}": v for k, v in res_j.items()})
rows.append(row)
print(json.dumps(row), flush=True)
print(json.dumps({"device": str(engine.device), "threads": torch.get_num_threads(), "model": engine.name, "rows": rows}, indent=2))
if __name__ == "__main__":
main()