📖 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.

📓 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. Este submódulo te enseña a superar cada obstáculo.

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.

📦 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.
3
Entrenamiento: Pipelines reproducibles (Kubeflow, Airflow, Metaflow). Hiperparámetros versionados, artefactos registrados.
4
Validación: Tests automáticos de accuracy, fairness y latencia. Gate de calidad antes de desplegar.
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.
7
Re-entrenamiento: Trigger automático o programado cuando el modelo degrada. Cierre del bucle continuo.

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.

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.
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.

🔄
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 < 50ms, p99 < 200ms. Batching, caching, model optimization, 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.

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.

Métrica Descripción Objetivo típico Herramienta
Latencia p50 / p99 Tiempo de respuesta mediano y percentil 99 p50 < 50ms, p99 < 200ms Prometheus, Datadog
Throughput (RPS) Requests por segundo soportados Depende del caso Locust, k6, wrk
Availability (SLA) Porcentaje de uptime 99.9 % (8.7h 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

🧮 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.

¿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.

📦
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
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

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.

⚖️
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
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.

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.

📱 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) 🧪

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.

🟢 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.

📝 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.

🔵🟢
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.

📊 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).

📝
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:

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

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.