DEV Community

kirandeepjassal-crypto
kirandeepjassal-crypto

Posted on Originally published at prepstack.co.in

AI Deployment & MLOps in 2026 — Serving, Monitoring, Cloud, and a Full End-to-End Project (with Code)

A model that gets 0.94 F1 in a notebook is worth nothing until it's serving real requests, staying healthy under load, and being retrained before it goes stale. The gap between "trained" and "in production" is where most AI projects quietly die — not because the model was bad, but because nobody owned deployment, monitoring, and maintenance.

This is the MLOps playbook: deploying models (REST/batch/streaming/serverless), best practices (versioning, CI/CD, reproducibility, the registry), monitoring live systems (drift, performance, business metrics), cloud strategies, and a complete end-to-end app — with real code throughout.

The one idea

Deployment is not a model.pkl on a server. It's a versioned artifact + reproducible serving image + an API + health checks + autoscaling + monitoring + a rollback path. MLOps = DevOps for ML plus the things ML adds: data versioning, a model registry, experiment tracking, and retraining (because models go stale in a way code never does).

Three serving patterns — pick by latency

Pattern Latency When
Real-time (REST/gRPC) ms per-request, now (fraud check, chatbot)
Batch minutes-hours scheduled bulk scoring (nightly churn)
Streaming sub-second react to events (anomaly on a sensor stream)

The bug that kills more models than any other: serving/training skew

The serving code preprocesses input differently than training did. Fix it by saving the entire pipeline — preprocessing + model — as one artifact:

model = Pipeline([("prep", preprocessor), ("clf", LGBMClassifier(n_estimators=400))])
model.fit(X_train, y_train)
joblib.dump(model, "model.joblib")   # preprocessing + model travel together. Cannot drift apart.
Enter fullscreen mode Exit fullscreen mode

At serving time, joblib.load("model.joblib").predict(raw_df) applies the exact training-time preprocessing. This single discipline prevents the most common production failure.

A real-time REST service (FastAPI) — three non-negotiables

MODEL = joblib.load("model.joblib")    # 1. load once at startup
MODEL_VERSION = "1.4.0"

@app.post("/predict")def predict(features: CustomerFeatures):
    df = pd.DataFrame([features.model_dump()])
    proba = float(MODEL.predict_proba(df)[0, 1])
    return {"churn_probability": proba, "will_churn": proba > 0.5,
            "model_version": MODEL_VERSION}   # 2. ALWAYS return the version

@app.get("/health/ready")def ready(): return {"status": "ready"}      # 3. readiness + liveness endpoints
Enter fullscreen mode Exit fullscreen mode

Version everything — the three artifacts

code (git SHA) + data (DVC / dataset hash) + model (registry version) = a reproducible prediction
Enter fullscreen mode Exit fullscreen mode

With a registry (MLflow), serving loads by stage, not a file path:

MODEL = mlflow.pyfunc.load_model("models:/churn-model/Production")
Enter fullscreen mode Exit fullscreen mode

Swapping models becomes "promote version 6 to Production" — no redeploy, full audit trail, and rollback is just promoting the previous version.

The ML-specific CI/CD piece: the quality gate

- name: Quality gate - block deploy if metrics regress
  run: |
    python - <<'PY'
    import json; m = json.load(open("metrics.json"))
    assert m["roc_auc"] >= 0.82, f"ROC-AUC {m['roc_auc']} below 0.82"
    assert m["f1"] >= 0.70, f"F1 {m['f1']} below 0.70"
    PY
Enter fullscreen mode Exit fullscreen mode

A new model only ships if it meets minimum metrics on a held-out set.

Monitor four layers (not just accuracy)

  1. System health - latency (p50/p95/p99), error rate, throughput (Prometheus + Grafana).
  2. Data drift - production inputs drift from training inputs; accuracy silently drops with no error thrown. Detect with a KS-test per feature; tools: Evidently, NannyML, WhyLabs.
  3. Concept drift - inputs look the same but the input->output relationship changed. Shows up as falling live AUC once true outcomes arrive.
  4. Business metrics - revenue saved, fraud caught, tickets deflected. A model with great AUC that doesn't move the business metric is a science project, not a product.

Cloud strategy: containerize once, then choose

Option Best for Ops
Managed endpoints (SageMaker, Vertex, Azure ML) fastest to prod lowest
Kubernetes (AKS/EKS/GKE) full control, multi-model, GPU sharing highest
Serverless (Cloud Run, Lambda) low/spiky traffic, scale-to-zero low

Start with a managed endpoint unless you have a reason not to.

The end-to-end project

A customer-churn system wired end to end: train.py (train -> quality gate -> register to MLflow) -> FastAPI service in Docker (loads from the registry, exports Prometheus metrics) -> monitor.py (KS-test drift + live-AUC performance) -> GitHub Actions CI/CD with a schedule block that retrains every Monday, runs the same quality gate, and only ships if the new model clears the bar.

The MLOps maturity ladder

Level 0 (notebook -> model.pkl -> manual upload) -> Level 1 (automated training) -> Level 2 (CI/CD + registry + quality gate) -> Level 3 (drift + performance + business monitoring) -> Level 4 (auto-retraining). Most teams should target Level 2-3. Match the maturity to the stakes.

The right mental model

MLOps is treating a model as a living production system, not a deliverable. Three habits:

  1. One artifact, one version, always returned.
  2. Gate every deploy on quality.
  3. Assume the model will rot - monitor and retrain. Drift is the default, not an edge case. Build the feedback loop before you need it.

The full guide has every file (train/serve/monitor/CI), the Dockerfile, the K8s manifest with HPA, the Azure ML managed-endpoint code, and the mental checklist for \"in production\":

https://prepstack.co.in/blog/ai-deployment-mlops-production-serving-monitoring-cloud-end-to-end-project

Originally published on PrepStack.

Top comments (0)