Canary, blue-green y shadow para modelos LLM: cómo desplegar una versión nueva sin tirar el SLO

Este post complementa los de Autoscaling LLM en Kubernetes (el autoscaler convive con el rollout y debe respetarlo), Observabilidad GPU para inferencia LLM (las métricas que actúan como gate vienen de ahí), Evals para LLMs (la eval que decide si el nuevo modelo está listo), LLM-as-judge (la técnica que pone el “quality” en el gate de canary) y Retrain: cerrar el bucle (el step previo del que sale el modelo nuevo).

TL;DR

Promocionar una versión nueva de un modelo LLM al cluster productivo sin cortar tráfico ni romper SLO exige despliegue progresivo. Las tres estrategias canónicas —blue-green, canary, shadow— responden a preguntas distintas y tienen costes distintos. Blue-green: pool completo nuevo levantado en paralelo, conmutación atómica del load balancer. Rollback instantáneo (volver a apuntar al pool viejo); exige el doble de GPUs durante la ventana. Canary: el tráfico se reparte progresivamente entre la versión vieja y la nueva (1 % → 5 % → 25 % → 100 %), midiendo en cada salto gates de regresión; consume incrementalmente menos hardware pero expone usuarios reales al modelo nuevo desde el primer porcentaje. Shadow / mirror: el viejo modelo sirve el 100 % del tráfico real al cliente y, en paralelo, una copia de cada request va al nuevo modelo sin devolver su respuesta al usuario; aísla del riesgo de calidad pero gasta GPU del nuevo en respuestas que nadie consume, y no funciona bien con streaming SSE largo. La elección depende de tres factores: presupuesto GPU disponible, criticidad del servicio y disponibilidad de eval automática rápida. Las cinco métricas de regresión que cualquier canary LLM gatear son: TTFT P95, error rate (HTTP 5xx + finish_reason="length" prematuro), quality score con LLM-as-judge sobre golden set, drift estadístico de embeddings de output (Wasserstein o KL contra distribución del baseline) y coste por request (tokens/s y kW/request). En Kubernetes, Argo Rollouts gestiona el tráfico y los AnalysisTemplate como gates automáticos; Flagger es la alternativa más opinionada. vLLM v1 no soporta hot model swap robusto a mayo 2026, así que la unidad de rollout es la réplica entera (deployment v2 al lado de deployment v1). Los tres pitfalls específicos: sticky sessions del LB rompen la comparabilidad estadística del canary (un cliente A siempre cae al nuevo, B al viejo — las poblaciones no son equivalentes); eval semántica con LLM-as-judge tarda 2–8 segundos por sample y no sirve como gate en tiempo real (se usa en post-análisis o offline pre-promoción); el streaming SSE complica el shadow porque hay que descartar la respuesta del nuevo modelo sin afectar a la del viejo. Este post incluye un manifest Argo Rollouts mínimo aplicable a un cluster genérico con NVIDIA GPU Operator.

Estás aquí: DEPLOY (y la transición a RETRAIN)

Estás aquí: DEPLOY · canary cierra el círculo iniciado en RETRAIN1 · Data2 · Tune3 · Eval4 · Deploy5 · Observe6 · Retrain

Un modelo nuevo no aparece por arte de magia en el cluster: viene del bucle de retrain o de una actualización del proveedor de pesos. El paso entre “tengo un artefacto que pasó eval offline” y “está sirviendo el 100 % del tráfico” es exactamente este post.

La analogía: el estreno de una obra en teatro

Una compañía de teatro va a estrenar una nueva versión de una obra que lleva un año en cartel con éxito. La compañía sabe varias cosas duras: el público actual paga por una experiencia consistente; un mal estreno daña el negocio durante meses; pero no estrenar nada deja a la compañía obsoleta frente a la competencia.

Las tres rutas de estreno que la dirección puede elegir son las mismas tres del rollout LLM.

Ensayo general a puerta cerrada (shadow / mirror). Los actores nuevos representan la obra entera ante un teatro vacío. No hay público; nadie compra entrada. Tres pases enteros sirven para comprobar continuidad, tiempos y química del reparto. Es caro porque hay sueldos y alquiler del teatro, pero no expone al público al riesgo. Útil cuando el reparto nuevo está sin probar y el director quiere ver cómo aguanta una función completa antes de venderla. En LLM: el modelo nuevo procesa cada request real en paralelo al viejo pero sus respuestas se descartan; gastas GPU del nuevo en respuestas que nadie ve.

