File size: 5,263 Bytes
61b6fb9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | """Accuracy and load test for serve_jev.py (stdlib only).
python scripts/bench_jev.py accuracy URL CASES.jsonl # same cases as the offline test
python scripts/bench_jev.py load URL [--cases CASES.jsonl] [--levels 1,8,32,128,256] [--n 2000] [--procs 8]
load: closed loop, `c` clients spread over --procs generator processes (one Python process is one
core, it would measure itself). Without --cases a request is the small 2-question one used for
the other Jev servers (choice of 3 + noul); with --cases, each request is one real case with all
its questions.
"""
import argparse
import collections
import json
import multiprocessing as mp
import sys
import time
import urllib.error
import urllib.request
from concurrent.futures import ThreadPoolExecutor
SMALL = {"model": "jev-latest", "state": "Me habéis cobrado dos veces la cuota de septiembre, devolvedme una ya.",
"questions": {"dep": {"type": "choice", "instructions": "¿Qué departamento?",
"criteria": {"administracion": "recibos", "mantenimiento": "averías", "juridico": "morosos"}},
"urg": {"type": "noul", "instructions": "¿Es urgente?"}}}
def post(url, body, timeout=300):
req = urllib.request.Request(url, json.dumps(body).encode(), {"Content-Type": "application/json"})
t = time.perf_counter()
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.status, json.loads(r.read()), time.perf_counter() - t
except urllib.error.HTTPError as e:
return e.code, None, time.perf_counter() - t
except Exception as e: # noqa: BLE001
return type(e).__name__, None, time.perf_counter() - t
def body_of(c):
return {"state": c["state"], "questions": c["questions"], "lang": c.get("lang", "es")}
def accuracy(url, path):
cases = [json.loads(x) for x in open(path, encoding="utf-8") if x.strip()]
ok = tot = 0
with ThreadPoolExecutor(32) as ex:
for c, (st, out, _) in zip(cases, ex.map(lambda c: post(url, body_of(c)), cases)):
for qid, q in c["questions"].items():
a = out["answers"][qid]
if q["type"] == "noul":
pred = "true" if a["noul"] >= 0.5 else "false"
else:
pred = max(a["probabilities"], key=a["probabilities"].get)
ok += pred in c["gold"][qid]
tot += 1
print("accuracy %d/%d (%.1f %%)" % (ok, tot, 100 * ok / tot))
def _worker(args):
url, bodies, n, threads, warm = args
def one(i):
st, out, dt = post(url, bodies[i % len(bodies)])
return (st, dt, (sum(1 for _ in out["answers"]) if out else 0),
((out.get("usage") or {}).get("input_tokens", 0) if out else 0))
with ThreadPoolExecutor(threads) as ex:
list(ex.map(one, range(min(warm, n))))
return list(ex.map(one, range(n)))
def load(url, bodies, levels, n, procs):
health = url.rsplit("/v1/", 1)[0] + "/health"
for c in levels:
p = min(procs, c)
per = [c // p + (i < c % p) for i in range(p)]
reqs = [max(1, n * k // c) for k in per]
def stats():
try:
with urllib.request.urlopen(health, timeout=10) as r:
return json.loads(r.read())["batching"]
except Exception: # noqa: BLE001
return {}
s0 = stats()
t0 = time.time()
with mp.Pool(p) as pool:
res = [x for part in pool.map(_worker, [(url, bodies[i::p] or bodies, reqs[i], per[i], per[i]) for i in range(p)])
for x in part]
dur = time.time() - t0
s1 = stats()
ms = sorted(x[1] * 1000 for x in res)
q = sum(x[2] for x in res)
tok_q = sum(x[3] for x in res) / max(1, q)
k = len(ms)
rows = (s1.get("rows", 0) - s0.get("rows", 0)) / max(1, s1.get("calls", 0) - s0.get("calls", 0))
busy = (s1.get("gpu_s", s1.get("busy_s", 0)) - s0.get("gpu_s", s0.get("busy_s", 0))) / dur
print(" c=%-4d n=%-5d %7.0f req/s %7.0f questions/s p50 %6.1f ms p90 %6.1f p95 %6.1f p99 %6.1f "
"input tok/question %6.1f rows/forward %5.1f in forward %3.0f %% %s"
% (c, k, k / dur, q / dur, ms[k // 2], ms[int(k * .9)], ms[int(k * .95)], ms[min(k - 1, int(k * .99))],
tok_q, rows, 100 * busy, dict(collections.Counter(x[0] for x in res))), flush=True)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("mode", choices=["accuracy", "load"])
ap.add_argument("url")
ap.add_argument("cases", nargs="?")
ap.add_argument("--cases", dest="cases_opt")
ap.add_argument("--levels", default="1,8,32,128,256")
ap.add_argument("--n", type=int, default=2000)
ap.add_argument("--procs", type=int, default=8)
a = ap.parse_args()
url = a.url.rstrip("/") + "/v1/systemone"
if a.mode == "accuracy":
return accuracy(url, a.cases)
src = a.cases_opt or a.cases
bodies = [body_of(json.loads(x)) for x in open(src, encoding="utf-8") if x.strip()] if src else [SMALL]
load(url, bodies, [int(x) for x in a.levels.split(",")], a.n, a.procs)
if __name__ == "__main__":
sys.exit(main())
|