← Todo el material
Curso Intermedio 23 de agosto de 2026

MLOps — Módulo 8: serving de modelos — batch vs online

Octavo módulo del Curso de MLOps: poner el modelo a responder de verdad. Batch (predicciones masivas offline, tipo score nocturno de la cartera) vs online (endpoint REST de baja latencia). Empaquetar el modelo en un contenedor y exponerlo con BentoML o FastAPI, con herramientas reales y código comentado.

☰ Índice del contenido

En el Módulo 7 el modelo pasó todos los guardianes: datos validados, mejor que producción, registrado. Está en el registry, versionado, listo. Y sin embargo no sirve para nada todavía, porque un modelo en un registry no responde preguntas — es un archivo esperando. Serving es el paso que convierte ese archivo en algo que produce predicciones para el resto del mundo. Y la primera decisión no es qué herramienta usar, sino cómo tiene que responder: ¿millones de predicciones de una, en la noche, sin apuro? ¿O una predicción a la vez, ahora mismo, en milisegundos? Ese cruce parte este módulo en dos.

La tesis del módulo

Servir un modelo no es una sola cosa: es dos problemas distintos con la misma pieza adentro. El batch scorea millones de filas offline cuando nadie mira; el online responde una a una, en vivo, contra un reloj. Elegir mal el modo te hace pagar latencia que no necesitas o construir un endpoint para algo que un job nocturno resolvía gratis.

Los dos modos de servir

Batch (por lotes) — el modelo scorea un conjunto grande de datos de una sola vez, offline, sin que nadie espere la respuesta en vivo. El caso típico: un job que cada noche calcula el score de churn de toda la cartera de clientes y deja el resultado en una tabla, listo para que el CRM lo consulte al día siguiente. Nadie está esperando frente a una pantalla; importa terminar antes del amanecer, no responder en 50 ms.

Online (en línea, real-time) — el modelo vive detrás de un endpoint REST y responde una predicción a la vez, en el momento, con baja latencia. El caso típico: un cliente entra a la web, el sistema pide “¿este usuario va a abandonar?” y necesita la respuesta ahora para decidir si mostrarle una oferta. Aquí cada milisegundo lo sufre un humano esperando.


                ┌──────────────────┐
   registry ──► │   MODELO (v3)    │
                └──────────────────┘
                   │            │
         batch     │            │  online
       (nocturno)  ▼            ▼  (en vivo)
       ┌──────────────┐   ┌──────────────┐
       │ job scorea   │   │ endpoint REST│
       │ TODA la      │   │ 1 request    │
       │ cartera      │   │ 1 respuesta  │
       │ a una tabla  │   │ en ms        │
       └──────────────┘   └──────────────┘

La misma pieza — el modelo del registry — se sirve de las dos formas. Lo que cambia es el envoltorio.

La base común: empaquetar en un contenedor

Sea batch u online, el modelo no viaja solo: viaja con su versión exacta de Python, sus librerías y su código de features, dentro de un contenedor. Es el mismo Docker del Módulo 5 de Deploy, ahora con un modelo adentro. Empaquetar mata el “en mi máquina predecía distinto”: el contenedor que probaste es bit por bit el que corre en producción, y con él viajan las mismas transformaciones de features que evitan el skew del Módulo 6.

Servir online con BentoML

BentoML (framework de serving de modelos) empaqueta modelo, código y dependencias en un artefacto y te da el endpoint REST casi gratis. Defines un Service y él expone la API, arma el contenedor y maneja el batching de requests por debajo:

# service.py — servir un modelo online con BentoML
import bentoml
import numpy as np
from pydantic import BaseModel

# El contrato de entrada: qué features espera el endpoint.
class Cliente(BaseModel):
    gasto_promedio_30d: float
    antiguedad_meses: int
    usa_app: int


@bentoml.service(resources={"cpu": "2"}, traffic={"timeout": 10})
class Churn:
    # Carga el modelo del registry de BentoML al arrancar, una sola vez.
    modelo_ref = bentoml.models.get("churn_clientes:latest")

    def __init__(self):
        self.modelo = bentoml.sklearn.load_model(self.modelo_ref)

    @bentoml.api
    def predict(self, cliente: Cliente) -> dict:
        X = np.array([[cliente.gasto_promedio_30d,
                       cliente.antiguedad_meses,
                       cliente.usa_app]])
        proba = float(self.modelo.predict_proba(X)[0, 1])
        return {"prob_churn": proba, "modelo": self.modelo_ref.tag.version}

Lo levantas, lo empaquetas y lo contenerizas con tres comandos:

bentoml serve service:Churn          # local, para probar
bentoml build                        # empaqueta modelo + código en un Bento
bentoml containerize churn:latest    # y el Bento en una imagen Docker

Si prefieres control total y ya conoces la herramienta del curso de agentes, FastAPI hace lo mismo cargando el modelo del registry a mano — más código, más perillas:

# main.py — el mismo endpoint con FastAPI y el modelo del registry MLflow
from fastapi import FastAPI
from pydantic import BaseModel
import mlflow.sklearn

app = FastAPI()
# Carga la versión que hoy tiene el alias champion en el registry (Módulo 4).
modelo = mlflow.sklearn.load_model("models:/churn_clientes@champion")


class Cliente(BaseModel):
    gasto_promedio_30d: float
    antiguedad_meses: int
    usa_app: int


@app.post("/predict")
def predict(cliente: Cliente):
    X = [[cliente.gasto_promedio_30d, cliente.antiguedad_meses, cliente.usa_app]]
    return {"prob_churn": float(modelo.predict_proba(X)[0, 1])}

El mapa de herramientas de serving

Más allá de estas dos, el panorama que verás nombrado:

HerramientaSaborCuándo es tu opción
FastAPITú armas el endpointControl total, ya lo conoces, casos simples
BentoMLEmpaqueta modelo + API + imagenServing de ML sin reinventar la rueda
Seldon / KServeServing sobre KubernetesYa vives en K8s, muchos modelos, autoscaling
Vertex AI / SageMaker AIEndpoint gestionado en la nubeNo quieres administrar servidores tú mismo

Regla de decisión: FastAPI si quieres control y el caso es simple; BentoML si quieres casi todo resuelto; KServe/Seldon cuando Kubernetes ya es tu casa; Vertex o SageMaker AI cuando prefieres pagar por no mantener infra.

Batch vs online: la tabla de decisión

BatchOnline
Cómo respondeMillones de filas de unaUna predicción a la vez
LatenciaMinutos/horas, no importaMilisegundos, crítica
DisparadorCron (ej. nocturno)Un request en vivo
CostoBarato (corre y apaga)Servidor siempre encendido
Caso típicoScore de toda la carteraOferta al usuario en la web
FormaJob programado (M5)Endpoint REST (BentoML/FastAPI)

Cuidado con montar online porque suena moderno. Un endpoint 24/7 cuesta plata todos los segundos, aunque nadie lo llame — y muchísimos casos de negocio (scoring de cartera, segmentación, alertas diarias) se resuelven con un job batch nocturno que corre en minutos y se apaga. Antes de construir el endpoint, pregúntate: ¿alguien está esperando esta predicción en vivo? Si la respuesta puede llegar mañana en una tabla, es batch.

Cuidado con cargar el modelo dentro del handler. En el predict, el modelo ya tiene que estar cargado en memoria (en __init__ o al arrancar el módulo). Si lo lees del registry en cada request, cada llamada paga el costo de deserializar el modelo entero — latencia por las nubes y un endpoint que se cae bajo carga.

Errores comunes

  • Servir online lo que era batch. Un endpoint carísimo, siempre prendido, para algo que un cron nocturno resolvía gratis. Si nadie espera la respuesta en vivo, es batch.
  • Cargar el modelo en cada request. Deserializar el modelo dentro del handler mata la latencia. Se carga una vez al arrancar y se reusa.
  • Servir sin contenedor. “Funciona en mi notebook” y explota en el servidor por otra versión de sklearn. El modelo viaja en su contenedor, con sus dependencias fijas.
  • Recalcular las features distinto al servir. El endpoint tiene que aplicar exactamente las mismas transformaciones que el entrenamiento. Reusa la función de features (M6) o vuelve el skew.
  • Hardcodear la versión del modelo. Cargar churn_v3.pkl a mano te obliga a redeployar por cada versión nueva. Carga por alias del registry (@champion) y la promoción del M4 hace el resto.

La frase para llevarse

“Un modelo en el registry no sirve a nadie: es un archivo esperando. El serving lo pone a responder — y la única pregunta que decide cómo es si alguien está esperando la respuesta ahora, o puede leerla mañana en una tabla.”

Qué viene

Con esto cierra el bloque intermedio: ya sabes automatizar el entrenamiento (M5), servir features consistentes (M6), blindar el proceso con CI/CD/CT (M7) y poner el modelo a responder (M8). Pero desplegar no es el final — es el comienzo del problema real. En el Módulo 9: monitoreo de modelos empieza el bloque avanzado, donde vigilamos que el modelo que hoy responde bien no se degrade en silencio mañana.

Material relacionado

MLOpsServingBentoMLDockerPipelines

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.