ML Deployment

From Jupyter Notebook to Production API: ML Model Deployment in 2026

A model that scores 0.94 in a notebook is not a product. To deploy an ML model to a production API you have to answer questions the notebook never asked: how does it load, how fast does it respond under concurrency, what does it cost per thousand requests, and what happens on the first request after it's been idle. Here is the path from model.pkl to an endpoint you can put in front of real traffic in 2026.

Step 1: Wrap the model in a real API

The notebook calls model.predict() inline. Production needs a service with a request schema, input validation, and a health check. FastAPI is the common choice because it gives you typed request bodies and async handling with very little code:

from fastapi import FastAPI
from pydantic import BaseModel
import joblib

app = FastAPI()
model = joblib.load("model.pkl")   # load ONCE at startup, not per request

class PredictRequest(BaseModel):
    features: list[float]

@app.get("/healthz")
def health():
    return {"status": "ok"}

@app.post("/predict")
def predict(req: PredictRequest):
    score = model.predict([req.features])[0]
    return {"score": float(score)}

The single most common performance bug in ML serving is loading the model inside the request handler. Load it once at startup and hold it in memory; a cold joblib.load or a multi-gigabyte weights file on every request will destroy your latency.

Step 2: Containerize it reproducibly

"Works in my conda env" is the ML version of "works on my machine." Pin everything and ship a container so the runtime is identical everywhere:

FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

Pin exact versions in requirements.txt (numpy==2.1.3, not numpy). A model trained against one NumPy or scikit-learn version can produce different — or broken — output against another, and an unpinned build will eventually pick up a new one silently.

Step 3: Decide GPU vs CPU honestly

This is the decision that dominates your bill. GPUs are essential for large deep-learning models and wasteful for everything else:

  • CPU is fine for classical ML (trees, linear models, most scikit-learn), small transformers, and anything where per-request compute is modest. It is 10–40x cheaper and far easier to autoscale.
  • GPU is worth it for large language models, diffusion models, and heavy vision workloads where CPU latency would be unusable. But a GPU instance idles at the same price whether it serves one request or a thousand, so utilization is everything.

Measure before you commit. A quantized model or ONNX Runtime on CPU often turns a "we need GPUs" assumption into a much cheaper CPU deployment. Benchmark your actual p95 latency at your actual concurrency, not a single-request demo.

Step 4: Autoscaling and the cold-start trap

Inference load is spiky, so you want to scale out under traffic and down when idle. The catch is cold starts: when a new instance spins up, it has to pull a multi-gigabyte image and load weights into memory before it can serve — which can be 30 seconds to several minutes for a large model on a GPU. Scale-to-zero saves money and produces brutal tail latency for the unlucky request that triggers a cold start.

The usual resolutions:

  • Keep a warm minimum of one or two instances so steady traffic never hits a cold start; only scale-to-zero for truly bursty, latency-tolerant workloads.
  • Shrink the image and the load path — bake weights into the image or a fast volume, and prefer formats that memory-map instead of deserializing.
  • Scale on a queue depth or concurrency metric, not CPU — GPU utilization and CPU are only loosely correlated for inference.

Step 5: The cost traps nobody warns you about

  • Idle GPUs. A single always-on GPU instance can quietly cost more per month than a small team's entire SaaS bill. If utilization is low, batching or CPU is your friend.
  • Egress. Returning large payloads (images, embeddings) across regions or out of the cloud adds up fast. Keep inference close to whatever calls it.
  • Over-provisioned memory. Loading a model per worker multiplies RAM; --workers 4 with a 4 GB model needs 16 GB, not 4.

Managed endpoints vs your own infrastructure

You have two roads, and the right one depends on scale and control:

  • Managed inference (SageMaker endpoints, Vertex AI, or a specialized GPU host) is the fastest way to a working endpoint and handles autoscaling and rollout for you. You pay a premium and accept their constraints — which is a fine trade early, or when latency isn't everything.
  • Your own infrastructure (ECS/Fargate for CPU, or GPU nodes on your own cluster) costs less per unit at scale and gives you full control over networking, cost, and data locality — which matters when the model runs on sensitive data or must live in a specific environment, the same driver behind deploying inside a customer's VPC.

A reasonable default: start on a managed endpoint to validate the product, and move to your own infrastructure once traffic is steady enough that the per-request premium and the lack of control outweigh the convenience.

The through-line is that model deployment is 20% modeling and 80% the same infrastructure discipline as any other service — containers, scaling, monitoring, and cost control — plus a few ML-specific traps like cold starts and idle GPUs. The failure modes rhyme with every other "works locally, breaks in production" story. If you would rather ship the model than become an inference-ops engineer, we can stand the whole pipeline up for you — managed endpoint or your own cloud, GPU or CPU, with the autoscaling and monitoring already wired in.

Frequently asked questions

How do I deploy an ML model to a production API?

Wrap the model in a web service (FastAPI is common) that loads the model once at startup and exposes a validated /predict endpoint plus a health check, containerize it with pinned dependencies, then run it on infrastructure that autoscales — a managed inference endpoint to start, or your own ECS/GPU setup at scale. Add monitoring and keep a warm instance to avoid cold-start latency.

Do I need a GPU to serve my model?

Usually only for large deep-learning models (LLMs, diffusion, heavy vision). Classical ML and small models run fine and far cheaper on CPU, especially with ONNX Runtime or quantization. Benchmark your real p95 latency at real concurrency before paying for GPUs.

What causes cold starts in ML inference and how do I fix them?

A cold start happens when a new instance must pull a large image and load model weights into memory before serving, which can take 30 seconds to minutes for big models. Fix it by keeping a warm minimum of instances, shrinking the image and load path, and only scaling to zero for bursty, latency-tolerant workloads.

Should I use a managed endpoint or my own infrastructure?

Start with a managed endpoint (SageMaker, Vertex AI, or a GPU host) to reach a working API quickly. Move to your own infrastructure when traffic is steady and the per-request premium, latency needs, or data-locality requirements justify the added operational work.

Rather have someone handle this end-to-end?

If you'd rather not become an infrastructure engineer to ship your project, we take a GitHub repo and handle the whole deployment — managed for you, or inside your own AWS, GCP, or Azure. No developer needed on your side.

Get your project deployed →