Spaces:
Sleeping
Sleeping
Ronti Patange
Add full VLM benchmark suite and results: model comparison, prompt engineering, ZeroGPU deploy
84d7f71 | """Parallel benchmark harness: fires N concurrent requests at llama-server's | |
| parallel slots (default 4) instead of one-at-a-time. Same disambiguated v2 prompt | |
| and scoring; cuts wall-time ~Nx. Per-image latency is still recorded (though | |
| concurrent requests share the GPU, so per-call time rises while throughput wins). | |
| Usage: python bench_parallel.py [workers] (default 4) | |
| """ | |
| import base64, json, time, glob, os, sys, requests | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| URL = "http://127.0.0.1:8080/v1/chat/completions" | |
| WORKERS = int(sys.argv[1]) if len(sys.argv) > 1 else 4 | |
| OUTDIR = os.path.dirname(os.path.abspath(__file__)) | |
| IMGDIR = os.path.join(OUTDIR, "bench_hf_images") | |
| RESULTS = os.path.join(OUTDIR, "bench_hf_results_parallel.jsonl") | |
| CLASSES = ["burger","butter_naan","chai","chapati","chole_bhature","dal_makhani", | |
| "dhokla","fried_rice","idli","jalebi","kaathi_rolls","kadai_paneer","kulfi", | |
| "masala_dosa","momos","paani_puri","pakode","pav_bhaji","pizza","samosa"] | |
| DESC = { | |
| "burger": "bun with a patty", | |
| "butter_naan": "teardrop-shaped leavened flatbread, glossy with butter", | |
| "chai": "milky tea in a cup or glass", | |
| "chapati": "plain thin unleavened round flatbread, no filling", | |
| "chole_bhature": "chickpea curry served WITH a large puffy fried bread (bhatura)", | |
| "dal_makhani": "creamy dark black-lentil curry (only if clearly lentils)", | |
| "dhokla": "steamed yellow spongy savoury cake, cut in squares", | |
| "fried_rice": "stir-fried rice with visible separate grains", | |
| "idli": "plain white round steamed rice cakes", | |
| "jalebi": "bright orange crispy spiral-shaped sweet", | |
| "kaathi_rolls": "a rolled paratha/wrap around a filling, NOT a curry", | |
| "kadai_paneer": "white paneer cubes in a thick tomato-pepper gravy", | |
| "kulfi": "dense frozen milk dessert, often on a stick", | |
| "masala_dosa": "large thin folded crispy crepe with potato filling", | |
| "momos": "pleated steamed or fried dumplings", | |
| "paani_puri": "small round hollow crispy puris (golgappa)", | |
| "pakode": "irregular deep-fried fritters/clumps", | |
| "pav_bhaji": "mashed red-orange vegetable curry served WITH soft bread rolls (only if mashed veg + pav)", | |
| "pizza": "flat bread base with cheese and toppings", | |
| "samosa": "triangular fried pastry with filling", | |
| } | |
| PROMPT = ("You are shown a photo of a single Indian dish. Identify which ONE dish it is.\n" | |
| "Choose the best match from this list (name: description):\n" | |
| + "\n".join(f"- {c}: {DESC[c]}" for c in CLASSES) | |
| + "\n\nDo not default to pav_bhaji or dal_makhani unless the photo clearly matches " | |
| "their descriptions. Respond with ONLY the exact class name (left of the colon), nothing else.\nAnswer:") | |
| def classify(path, truth): | |
| with open(path, "rb") as f: | |
| b64 = base64.b64encode(f.read()).decode() | |
| body = {"messages": [{"role": "user", "content": [ | |
| {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}, | |
| {"type": "text", "text": PROMPT}]}], | |
| "temperature": 0, "max_tokens": 30} | |
| t = time.time() | |
| try: | |
| r = requests.post(URL, json=body, timeout=300) | |
| raw = r.json()["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| raw = f"ERROR:{e}" | |
| dt = time.time() - t | |
| pred = raw.lower().replace(" ", "_").replace("-", "_").strip(" .\n\"'") | |
| for c in CLASSES: | |
| if c in pred: | |
| pred = c | |
| break | |
| return {"truth": truth, "pred": pred, "ok": pred == truth, "sec": round(dt, 1), "raw": raw} | |
| def main(): | |
| jobs = [] | |
| for c in CLASSES: | |
| for p in sorted(glob.glob(os.path.join(IMGDIR, f"{c}_*.jpg"))): | |
| jobs.append((p, c)) | |
| print(f"{len(jobs)} images, {WORKERS} concurrent workers...", flush=True) | |
| open(RESULTS, "w").close() | |
| correct = done = 0 | |
| wall0 = time.time() | |
| with ThreadPoolExecutor(max_workers=WORKERS) as ex: | |
| futs = [ex.submit(classify, p, t) for p, t in jobs] | |
| for fut in as_completed(futs): | |
| res = fut.result() | |
| correct += res["ok"]; done += 1 | |
| with open(RESULTS, "a", encoding="utf-8") as f: | |
| f.write(json.dumps(res) + "\n") | |
| if done % 20 == 0 or done == len(jobs): | |
| print(f" {done}/{len(jobs)} acc {correct}/{done}={100*correct/done:.0f}%", flush=True) | |
| wall = time.time() - wall0 | |
| rows = [json.loads(l) for l in open(RESULTS, encoding="utf-8")] | |
| t = sorted(r["sec"] for r in rows if r["sec"] > 0) | |
| print(f"\n=== RESULT (parallel x{WORKERS}) ===", flush=True) | |
| print(f"Top-1 accuracy: {correct}/{done} = {100*correct/done:.1f}%", flush=True) | |
| print(f"Wall time: {wall:.0f}s for {done} images ({wall/done:.2f}s/image throughput)", flush=True) | |
| print(f"Per-call latency (concurrent): median={t[len(t)//2]:.1f} mean={sum(t)/len(t):.1f} max={t[-1]:.1f}", flush=True) | |
| print("BENCH_DONE", flush=True) | |
| if __name__ == "__main__": | |
| main() | |