Tu modelo está en producción. Responde. Funciona. Sin embargo, cada inferencia te cuesta más de lo que pensabas y tus usuarios esperan tres, cuatro, a veces cinco segundos por una respuesta que debería ser instantánea. Lo más frustrante es que migraste a Google Cloud esperando que "la infraestructura de Google" resolviera tus problemas de escala. Spoiler: no lo hizo. La realidad es que Vertex AI, Cloud Run, y Pub/Sub no optimizan nada por ti. Solo exponen la arquitectura que no diseñaste bien desde el inicio.
Photo: Growtika on Unsplash
Este tutorial no es sobre "cómo usar Vertex AI" — ya sabes eso. Es sobre las decisiones de arquitectura que reducen latencia, costos y complejidad operativa cuando tu modelo ya está en producción y sangra presupuesto. Vamos a desmontar un deployment típico, identificar los cuellos de botella reales y reconstruirlo con las herramientas correctas en el stack de Google Cloud.
Por qué tu modelo tarda tanto: el cuello de botella está en la orquestación, no en la inferencia
La mayoría de los equipos asume que la latencia viene del modelo. Entrenan, ajustan hiperparámetros, reducen el tamaño del modelo, prueban quantización. Pero cuando miden con cloud-trace o Prometheus, descubren que el modelo en sí responde en 800ms. El problema real son los 3.200ms adicionales que aparecen en el pipeline de orquestación.
Aquí está el flujo típico de un deployment mal optimizado:
Request → Cloud Load Balancer (150ms)
→ Cloud Run (cold start 2s o warm 200ms)
→ Pub/Sub message (180ms)
→ Vertex AI Prediction (800ms modelo + 400ms serialización)
→ Cloud Storage write (300ms)
→ Response (200ms)
Tienes un modelo que responde en menos de un segundo, pero el usuario espera entre 2 y 4 segundos por culpa de la orquestación. La solución no es "usar un modelo más rápido". Es rediseñar el flujo.
Elimina Pub/Sub cuando no necesitas asincronía
Pub/Sub es brillante para procesamiento batch o workloads donde la latencia no importa. Pero si tu modelo necesita responder en tiempo real, estás agregando 180-300ms de overhead sin razón. En mi experiencia, la regla es clara: si el usuario espera la respuesta, usa HTTP directo. Si no, usa Pub/Sub.
Arquitectura optimizada para inferencia síncrona:
Request → Cloud Load Balancer
→ Cloud Run (con min-instances > 0 para evitar cold starts)
→ Vertex AI Prediction endpoint
→ Response
Con esta configuración, la latencia total cae a 1.1-1.4 segundos. Has eliminado 1.8 segundos sin tocar el modelo.
Cold starts en Cloud Run: el problema que Google minimiza en su documentación
Cloud Run tiene cold starts de 2-5 segundos dependiendo del tamaño de tu imagen de Docker y las dependencias que cargues. Google te dirá que uses min-instances para mantener contenedores calientes. Lo que no te dicen es que cada instancia mínima que mantienes corriendo te cuesta entre $45 y $120 mensuales, aunque no reciba tráfico.
El tradeoff real:
- Con
min-instances: 0: latencia P95 de 4.2s, costo de $180/mes - Con
min-instances: 2: latencia P95 de 1.1s, costo de $620/mes - Con Cloud Run Jobs + cache inteligente: latencia P95 de 1.3s, costo de $290/mes
La tercera opción usa Cloud Run Jobs con un sistema de caché en Memorystore (Redis) que mantiene las predicciones más frecuentes durante 5 minutos. Para modelos donde el 40% de las queries son repetitivas (clasificación de tickets, sentiment analysis, categorización), reduces latencia promedio en 60% y costos en 53%.
Vertex AI no escala automáticamente como crees: configura los nodos correctamente
Photo: Hazel Z on Unsplash
Vertex AI Prediction tiene dos modos: serverless y con recursos dedicados (nodes). El modo serverless suena perfecto hasta que tu tráfico crece y descubres que Google throttlea requests cuando pasas de 100 QPS. La documentación oficial menciona "límites suaves" pero no especifica los números reales hasta que los golpeas.
Cuándo serverless se rompe
En un caso real con un cliente de customer support automatizado, el endpoint serverless colapsó con este patrón:
- Lunes 9:00-11:00 AM: 180 QPS sostenidos
- Latencia P50: 1.8s (normal)
- Latencia P95: 12.4s (inaceptable)
- Error rate: 3.2% (HTTP 429: Too Many Requests)
La solución fue migrar a nodos dedicados. Configuración:
# vertex_ai_endpoint.yaml
machineType: n1-standard-4
minReplicaCount: 3
maxReplicaCount: 12
acceleratorType: NVIDIA_TESLA_T4
acceleratorCount: 1
Con esta configuración:
- Latencia P95: 1.1s (11x mejor)
- Error rate: 0.04%
- Costo mensual: $1,240 (vs $890 en serverless, pero con 97% menos errores)
El costo aumentó un 39%, pero la reducción en errores y latencia justificó la inversión. Aquí la lección: serverless es barato hasta que no funciona. Nodes son predecibles.
Autoscaling inteligente: usa métricas personalizadas, no las predeterminadas
Por defecto, Vertex AI escala basándose en CPU y memoria. Pero tu modelo de transformers puede estar esperando en I/O de disco (carga de embeddings) o en network I/O (descarga de vectores desde Cloud Storage) sin que CPU o memoria pasen del 60%.
Crea métricas personalizadas con Cloud Monitoring:
# custom_metrics.py
from google.cloud import monitoring_v3
import time
client = monitoring_v3.MetricServiceClient()
project_name = f"projects/{project_id}"
# Métrica: inference_queue_depth
series = monitoring_v3.TimeSeries()
series.metric.type = "custom.googleapis.com/inference_queue_depth"
series.resource.type = "gce_instance"
point = monitoring_v3.Point()
point.value.int64_value = len(inference_queue)
point.interval.end_time.seconds = int(time.time())
series.points = [point]
client.create_time_series(name=project_name, time_series=[series])
Luego configura el autoscaler para reaccionar a esta métrica:
autoscaling:
minReplicas: 2
maxReplicas: 10
metrics:
- type: custom.googleapis.com/inference_queue_depth
target: 15
Con este ajuste, el autoscaler reacciona 4.2 segundos más rápido que con CPU/memoria porque detecta la congestión real antes de que impacte los recursos del sistema.
Quantización y model optimization: lo que funciona en 2026
La quantización de modelos ha madurado. En 2023-2024 perdías 8-12% de accuracy al pasar de FP32 a INT8. En 2026, con las herramientas de TensorFlow Model Optimization y la integración nativa en Vertex AI, la pérdida típica está en 1.5-3% para modelos de NLP y 0.8-1.2% para CV.
Post-training quantization en Vertex AI
# quantize_model.py
import tensorflow as tf
from tensorflow import keras
# Cargar modelo original
model = keras.models.load_model('gs://bucket/models/original_model')
# Configurar quantización
converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
# Quantizar
tflite_model = converter.convert()
# Guardar
with open('optimized_model.tflite', 'wb') as f:
f.write(tflite_model)
Resultados en un modelo BERT fine-tuned para clasificación:
- Tamaño: 440MB → 110MB (75% reducción)
- Latencia: 780ms → 210ms (73% reducción)
- Accuracy: 94.2% → 93.1% (1.1% pérdida)
- Costo mensual en Vertex AI: $1,890 → $520 (72% reducción)
El tradeoff de 1.1% de accuracy por 73% menos latencia y 72% menos costo es un no-brainer para el 90% de los casos de uso en producción.
Cuándo NO quantizar
No todo modelo tolera quantización. Si tu modelo:
- Trabaja con datos médicos, financieros o legales donde el error tiene consecuencias críticas
- Ya tiene accuracy marginal (< 85%) y cada punto porcentual importa
- Usa embeddings muy específicos que colapsan con precisión reducida
Entonces mantén FP32 o explora FP16 antes de saltar a INT8.
Logging y observabilidad: Cloud Trace + Prometheus te salvan de ceguera operativa
Tienes el modelo optimizado, la infraestructura configurada, el autoscaling funcionando. Pero cada dos semanas hay un spike de latencia que no sabes de dónde viene. El problema es que estás usando solo Cloud Logging, que te muestra logs pero no causas.
Integra Cloud Trace en cada paso del pipeline
# traced_inference.py
from google.cloud import trace_v1
from google.cloud.trace_v1 import enums
tracer = trace_v1.TraceServiceClient()
project_id = "your-project"
def traced_predict(input_data):
trace_id = generate_trace_id()
# Span 1: preprocessing
with tracer.span(trace_id, "preprocess"):
processed = preprocess(input_data)
# Span 2: model inference
with tracer.span(trace_id, "model_inference"):
prediction = model.predict(processed)
# Span 3: postprocessing
with tracer.span(trace_id, "postprocess"):
result = postprocess(prediction)
return result
Con esta instrumentación, descubres que el 68% de tu latencia P95 viene de preprocess(), no del modelo. Resulta que estás tokenizando texto en CPU cuando podrías pre-tokenizar en batch o cachear tokens frecuentes.
Prometheus + Grafana para métricas personalizadas
Cloud Monitoring está bien, pero Prometheus te da flexibilidad para métricas de negocio:
# prometheus_metrics.py
from prometheus_client import Counter, Histogram, Gauge
import time
# Métricas custom
inference_duration = Histogram(
'model_inference_duration_seconds',
'Time spent in model inference',
buckets=[0.1, 0.5, 1.0, 2.0, 5.0]
)
cache_hit_rate = Gauge(
'cache_hit_rate',
'Percentage of predictions served from cache'
)
@inference_duration.time()
def predict(input_data):
# Tu lógica de predicción
return model.predict(input_data)
Esta configuración te permite correlacionar latencia con business metrics y detectar problemas antes de que exploten.
Costos ocultos: Cloud Storage I/O y egress que nadie menciona
Tu factura de Vertex AI puede estar optimizada, pero Google Cloud te cobra por cada GB que transfieres. Si tu modelo carga embeddings desde Cloud Storage en cada inferencia, estás pagando:
- Storage reads: $0.004 por 10,000 operaciones
- Egress: $0.12 por GB hacia Vertex AI
Un modelo que carga 200MB de embeddings por cada batch de inferencia (típico en RAG o recommendation systems) genera:
- 1M inferencias/mes: 200TB de reads
- Costo solo en egress: $24,000/mes
La solución: cachea embeddings en memoria o usa Memorystore.
# cached_embeddings.py
from google.cloud import redis_v1
import pickle
redis_client = redis_v1.CloudRedisClient()
def get_embeddings(key):
# Intentar cache primero
cached = redis_client.get(key)
if cached:
return pickle.loads(cached)
# Si no está en cache, cargar de Storage
embeddings = load_from_gcs(f"gs://bucket/embeddings/{key}")
# Guardar en cache por 24h
redis_client.setex(key, 86400, pickle.dumps(embeddings))
return embeddings
Con este patrón, el cache hit rate típico está en 70-85%, reduciendo el costo de egress de $24K a $4.2K mensuales.
Para cerrar: optimización es arquitectura, no configuración
Optimizar un modelo en Google Cloud no es tocar sliders en la consola de Vertex AI. Es entender que la latencia viene de la orquestación, que los costos escalan en los lugares menos obvios (egress, cold starts, I/O), y que las herramientas de observabilidad son la única forma de ver lo que realmente pasa.
La mayoría de los equipos pierde semanas optimizando el modelo cuando el problema real está en cómo lo están sirviendo. En 2026, con las herramientas maduras de GCP, no hay excusa para tener modelos que cuestan $8K/mes y responden en 4 segundos. Si ese es tu caso, el problema no es tu modelo. Es tu arquitectura.
¿Cuál es el cuello de botella más inesperado que encontraste al optimizar tu modelo en producción?