📖 Teoría

Puesta en Producción de Modelos ML

De Jupyter Notebook a producción: Docker, Kubernetes, TensorFlow Serving, TorchServe, Triton, BentoML, KServe. Plataformas cloud: Vertex AI, SageMaker, Azure ML. Soluciones no-code: HF Spaces, Gradio, Replicate. CI/CD, monitorización, drift detection y optimización de costes.

¿Qué significa "poner en producción" un modelo?

Entrenar un modelo en un Jupyter Notebook es solo el 10 % del trabajo. Producción implica que tu modelo responda a peticiones reales, de usuarios reales, 24/7, de forma fiable, escalable y monitorizada.

El camino desde un prototipo funcional hasta un servicio en producción abarca múltiples etapas: refactorizar el código de entrenamiento en scripts limpios y testeables, exponer el modelo a través de una API o un framework de serving, empaquetar todo en un contenedor Docker reproducible, y desplegarlo en una infraestructura que garantice disponibilidad, escalabilidad y observabilidad. Cada una de estas etapas introduce complejidad técnica propia, y dominarlas es lo que diferencia a un data scientist de un ML engineer.

El concepto clave es que el modelo es solo una pieza de un sistema mucho mayor. En su famoso paper Hidden Technical Debt in Machine Learning Systems (Sculley et al., 2015), los investigadores de Google estimaron que el código del modelo representa menos del 5 % del código total de un sistema ML en producción. El resto lo forman pipelines de datos, infraestructura de serving, monitorización, configuración y testing.

📓 Notebook Prototipo local 🧹 Refactor Scripts + tests 🔌 API / Serving FastAPI, TFServing… 🐳 Contenedor Docker + CI/CD 🚀 Producción Cloud / Edge

📊 La realidad: solo el 54 % de los modelos llegan a producción

Según Gartner (2022), la mayoría de modelos de ML nunca salen del laboratorio. Las principales causas son: falta de infraestructura, datos inconsistentes entre entrenamiento y producción, y ausencia de procesos MLOps.

Además, un informe de VentureBeat estima que hasta el 87 % de los proyectos de data science no llegan a producción. Este submódulo te enseña a superar cada obstáculo paso a paso.

Si vienes del mundo del software tradicional, piensa en el despliegue de un modelo como una versión más exigente del despliegue de una aplicación web: además de preocuparte por disponibilidad, seguridad y escalabilidad, también tienes que gestionar pesos del modelo (archivos de varios GB), dependencias numéricas (CUDA, cuDNN, versiones exactas de PyTorch/TensorFlow), hardware heterogéneo (CPUs, GPUs, TPUs) y degradación silenciosa (el modelo no "crashea", simplemente empieza a dar peores predicciones si los datos cambian).

A lo largo de este submódulo recorreremos todas las herramientas y patrones que necesitas para llevar un modelo desde tu notebook local hasta un servicio de producción robusto. Si te interesa la parte de optimización de modelos para dispositivos con recursos limitados, consulta el submódulo de Edge Computing. Para entender las métricas de evaluación que se mencionan aquí, revisa la teoría de Machine Learning Clásico.

El ciclo MLOps

MLOps (Machine Learning Operations) es la disciplina que aplica prácticas DevOps al ciclo de vida de modelos de ML. No es solo "desplegar": es un bucle continuo de datos → entrenamiento → validación → despliegue → monitorización → re-entrenamiento.

El término fue popularizado a partir de 2020, pero sus raíces se remontan a la guía de Google Cloud sobre MLOps, que define tres niveles de madurez: Nivel 0 (manual, sin automatización), Nivel 1 (pipelines de ML automatizados) y Nivel 2 (CI/CD completo para pipelines de ML). La mayoría de equipos empiezan en el Nivel 0 y el objetivo es alcanzar al menos el Nivel 1 para garantizar reproducibilidad y eficiencia.

En la práctica, MLOps combina tres áreas: ingeniería de datos (ingesta, limpieza, feature engineering), ingeniería de modelos (entrenamiento, evaluación, optimización) e ingeniería de software (APIs, contenedores, CI/CD, monitorización). Un ML engineer eficaz necesita competencias en las tres.

📦 Datos ⚙️ Features 🧠 Entrena Valida 🚀 Deploy 📊 Monitor 🔄 Retrain MLOps Ciclo continuo
1
Datos: Ingesta, limpieza, versionado (DVC, Delta Lake). Los datos son el combustible; si cambian, el modelo debe adaptarse.
2
Feature Engineering: Transformaciones reproducibles (feature stores como Feast, Tecton). Mismas features en train y en producción — un desajuste aquí se llama training-serving skew.
3
Entrenamiento: Pipelines reproducibles (Kubeflow, Airflow, Metaflow). Hiperparámetros versionados, artefactos registrados en MLflow o W&B.
4
Validación: Tests automáticos de accuracy, fairness y latencia. Gate de calidad antes de desplegar — si no supera el umbral, el pipeline se detiene.
5
Despliegue: Containerización (Docker), orquestación (K8s), canary deployments, blue-green. Este submódulo se centra aquí.
6
Monitorización: Data drift, concept drift, latencia p99, uso de recursos. Alertas automáticas con herramientas como Evidently o Prometheus.
7
Re-entrenamiento: Trigger automático o programado cuando el modelo degrada. Cierre del bucle continuo que garantiza que el modelo sigue siendo relevante.

Es importante entender que MLOps no es un producto que compras, sino una cultura y un conjunto de prácticas. Puedes empezar con herramientas sencillas (MLflow para tracking, Docker para reproducibilidad, GitHub Actions para CI/CD) e ir adoptando componentes más avanzados (Kubernetes, Triton, feature stores) a medida que tu sistema madure. Para una guía completa de herramientas, consulta el repositorio Awesome MLOps y la web ml-ops.org.

Tipos de despliegue

No todos los modelos se sirven igual. El patrón de despliegue depende de la latencia requerida, el volumen de datos y el caso de uso. Elegir el patrón adecuado es una de las decisiones arquitectónicas más importantes, porque determina la infraestructura, el coste y la experiencia de usuario.

Real-time (Online)
El modelo responde a cada petición en milisegundos. Típico de APIs REST/gRPC. Ejemplo: clasificación de imagen al subir una foto, chatbot, recomendaciones en tiempo real.
📦
Batch (Offline)
Se procesan grandes volúmenes de datos periódicamente (cada hora, diario). Ejemplo: scoring de millones de usuarios para email marketing, procesamiento de logs.
🌊
Streaming
Inferencia continua sobre flujos de datos (Kafka, Flink). Ejemplo: detección de fraude en transacciones financieras, IoT analytics.
📱
Edge / On-Device
El modelo corre directamente en el dispositivo del usuario (móvil, navegador, embebido). Ejemplo: autocompletado en teclado, filtros de cámara, conducción autónoma. Ver submódulo Edge Computing.

La elección no es excluyente: muchos sistemas combinan varios patrones. Por ejemplo, un sistema de recomendación puede usar batch para pre-calcular candidatos cada noche y real-time para re-rankear los resultados según el contexto inmediato del usuario. De forma similar, un sistema de detección de fraude puede usar streaming para alertas inmediatas y batch para análisis retrospectivos más profundos.

Aspecto Real-time Batch Streaming Edge
Latencia< 100 msMinutos–horasSeg–min< 50 ms
Throughput100–10k RPSMillones/batchContinuo1 dispositivo
Infra típicaK8s + GPUSpark / AirflowKafka + FlinkNPU / MCU
EscaladoHorizontalParaleloParticionesNo aplica
CosteMedio-altoBajo/medioMedioHW upfront
EjemploChatbotScoring CRMFraude financieroFiltro cámara

Retos de producción

Pasar de "funciona en mi máquina" a "funciona para 10 millones de usuarios" implica enfrentarse a problemas que no existen en un notebook. A continuación describimos los seis retos fundamentales que todo equipo de ML encuentra al llevar modelos a producción.

🔄
Reproducibilidad
Mismo código + mismos datos = mismo modelo. Dependencias, seeds, versiones de CUDA… todo debe estar controlado.
📈
Escalabilidad
De 10 req/s a 10 000 req/s. Autoscaling, load balancing, réplicas, colas de mensajes.
⏱️
Latencia
SLAs estrictos: p50 < 50 ms, p99 < 200 ms. Batching, caching, optimización del modelo, GPU scheduling.
📊
Monitorización
¿El modelo sigue funcionando bien? Data drift, concept drift, métricas de negocio. Prometheus + Grafana.
🔐
Seguridad
Autenticación, rate limiting, adversarial attacks, privacidad de datos, GDPR.
💰
Coste
GPUs cloud son caras ($2–30/h). Optimización: spot instances, serverless, right-sizing, model compression.