Reparto por funciones, alternando (canary). En lugar de cambiar todo el reparto de golpe, las funciones de jueves son del reparto nuevo, las del viernes del viejo, las de sábado mitad y mitad. La dirección lee los comentarios del libro de visitas y la afluencia de público función a función, decidiendo al cabo de dos semanas si promociona el reparto nuevo a titular o lo retira. Más barato que el ensayo general porque las funciones venden entrada igual, pero expone público real al riesgo desde el primer jueves. En LLM: el tráfico se reparte progresivamente entre la versión vieja y la nueva, midiendo gates en cada salto.

Doble compañía con cambio atómico (blue-green). La compañía contrata el reparto nuevo, lo prepara durante un mes a puerta cerrada, y un sábado anuncia: “a partir del próximo estreno todas las funciones son con el reparto nuevo”. Si la primera función va mal, se vuelve al reparto viejo en 24 horas — pero durante ese mes de preparación se paga doble sueldo a las dos compañías. En LLM: dos pools completos del mismo tamaño, conmutación instantánea del LB de uno a otro, rollback en segundos si las métricas se rompen.

La analogía sostiene también la decisión: la elección depende de cuán crítica sea la obra para el negocio (criticidad del servicio LLM), cuánto presupuesto hay para sostener dos repartos a la vez (presupuesto GPU), y cuánta confianza se tiene en el nuevo reparto a partir de los ensayos de cámara (eval offline previa al canary).

Las tres estrategias en detalle

Tres estrategias de rollout LLM con sus tradeoffsBLUE-GREENPool v1 (azul) sirve 100%Pool v2 (verde) levantado en idleConmutación LB: v1 → v2 instantáneaRollback: re-conmutar a v1+ Rollback instantáneo+ Test E2E en pool real− Doble GPU durante ventana− Switch grande = riesgo totalCaso típico: actualización menorde proveedor (FP8 → FP4 nuevaversión del mismo modelo).CANARYv1 sirve mayoría · v2 fracciónReparto progresivo: 1→5→25→100%Gate de regresión entre saltosAuto-rollback si gate falla+ Exposición controlada+ GPU incremental, no doble− Usuarios reales en muestra− Sticky sessions rompen muestreoCaso típico: cambio de modelo(Llama 70B → Llama 3.3 70Bfine-tuned por dominio).SHADOW · MIRRORv1 sirve 100% al usuariov2 recibe copia de cada requestRespuesta v2 se descartaComparación offline v1 vs v2+ Cero exposición al riesgo+ Tráfico real en v2 sin daño− GPU del v2 100% sin valor user− Mal fit con streaming SSE largoCaso típico: validación pre-canaryde modelo de arquitectura distinta(denso → MoE).

Blue-green

El operador mantiene dos pools de réplicas idénticos en tamaño: el azul (versión productiva v1) y el verde (versión candidata v2). Cuando v2 está validado offline (eval pasada, smoke tests), el switch del LoadBalancer redirige el 100 % del tráfico de azul a verde en un solo paso. Si las métricas del SLO se rompen, el switch vuelve atrás en segundos.

Coste: 2× GPUs durante toda la ventana (preparación de v2 + ventana de observación post-switch). Para un cluster de 16 GPUs sirviendo Llama 70B con TP=4 (4 réplicas), preparar el blue-green requiere 16 GPUs adicionales durante 1–3 días.

Riesgo: el switch es atómico — si v2 tiene un problema que no apareció en eval offline pero sí aparece a escala (por ejemplo, edge cases que solo se ven a 200 RPS), el 100 % de usuarios lo nota a la vez. El rollback es instantáneo, pero las requests del primer minuto post-switch ya se vieron afectadas. Por tanto blue-green es preferible cuando se tiene alta confianza en v2 (cambio menor: misma arquitectura, mismo formato, solo nueva versión de pesos) y se prioriza rollback inmediato sobre exposición gradual.

Canary

El operador despliega v2 con un número pequeño de réplicas (típicamente 1) junto al pool de v1. El LoadBalancer reparte progresivamente el tráfico siguiendo un cronograma: 1 % durante 30 minutos → 5 % durante 1 hora → 25 % durante 2 horas → 50 % durante 4 horas → 100 %. Entre cada salto, un gate de análisis evalúa métricas de regresión sobre el tráfico que ya está cayendo en v2. Si el gate falla, el rollback retira el tráfico de v2 automáticamente y deja v1 sirviendo todo.

Coste: incremental. Al inicio (1 % de tráfico) basta una réplica v2; al 50 % se necesita la mitad de réplicas v2 que el total de v1. Pico de GPU adicional durante el canary: ~30–50 % por encima del baseline.

