Reference: A Production ML Pipeline
A reference ML pipeline for Python shops: batch training with PyTorch 2.6+ or scikit-learn, artifact registry, FastAPI serving, and monitoring for drift and latency - not notebook-only science projects.
Recipe
Quick-reference recipe card - copy-paste ready.
fraud-ml/
pipelines/train.py # scheduled training job
pipelines/features.py # feature store writes
serving/app.py # FastAPI inference
models/ # exported weights (S3 in prod)
eval/metrics.py # offline gates
monitoring/drift.py # prod distribution checksWhen to reach for this:
- First model going to production
- MLOps RFC baseline
- Auditing gap between notebook and serving
Working Example
"""pipelines/train.py - batch training with eval gate."""
from __future__ import annotations
import json
from pathlib import Path
import joblib
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from pipelines.features import load_feature_frame
def train(version: str, min_auc: float = 0.82) -> Path:
X, y = load_feature_frame()
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
model = GradientBoostingClassifier(random_state=42)
model.fit(X_train, y_train)
auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])
if auc < min_auc:
raise RuntimeError(f"AUC {auc:.3f} below gate {min_auc}")
out = Path(f"models/{version}/model.joblib")
out.parent.mkdir(parents=True, exist_ok=True)
joblib.dump(model, out)
out.with_suffix(".metrics.json").write_text(json.dumps({"auc": auc}))
return out"""serving/app.py - inference API."""
from __future__ import annotations
import joblib
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
MODEL = joblib.load("models/prod/model.joblib")
class Features(BaseModel):
amount: float = Field(gt=0)
merchant_risk_score: float
class Score(BaseModel):
fraud_probability: float
model_version: str = "2026.07.09"
@app.post("/v1/score", response_model=Score)
def score(body: Features) -> Score:
prob = float(MODEL.predict_proba([[body.amount, body.merchant_risk_score]])[0, 1])
return Score(fraud_probability=prob)What this demonstrates:
- Training job fails CI if eval gate misses
- Serving loads versioned artifact; Pydantic validates inputs
- Clear split between batch train and online inference
- Metrics artifact travels with model for audit
Deep Dive
How It Works
- Feature pipeline - Batch writes to warehouse; online path reads precomputed or low-latency subset.
- Registry - S3 or MLflow stores
model.joblib+ metadata JSON. - Promotion - Shadow or canary compare new model AUC/latency before alias swap.
- Monitoring - Log predictions; compare input distribution weekly (PSI).
- Rollback - Previous model version in registry; flag alias revert.
Pipeline Stages
| Stage | Tooling |
|---|---|
| Ingest | Airflow/Prefect job |
| Train | sklearn / PyTorch script in container |
| Eval | pytest + metric thresholds |
| Deploy | Update serving image env MODEL_VERSION |
| Monitor | Grafana + batch drift job |
Python Notes
# monitoring/drift.py - simplified PSI check
def population_stability_index(expected: list[float], actual: list[float]) -> float:
...Gotchas
- Training-serving skew - Different feature code paths. Fix: Shared
features.pypackage imported both sides. - Notebook as production - No tests or versioning. Fix: Export functions to
pipelines/modules. - No input validation - Garbage crashes model. Fix: Pydantic bounds on serving API.
- GPU train, CPU serve mismatch - Export format issues. Fix: Standardize on ONNX or joblib per model family.
- Ignoring drift - Silent quality decay. Fix: Weekly batch report + alert threshold.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Batch-only scoring | Nightly fraud batch | Real-time decline needed |
| Embedded model in rules | Tiny linear model | Deep network inference |
| Triton server | High RPS GPU | Small sklearn logistic |
| Vendor AutoML | Spike only | Long-term ownership needed |
FAQs
PyTorch vs sklearn reference?
sklearn for tabular baseline; swap train block for PyTorch when deep model required.
Where run training?
Scheduled K8s Job or managed pipeline; not on serving pods.
Feature store required?
Start with warehouse tables; adopt store when online/offline skew hurts.
LLM in same pipeline?
Separate eval (hallucination, cost) and guardrails - see LLM section.
Model approval?
Data science signs metrics; platform signs serving SLO; PM signs business metric.
PII in features?
Hash or tokenize; document retention in governance policy.
Latency SLO?
Measure p95 in canary; autoscale CPU serving before GPU unless needed.
Experiment tracking?
MLflow or W&B for params/metrics; link run id to deployed version.
Multi-model?
Router in serving app or separate deployments per model family.
Testing?
Fixture rows with expected scores; contract test train→serve feature parity.
Related
- Reference: A Data Platform / ETL System - feature upstream
- Reference: A FastAPI SaaS Backend - serving host
- MLOps Basics - operations depth
- Classical ML Pipelines - sklearn patterns
- Scalability Benchmarks - load testing inference
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.