openjev-p300x2 / scripts /load_test.py
changh95's picture
openjev (Qwen3.5-4B NLI classifier) on P300x2: TT code, vLLM pooling patches, results, Flappy Bird demo videos + viewer
32bed67 verified
Raw History Blame Contribute Delete
4.47 kB
"""Latency / concurrency check for the served classifier.
python scripts/load_test.py --url http://localhost:8000 --clients 8 --requests 8 --batch 1
Reports per-request latency (single input), and throughput with N concurrent clients each
sending `batch` inputs per request. Also asserts every response is label-consistent with a
sequential baseline (state isolation between requests).
"""
import argparse
import json
import statistics
import threading
import time
import urllib.request
TEMPLATE = "Premise: {premise}\nHypothesis: {hypothesis}"
PAIRS = [
("A man is playing a guitar.", "Someone is making music."),
("A man is playing a guitar.", "A woman is sleeping."),
("Two dogs are running through a field.", "There are animals outdoors."),
("It is raining heavily outside.", "The weather is dry."),
("She bought three apples and two oranges at the market.", "She purchased fruit."),
("The meeting was postponed because the CEO was ill.", "The CEO was not well."),
("The Eiffel Tower is located in Paris, France, and was completed in 1889.", "The Eiffel Tower is in Germany."),
("Which gas do plants absorb during photosynthesis?", "The correct answer is: carbon dioxide"),
]
def post(url, payload, timeout=600):
req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--url", default="http://localhost:8000")
ap.add_argument("--model", default="AlexWortega/openjev")
ap.add_argument("--clients", type=int, default=8)
ap.add_argument("--requests", type=int, default=8, help="requests per client")
ap.add_argument("--batch", type=int, default=1, help="inputs per request")
args = ap.parse_args()
texts = [TEMPLATE.format(premise=p, hypothesis=h) for p, h in PAIRS]
# Sequential baseline: single-input requests.
base = []
lat = []
for t in texts:
t0 = time.time()
out = post(f"{args.url}/classify", {"model": args.model, "input": [t]})
lat.append(time.time() - t0)
base.append(out["data"][0]["probs"])
print(f"single-input latency: median {statistics.median(lat)*1e3:.0f} ms, min {min(lat)*1e3:.0f} ms, max {max(lat)*1e3:.0f} ms")
# One request with all inputs (batched prefill step).
t0 = time.time()
out = post(f"{args.url}/classify", {"model": args.model, "input": texts})
dt = time.time() - t0
print(f"one request x {len(texts)} inputs: {dt*1e3:.0f} ms total, {dt/len(texts)*1e3:.0f} ms/input")
for got, ref in zip(out["data"], base):
assert max(abs(a - b) for a, b in zip(got["probs"], ref)) < 1e-3, (got["probs"], ref)
# Concurrent clients.
results, errors = [], []
lock = threading.Lock()
def client(cid):
for r in range(args.requests):
idx = [(cid + r + k) % len(texts) for k in range(args.batch)]
t0 = time.time()
try:
out = post(f"{args.url}/classify", {"model": args.model, "input": [texts[i] for i in idx]})
dt = time.time() - t0
for k, d in enumerate(out["data"]):
ref = base[idx[k]]
if max(abs(a - b) for a, b in zip(d["probs"], ref)) >= 1e-3:
with lock:
errors.append((cid, r, idx[k], d["probs"], ref))
with lock:
results.append(dt)
except Exception as e: # noqa: BLE001
with lock:
errors.append((cid, r, repr(e)))
t0 = time.time()
threads = [threading.Thread(target=client, args=(c,)) for c in range(args.clients)]
[t.start() for t in threads]
[t.join() for t in threads]
wall = time.time() - t0
n_inputs = args.clients * args.requests * args.batch
print(f"{args.clients} clients x {args.requests} req x {args.batch} inputs: {n_inputs} inputs in {wall:.2f}s "
f"= {n_inputs/wall:.1f} inputs/s; per-request latency median {statistics.median(results)*1e3:.0f} ms "
f"p95 {sorted(results)[int(0.95*len(results))-1]*1e3:.0f} ms")
if errors:
print("ERRORS/MISMATCHES:", errors[:5])
raise SystemExit(1)
print("OK: all concurrent results identical to the sequential baseline")
if __name__ == "__main__":
main()