oddadmix's picture
Upload app.py with huggingface_hub
fc144fa verified
Raw
History Blame Contribute Delete
3.65 kB
"""
Nawah-Router — FastAPI backend for the interactive routing demo.
One endpoint. The model scores every supplied category in a single forward pass, so the frontend
can re-route on each keystroke without a per-category cost.
"""
import os
import time
import torch
from fastapi import FastAPI
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from transformers import AutoTokenizer
from routing_model import MAX_ROUTES, RouterModel, build_text, detect_lang, route
HF_TOKEN = os.environ.get("MODEL_HF_TOKEN") or os.environ.get("HF_TOKEN")
# Three backbones on the same head and the same task. The two 6M's are bidirectional BERT
# encoders (one Arabic-only, one pretrained jointly on Arabic+English); the 52M is a Llama
# decoder. Measured on the same eval sets in one session each - see the model cards.
REGISTRY = {
"52M": {"repo": "oddadmix/Nawah-Router-v3",
"label": "Nawah-Router-v3 (Llama decoder, Arabic)"},
"6M-BERT": {"repo": "oddadmix/Nawah-Router-BERT-6M-v2",
"label": "Nawah-Router-BERT-6M-v2 (BERT encoder, Arabic)"},
"6M-BILINGUAL": {"repo": "oddadmix/Nawah-Router-BERT-6M-bilingual-pretrained",
"label": "Nawah-Router-BERT-6M-bilingual-pretrained (BERT encoder, English+Arabic)"},
}
DEFAULT = os.environ.get("MODEL_KEY", "6M-BERT")
LOADED = {}
for key, spec in REGISTRY.items():
print(f"[*] loading {spec['repo']}", flush=True)
tok = AutoTokenizer.from_pretrained(spec["repo"], token=HF_TOKEN)
mdl = RouterModel.from_pretrained(spec["repo"], token=HF_TOKEN)
LOADED[key] = {"tok": tok, "model": mdl, "label": spec["label"],
"repo": spec["repo"],
"params": sum(p.numel() for p in mdl.parameters())}
print(f"[+] {key}: {LOADED[key]['params']/1e6:.2f}M params", flush=True)
torch.set_num_threads(int(os.environ.get("OMP_NUM_THREADS", 4)))
MODEL_ID = REGISTRY[DEFAULT]["repo"]
TOK, MODEL = LOADED[DEFAULT]["tok"], LOADED[DEFAULT]["model"]
app = FastAPI()
class RouteReq(BaseModel):
text: str = ""
cats: list[str] = []
model: str = DEFAULT
@app.get("/")
def index():
return FileResponse("static/index.html")
@app.get("/api/ready")
def ready():
return {"ready": True, "model": MODEL_ID, "default": DEFAULT,
"params": LOADED[DEFAULT]["params"], "max_routes": MAX_ROUTES,
"models": [{"key": k, "label": v["label"], "repo": v["repo"],
"params": v["params"]} for k, v in LOADED.items()]}
@app.post("/api/route")
def api_route(req: RouteReq):
cats = [c.strip() for c in req.cats if c and c.strip()][:MAX_ROUTES]
text = (req.text or "").strip()
if not text or not cats:
return JSONResponse({"results": [], "ms": 0, "tokens": 0})
sel = LOADED.get(req.model) or LOADED[DEFAULT]
t0 = time.perf_counter()
res = route(sel["model"], sel["tok"], text, cats)
full, _ = build_text(text, cats)
ntok = len(sel["tok"].encode(full, add_special_tokens=False))
order = {r["route"]: r["score"] for r in res}
return JSONResponse({
# returned in the caller's order so the UI does not reshuffle rows under the cursor
"results": [{"route": c, "score": order.get(c, 0.0)} for c in cats],
"top": max(range(len(cats)), key=lambda i: order.get(cats[i], 0.0)),
"ms": round((time.perf_counter() - t0) * 1000), "tokens": ntok, "lang": detect_lang(text),
"model": sel["label"], "params": sel["params"]})
app.mount("/static", StaticFiles(directory="static"), name="static")