Riesgo: usuarios reales están viendo v2 desde el primer 1 %. Si v2 produce respuestas con calidad degradada pero TTFT y error rate normales, los usuarios afectados perciben la degradación sin que el gate la detecte (a menos que el gate incluya quality drift, que tarda). Por tanto canary es preferible cuando se tiene confianza media en v2 (cambio significativo: arquitectura o entrenamiento distinto) y se acepta que un % bajo de usuarios sea conejillo.

Shadow / mirror

El LoadBalancer envía el 100 % del tráfico real a v1 (que responde al cliente) y duplica cada request hacia v2 (cuya respuesta se descarta o se guarda para análisis offline). El cliente nunca ve v2; nunca está expuesto al riesgo.

Coste: 100 % adicional del compute de v2 sin valor de usuario directo durante toda la ventana de shadow. Para un cluster de 16 GPUs sirviendo Llama 70B con TP=4 (4 réplicas), un shadow del mismo tamaño consume 16 GPUs adicionales a tiempo completo.

Riesgo: el shadow es el más seguro para el usuario. Pero tiene dos limitaciones serias: (a) si v2 tiene un cuello de botella que causa que la copia de request al shadow tarde mucho, el proxy de shadowing puede consumir conexiones del LB; debe estar out-of-band (asíncrono); (b) el streaming SSE largo complica la mirroring porque hay que mantener dos streams paralelos y descartar uno mientras el otro fluye al cliente. Patrón habitual: shadow solo de requests no-streaming (completiones cortas, classification), eval offline manual de las requests con streaming.

Las cinco métricas de regresión que actúan como gate

Sin gates automáticos, el “canary” es solo un nombre bonito para “rollout manual con un porcentaje variable”. Los gates son la pieza que convierte el canary en una operación defendible.

Métrica 1 — TTFT P95. Comparación P95 del nuevo modelo contra P95 del baseline (v1) en ventanas de 5 minutos. Gate: ttft_p95(v2) / ttft_p95(v1) < 1.10. Detecta regresiones de latencia de prefill (modelo nuevo más lento) o problemas de motor (config subóptima). Fuente: vllm:time_to_first_token_seconds_bucket —ver Observabilidad GPU para inferencia LLM—.

Métrica 2 — Error rate. Suma de HTTP 5xx + 4xx no esperados + tasa de finish_reason="length" prematuro (respuestas cortadas porque el modelo nuevo no genera EOS). Gate: error_rate(v2) - error_rate(v1) < 0.01 (1 punto porcentual). Detecta crashes del motor, tokenizer roto, problemas de generación. Fuente: vllm:request_success_total{status=...}.

Métrica 3 — Quality score (LLM-as-judge). Sobre un golden set de 200–1 000 prompts representativos, se ejecutan v1 y v2 offline y un modelo juez (típicamente más grande: GPT-4 class, Claude, Llama 405B local) puntúa cada par. Gate típico: mean_score(v2) >= mean_score(v1) - 0.05. Esta métrica no se mide en tiempo real durante el canary — la inferencia del juez tarda 2–8 segundos por sample y no escala como gate inline. Se usa como gate offline pre-promoción (antes de empezar el canary) y como post-mortem sobre muestra de tráfico real capturado durante el canary. Ver LLM-as-judge para la mecánica.

Métrica 4 — Drift estadístico de output. Para cada request que cae en v2 durante el canary, embeber la respuesta con un modelo de embedding ligero (e5, BGE) y comparar la distribución de embeddings de v2 contra la distribución del baseline v1 sobre la misma ventana. Métricas usables: Wasserstein distance, divergencia KL, o más simple, comparar medias y varianzas por dimensión. Gate: distancia normalizada < umbral calibrado (típicamente Wasserstein < 0.15). Detecta cambios sutiles en estilo, longitud, vocabulario que LLM-as-judge no captura sin pasar también por él. Es rápida: el embedding ligero tarda ~50 ms por respuesta.

Métrica 5 — Coste por request. Tokens out / request y kW / request. Gate: cost_per_request(v2) / cost_per_request(v1) < 1.20. Detecta modelos nuevos que generan respuestas significativamente más largas o que consumen más energía por la misma carga (degradación de quantization, fallo de optimizations). Sin este gate, una “actualización” puede duplicar la factura silenciosamente.

MétricaTipoLatencia de medidaGate típicoDetección
TTFT P95Cuantitativa5 min< 110% baselineRegresión de latencia
Error rateCuantitativa1 min< 1pp sobre baselineCrashes, generation broken
Quality (LLM-judge)Semántica offlinehoras, sobre golden> baseline − 0.05Calidad funcional
Drift estadísticoEstadística~5 minWasserstein < 0.15Estilo, longitud, vocabulario
Coste por requestCuantitativa5 min< 120% baselineEficiencia económica/energética

