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:
| Herramienta | Sabor | Cuándo es tu opción |
|---|---|---|
| FastAPI | Tú armas el endpoint | Control total, ya lo conoces, casos simples |
| BentoML | Empaqueta modelo + API + imagen | Serving de ML sin reinventar la rueda |
| Seldon / KServe | Serving sobre Kubernetes | Ya vives en K8s, muchos modelos, autoscaling |
| Vertex AI / SageMaker AI | Endpoint gestionado en la nube | No 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
| Batch | Online | |
|---|---|---|
| Cómo responde | Millones de filas de una | Una predicción a la vez |
| Latencia | Minutos/horas, no importa | Milisegundos, crítica |
| Disparador | Cron (ej. nocturno) | Un request en vivo |
| Costo | Barato (corre y apaga) | Servidor siempre encendido |
| Caso típico | Score de toda la cartera | Oferta al usuario en la web |
| Forma | Job 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.pkla 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
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.