| from datetime import datetime |
| import time |
|
|
| from fastapi import FastAPI, HTTPException |
| from fastapi.staticfiles import StaticFiles |
| from fastapi.responses import HTMLResponse |
| from pydantic import BaseModel |
| from app.inference import run_all_models, run_benchmark |
|
|
| app = FastAPI(title="Model Speed Comparator", version="1.0.0") |
| app.mount("/static", StaticFiles(directory="static"), name="static") |
|
|
| comparison_history = [] |
| MAX_HISTORY_ITEMS = 10 |
|
|
| class TextInput(BaseModel): |
| text: str |
|
|
|
|
| class BatchInput(BaseModel): |
| texts: list[str] |
|
|
|
|
| session_stats = { |
| "total_requests": 0, |
| "latency_sum": { |
| "baseline": 0.0, |
| "onnx": 0.0, |
| "quantized": 0.0, |
| }, |
| "fastest_count": { |
| "baseline": 0, |
| "onnx": 0, |
| "quantized": 0, |
| }, |
| } |
|
|
|
|
| def _build_summary(results: dict) -> dict: |
| quantized_latency = max(results["quantized"]["latency_ms"], 0.01) |
| return { |
| "fastest": min(results, key=lambda x: results[x]["latency_ms"]), |
| "smallest": min(results, key=lambda x: results[x]["model_size_mb"]), |
| "speedup_vs_baseline": round( |
| results["baseline"]["latency_ms"] / quantized_latency, 1 |
| ) |
| } |
|
|
|
|
| def _model_error(exc: Exception) -> HTTPException: |
| return HTTPException( |
| status_code=503, |
| detail=( |
| "Model loading or inference failed. Confirm dependencies are installed, " |
| "run the server from the project root, and check the models folder. " |
| f"Original error: {exc}" |
| ), |
| ) |
|
|
|
|
| def _add_history_item(text: str, results: dict, summary: dict) -> None: |
| comparison_history.insert( |
| 0, |
| { |
| "timestamp": datetime.now().isoformat(timespec="seconds"), |
| "input": text, |
| "results": results, |
| "summary": summary, |
| }, |
| ) |
| del comparison_history[MAX_HISTORY_ITEMS:] |
|
|
|
|
| def _record_stats(results: dict, summary: dict) -> None: |
| session_stats["total_requests"] += 1 |
| session_stats["fastest_count"][summary["fastest"]] += 1 |
| for model_name, result in results.items(): |
| session_stats["latency_sum"][model_name] += result["latency_ms"] |
|
|
|
|
| def _run_comparison(text: str, include_history: bool = True) -> dict: |
| results = run_all_models(text) |
| summary = _build_summary(results) |
| _record_stats(results, summary) |
| if include_history: |
| _add_history_item(text, results, summary) |
| return { |
| "input": text, |
| "results": results, |
| "summary": summary, |
| } |
|
|
|
|
| def _validate_batch_texts(texts: list[str]) -> list[str]: |
| if not texts: |
| raise HTTPException(status_code=400, detail="At least one text is required") |
| if len(texts) > 10: |
| raise HTTPException(status_code=400, detail="Batch size cannot exceed 10 texts") |
| cleaned = [text.strip() for text in texts] |
| if any(not text for text in cleaned): |
| raise HTTPException(status_code=400, detail="Batch texts cannot be empty") |
| return cleaned |
|
|
| @app.get("/", response_class=HTMLResponse) |
| async def root(): |
| with open("static/index.html") as f: |
| return f.read() |
|
|
| @app.post("/compare") |
| async def compare_models(input: TextInput): |
| if not input.text.strip(): |
| raise HTTPException(status_code=400, detail="Text cannot be empty") |
|
|
| try: |
| return _run_comparison(input.text) |
| except Exception as exc: |
| raise _model_error(exc) from exc |
|
|
|
|
| @app.post("/compare/batch") |
| async def compare_batch(input: BatchInput): |
| texts = _validate_batch_texts(input.texts) |
| started = time.perf_counter() |
| items = [] |
|
|
| try: |
| for text in texts: |
| items.append(_run_comparison(text)) |
| except Exception as exc: |
| raise _model_error(exc) from exc |
|
|
| total_ms = round((time.perf_counter() - started) * 1000, 2) |
| return { |
| "count": len(items), |
| "total_wall_time_ms": total_ms, |
| "avg_wall_time_per_text_ms": round(total_ms / len(items), 2), |
| "throughput_texts_per_second": round(len(items) / (total_ms / 1000), 2) if total_ms > 0 else 0, |
| "items": items, |
| } |
|
|
| @app.post("/benchmark") |
| async def benchmark_models(input: TextInput): |
| if not input.text.strip(): |
| raise HTTPException(status_code=400, detail="Text cannot be empty") |
|
|
| try: |
| benchmark = run_benchmark(input.text, iterations=20) |
| except Exception as exc: |
| raise _model_error(exc) from exc |
|
|
| summary = _build_summary(benchmark["latest_results"]) |
|
|
| return { |
| "input": input.text, |
| "summary": summary, |
| **benchmark, |
| } |
|
|
| @app.get("/history") |
| async def get_history(): |
| return {"history": comparison_history} |
|
|
| @app.get("/stats") |
| async def get_stats(): |
| total = session_stats["total_requests"] |
| avg_latency = {} |
| for model_name, latency_sum in session_stats["latency_sum"].items(): |
| avg_latency[model_name] = round(latency_sum / total, 2) if total else 0 |
|
|
| return { |
| "total_requests": total, |
| "avg_latency": avg_latency, |
| "fastest_count": session_stats["fastest_count"], |
| } |
|
|
| @app.get("/health") |
| async def health(): |
| return {"status": "ok"} |
|
|