La mecánica en Kubernetes: Argo Rollouts

Argo Rollouts extiende el Deployment estándar de Kubernetes con un nuevo recurso Rollout que orquesta la progresión del tráfico y los análisis automáticos. Se integra con cualquier service mesh (Istio, Linkerd) o controlador de ingress que soporte traffic splitting (NGINX, Traefik, Gateway API).

Ejemplo mínimo de canary 1 → 5 → 25 → 100 % con gates de TTFT y error rate:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: { name: vllm-llama70b }
spec:
  replicas: 4
  strategy:
    canary:
      canaryService: vllm-llama70b-canary
      stableService: vllm-llama70b-stable
      trafficRouting:
        nginx:
          stableIngress: vllm-llama70b-ingress
      steps:
        - setWeight: 1
        - pause: { duration: 30m }
        - analysis: { templates: [{ templateName: ttft-error-gate }] }
        - setWeight: 5
        - pause: { duration: 1h }
        - analysis: { templates: [{ templateName: ttft-error-gate }] }
        - setWeight: 25
        - pause: { duration: 2h }
        - analysis: { templates: [{ templateName: ttft-error-gate }, { templateName: drift-gate }] }
        - setWeight: 50
        - pause: { duration: 4h }
        - analysis: { templates: [{ templateName: ttft-error-gate }, { templateName: drift-gate }] }
        - setWeight: 100
  selector: { matchLabels: { app: vllm-llama70b } }
  template:
    metadata: { labels: { app: vllm-llama70b } }
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.10.0
          args: [ --model=/models/llama-70b-fp8-v2 ]   # versión nueva
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: { name: ttft-error-gate }
spec:
  metrics:
    - name: ttft-p95-ratio
      interval: 1m
      count: 5
      failureLimit: 1
      successCondition: result < 1.10
      provider:
        prometheus:
          address: http://prometheus.observability.svc:9090
          query: |
            histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version="v2"}[5m])))
            /
            histogram_quantile(0.95, sum by(le)(rate(vllm:time_to_first_token_seconds_bucket{version="v1"}[5m])))            
    - name: error-rate-diff
      interval: 1m
      count: 5
      failureLimit: 1
      successCondition: result < 0.01
      provider:
        prometheus:
          address: http://prometheus.observability.svc:9090
          query: |
            sum(rate(vllm:request_total{version="v2",status=~"5.."}[5m])) / sum(rate(vllm:request_total{version="v2"}[5m]))
            -
            sum(rate(vllm:request_total{version="v1",status=~"5.."}[5m])) / sum(rate(vllm:request_total{version="v1"}[5m]))            

Si cualquiera de los AnalysisTemplate falla, Argo Rollouts retrocede automáticamente: pone weight=0 en el canary, alerta al operador, mantiene v1 sirviendo el 100 %. La operación humana se reduce a investigar el fallo y decidir si re-lanzar o abortar.

Flagger ofrece una alternativa más opinionada: la progresión del weight es automática en función del éxito de las métricas en vez de pausa fija; el operador define un objetivo (maxWeight: 100, stepWeight: 10, metrics: [...]) y Flagger sube o baja según comportamiento. Ambas son maduras en mayo 2026; la elección suele venir dictada por qué service mesh ya está en el cluster.

El detalle de vLLM: por qué no se hace “hot swap” del modelo

A mayo 2026, vLLM v1 no soporta cambio caliente del modelo dentro de la misma réplica sin reiniciar el motor. El comando --model se evalúa al arranque; cambiarlo requiere re-instanciar el LLMEngine, lo que reinicia conexiones y descarta el KV cache. Por tanto la unidad de rollout es la réplica entera: no se hace “v1 carga el modelo nuevo en una de sus GPUs” sino “se levanta una réplica v2 al lado de una réplica v1 y se reparte tráfico vía LB”.

TensorRT-LLM con Triton tiene un mecanismo similar: cambiar el modelo exige reload del backend Triton. SGLang tampoco soporta hot swap robusto. La consecuencia operativa: el rollout LLM siempre va a costar GPUs adicionales durante la ventana, y la elección entre blue-green, canary y shadow es exactamente la pregunta de cuántas adicionales y cuánto tiempo.

Los tres pitfalls específicos del rollout LLM

