FastAPI দিয়ে model API
এই পাঠে যা শিখবেন
- FastAPI app structure — Pydantic, dependency, lifespan
- Async vs sync endpoint — কখন কোনটা
- Health probe pattern
- Batching, error handling, instrumentation
১ · কেন FastAPI
Flask দিয়ে ML serve করা যায়, কিন্তু FastAPI ML-এর জন্য better fit। Pydantic schema validation, async support, OpenAPI auto-documentation। Bangladesh ML team-এর majority FastAPI।
২ · Production-grade serving template
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel, Field
import joblib
import logging
import time
import os
logger = logging.getLogger("ml-api")
logging.basicConfig(level=logging.INFO)
MODEL_VERSION = os.getenv("MODEL_VERSION", "v1.3.0")
MODEL_PATH = os.getenv("MODEL_PATH", f"models/sentiment-{MODEL_VERSION}.joblib")
# --- lifespan: load model once at startup ---
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info(f"Loading model {MODEL_VERSION}...")
app.state.model = joblib.load(MODEL_PATH)
app.state.ready = True
logger.info("Model loaded.")
yield
# shutdown cleanup
logger.info("Shutting down.")
app = FastAPI(
title="Bangla Sentiment API",
version=MODEL_VERSION,
lifespan=lifespan,
)
# --- schemas ---
class ReviewIn(BaseModel):
text: str = Field(..., min_length=1, max_length=2000)
class PredictionOut(BaseModel):
label: str
confidence: float
model_version: str
class BatchIn(BaseModel):
texts: list[str] = Field(..., min_length=1, max_length=64)
# --- health probes ---
@app.get("/healthz")
def liveness():
return {"status": "alive"}
@app.get("/readyz")
def readiness(request: Request):
if not getattr(request.app.state, "ready", False):
raise HTTPException(503, "Model not loaded")
return {"status": "ready", "model_version": MODEL_VERSION}
# --- prediction ---
@app.post("/predict", response_model=PredictionOut)
def predict(req: ReviewIn, request: Request):
start = time.perf_counter()
try:
proba = request.app.state.model.predict_proba([req.text])[0]
label_idx = proba.argmax()
return PredictionOut(
label=request.app.state.model.classes_[label_idx],
confidence=float(proba[label_idx]),
model_version=MODEL_VERSION,
)
except Exception as e:
logger.exception("predict failed")
raise HTTPException(500, f"prediction error: {e}")
finally:
elapsed_ms = (time.perf_counter() - start) * 1000
logger.info(f"predict ok in {elapsed_ms:.1f}ms")
# --- batch endpoint ---
@app.post("/predict-batch")
def predict_batch(req: BatchIn, request: Request):
proba = request.app.state.model.predict_proba(req.texts)
return {
"predictions": [
{"label": request.app.state.model.classes_[p.argmax()], "confidence": float(p.max())}
for p in proba
],
"model_version": MODEL_VERSION,
}
৩ · Async vs sync
- Sync (def): CPU-bound model inference (PyTorch, sklearn) — actually fine, FastAPI runs in threadpool।
- Async (async def): I/O-bound (DB lookup, external API call)। Don't make CPU-bound async।
- Pitfall: async def-এ heavy CPU work → blocks event loop → other request slow।
- Recommendation: ML inference sync def; pre/post processing if I/O — separate async function।
৪ · Health probes detail
- Liveness (
/healthz): "process alive?" — restart trigger যদি fail। - Readiness (
/readyz): "ready to serve?" — load balancer route trigger। - Model load 2-3 মিনিট লাগে — readiness false during, true after।
- K8s-এ
readinessProbe.initialDelaySeconds: 30realistic।
৫ · Workers ও deployment
# dev — single process
$ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# prod — multiple workers (CPU-bound, GIL release per worker)
$ gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 60 \
--access-logfile -
# K8s — workers per pod, replicas auto-scale
# resources.requests.cpu × workers ≈ pod CPU
৬ · Batching
Single inference per request CPU-inefficient। Batching idea:
- Static batch: client sends batch (above
/predict-batch)। - Dynamic batch: server collects requests within window (e.g., 5ms), processes together। Triton/BentoML built-in।
- Trade-off: batching latency vs throughput।
৭ · Common pitfalls
- Model loaded per-request → 100x slower; lifespan-এ load।
- HTTPException-এ stack trace leak — production-এ generic error message।
- Logging blocking — async logger / aiologger consider।
- No timeout — slow request worker tie-up। gunicorn timeout setup।
ভাবনার প্রশ্ন
প্র ০১"Sync vs async — PyTorch model inference-এ কোনটা?"
FastAPI-তে common confusion।
Sync (def):
- FastAPI threadpool-এ run; event loop free থাকে।
- CPU-bound (PyTorch, sklearn) — সঠিক choice।
- Other I/O-bound endpoints concurrently serve।
Async (async def):
- Event loop-এ run; কোনো heavy CPU work block করে।
- I/O-bound (DB query, HTTP call) — appropriate।
- Pitfall: async def + PyTorch — event loop block হবে।
Hybrid:
- Async endpoint যা DB/cache lookup করে — async def।
- Inference part:
await asyncio.to_thread(model.predict, ...)বাrun_in_executor।
Bangladesh team common mistake:
- "Async = fast" ভেবে inference async def-এ — actually slower।
- Sync def + threadpool — same throughput, less complexity।
মূল উপলব্ধি: CPU-bound = sync; I/O-bound = async। Mix carefully। Default sync — simpler + correct।
প্র ০২"Workers কত set করবেন? GIL impact কী PyTorch-এ?"
Workers tuning crucial production performance।
Theoretical: workers = (2 × CPU cores) + 1 (Gunicorn doc)।
ML reality — GIL:
- Python GIL — single thread Python at a time।
- PyTorch heavy ops (matmul) — release GIL during C extension।
- So PyTorch threading internal benefits (e.g., 4 cores per inference)।
- Multiple workers + each multi-thread → contention।
Optimal workers:
- CPU model (sklearn, lightgbm): workers = cores। Each does one inference at a time।
- PyTorch CPU: workers = cores / 2 (because PyTorch uses 2-4 threads per inference)।
- GPU: workers = 1-2 per GPU (GPU is bottleneck, not CPU)।
Memory consideration:
- Each worker = full model load = full memory।
- Model 2 GB × 4 workers = 8 GB RAM।
- Pod resources request match।
Tuning approach:
- Load test (locust, k6) — RPS vs latency curve।
- Sweet spot: throughput plateau before latency degrades।
Common config (mid-size BD service):
- Pod: 4 CPU, 8 GB RAM, model 1 GB।
- Workers: 2 (PyTorch) or 4 (sklearn)।
- Replicas: HPA based on RPS।
মূল উপলব্ধি: Workers tuning model framework + memory + load profile-এর function। Default formula starting point only — load test-এ তো final say।
প্র ০৩"Rate limiting + circuit breaker — কোথায় add করবেন?"
FastAPI-এ resilience patterns।
Rate limiting:
- Per-client RPS limit।
- Implementations: slowapi (FastAPI-friendly), API gateway (Kong, Tyk)।
- Better at gateway — pod-level redundant।
Circuit breaker:
- External dependency failure detect → fail fast।
- e.g., Feature Store down — don't hang ৩০s; fast 503।
- pybreaker, tenacity for retries + circuit।
Timeout:
- External calls:
httpx.AsyncClient(timeout=2.0)। - DB: connection pool timeout।
- Total request: gunicorn
--timeout 60।
Bulkhead:
- Different connection pools per dependency — one slow doesn't block all।
Graceful degradation:
- Feature Store down → use defaults (cached aggregate)।
- Model fallback — simple rule if ML model unavailable।
BD context — payment fraud:
- External enrichment API down — model use without it (less accurate, but transactional flow continues)।
- Vs hard-fail — entire payment refused — user experience disaster।
মূল উপলব্ধি: Resilience layered: gateway (rate limit) + service (circuit) + degradation (fallback)। Each layer fast-fail, never silent block।
প্র ০৪"Logging — request body log করা উচিত?"
Logging trade-off — debug vs privacy + cost।
Pro logging:
- Debug — "এই input-এ কী prediction"।
- Drift analysis — input distribution offline।
- Replay — issue reproduce।
Con logging:
- PII leak risk — name, NID, mobile।
- Log volume explosion — cost।
- GDPR/privacy regulation।
Compromise patterns:
- Sample: 1% requests log full body।
- Hash: PII hashed before log।
- Schema only: log "got 5 features" not values।
- Async log to data lake: raw log encrypted bucket; offline analysis।
Always log:
- Request ID (correlation)।
- User ID (hashed)।
- Latency, status code।
- Model version।
- Error stack (failures only)।
BD regulation note:
- Bangladesh Bank — financial transaction log retain 5 years।
- Personal Data Protection Act draft — data minimization।
- Compliance team early consult।
মূল উপলব্ধি: Strategic logging — full body sample + structured metadata always। Privacy first; debug-ability next; cost mindful।
অনুশীলন
- Build: উপরের template নিজের model-এ adapt করুন। localhost-এ run।
uvicorn main:app --reload।http://localhost:8000/docs— Swagger UI auto-generated। - Load test: locust দিয়ে 100 RPS load — p99 latency কত?
pip install locust; locustfile.py লিখুন;locustcommand চালান। Web UI-তে hatch rate set। - চিন্তা: এই service-এ Prometheus metrics যোগ করতে কী করবেন?
prometheus-fastapi-instrumentatorinstall + 1 line setup।/metricsendpoint auto-expose। Lesson 27-এ details।