De entre estos retos, la reproducibilidad y la monitorización son los que más se subestiman. Un modelo que no se puede reproducir es un modelo que no se puede depurar. Y un modelo sin monitorización es una bomba de relojería: degradará en silencio conforme los datos cambien (fenómeno conocido como concept drift). Para profundizar en técnicas de detección de drift, consulta la sección de Monitorización y drift más adelante.

La seguridad en modelos ML añade vectores de ataque específicos respecto al software tradicional: adversarial attacks (inputs manipulados para engañar al modelo), model inversion (extraer datos de entrenamiento a partir de las predicciones) y model stealing (replicar el modelo accediendo a la API). El paper Adversarial Examples Are Not Easily Detected (Carlini & Wagner) ofrece una buena introducción a estos riesgos.

Métricas clave de producción

En producción, la accuracy del modelo es solo una de muchas métricas. Necesitas monitorizar el sistema completo: infraestructura, calidad del modelo y métricas de negocio. La combinación de estos tres niveles forma lo que se conoce como observabilidad de ML.

Métrica Descripción Objetivo típico Herramienta
Latencia p50 / p99 Tiempo de respuesta mediano y percentil 99 p50 < 50 ms, p99 < 200 ms Prometheus, Datadog
Throughput (RPS) Requests por segundo soportados Depende del caso Locust, k6, wrk
Availability (SLA) Porcentaje de uptime 99.9 % (8.7 h down/año) Uptime Robot, PagerDuty
Error rate % de peticiones que fallan (5xx) < 0.1 % Sentry, ELK Stack
GPU utilization % de uso de la GPU > 70 % (eficiencia) nvidia-smi, DCGM
Model accuracy (online) Accuracy medida sobre datos reales ≥ accuracy de test Evidently, Whylabs
Data drift score Divergencia entre datos de train y producción KS test p > 0.05 Evidently, NannyML
Coste por inferencia $ por cada predicción Minimizar Cloud billing APIs

Las métricas de percentil (p50, p99) son especialmente importantes: la latencia media puede ser buena mientras que el 1 % de usuarios sufre tiempos inaceptables. Un buen SLA de producción especifica ambos percentiles. Por ejemplo, Amazon mide el p99.9 internamente para garantizar que incluso el 0.1 % de requests más lentos cumplan umbrales. Para entender en profundidad las métricas de evaluación de modelos (accuracy, precision, recall, F1, AUC-ROC), consulta el explorador de métricas en ML Clásico.

🧮 Calculadora de coste de inferencia

Estima el coste mensual de servir tu modelo en la nube según el volumen de tráfico y el tipo de instancia.

Mapa de decisión: ¿qué solución necesito?

No existe una única forma de desplegar un modelo. La solución óptima depende de tu perfil técnico, el volumen de tráfico, el presupuesto y los requisitos de latencia. El siguiente árbol de decisión te guía paso a paso hacia la herramienta más adecuada.

¿Tu equipo tiene DevOps? No → Soluciones managed Sí → Infraestructura propia Prototipo / demo HF Spaces, Gradio Streamlit Cloud Producción managed Vertex AI, SageMaker Azure ML, Replicate ¿Mucho tráfico (> 1k RPS)? Poco tráfico Docker + Cloud Run ECS, App Runner GPU + baja latencia K8s + Triton / TFServing KServe, Seldon Core CPU / serverless BentoML + K8s Lambda + API Gateway 💡 Resumen rápido Demo → HF Spaces / Gradio · Startup → Vertex AI / SageMaker · Scale-up → Docker + K8s Enterprise GPU → Triton + KServe · Low traffic → Cloud Run / Lambda · Edge → ver submódulo Edge Computing
🗺️ Roadmap de este submódulo: Recorreremos cada una de estas opciones en detalle, desde contenedores Docker y Kubernetes hasta plataformas managed (Vertex AI, SageMaker) y frameworks de serving (TFServing, Triton, BentoML). Al final tendrás claro qué herramientas usar para tu caso concreto.

¿Por qué Docker para ML?

Docker resuelve el problema más antiguo del desarrollo de software: "funciona en mi máquina". En ML, este problema se multiplica: versiones de CUDA, librerías de Python, pesos del modelo, preprocesamiento… Un contenedor empaqueta todo en una unidad reproducible.

En un flujo de trabajo típico sin Docker, un data scientist entrena un modelo en su laptop con PyTorch 2.0, Python 3.10 y determinadas versiones de NumPy y Pillow. Cuando el equipo de ingeniería intenta desplegar ese modelo, descubre que el servidor tiene Python 3.8, otra versión de CUDA, y las dependencias entran en conflicto. Este escenario, lejos de ser anecdótico, es la norma en la industria y una de las principales causas de que los modelos no lleguen a producción.

Docker elimina este problema al crear un entorno inmutable y reproducible que viaja con el modelo. Da igual si lo ejecutas en tu laptop, en un servidor de CI/CD o en un clúster de Kubernetes: el contenedor siempre se comporta igual. Por eso Docker se ha convertido en un requisito casi universal para despliegue de modelos ML. Para una guía práctica paso a paso, consulta el tutorial de este submódulo, donde construimos un contenedor Docker para un modelo real.

📦
Reproducibilidad
Mismas dependencias en local, CI y producción. Adiós conflictos de versiones.
🔄
Portabilidad
El mismo contenedor corre en AWS, GCP, Azure, on-premise o tu laptop.
🏗️
Aislamiento
Cada modelo en su propio contenedor con sus propias dependencias. Sin conflictos.
Escalado
Levanta N réplicas del mismo contenedor en segundos. Kubernetes lo hace automáticamente.

Conceptos clave de Docker

ConceptoDescripciónAnalogía ML
Image Plantilla read-only con el SO, deps y código. Se construye con docker build. El "checkpoint" de tu entorno
Container Instancia en ejecución de una imagen. Se crea con docker run. El proceso de inferencia activo
Dockerfile Receta que define cómo construir la imagen paso a paso. Tu "pipeline de setup"
Volume Almacenamiento persistente montado en el contenedor. Donde guardar pesos y datos
Registry Almacén de imágenes (Docker Hub, ECR, GCR, ACR). Tu "model zoo" de contenedores
Layer Cada instrucción del Dockerfile crea una capa cacheada. Optimización: deps primero, código después
Compose Orquestación multi-contenedor local (docker compose up). Modelo + API + base de datos juntos

La arquitectura de Docker se basa en un modelo cliente-servidor. El Docker Engine (daemon) se ejecuta en el host y gestiona imágenes, contenedores, redes y volúmenes. Cada contenedor comparte el kernel del host pero tiene su propio espacio de usuario aislado (sistema de archivos, procesos, red). Esto es más eficiente que las máquinas virtuales, que necesitan un sistema operativo completo por cada instancia.

Host OS (Linux kernel) 🐳 Docker Engine (containerd + runc) Container 1 FastAPI + model.pt Python 3.11 + PyTorch Container 2 TFServing + model.pb TF 2.15 + CUDA 12 Container 3 Triton Server Multi-model + gRPC Container 4 Prometheus Monitoring stack

Lo clave para ML es que cada contenedor tiene sus propias dependencias completamente aisladas. Esto permite tener un contenedor con PyTorch 2.x y CUDA 12, otro con TensorFlow 2.14 y CUDA 11, y un tercero con Triton — todo en la misma máquina sin conflictos. Para una introducción completa a Docker, la documentación oficial es el mejor punto de partida.

Dockerfile para modelos de ML

Un buen Dockerfile para ML sigue el principio de capas ordenadas: las dependencias del sistema primero (cambian poco), luego las de Python, y por último el código y los pesos del modelo (cambian a menudo). Esto maximiza el cache de Docker.

# ═══════════════════════════════════════════════════
# Dockerfile · FastAPI + PyTorch inference server
# ═══════════════════════════════════════════════════

# ── Stage 1: base con CUDA (si necesitas GPU) ──
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04 AS base

ENV DEBIAN_FRONTEND=noninteractive \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