Pitfall 1 — sticky sessions rompen la comparabilidad del canary. Si el LoadBalancer hace session affinity por IP del cliente (común en NGINX, Traefik con loadbalancer.kubernetes.io/session-affinity: ClientIP), un usuario A siempre cae en v2 mientras B siempre cae en v1. Las distribuciones de carga, perfiles de prompt y comportamiento de cliente no son aleatorias entre los dos pools, lo que invalida estadísticamente cualquier comparación de gates. Solución: para canary, desactivar session affinity (sessionAffinity: None) o usar affinity por request-id aleatorio. Si la app cliente exige sticky por funcionalidad (memoria conversacional persistida en cache), el canary no es la estrategia adecuada — usar blue-green o shadow.

Pitfall 2 — LLM-as-judge no es gate inline en tiempo real. La tentación de usar quality score como gate live es alta, pero la latencia del juez (2–8 s por sample) hace inviable evaluar más que un sampling del 1–2 % del tráfico, y los resultados llegan con minutos de retraso. Soluciones operativas: (a) eval offline pre-canary sobre golden set como pre-requisito para arrancar (si falla, ni se inicia el canary); (b) durante el canary, capturar requests + responses de v2 a tiempo real y correr el juez asíncrono en un job batch que termina antes del siguiente salto; (c) usar drift estadístico de embeddings como proxy rápido de calidad inline, y reservar el juez para gates intermedios entre saltos.

Pitfall 3 — streaming SSE complica el shadow. El mirror de tráfico clásico (NGINX mirror, Istio MirrorPolicy) está pensado para HTTP de request/response — copia la request, deja al servidor primario responder al cliente, y duplica la request al secundario descartando la respuesta. Con SSE, la respuesta del secundario es un stream continuo de varios segundos, y mantener dos streams en paralelo carga doblemente al proxy. Soluciones: (a) shadow solo de requests no-streaming (chat sin stream, embeddings, classification, batch eval), (b) shadow del tráfico streaming pero con timeout corto en el secundario (descartar el shadow si tarda más de 30 s), (c) reemplazar el shadow por canary con weight pequeño (1 %) que sí soporta streaming bien.

Aplicado a hardware on-premise típico

Para un cluster genérico de 4 nodos × 4×H100 SXM 80 GB = 16 GPUs, sirviendo Llama 70B FP8 con TP=4 (4 réplicas posibles, una por nodo):

  • Blue-green: imposible mantener dos pools completos de 4 réplicas sin GPUs adicionales. Solución práctica: blue-green con pools reducidos (2 réplicas v1 + 2 réplicas v2) durante la ventana, degradación de capacidad aceptada (mitad del SLO de RPS sostenido), o disponer de un cluster paralelo (otro nodo) reservado para rollouts.
  • Canary: factible. Empezar con 3 réplicas v1 + 1 réplica v2 (25 % weight nominal pero también peso variable de tráfico). Avanzar a 2 v1 + 2 v2 al 50 %, luego 1 v1 + 3 v2, finalmente 0 v1 + 4 v2.
  • Shadow: complicado por el coste de GPU. Reservar para validación pre-canary de cambios mayores, durante una ventana corta (4–8 horas) con tráfico shadowed limitado a una muestra (10–20 % de requests, no 100 %).

Para clusters de 8 nodos GPU, los tres patrones son sostenibles. La regla operativa: el presupuesto de rollout es típicamente el 25–30 % de la capacidad sostenida del cluster — comprar para el pico + ese head-room cuadra los números del capacity planning.

Lo que no hemos cubierto (próximos artículos)

  • Rollouts multi-region: cómo coordinar canary cuando el cluster está distribuido geográficamente.
  • A/B testing de prompts (no de modelos): el mismo modelo con dos system prompts distintos, medir conversion.
  • Rollback de embeddings: cambiar el modelo de embeddings de un sistema RAG implica re-embedir todo el corpus — la mecánica de canary es distinta. Ver RAG corpus curation.
  • Feature flags para LLM: granularidad por tenant o por feature dentro del mismo modelo.
  • Continuous deployment end-to-end: integración con el retrain pipeline para que un nuevo adapter se promocione automáticamente tras pasar evals.

Ver también

Referencias

  • Argo Rollouts project — argoproj.io/argo-rollouts (CRD Rollout y AnalysisTemplate).
  • Flagger project — fluxcd.io/flagger (alternativa con progresión automática).
  • Istio — Traffic Mirroring (mirror configurable a nivel VirtualService).
  • NGINX Ingress — Canary annotations (nginx.ingress.kubernetes.io/canary-*).
  • vLLM project — issue tracker sobre hot model swap (estado a mayo 2026: en diseño, no production-ready).
  • Hou et al. — DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving (OSDI 2024) — referencia sobre métricas de goodput aplicables a gates de canary.
  • Bürkner et al. — Statistical methods for detecting model drift in production (artículos varios sobre Wasserstein y KL en monitoring ML).