nirnay / code /bench /bench_latency_nirnay.py
nandukmelath's picture
Nirnay v2 (bge-base, default) + v1 (fast/): weights, package, code, benchmarks
00ee773 verified
Raw History Blame Contribute Delete
4.23 kB
"""Nirnay CPU latency on this machine with the exact Laya latency workload
(see bench_latency_laya.py). Writes bench/results/nirnay_latency.json.
Reports both regimes honestly:
cold_schema question/option encodings NOT cached (first call with a new schema)
warm schema cached (every later call with the same questions; the normal case)
python bench\\bench_latency_nirnay.py [model_dir]
"""
import json
import os
import sys
import time
import numpy as np
import psutil
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
sys.path.insert(0, ROOT)
sys.path.insert(0, HERE)
from bench_latency_laya import STATE_EN, qs # noqa: E402 (identical workload)
import nirnay # noqa: E402
OUT = os.path.join(HERE, "results", "nirnay_latency%s.json" % (("_" + os.environ["BENCH_TAG"]) if os.environ.get("BENCH_TAG") else ""))
def timed(fn, warmup=2, reps=10):
for _ in range(warmup):
fn()
ts = []
for _ in range(reps):
t = time.perf_counter()
fn()
ts.append((time.perf_counter() - t) * 1000)
return {"p50_ms": round(float(np.percentile(ts, 50)), 2), "p95_ms": round(float(np.percentile(ts, 95)), 2),
"mean_ms": round(float(np.mean(ts)), 2), "min_ms": round(float(np.min(ts)), 2)}
def main():
model_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "models", "nirnay-v1")
threads = [int(x) for x in os.environ.get("BENCH_THREADS", "1,4,8,16").split(",")]
proc = psutil.Process()
rss0 = proc.memory_info().rss
t = time.perf_counter()
m = nirnay.load(model_dir, threads=max(threads))
load_ms = round((time.perf_counter() - t) * 1000, 1)
res = {"meta": {"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), "cpu": "2x Xeon E5-2630 v3 (HP Z840)",
"model": m.name, "model_dir": model_dir},
"cold_load_ms": load_ms, "rss_delta_mb": round((proc.memory_info().rss - rss0) / 2**20, 1),
"disk_mb": round(sum(os.path.getsize(os.path.join(model_dir, f)) for f in os.listdir(model_dir)) / 2**20, 1),
"by_threads": {}}
print("%s loaded in %.0f ms (+%.0f MB RSS, %.0f MB on disk, backend %s)" % (
m.name, load_ms, res["rss_delta_mb"], res["disk_mb"], m.backend))
res["backend"] = m.backend
import torch
for th in threads:
m = nirnay.load(model_dir, threads=th) # ONNX Runtime fixes its thread pool per session
torch.set_num_threads(th)
per = {}
for n in (1, 5, 10, 50):
q = qs(n)
def cold():
m.cache.d.clear()
m.predict(STATE_EN, q)
r_cold = timed(cold)
r_warm = timed(lambda: m.predict(STATE_EN, q))
per["%d_questions" % n] = {"warm": r_warm, "cold_schema": r_cold,
"ms_per_question_warm": round(r_warm["p50_ms"] / n, 3)}
print(" threads=%2d %2dq warm p50 %7.2f ms (p95 %7.2f) cold-schema p50 %7.2f ms" %
(th, n, r_warm["p50_ms"], r_warm["p95_ms"], r_cold["p50_ms"]), flush=True)
res["by_threads"][str(th)] = per
# throughput: many states, same questions (predict_batch)
m = nirnay.load(model_dir, threads=8)
torch.set_num_threads(8)
states = [{"ticket": {"subject": "Case %d" % i, "messages": [{"from": "customer", "text": STATE_EN["ticket"]["messages"][0]["text"][: 80 + 3 * i]}]}}
for i in range(64)]
q10 = qs(10)
m.predict_batch(states[:4], q10)
t = time.perf_counter()
m.predict_batch(states, q10)
el = time.perf_counter() - t
res["throughput_10q"] = {"states": len(states), "seconds": round(el, 3),
"decisions_per_s": round(len(states) * 10 / el, 1),
"states_per_s": round(len(states) / el, 1)}
print(" throughput: %.0f decisions/s (%.1f states/s, 10 questions each)" %
(res["throughput_10q"]["decisions_per_s"], res["throughput_10q"]["states_per_s"]))
res["peak_rss_mb"] = round(proc.memory_info().rss / 2**20, 1)
os.makedirs(os.path.dirname(OUT), exist_ok=True)
json.dump(res, open(OUT, "w"), indent=2)
print("wrote", OUT)
if __name__ == "__main__":
main()