RUN apt-get update && apt-get install -y --no-install-recommends \
    python3.11 python3-pip python3.11-venv && \
    rm -rf /var/lib/apt/lists/*

# ── Stage 2: dependencias Python (cacheadas) ──
FROM base AS deps
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# ── Stage 3: código y modelo ──
FROM deps AS final
COPY src/ ./src/
COPY models/model.pt ./models/

# Puerto de la API
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# Arrancar con uvicorn (workers = nº CPUs)
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
# ═══════════════════════════════════════════════════
# Dockerfile · TensorFlow Serving con modelo propio
# ═══════════════════════════════════════════════════

FROM tensorflow/serving:2.14.0-gpu

# Copiar modelo exportado en formato SavedModel
# Estructura: models/my_model/1/saved_model.pb
COPY models/my_model /models/my_model

# Variables de entorno
ENV MODEL_NAME=my_model \
    MODEL_BASE_PATH=/models

# TFServing escucha en 8501 (REST) y 8500 (gRPC)
EXPOSE 8500 8501

# El entrypoint de la imagen base ya arranca tf_model_server

Multi-stage builds

Los modelos de ML generan imágenes enormes (PyTorch + CUDA puede superar los 8 GB). Las multi-stage builds reducen el tamaño final descartando herramientas de compilación y conservando solo lo necesario para ejecutar.

EstrategiaTamaño típicoBeneficio
Imagen base completa 6–10 GB Fácil, todo incluido
Multi-stage (runtime only) 2–4 GB Sin compiladores, docs, tests
Slim / distroless 1–2 GB Solo el runtime de Python + modelo
CPU-only (sin CUDA) 500 MB – 1 GB Ideal si no necesitas GPU

💡 Best practices para imágenes ML ligeras

  • Usa --no-cache-dir en pip install
  • Combina RUN en una sola capa: apt-get update && apt-get install && rm -rf /var/lib/apt/lists/*
  • Usa .dockerignore para excluir notebooks, checkpoints de entrenamiento, datasets
  • Considera python:3.11-slim como base si no necesitas CUDA
  • Monta los pesos del modelo como volumen si son muy grandes (> 1 GB)

GPU en Docker: nvidia-container-toolkit

Para usar GPUs NVIDIA dentro de contenedores necesitas el NVIDIA Container Toolkit (antes nvidia-docker2). Permite que el contenedor acceda a las GPUs del host sin instalar CUDA dentro de la imagen.

# ═══════════════════════════════════════════════════
# Instalar NVIDIA Container Toolkit (Ubuntu)
# ═══════════════════════════════════════════════════

# 1. Añadir repositorio
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
    sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

# 2. Instalar
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# 3. Verificar
docker run --rm --gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi

# ═══════════════════════════════════════════════════
# Ejecutar tu contenedor con GPU
# ═══════════════════════════════════════════════════

# Todas las GPUs
docker run --gpus all -p 8000:8000 my-model:latest

# GPU específica
docker run --gpus '"device=0"' -p 8000:8000 my-model:latest

# Limitar memoria GPU
docker run --gpus all --shm-size=2g -p 8000:8000 my-model:latest
⚠️ Importante: El driver NVIDIA debe estar instalado en el host, no en el contenedor. La versión de CUDA del contenedor debe ser compatible con el driver del host. Usa nvidia-smi en el host para ver la versión máxima de CUDA soportada.

Docker Compose para servicios ML

En producción raramente tienes solo el modelo. Un sistema típico incluye: API de inferencia, base de datos de features, cola de mensajes, monitorización… Docker Compose orquesta todos estos servicios localmente y sirve de plantilla para el despliegue en Kubernetes.

# ═══════════════════════════════════════════════════
# docker-compose.yml · Stack de inferencia ML
# ═══════════════════════════════════════════════════
version: "3.8"

services:
  # ── Modelo de inferencia ──
  inference:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    volumes:
      - ./models:/app/models:ro    # Pesos del modelo (read-only)
    environment:
      - MODEL_PATH=/app/models/model.pt
      - WORKERS=2
      - DEVICE=cuda
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped

  # ── Redis para caching de predicciones ──
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

  # ── Prometheus para métricas ──
  prometheus:
    image: prom/prometheus:v2.48.0
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro

  # ── Grafana para dashboards ──
  grafana:
    image: grafana/grafana:10.2.0
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    depends_on:
      - prometheus

volumes:
  redis-data:

Registros de contenedores (Container Registries)

Una vez construida la imagen, necesitas almacenarla en un registry desde donde los servidores de producción puedan descargarla.

RegistryProveedorCaracterísticasCoste
Docker Hub Docker Inc. El más conocido. 1 repo privado gratis. Imágenes oficiales de TF, PyTorch, NVIDIA. Gratis (limitado) / $5–9/mes
Amazon ECR AWS Integrado con ECS/EKS. Escaneo de vulnerabilidades. Lifecycle policies. $0.10/GB/mes
Google AR GCP Artifact Registry (sustituye a GCR). Integrado con GKE y Cloud Run. $0.10/GB/mes
Azure ACR Microsoft Integrado con AKS. Geo-replicación. Tasks para CI/CD. $0.17–1.67/día
GitHub GHCR GitHub Integrado con GitHub Actions. Gratis para repos públicos. Gratis (público) / $0.008/GB
# ═══════════════════════════════════════════════════
# Push a Amazon ECR
# ═══════════════════════════════════════════════════

# 1. Login
aws ecr get-login-password --region eu-west-1 | \
    docker login --username AWS --password-stdin 123456789.dkr.ecr.eu-west-1.amazonaws.com

# 2. Tag
docker tag my-model:latest 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-model:v1.2.0

# 3. Push
docker push 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-model:v1.2.0

# ═══════════════════════════════════════════════════
# Push a Google Artifact Registry
# ═══════════════════════════════════════════════════

# 1. Auth
gcloud auth configure-docker europe-west1-docker.pkg.dev

# 2. Tag
docker tag my-model:latest europe-west1-docker.pkg.dev/my-project/ml-models/my-model:v1.2.0

# 3. Push
docker push europe-west1-docker.pkg.dev/my-project/ml-models/my-model:v1.2.0
💡 Tip: Las imágenes ML con CUDA pueden pesar 4–8 GB. Usa docker image prune regularmente y configura lifecycle policies en tu registry para eliminar imágenes antiguas automáticamente. Considera también distroless images para minimizar la superficie de ataque.

¿Por qué Kubernetes para ML?

Docker ejecuta contenedores en una máquina. Pero en producción necesitas: múltiples réplicas, autoscaling, rolling updates sin downtime, health checks, gestión de GPUs, y recuperación ante fallos. Kubernetes (K8s) es el estándar de facto para orquestar todo esto.

Kubernetes fue desarrollado originalmente por Google (inspirado en su sistema interno Borg) y liberado como open-source en 2014. Hoy en día, según la encuesta anual de la CNCF (2023), el 84 % de las organizaciones usan o evalúan Kubernetes en producción. Para ML, Kubernetes ofrece una ventaja adicional: la capacidad de gestionar GPUs como recursos, de modo que puedes solicitar "1 GPU NVIDIA T4" para tu pod de inferencia de la misma forma que solicitas CPU y memoria.

⚖️
Autoscaling
Escala réplicas automáticamente según CPU, memoria o métricas custom (RPS, latencia, cola GPU).
🔄
Rolling Updates
Despliega nuevas versiones del modelo sin downtime. Rollback automático si falla el health check.
🎮
GPU Scheduling
Asigna GPUs a pods de forma inteligente. Soporta NVIDIA, AMD e Intel con device plugins.
🩺
Self-healing
Si un pod muere, K8s lo reinicia. Si un nodo falla, redistribuye los pods automáticamente.

Conceptos clave de Kubernetes

CONTROL PLANE API Server Scheduler Controller Mgr etcd Worker Node 1 (GPU) Pod: inference-v2 Container: triton-server Pod: inference-v2 Container: triton-server Pod: preprocess Container: fastapi Worker Node 2 (CPU) Pod: monitoring Container: prometheus Pod: redis Container: redis:7 Pod: grafana + alertmanager Container: grafana Container: alertmanager

En el diagrama anterior se observa la separación fundamental entre el control plane (que gestiona el estado deseado del clúster) y los worker nodes (que ejecutan los contenedores). Para inferencia ML, los worker nodes se configuran típicamente con GPUs dedicadas, y Kubernetes se encarga de asignar los pods a los nodos con GPU disponible.

RecursoQué haceCuándo usarlo en ML
Deployment Gestiona réplicas de pods con rolling updates. Desplegar N réplicas de tu modelo de inferencia.
Service Expone pods con una IP estable y load balancing. Punto de entrada único para tus réplicas de modelo.
Ingress Enrutamiento HTTP/HTTPS externo. Exponer tu API al exterior con TLS y rutas.
HPA Horizontal Pod Autoscaler. Escala pods según métricas. Escalar inference pods según RPS o GPU %.
ConfigMap / Secret Configuración y secretos inyectados en pods. API keys, rutas de modelo, hiperparámetros de serving.
PersistentVolume Almacenamiento persistente para pods. Almacenar pesos de modelos compartidos entre réplicas.
Job / CronJob Tareas puntuales o programadas. Re-entrenamiento programado, batch inference.

Deployment YAML para inferencia ML

Un Deployment de K8s para un modelo de inferencia con GPU, health checks y autoscaling.

# ═══════════════════════════════════════════════════
# deployment.yaml · Inference server con GPU
# ═══════════════════════════════════════════════════
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inference-server
  labels:
    app: inference
    version: v2.1.0
spec:
  replicas: 3                        # Réplicas iniciales
  selector:
    matchLabels:
      app: inference
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                    # +1 pod durante update
      maxUnavailable: 0              # 0 downtime
  template:
    metadata:
      labels:
        app: inference
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
    spec:
      containers:
        - name: model
          image: 123456789.dkr.ecr.eu-west-1.amazonaws.com/my-model:v2.1.0
          ports:
            - containerPort: 8000
          resources:
            requests:
              cpu: "2"
              memory: "4Gi"
              nvidia.com/gpu: "1"     # ← Solicita 1 GPU
            limits:
              cpu: "4"
              memory: "8Gi"
              nvidia.com/gpu: "1"
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 30    # Tiempo para cargar modelo
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 30
          env:
            - name: MODEL_PATH
              value: /models/model.pt
            - name: MAX_BATCH_SIZE
              value: "16"
          volumeMounts:
            - name: model-weights
              mountPath: /models
              readOnly: true
      volumes:
        - name: model-weights
          persistentVolumeClaim:
            claimName: model-pvc
      tolerations:                     # Permitir scheduling en nodos GPU
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
---
# ═══════════════════════════════════════════════════
# Service · Load balancer interno
# ═══════════════════════════════════════════════════
apiVersion: v1
kind: Service
metadata:
  name: inference-service
spec:
  selector:
    app: inference
  ports:
    - port: 80
      targetPort: 8000
  type: ClusterIP
---
# ═══════════════════════════════════════════════════
# HPA · Autoscaling basado en CPU
# ═══════════════════════════════════════════════════
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: inference-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: inference-server
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300   # Esperar 5 min antes de reducir

Scaling avanzado para ML

El HPA estándar escala por CPU/memoria, pero en ML queremos escalar por métricas como requests per second, latencia p99 o GPU utilization. Herramientas como KEDA lo hacen posible.

MecanismoQué escalaMétrica típica MLHerramienta
HPA Pods horizontalmente CPU %, memoria, custom metrics K8s nativo
VPA Recursos de pods verticalmente Ajustar CPU/RAM según uso real K8s VPA addon
KEDA Pods por eventos Mensajes en cola, RPS, cron KEDA
Cluster Autoscaler Nodos del clúster Pods pendientes sin recursos Cloud provider
Karpenter Nodos (AWS) Provisionado inteligente de instancias GPU Karpenter

🎯 Scale-to-zero para ahorro de costes

Si tu modelo solo recibe tráfico en horario laboral, puedes usar KEDA o Knative para escalar a 0 réplicas cuando no hay peticiones, y levantar pods bajo demanda (cold start ~10–30s). Ideal para modelos de baja frecuencia. Alternativa: serverless con Cloud Run o AWS Lambda.

Kubernetes managed: EKS, GKE, AKS

Gestionar un clúster K8s desde cero es complejo. Los 3 grandes clouds ofrecen Kubernetes managed donde ellos se encargan del control plane y tú solo gestionas los nodos y workloads. Esta es la opción recomendada para la gran mayoría de equipos, ya que elimina la carga operativa de mantener etcd, el API server y los controllers. Puedes centrarte en desplegar tus modelos en lugar de administrar la infraestructura.

ServicioCloudGPU soporteExtras para MLCoste control plane
EKS AWS P4d, G5, Inf2, Trn1 SageMaker Operators, Karpenter, EFA $0.10/h (~$73/mes)
GKE Google Cloud A100, L4, T4, TPUs Autopilot, GKE + Vertex AI, GPU time-sharing Gratis (Autopilot: paga por pod)
AKS Azure A100, T4, V100 Azure ML integration, KAITO (LLM addon) Gratis (pay for nodes)

Alternativas a Kubernetes

K8s es potente pero complejo. Para muchos casos de ML hay alternativas más simples que cubren el 80 % de las necesidades.

☁️
Cloud Run (GCP)
Serverless containers. Scale-to-zero. Soporte GPU (L4). Ideal para APIs de inferencia con tráfico variable. → Docs
🟠
AWS ECS / Fargate
Orquestación de contenedores de AWS. Fargate = serverless (sin gestionar nodos). Buena alternativa a EKS si no necesitas K8s. → Docs
AWS Lambda
Funciones serverless. Hasta 10 GB de imagen, 15 min timeout. Para modelos pequeños (CPU) con tráfico esporádico. → Docs
🚂
Railway / Render
PaaS simples para desplegar contenedores. Sin gestionar infra. Ideal para prototipos y demos con poco tráfico. → Railway
SoluciónComplejidadGPUScale-to-zeroCoste mínimoMejor para
Kubernetes🔴 Alta✅ FullCon KEDA~$150/mesEnterprise, multi-modelo
Cloud Run🟢 Baja✅ L4✅ Nativo$0 (idle)APIs con tráfico variable
ECS Fargate🟡 MediaCon target tracking~$30/mesAWS ecosystem, CPU
Lambda🟢 Baja✅ Nativo$0 (idle)Modelos < 10 GB, CPU
Railway/Render🟢 Baja$5/mesPrototipos, demos
💡 Regla general: ¿< 100 RPS y sin GPU? → Cloud Run / Lambda. ¿GPU + tráfico medio? → Cloud Run GPU / ECS con instancias GPU. ¿Multi-modelo, A/B, canary, alta personalización? → Kubernetes + KServe.

¿Qué es Model Serving?

Model Serving es el proceso de exponer un modelo entrenado como un servicio que acepta peticiones y devuelve predicciones. Puedes hacerlo con una API Flask/FastAPI sencilla, pero los frameworks de serving dedicados optimizan: batching dinámico, gestión de GPU, versionado de modelos, A/B testing y monitorización.

La diferencia entre servir un modelo con una API "casera" y un framework de serving dedicado es similar a la diferencia entre un servidor web Python básico y Nginx: ambos sirven HTTP, pero uno está optimizado para rendimiento, concurrencia y producción. En el contexto de ML, los frameworks de serving añaden batching dinámico (agrupan múltiples requests para procesarlos juntos en GPU, maximizando el throughput), versionado automático (cargan nuevas versiones del modelo sin downtime) y health monitoring (detectan si el modelo está respondiendo correctamente).

📱 Cliente REST / gRPC Load Balancer Nginx / Envoy A/B routing Rate limiting Serving Framework Pre-process Batching Inference Post-process Model Store v1 (prod) ✅ v2 (canary) 🔄 v3 (staging) 🧪

El flujo estándar de serving comienza cuando un cliente (aplicación web, móvil u otro servicio) envía una petición con los datos de entrada. Un load balancer distribuye las peticiones entre las réplicas del servidor, aplicando opcionalmente políticas de A/B testing o canary. El framework de serving se encarga del pre-procesamiento, el batching (agrupar peticiones para eficiencia GPU), la inferencia propiamente dicha, y el post-procesamiento. Finalmente, el model store gestiona las versiones del modelo, permitiendo rollbacks instantáneos si una nueva versión degrada métricas.

TensorFlow Serving

TensorFlow Serving es el servidor de inferencia oficial de Google para modelos TensorFlow. Altamente optimizado, soporta batching dinámico, versionado de modelos y gRPC.

🔥
Rendimiento
Optimizado en C++ para latencia ultra-baja. Batching dinámico configurable.
📦
Versionado
Carga automática de nuevas versiones. Rollback instant. A/B entre versiones.
🔌
APIs
REST (8501) + gRPC (8500). Predict, Classify, Regress endpoints.
⚠️
Limitaciones
Solo SavedModel format. No soporta PyTorch nativamente.
# ═══════════════════════════════════════════════════
# 1. Exportar modelo como SavedModel
# ═══════════════════════════════════════════════════
import tensorflow as tf

model = tf.keras.models.load_model("my_model.keras")

# Exportar con versión (directorio numérico)
export_path = "models/my_model/1"    # ← versión 1
model.export(export_path)
print(f"✅ Modelo exportado a {export_path}")

# ═══════════════════════════════════════════════════
# 2. Levantar TF Serving con Docker
# ═══════════════════════════════════════════════════
# docker run -p 8501:8501 \
#     -v $(pwd)/models/my_model:/models/my_model \
#     -e MODEL_NAME=my_model \
#     tensorflow/serving:2.14.0-gpu

# ═══════════════════════════════════════════════════
# 3. Hacer predicciones via REST
# ═══════════════════════════════════════════════════
import requests
import numpy as np

data = {"instances": np.random.randn(1, 224, 224, 3).tolist()}
url = "http://localhost:8501/v1/models/my_model:predict"

response = requests.post(url, json=data)
predictions = response.json()["predictions"]
print(f"🎯 Predicción: {np.argmax(predictions[0])}")

TorchServe

TorchServe es el servidor oficial de PyTorch, mantenido conjuntamente por AWS y Meta. Soporta modelos PyTorch y TorchScript, con handlers personalizables para pre/post-procesamiento.

# ═══════════════════════════════════════════════════
# 1. Crear Model Archive (.mar)
# ═══════════════════════════════════════════════════
# torch-model-archiver \
#     --model-name resnet50 \
#     --version 1.0 \
#     --serialized-file model.pt \
#     --handler image_classifier \
#     --export-path model_store/

# ═══════════════════════════════════════════════════
# 2. Arrancar TorchServe
# ═══════════════════════════════════════════════════
# torchserve --start --model-store model_store \
#     --models resnet50=resnet50.mar \
#     --ts-config config.properties

# ═══════════════════════════════════════════════════
# 3. Handler personalizado (opcional)
# ═══════════════════════════════════════════════════
from ts.torch_handler.base_handler import BaseHandler
import torch
from torchvision import transforms
from PIL import Image
import io

class MyHandler(BaseHandler):
    def preprocess(self, data):
        """Pre-proceso: decode imagen + transformaciones."""
        transform = transforms.Compose([
            transforms.Resize(256),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406],
                                 [0.229, 0.224, 0.225]),
        ])
        images = []
        for row in data:
            image = Image.open(io.BytesIO(row["body"]))
            images.append(transform(image))
        return torch.stack(images)

    def inference(self, data):
        """Inferencia sobre batch."""
        with torch.no_grad():
            return self.model(data)

    def postprocess(self, output):
        """Post-proceso: softmax + top-5."""
        probs = torch.nn.functional.softmax(output, dim=1)
        top5 = torch.topk(probs, 5, dim=1)
        return [{"top5": top5.indices[i].tolist(),
                 "probs": top5.values[i].tolist()}
                for i in range(len(output))]

# ═══════════════════════════════════════════════════
# 4. Hacer predicciones
# ═══════════════════════════════════════════════════
import requests

with open("cat.jpg", "rb") as f:
    response = requests.post(
        "http://localhost:8080/predictions/resnet50",
        files={"data": f}
    )
print(response.json())

NVIDIA Triton Inference Server

Triton es el servidor de inferencia más completo: soporta múltiples frameworks (TF, PyTorch, ONNX, TensorRT, Python custom), múltiples modelos en el mismo servidor, y optimizaciones avanzadas (dynamic batching, model ensembles, GPU sharing).

⚡ ¿Por qué elegir Triton?

  • Multi-framework: TensorFlow, PyTorch, ONNX, TensorRT, OpenVINO, Python
  • Dynamic batching: Agrupa requests automáticamente para maximizar GPU throughput
  • Model ensemble: Pipelines de múltiples modelos (pre → modelo → post) en un solo request
  • Concurrent model execution: Varios modelos en la misma GPU con gestión de memoria
  • Métricas Prometheus: Latencia, throughput, cola de batch, GPU utilization
  • Soporte K8s: Helm chart oficial, integración con KServe
# ═══════════════════════════════════════════════════
# Estructura del model repository
# ═══════════════════════════════════════════════════
# model_repository/
# ├── resnet50/
# │   ├── config.pbtxt
# │   └── 1/
# │       └── model.onnx
# └── bert_qa/
#     ├── config.pbtxt
#     └── 1/
#         └── model.plan          ← TensorRT engine

# ═══════════════════════════════════════════════════
# config.pbtxt (ejemplo para ONNX)
# ═══════════════════════════════════════════════════
# name: "resnet50"
# platform: "onnxruntime_onnx"
# max_batch_size: 32
# input [{
#   name: "input"
#   data_type: TYPE_FP32
#   dims: [3, 224, 224]
# }]
# output [{
#   name: "output"
#   data_type: TYPE_FP32
#   dims: [1000]
# }]
# dynamic_batching {
#   preferred_batch_size: [8, 16]
#   max_queue_delay_microseconds: 5000
# }
# instance_group [{
#   count: 2
#   kind: KIND_GPU
# }]

# ═══════════════════════════════════════════════════
# Docker run
# ═══════════════════════════════════════════════════
# docker run --gpus all -p 8000:8000 -p 8001:8001 -p 8002:8002 \
#     -v $(pwd)/model_repository:/models \
#     nvcr.io/nvidia/tritonserver:24.01-py3 \
#     tritonserver --model-repository=/models

# ═══════════════════════════════════════════════════
# Cliente Python (tritonclient)
# ═══════════════════════════════════════════════════
import tritonclient.http as httpclient
import numpy as np

client = httpclient.InferenceServerClient(url="localhost:8000")

# Verificar que el modelo está cargado
assert client.is_model_ready("resnet50"), "Modelo no disponible"

# Preparar input
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
inputs = [httpclient.InferInput("input", input_data.shape, "FP32")]
inputs[0].set_data_from_numpy(input_data)

# Inferir
result = client.infer("resnet50", inputs)
output = result.as_numpy("output")
print(f"🎯 Clase predicha: {np.argmax(output)}")

BentoML

BentoML es un framework Python-first que simplifica todo el flujo: desde guardar el modelo hasta generar un contenedor optimizado listo para producción. Ideal para equipos que quieren deployment rápido sin escribir Dockerfiles ni YAML de K8s.

# ═══════════════════════════════════════════════════
# 1. Guardar modelo en BentoML model store
# ═══════════════════════════════════════════════════
import bentoml
import torch

model = torch.load("model.pt")
bentoml.pytorch.save_model("my_classifier", model)

# ═══════════════════════════════════════════════════
# 2. Definir Service (service.py)
# ═══════════════════════════════════════════════════
import bentoml
import numpy as np
from PIL import Image

@bentoml.service(
    resources={"gpu": 1, "memory": "4Gi"},
    traffic={"timeout": 30},
)
class ImageClassifier:
    def __init__(self):
        self.model = bentoml.pytorch.load_model("my_classifier")
        self.model.eval()

    @bentoml.api
    async def predict(self, image: Image.Image) -> dict:
        # Preprocesar
        tensor = self.preprocess(image)
        # Inferir
        with torch.no_grad():
            output = self.model(tensor.unsqueeze(0))
        # Devolver resultado
        prob = torch.softmax(output, dim=1)
        return {"class": int(prob.argmax()), "confidence": float(prob.max())}

# ═══════════════════════════════════════════════════
# 3. Servir localmente
# ═══════════════════════════════════════════════════
# bentoml serve service:ImageClassifier

# ═══════════════════════════════════════════════════
# 4. Containerizar y desplegar
# ═══════════════════════════════════════════════════
# bentoml build                    # Crear Bento
# bentoml containerize my_classifier:latest  # Docker image
# docker push my-registry/my_classifier:v1

KServe y Seldon Core (Kubernetes-native)

Para despliegues enterprise en Kubernetes, existen plataformas que añaden superpoderes sobre K8s: versionado, canary, A/B, autoscaling, explicabilidad…

🚀
KServe (ex-KFServing)
Parte de Kubeflow. Inference custom resources en K8s. Soporta TF, PyTorch, ONNX, Triton, XGBoost, Hugging Face. Scale-to-zero con Knative. Canary rollouts. Transformer (pre/post) sidecar. El estándar de facto para ML serving en K8s.
🔮
Seldon Core
Open-source con versión enterprise. Inference graphs complejos (routers, combiners, transformers). Explicabilidad integrada (Alibi). A/B testing nativo. Multi-armed bandits. Ideal para MLOps enterprise con pipelines de inferencia complejos.
# ═══════════════════════════════════════════════════
# KServe InferenceService con canary deployment
# ═══════════════════════════════════════════════════
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: image-classifier
spec:
  predictor:
    # ── Versión actual (90% tráfico) ──
    canaryTrafficPercent: 10
    model:
      modelFormat:
        name: pytorch
      storageUri: "gs://my-bucket/models/resnet50/v1"
      resources:
        limits:
          nvidia.com/gpu: "1"
          memory: "8Gi"
    # ── Versión canary (10% tráfico) ──
    canary:
      model:
        modelFormat:
          name: pytorch
        storageUri: "gs://my-bucket/models/resnet50/v2"
        resources:
          limits:
            nvidia.com/gpu: "1"
            memory: "8Gi"
  transformer:
    containers:
      - name: preprocess
        image: my-registry/preprocess:v1
        env:
          - name: STORAGE_URI
            value: "gs://my-bucket/config"

Comparativa de frameworks de serving

Framework Frameworks ML Batching GPU Complejidad Mejor para
FastAPI custom Cualquiera Manual 🟢 Baja Prototipos, APIs simples, control total
TF Serving TensorFlow ✅ Dinámico 🟡 Media Producción TF, alta eficiencia C++
TorchServe PyTorch ✅ Dinámico 🟡 Media Producción PyTorch, handlers custom
Triton Multi (TF, PT, ONNX…) ✅ Avanzado ✅✅ 🔴 Alta Multi-modelo, máxima GPU efficiency
BentoML Multi (Python-first) ✅ Adaptive 🟢 Baja Desarrollo rápido, containerización auto
KServe Multi + Triton ✅ (vía backend) 🔴 Alta K8s enterprise, canary, scale-to-zero
Seldon Core Multi 🔴 Alta Inference graphs, A/B, explicabilidad
vLLM LLMs (HF) ✅ Continuous ✅✅ 🟡 Media Servir LLMs con PagedAttention

🧪 Selector de framework de serving

Responde 4 preguntas y te recomendamos el framework óptimo para tu caso.

Panorama de plataformas cloud para ML

No siempre hace falta gestionar Kubernetes y Triton. Los 3 grandes proveedores cloud ofrecen plataformas managed que abstraen la infraestructura y te permiten centrarte en el modelo. Por otra parte, existen soluciones low-code para usuarios que no necesitan control total.

El espectro de soluciones varía desde plataformas de "cero código" como Hugging Face Spaces (donde un simple git push despliega tu modelo) hasta infraestructura bare metal gestionada por tu equipo. La elección depende fundamentalmente de tres factores: la complejidad técnica que tu equipo puede asumir, los requisitos de personalización del despliegue, y el presupuesto. No siempre más control es mejor: muchos equipos gastan más tiempo gestionando Kubernetes que mejorando sus modelos.

🟢 Cero código 🔴 Control total HF HF Spaces Grad Gradio Rep Replicate SM SageMaker Vtx Vertex AI EKS K8s+Triton DIY Bare metal

Soluciones para equipos no técnicos

Si tu objetivo es crear un demo, prototipo o servir un modelo con poco tráfico sin gestionar infraestructura, estas plataformas son para ti.

🤗
Hugging Face Spaces
Despliega apps Gradio/Streamlit con un git push. GPU gratuitas (limitadas) o de pago. Perfecto para demos y compartir modelos con la comunidad.
→ huggingface.co/spaces
🎨
Gradio
Crea interfaces web para tu modelo con 5 líneas de Python. Auto-genera API REST. Se integra con HF Spaces.
→ gradio.app
📊
Streamlit Cloud
Dashboards y apps ML desplegados desde GitHub. Gratis para repos públicos. Ideal para data science apps y visualización.
→ streamlit.io/cloud
🔁
Replicate
API-as-a-service para modelos ML. Empaqueta tu modelo con Cog y consigues una API con GPU en minutos. Pago por uso (por segundo GPU).
→ replicate.com
🍳
Modal
Serverless GPU computing. Define funciones Python que se ejecutan en la nube con GPU. Ideal para batch inference y fine-tuning.
→ modal.com
🌐
Inference Endpoints (HF)
Deploy 1-click de cualquier modelo de HF Hub en GPU dedicada. Autoscaling incluido. Desde $0.06/h (CPU) a $6.5/h (A100).
→ HF Endpoints
# ═══════════════════════════════════════════════════
# Demo con Gradio: clasificación de imágenes
# ═══════════════════════════════════════════════════
import gradio as gr
from transformers import pipeline

classifier = pipeline("image-classification", model="google/vit-base-patch16-224")

demo = gr.Interface(
    fn=lambda img: {p["label"]: p["score"] for p in classifier(img)},
    inputs=gr.Image(type="pil"),
    outputs=gr.Label(num_top_classes=5),
    title="🖼️ Clasificador de imágenes (ViT)",
    description="Sube una imagen y el modelo la clasificará."
)

demo.launch()   # → localhost:7860
# Para desplegar en HF Spaces: git push al repo del Space

Google Cloud: Vertex AI

Vertex AI es la plataforma de ML end-to-end de Google Cloud. Cubre todo el ciclo: desde datos (BigQuery, Feature Store) hasta serving (endpoints, batch prediction) pasando por entrenamiento (custom training, AutoML, Workbench).

1
Vertex AI Workbench: Jupyter notebooks managed con acceso a GPUs/TPUs. Integrado con BigQuery.
2
Custom Training: Envía tu código de entrenamiento (TF/PyTorch/XGBoost) a máquinas cloud con GPU. Soporte distribuido.
3
Model Registry: Versiona y organiza modelos. Evaluación automática de métricas.
4
Endpoints: Despliegue 1-click con autoscaling. Soporta A/B testing (traffic splitting). GPU/TPU.
5
Batch Prediction: Procesa millones de registros en paralelo sin mantener un endpoint activo.
6
Pipelines: Orquestación de workflows ML con Kubeflow Pipelines o TFX.
7
Feature Store: Almacén centralizado de features con baja latencia para serving online.
8
Model Monitoring: Detección automática de data drift y feature skew.
# ═══════════════════════════════════════════════════
# Despliegue en Vertex AI con Python SDK
# ═══════════════════════════════════════════════════
from google.cloud import aiplatform

aiplatform.init(project="my-project", location="europe-west1")

# 1. Subir modelo al Model Registry
model = aiplatform.Model.upload(
    display_name="image-classifier-v2",
    artifact_uri="gs://my-bucket/models/resnet50/",
    serving_container_image_uri=(
        "europe-docker.pkg.dev/vertex-ai/prediction/"
        "tf2-gpu.2-14:latest"
    ),
)

# 2. Crear endpoint
endpoint = aiplatform.Endpoint.create(
    display_name="classifier-endpoint",
)

# 3. Desplegar modelo en endpoint
model.deploy(
    endpoint=endpoint,
    machine_type="n1-standard-4",
    accelerator_type="NVIDIA_TESLA_T4",
    accelerator_count=1,
    min_replica_count=1,
    max_replica_count=5,         # Autoscaling
    traffic_percentage=100,
)

# 4. Hacer predicciones
import numpy as np
instances = [np.random.randn(224, 224, 3).tolist()]
prediction = endpoint.predict(instances=instances)
print(prediction.predictions)

AWS: SageMaker

Amazon SageMaker es la plataforma ML más completa del ecosistema AWS. Incluye notebooks, entrenamiento, HPO, Model Registry, endpoints de inferencia en tiempo real/batch/async/serverless, y herramientas de MLOps (Pipelines, Model Monitor, Clarify).

ComponenteQué haceCuándo usarlo
StudioIDE web para todo el ciclo MLDesarrollo y experimentación
Training JobsEntrenamiento en instancias cloud (GPU/Trainium)Modelos que no caben en tu laptop
Real-time EndpointsAPI persistente con autoscalingProducción con tráfico constante
Serverless InferenceScale-to-zero, pago por invocaciónTráfico esporádico (< 1 RPS)
Async InferenceProcesa requests grandes en backgroundModelos lentos (> 60s), documentos largos
Batch TransformProcesa datasets completos en paraleloScoring masivo, nightly jobs
Model RegistryVersiona y aprueba modelosCI/CD y governance
PipelinesWorkflows ML reproducibles (DAGs)Automatizar train → eval → deploy
Model MonitorDetecta data drift y biasMonitorización continua en producción
# ═══════════════════════════════════════════════════
# Despliegue en SageMaker con Python SDK
# ═══════════════════════════════════════════════════
import sagemaker
from sagemaker.pytorch import PyTorchModel

session = sagemaker.Session()
role = "arn:aws:iam::123456789:role/SageMakerRole"

# 1. Crear modelo
pytorch_model = PyTorchModel(
    model_data="s3://my-bucket/models/model.tar.gz",
    role=role,
    framework_version="2.1",
    py_version="py310",
    entry_point="inference.py",     # Script de pre/post proceso
)

# 2. Desplegar endpoint
predictor = pytorch_model.deploy(
    instance_type="ml.g4dn.xlarge",   # GPU T4
    initial_instance_count=1,
    endpoint_name="my-classifier-v2",
)

# 3. Predicción
import numpy as np
response = predictor.predict(np.random.randn(1, 3, 224, 224).tolist())
print(response)

# 4. Limpiar (¡no olvidar para no seguir pagando!)
predictor.delete_endpoint()

Microsoft Azure: Azure Machine Learning

Azure ML ofrece un ecosistema similar a SageMaker/Vertex, integrado con el stack Microsoft (Azure DevOps, Power BI, Synapse). Destaca por su CLI v2 y integración con VS Code y GitHub Actions.

🔷 Highlights de Azure ML

  • Managed Endpoints: Similar a SageMaker endpoints. Real-time y batch. Autoscaling. Blue-green deploys.
  • KAITO: Add-on de K8s para desplegar LLMs en AKS con un click (Llama, Falcon, Mistral).
  • Responsible AI Dashboard: Fairness, explicabilidad, error analysis integrados.
  • Prompt Flow: Herramienta visual para construir aplicaciones LLM (RAG, agents).
  • CLI v2: Gestión declarativa de todo el ciclo ML con YAML + CLI.

Comparativa de plataformas cloud

Característica Vertex AI (GCP) SageMaker (AWS) Azure ML
FortalezaBigQuery + TPUs, AutoMLEcosistema AWS, más completoIntegración Microsoft, KAITO
GPUs disponiblesT4, L4, A100, H100, TPUsT4, A10G, A100, Inf2, Trn1T4, V100, A100, H100
Serverless inferenceCloud Run + GPUSageMaker ServerlessManaged Online (min 1 replica)
Scale-to-zero✅ Cloud Run✅ Serverless endpoints❌ (min 1 réplica)
AutoML✅ Vertex AutoML✅ Autopilot✅ AutoML
Feature Store✅ Integrado✅ SageMaker FS✅ Managed FS
Model Monitoring✅ Integrado✅ Model Monitor✅ Data Collector
PipelinesKubeflow / TFXSageMaker PipelinesAzure ML Pipelines
LLM supportModel Garden, GeminiBedrock, JumpStartAzure OpenAI, KAITO
Coste mínimo endpoint~$0.05/h (CPU)~$0.07/h (CPU)~$0.10/h (CPU)
Mejor paraEquipos Data + BigQueryYa usan AWSEnterprise Microsoft
💡 ¿Cuál elegir? La decisión suele depender de qué cloud ya usa tu empresa, no de las features ML (que son comparables). Si empiezas de cero: GCP Vertex AI tiene la mejor relación sencillez/potencia. Si tu empresa es AWS: SageMaker. Si es Microsoft: Azure ML.

Widget: estimador de coste por plataforma

☁️ Estimador de coste mensual de serving

Compara el coste estimado de servir tu modelo en las 3 grandes plataformas cloud y en alternativas managed.

CI/CD para modelos de ML

En software tradicional, CI/CD automatiza build → test → deploy. En ML añadimos pasos específicos: validación del modelo, tests de accuracy, comparación con el modelo en producción, y despliegue condicional.

La diferencia clave respecto al CI/CD clásico es que en ML tenemos dos artefactos que versionear: el código (scripts de entrenamiento, preprocesamiento, API) y el modelo (pesos, hiperparámetros, métricas). Además, los pipelines de ML incluyen un paso de evaluación que actúa como "quality gate": si el nuevo modelo no supera en accuracy al modelo en producción, el pipeline se detiene y no se despliega. Este concepto se conoce como model validation gate y es fundamental para evitar regresiones en producción.

Herramientas como CML (Continuous Machine Learning) de Iterative permiten integrar métricas de modelos directamente en pull requests de GitHub/GitLab, de modo que el equipo puede revisar el impacto de un cambio en el modelo antes de aprobar el merge. Para una guía completa del stack de herramientas CI/CD para ML, el curso gratuito Made With ML es una excelente referencia.

📝 Push code Git + DVC 🔨 Build + Test Unit tests, lint 🧠 Train / Eval GPU job, métricas Quality Gate acc ≥ threshold? 🐳 Containerize Docker → Registry 🚀 Canary → Promote 10% → 50% → 100% Herramientas populares: GitHub Actions · GitLab CI · Jenkins · Argo Workflows · Tekton · CircleCI DVC · MLflow · W&B · CML · Evidently · Great Expectations
# ═══════════════════════════════════════════════════
# .github/workflows/ml-cicd.yml
# ═══════════════════════════════════════════════════
name: ML CI/CD Pipeline

on:
  push:
    branches: [main]
    paths: ["src/**", "models/**", "requirements.txt"]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11" }
      - run: pip install -r requirements.txt
      - run: pytest tests/ -v --tb=short

  evaluate:
    needs: test
    runs-on: ubuntu-latest     # O self-hosted con GPU
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11" }
      - run: pip install -r requirements.txt

      # Evaluar modelo nuevo vs producción
      - name: Evaluate model
        run: |
          python evaluate.py \
            --model models/model_new.pt \
            --data data/test_set.csv \
            --output metrics.json

      # Quality gate: fail si accuracy < 0.90
      - name: Quality gate
        run: |
          python -c "
          import json
          m = json.load(open('metrics.json'))
          assert m['accuracy'] >= 0.90, f'Accuracy {m[\"accuracy\"]} < 0.90'
          assert m['latency_p99'] <= 200, f'Latency {m[\"latency_p99\"]}ms > 200ms'
          print('✅ Quality gate passed')
          "

  deploy:
    needs: evaluate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build & push Docker image
        run: |
          docker build -t ghcr.io/${{ github.repository }}/model:${{ github.sha }} .
          docker push ghcr.io/${{ github.repository }}/model:${{ github.sha }}

      - name: Deploy canary (10%)
        run: |
          kubectl set image deployment/inference \
            model=ghcr.io/${{ github.repository }}/model:${{ github.sha }}
          kubectl patch hpa inference-hpa \
            -p '{"metadata":{"annotations":{"canary":"true"}}}'

Model Registry y versionado

Un Model Registry es un catálogo centralizado donde se almacenan, versionan y aprueban modelos antes de ir a producción. Es el equivalente al container registry pero para artefactos ML.

HerramientaTipoHighlightsIntegración
MLflow Open-source Tracking, registry, serving. El más popular. Stage transitions (Staging → Production). TF, PyTorch, Sklearn, Spark
Weights & Biases SaaS / Self-hosted Experiment tracking excepcional. Registry con lineage. Sweeps (HPO). Todo framework Python
Neptune SaaS UI limpia. Metadata store. Comparación de runs. TF, PyTorch, Sklearn, XGBoost
DVC Open-source Git para datos y modelos. Pipelines reproducibles. Integrado con Git. Agnóstico (archivos)
Cloud natives Managed SageMaker Model Registry, Vertex AI Model Registry, Azure ML Registry. Ecosistema de cada cloud
# ═══════════════════════════════════════════════════
# MLflow: track experiment + register model
# ═══════════════════════════════════════════════════
import mlflow
import mlflow.pytorch

mlflow.set_tracking_uri("http://mlflow-server:5000")
mlflow.set_experiment("image-classification")

with mlflow.start_run(run_name="resnet50-v2"):
    # Log hiperparámetros
    mlflow.log_params({
        "model": "resnet50",
        "lr": 0.001,
        "batch_size": 64,
        "epochs": 20,
        "optimizer": "AdamW",
    })

    # Entrenar modelo...
    model = train(...)

    # Log métricas
    mlflow.log_metrics({
        "accuracy": 0.923,
        "f1_score": 0.918,
        "latency_p99_ms": 45,
    })

    # Log modelo → Model Registry
    mlflow.pytorch.log_model(
        model,
        artifact_path="model",
        registered_model_name="image-classifier",
    )

# ═══════════════════════════════════════════════════
# Promover modelo a producción
# ═══════════════════════════════════════════════════
from mlflow import MlflowClient

client = MlflowClient()
client.transition_model_version_stage(
    name="image-classifier",
    version=2,
    stage="Production",
    archive_existing_versions=True,   # Archiva la v1
)

Estrategias de despliegue

No basta con desplegar: necesitas una estrategia que minimice el riesgo de que un modelo defectuoso afecte a los usuarios. En ML, un modelo puede fallar de formas sutiles (dar predicciones peores sin producir errores técnicos), lo que hace que las estrategias de despliegue progresivo sean especialmente importantes.

La estrategia más recomendada para ML es el canary deployment, donde la nueva versión del modelo recibe inicialmente un porcentaje pequeño de tráfico (por ejemplo, 5 %) y se monitoriza activamente. Si las métricas (accuracy, latencia, error rate) se mantienen estables o mejoran, se incrementa progresivamente el porcentaje. Si se detecta degradación, se revierte automáticamente al modelo anterior. Plataformas como KServe y Seldon Core implementan canary deployments de forma nativa.

🔵🟢
Blue-Green
Dos entornos idénticos. Se cambia el tráfico de uno a otro instantáneamente. Rollback inmediato. Requiere doble infraestructura.
🐤
Canary
La nueva versión recibe un % pequeño de tráfico (5–10%). Si las métricas son buenas, se aumenta progresivamente. Ideal para ML.
🔄
Rolling Update
Se actualizan las réplicas una a una. Sin downtime pero sin control de tráfico. El patrón por defecto de K8s.
🧪
Shadow / Dark Launch
El nuevo modelo recibe tráfico real pero sus respuestas se descartan. Se comparan métricas sin afectar al usuario. Ideal para modelos críticos.
🅰️🅱️
A/B Testing
Se asigna aleatoriamente a cada usuario una versión del modelo. Se miden métricas de negocio (CTR, conversión). Requiere infra de experimentación.

Monitorización y detección de drift

Un modelo que era bueno ayer puede ser malo hoy si los datos cambian. Data drift (cambian las features de entrada) y concept drift (cambia la relación input → output) son los enemigos silenciosos de los modelos en producción.

Es importante entender la diferencia entre ambos tipos de drift. En el data drift, las distribuciones de las variables de entrada cambian respecto a las del entrenamiento. Por ejemplo, un modelo de clasificación de imágenes entrenado con fotos diurnas puede recibir fotos nocturnas en producción. En el concept drift, lo que cambia es la relación subyacente entre inputs y outputs: las mismas features ahora predicen algo diferente. Un ejemplo clásico es un modelo de predicción de demanda que se entrenó antes de una pandemia — el comportamiento de compra cambió fundamentalmente, invalidando la relación aprendida. Ambos tipos requieren monitorización activa y, en muchos casos, re-entrenamiento automático del modelo. Para una revisión formal, consulta el paper A Survey on Concept Drift Adaptation (Lu et al., 2020).

📊 Data Drift La distribución de inputs cambia Ejemplo: entrenas con fotos diurnas, llegan fotos nocturnas en producción Detectar con: KS test · PSI · Wasserstein distance 🔀 Concept Drift La relación X → Y cambia Ejemplo: el comportamiento de compra cambia post-pandemia Detectar con: Accuracy decay · ADWIN · Page-Hinkley
HerramientaOpen / SaaSDetectaHighlights
Evidently Open-source Data drift, concept drift, data quality Dashboards interactivos, test suites. Se integra con Airflow, Prefect.
NannyML Open-source Performance estimation sin labels Estima accuracy sin ground truth. Ideal cuando los labels tardan días/semanas.
WhyLabs SaaS Data drift, anomalías, PII Basado en whylogs (open-source profiling). Alertas automáticas.
Prometheus + Grafana Open-source Métricas de sistema y custom Latencia, throughput, errores, GPU %. Alertas con AlertManager.
Cloud natives Managed Drift, skew SageMaker Model Monitor, Vertex AI Model Monitoring, Azure Data Collector.
# ═══════════════════════════════════════════════════
# Evidently: test de data drift
# ═══════════════════════════════════════════════════
from evidently.test_suite import TestSuite
from evidently.tests import (
    TestShareOfDriftedColumns,
    TestColumnDrift,
)
import pandas as pd

# Datos de referencia (entrenamiento) y actuales (producción)
reference = pd.read_csv("data/train_features.csv")
current = pd.read_csv("data/production_features_today.csv")

# Crear suite de tests
suite = TestSuite(tests=[
    TestShareOfDriftedColumns(lt=0.3),     # < 30% columnas con drift
    TestColumnDrift("age"),                 # Test específico por columna
    TestColumnDrift("income"),
])

suite.run(reference_data=reference, current_data=current)

# Resultado
result = suite.as_dict()
if not result["summary"]["all_passed"]:
    print("⚠️ DRIFT DETECTADO — considerar re-entrenamiento")
    # Enviar alerta a Slack / PagerDuty
else:
    print("✅ Sin drift significativo")

# Generar dashboard HTML
suite.save_html("drift_report.html")

Observabilidad: los 3 pilares

Un sistema ML en producción necesita los 3 pilares de observabilidad: logs (qué pasó), métricas (cuánto) y traces (dónde el tiempo).

La observabilidad va más allá de la monitorización: mientras que la monitorización te dice que algo va mal, la observabilidad te permite entender por qué. En ML, esto es especialmente crítico porque los problemas suelen ser sutiles: un modelo que de repente responde más lento podría estar recibiendo imágenes de mayor resolución (data drift), o un cambio en la distribución de features podría estar provocando que el modelo explore ramas de decisión más complejas. Sin trazas detalladas que desglosen el tiempo por componente (preprocesamiento, inferencia, postprocesamiento), diagnosticar estos problemas es como buscar una aguja en un pajar.

El estándar de la industria para instrumentar observabilidad es OpenTelemetry, que proporciona una API unificada para logs, métricas y traces. Se integra con la mayoría de backends (Prometheus, Grafana, Jaeger, Datadog) y permite correlacionar los tres tipos de señales.

📝
Logs
Qué pasó: Requests, errores, predicciones, inputs/outputs.
Stack: ELK (Elasticsearch + Logstash + Kibana), Loki + Grafana, CloudWatch Logs.
📊
Métricas
Cuánto: Latencia, RPS, error rate, GPU %, accuracy, drift score.
Stack: Prometheus + Grafana, Datadog, CloudWatch Metrics.
🔍
Traces
Dónde el tiempo: Desglose de latencia por componente (preprocess, inference, postprocess).
Stack: Jaeger, Zipkin, OpenTelemetry, Datadog APM.

Optimización de costes en producción

Servir modelos ML en GPU es caro. Estrategias clave para reducir costes sin sacrificar rendimiento:

El coste de GPU es el factor dominante en la factura de producción de ML. Una GPU A100 en AWS (instancia p4d.24xlarge) cuesta más de $32/hora — casi $24 000/mes si se ejecuta 24/7. Incluso GPUs más modestas como la T4 ($0.53/h) suponen ~$380/mes por instancia. Por eso, optimizar costes es tan importante como optimizar métricas. Las siguientes 8 estrategias cubren desde optimizaciones a nivel de modelo (cuantización) hasta decisiones de infraestructura (spot instances, scale-to-zero). Lo ideal es combinar varias de ellas para obtener ahorros multiplicativos.

1
Model compression: Cuantización INT8, pruning, knowledge distillation → modelo más pequeño = instancia más barata. Ver submódulo Edge Computing.
2
Spot/Preemptible instances: Hasta 90% descuento. Ideal para batch inference y entrenamiento. No para endpoints real-time críticos.
3
Scale-to-zero: Con KEDA, Knative o Cloud Run. Si no hay tráfico, no pagas. Cold start: 10–60 segundos.
4
Right-sizing: ¿Necesitas una A100 o te basta un T4? Benchmarkea tu modelo en diferentes instancias y elige la más eficiente por $.
5
Batching dinámico: Agrupa requests → más throughput por GPU hora. Triton y TFServing lo soportan nativamente.
6
Caching: Redis/Memcached para cachear predicciones de inputs frecuentes. Reduce requests al modelo un 20–80%.
7
Multi-model serving: Triton permite múltiples modelos en la misma GPU. Maximiza utilización.
8
Reserved instances / CUDs: Compromiso de 1–3 años para descuentos del 30–60%. Para workloads estables.

Checklist de producción

Antes de desplegar un modelo, asegúrate de cubrir estos puntos:

CategoríaCheckHerramienta recomendada
📦 Reproducibilidad¿El entorno está containerizado?Docker + requirements.txt / poetry.lock
🧪 Testing¿Hay tests de accuracy, latencia y smoke?pytest + locust
📊 Métricas¿Se exponen métricas Prometheus?prometheus_client (Python)
🩺 Health checks¿Hay endpoint /health?FastAPI + livenessProbe
📝 Logging¿Se logean inputs y outputs?structlog + ELK / Loki
🔐 Seguridad¿Auth, rate limiting, input validation?API Gateway + WAF
🔄 Rollback¿Se puede revertir en < 5 min?K8s rollback / blue-green
📈 Drift¿Hay monitorización de drift?Evidently + alertas
💰 Costes¿Se conoce el coste por inferencia?Cloud billing + dashboards
📖 Docs¿La API está documentada?OpenAPI / Swagger (FastAPI auto)

Widget: decisor de arquitectura de despliegue

🗺️ ¿Qué arquitectura de despliegue necesitas?

Responde a estas preguntas y te recomendamos la arquitectura, herramientas y estimación de coste para tu caso.

Recursos y referencias

La puesta en producción de modelos ML es un campo que evoluciona rápidamente. A continuación recopilamos los recursos más relevantes organizados por tipo: guías prácticas, cursos, papers académicos y repositorios. Además, a lo largo de este submódulo hemos referenciado otros submódulos de esta web que complementan el contenido:

RecursoTipoDescripción
ml-ops.org Guía Referencia completa de MLOps: principios, herramientas y patrones.
Made With ML Curso Curso gratuito de MLOps end-to-end por Goku Mohandas.
Full Stack Deep Learning Curso Curso completo de producción ML por UC Berkeley.
Google MLOps Guide Whitepaper Niveles de madurez MLOps (0→2) por Google Cloud.
MLOps Tools Landscape Blog Mapa completo de herramientas MLOps actualizado regularmente.
Awesome MLOps GitHub Lista curada de papers, herramientas y recursos de MLOps.
Operationalizing ML (2022) Paper Encuesta académica sobre prácticas de MLOps en la industria.
Hidden Technical Debt in ML Paper El paper clásico de Google sobre deuda técnica en sistemas ML.
🎓 Recomendación: Empieza con Made With ML para una visión práctica, lee el Google MLOps Guide para entender los niveles de madurez, y el paper de Hidden Technical Debt para la perspectiva sistémica.