El router de inferencia LLM: la centralita L7 que en el post de canary llamábamos LoadBalancer

Este post es la continuación natural de Canary, blue-green y shadow para modelos LLM. Allí la mecánica de promoción depositó toda la complejidad de reparto de tráfico en una caja a la que llamamos “LoadBalancer”. La descripción era operacional —servía para entender la coreografía— pero estructuralmente vaga: lo que de verdad hace ese reparto es un router de inferencia L7 con awareness LLM, una pieza de pleno derecho del stack (capa 1 de las siete capas) que merece su propio post.

TL;DR

En el post anterior sobre canary llamamos LoadBalancer a la pieza que reparte tráfico entre los pools v1 estable y v2 candidato. La descripción servía para entender el flujo, pero técnicamente era borrosa: ni un LoadBalancer L4 (kube-proxy, MetalLB, IPVS) ni un LoadBalancer L7 HTTP genérico (NGINX o HAProxy sin extensión) saben qué es un modelo, qué es una versión, cuántos tokens cuesta una request, qué prefijo tiene el prompt o qué KV cache tiene caliente cada réplica. La pieza correcta es un router de inferencia LLM: un proxy L7 con conocimiento explícito del dominio. Combina cuatro funciones: catálogo de modelos (resolver model=llama-70b@v2service.namespace:port), traffic splitting (aplicar el weight de canary con hash determinista o sticky deliberado para A/B), política transversal (auth OIDC, rate limit y quota por tenant, redact PII pre-prompt, guardrails ligeros inline, propagación de tracing gen_ai.*) y failover/degradación (si v2 cae, redirigir a v1; si todo el cluster está saturado, devolver 503 con Retry-After en vez de encolar para siempre). La pieza no obvia que justifica su existencia técnica más allá de la operacional es el prefix-aware routing: el router decide a qué réplica de la flota va cada request en función del prefijo del prompt, para que un sistema RAG con el mismo system prompt + el mismo bloque de documentos recuperados acierte sistemáticamente en el prefix cache (RadixAttention en SGLang, PrefixCaching en vLLM, KV reuse en TensorRT-LLM) de la misma réplica, multiplicando el hit rate del 5–15 % (round-robin ciego) al 60–85 % (afinidad por prefix). Las piezas concretas en mayo 2026 son LiteLLM Proxy (la opción más simple, OpenAI-compatible, catálogo declarativo YAML), vLLM Production Stack router (específico para flotas vLLM, aware del KV cache y del prefix), Envoy AI Gateway (filtros Envoy LLM-aware, integrable con Istio), Kong AI Gateway (alternativa empresarial con plugin ecosystem), KGateway (CNCF en gestación) y NVIDIA Dynamo router (production-grade, aware de disaggregated serving prefill/decode). En el stack de siete capas vive en la capa 1 (gateway); en el de cinco niveles de madurez aparece a partir del nivel 3; en el ciclo de siete fases de despliegue es la última pieza que F6 cierra. Este post incluye un manifest mínimo aplicable a un cluster genérico de 4×H100 SXM.

Estás aquí: DEPLOY (capa 1 del stack)

Estás aquí: DEPLOY · capa 1 del stack (gateway / router de inferencia)1 · Data2 · Tune3 · Eval4 · Deploy5 · Observe6 · Retrain

El antecedente: lo que el post de canary llamaba “LoadBalancer”

En Canary, blue-green y shadow para modelos LLM describimos el flujo así: “el LoadBalancer reparte progresivamente el tráfico siguiendo un cronograma: 1 % → 5 % → 25 % → 100 %”. Era una descripción operacional correcta — el lector entendía la coreografía sin necesitar más. Pero técnicamente dejaba sin nombre a una pieza que merece tratamiento explícito, porque ninguno de los dos sentidos habituales de “LoadBalancer” hace lo que ese párrafo asumía:

  • Un LoadBalancer L4 —kube-proxy con iptables/IPVS, MetalLB, F5 BIG-IP en modo TCP— reparte paquetes IP sin mirar dentro del payload. No sabe qué modelo se pide, ni qué versión, ni cuántos tokens lleva, ni si el cliente tiene quota. No puede aplicar el weight del canary “para el modelo X versión 2”: para él todos los paquetes hacia el VIP vllm-llama70b son indistinguibles.
  • Un LoadBalancer L7 HTTP genérico —NGINX o HAProxy en modo HTTP sin extensión, una Service de tipo ClusterIP con backend múltiple— sí reparte por URL y puede hacer routing por header, pero no entiende el cuerpo OpenAI-compatible de la request. No sabe que {"model": "llama-70b", "messages": [...]} lleva en el campo model la clave de routing; no cuenta tokens; no aplica políticas sobre estructuras LLM; no hace prefix-aware routing porque eso exige parsear el messages y hashear el prefijo común.

La pieza que el post de canary asumía haciendo este trabajo es un router de inferencia L7 con awareness LLM. Una capa de pleno derecho, con su propia configuración, su propio CI/CD, sus propias métricas y sus propios pitfalls. Este post la nombra y la desmonta.

La analogía: la centralita y triage de un hospital con múltiples especialidades

Un hospital grande recibe pacientes que llegan a urgencias por puertas distintas y que necesitan especialidades distintas: traumatología, cardiología, pediatría, oncología. Hay tres modelos posibles de “puerta de entrada”.

Puerta única sin triage. Todos los pacientes esperan en la misma sala y los van pasando por orden de llegada al primer médico libre, sea su especialidad la que sea. Funciona en un consultorio de aldea con un único médico generalista. Cuando hay 200 pacientes al día y 12 especialidades, cae rápido en disfunción: el cardiólogo atiende esguinces, el pediatra atiende infartos, los recursos especializados se desperdician. Es el equivalente del LoadBalancer L4 — reparte cuerpos sin entender qué traen.

Puerta con receptionist que pregunta el síntoma. Una persona en mesa de entrada pregunta “¿qué le pasa?” y dirige al paciente al pasillo correcto. El cardiólogo ve solo cardiología, el pediatra solo niños. Mejor, pero el receptionist es lento, no calibra urgencias y no conoce el estado de las salas: puede mandar al cardiólogo del pasillo A cuando el del B está libre. Es el equivalente de un L7 HTTP genérico con path-based routing — reparte por categoría pero sin información del estado interno.

Triage profesional con awareness completo. Una enfermera de triage formada que conoce el catálogo de especialidades, sabe qué box está ocupado y cuál libre, recuerda al paciente recurrente cuyo expediente ya está abierto en el sistema (manda al mismo médico para continuidad), aplica política transversal (verifica cobertura del seguro, registra alérgenos, redirige a urgencias pediátricas si el paciente es menor) y, si la sala de cardiología cae por una avería del electrocardiograma, redirige al hospital del otro lado de la ciudad. Esta es la pieza que un hospital grande necesita. En LLM se llama router de inferencia.

La analogía sostiene hasta el último detalle, incluido el del “expediente ya abierto”: el paciente que vuelve al mismo médico es exactamente el cliente cuyo prompt comparte prefijo con el de hace 5 minutos. Si el router lo manda a la misma réplica, esa réplica todavía tiene el KV cache caliente y la request acierta el prefix cache. Si lo manda a una réplica distinta porque iba “la siguiente en round-robin”, el KV cache hay que reconstruirlo desde cero y la TTFT se va al doble. La enfermera de triage sabe esto. El LoadBalancer ciego no.

Las cuatro funciones del router de inferencia

Cuatro funciones que el router de inferencia combina1 · CATÁLOGO DE MODELOSResolver `model=llama-70b@v2` → service:portVersionado, aliases, lifecycle (preview/stable/deprecated)Lo que evita que el cliente conozca topología.Sin esto, cada cliente sabe IPs/puertos internos.2 · TRAFFIC SPLITTINGWeight de canary / blue-green / shadowHash determinista por request o sticky deliberadoLas particiones del post de canary se aplican aquí,no en el motor de inferencia.3 · POLÍTICA TRANSVERSALAuth OIDC · rate limit · quota por tenantRedact PII pre-prompt · guardrails ligeros inlineTracing gen_ai.* propagado · semantic cacheLo que se aplica una vez por todos los modelos.4 · FAILOVER · DEGRADACIÓNSi v2 cae → redirige a v1Si todo saturado → 503 con Retry-AfterCircuit breaker · health probes activosLo que evita encolar para siempre.

Función 1 — Catálogo de modelos

El router mantiene un catálogo declarativo que mapea identidad de modelo a deployment concreto:

models:
  - name: "llama-70b"                    # alias estable
    version: "v2"                         # versión canary
    weight: 5                             # 5% del tráfico
    endpoint: "vllm-llama70b-v2.inference.svc.cluster.local:8000"
    capabilities: [chat, tool_use]
    lifecycle: canary
  - name: "llama-70b"
    version: "v1"
    weight: 95
    endpoint: "vllm-llama70b-v1.inference.svc.cluster.local:8000"
    capabilities: [chat, tool_use]
    lifecycle: stable
  - name: "embedding-multilingual"
    version: "v1"
    weight: 100
    endpoint: "tei-bge-m3.inference.svc.cluster.local:8080"
    capabilities: [embeddings]
    lifecycle: stable

El cliente envía {"model": "llama-70b", "messages": [...]} sin saber que detrás hay dos pools de réplicas. El router resuelve. Si mañana migras de vLLM a SGLang para una versión concreta, el cliente no se entera; cambias el endpoint en el catálogo y listo.

Lo que se gana con este desacoplamiento es la libertad de mover topología sin romper clientes. Lo que cuesta es mantener disciplinada la convención de nombres (llama-70b siempre es el alias estable; llama-70b@v2 es la versión específica para canary). Sin esa disciplina, los aliases se ensucian con llama-70b-prod-fixed-real-final-v3 y el catálogo deja de ser navegable a las pocas semanas.

Función 2 — Traffic splitting

Las particiones del post de canary (1 % → 5 % → 25 % → 100 %) se materializan aquí, no en el motor de inferencia. El router calcula un hash determinista del request_id (o del user_id, si se quiere sticky) y lo mapea al rango de weights del catálogo. Para un weight [v1: 95, v2: 5], el 5 % del espacio hash cae en v2 y el 95 % en v1.

Tres decisiones de diseño que importan:

  • Hash por request_id aleatorio = muestreo independiente. Cada request es una observación independiente de la distribución v1 vs v2. Es el setting correcto para canary estadísticamente comparables.
  • Hash por user_id = sticky por usuario. El mismo cliente ve siempre el mismo pool. Útil para A/B testing con memoria conversacional persistida, pero rompe la comparabilidad estadística del canary porque las poblaciones de usuarios no son simétricas — pitfall explicado en el post anterior.
  • Hash por tenant_id = particionado fuerte. Tenant A va a v1, tenant B a v2. Es el patrón para clientes con SLA distintos o para validar v2 en un tenant interno antes de exponerlo a clientes externos.

Función 3 — Política transversal

Una vez por encima de todos los modelos, el router aplica:

  • Auth: OIDC con tokens JWT validados contra Keycloak / Authentik. Headers Authorization: Bearer ... traducidos a tenant_id y roles.
  • Rate limit: token bucket por tenant (X req/min) o por modelo (Y req/min para llama-70b porque es caro).
  • Quota: cuota mensual de tokens consumidos por tenant. El router cuenta gen_ai.usage.input_tokens + gen_ai.usage.output_tokens y rechaza con 429 Quota exceeded cuando se agota.
  • Redact PII pre-prompt: Presidio o Llama Guard en línea antes de que el prompt toque el modelo. Lo que el modelo no ve, no se entrena con ello, no se loguea, no se filtra.
  • Guardrails ligeros inline: PromptGuard 2, Llama Guard 4, Granite Guardian — los que aparecen en Guardrails y safety en LLMs— se ejecutan en el router porque su latencia (30–150 ms) cabe en el presupuesto de TTFT.
  • Propagación de tracing gen_ai.*: el router inicia el span padre, propaga traceparent al motor y emite los atributos gen_ai.system, gen_ai.request.model, gen_ai.request.version que el tracing OTel GenAI consume.
  • Semantic cache: para prompts repetidos exactos o con similitud semántica alta (embedding cosine > 0.97 contra cache previa), devuelve la respuesta cacheada sin tocar el motor. Ahorro típico en RAG con preguntas frecuentes: 20–40 % de las requests.

Función 4 — Failover y degradación

El router conoce el estado de salud de cada endpoint (health probes activos cada 5–15 s, latencia de TTFT recientes) y decide:

  • Si v2 devuelve 5xx persistente o no responde, circuit breaker abierto: el router redirige el tráfico que iba a v2 hacia v1 hasta que las probes vuelvan a verde. Esto es el rollback automático del canary en su forma más simple.
  • Si todo el cluster está saturado (todas las réplicas reportan num_requests_waiting > N durante T segundos), el router devuelve 503 Service Unavailable con Retry-After: 30 en vez de encolar para siempre. Mejor decirle al cliente “vuelve en 30 segundos” que tenerlo esperando 4 minutos y luego dar timeout.
  • Si hay multi-region o multi-cluster, failover cross-cluster vía DNS o L7: la región primaria cae, el router de la secundaria asume.

La pieza no obvia: prefix-aware routing

Esta es la función que un LoadBalancer convencional no puede hacer y que justifica un router específico de LLM más allá de las cuatro genéricas.

El KV cache de vLLM, SGLang y TensorRT-LLM puede reusar prefijos comunes entre requests —ver KV cache—. Concretamente:

  • vLLM con --enable-prefix-caching: detecta que la request actual comparte un prefijo (longitud múltiplo del block size, default 16 tokens) con una request anterior cuyas páginas todavía están en HBM, y reutiliza esas páginas en vez de reprocesarlas.
  • SGLang con RadixAttention: estructura el cache como un árbol radix indexado por tokens; cada request acierta el camino común del árbol y solo computa la cola.
  • TensorRT-LLM: feature similar, llamado KV cache reuse.

El hit rate del prefix cache es la métrica clave: cada token acertado es un token que no se procesa en prefill, reduciendo TTFT en proporción directa. Para un sistema RAG típico —system prompt de 400 tokens + documentos retrieved de 2 000 tokens + pregunta del usuario de 50 tokens— el prefijo común (system_prompt + docs) son 2 400 de los 2 450 tokens totales. Si el cache acierta, el prefill solo procesa 50 tokens en vez de 2 450: TTFT cae aproximadamente a la vigésima parte.

Pero el cache vive por réplica, no globalmente. Si dos requests con el mismo prefix de 2 400 tokens caen en réplicas distintas, ambas hacen el prefill completo: el cache de la primera no sirve a la segunda. La segunda paga el coste íntegro.

Con round-robin ciego (cualquier LB convencional), las requests se reparten uniformemente entre N réplicas. Para un cluster de 4 réplicas y 1 000 requests con el mismo system_prompt + docs, cada réplica recibe ~250 requests, pero las 4 hacen su propio “primer prefill” y los siguientes 249 se benefician dentro de su réplica. El hit rate global es decente pero no óptimo. Para tráfico con muchos sistemas prompts distintos y poca repetición intra-prefix, el hit rate ronda el 5–15 %.

Con prefix-aware routing, el router calcula un hash del prefijo del prompt (los primeros N tokens, o el system_prompt declarado en messages[0]) y mantiene una tabla de afinidad hash → réplica. Todas las requests con el mismo prefijo caen en la misma réplica. La primera paga el prefill completo; las 999 siguientes aciertan el cache. Hit rate global: 60–85 %.

El coste de implementarlo: el router debe parsear el body de la request (no solo el header HTTP), aplicar un tokenizer ligero o un hash basado en bytes, y mantener una tabla LRU/consistent-hash de afinidad que se rebalancea cuando una réplica entra o sale. Es trabajo de servidor, no de proxy genérico. vLLM Production Stack router lo implementa nativamente. NVIDIA Dynamo también. LiteLLM en su versión enterprise tiene un beta. Envoy AI Gateway lo está incorporando como filtro experimental.

La diferencia operativa para un RAG productivo: con prefix-aware routing, el mismo cluster sirve 2–4× más requests sin añadir GPUs, simplemente porque el prefill desaparece en la mayoría de los casos.

Token-aware load balancing

La segunda pieza no obvia. El round-robin clásico reparte por número de requests; pero un prompt de 50 tokens y otro de 8 000 tokens cuestan radicalmente distinto (factor ~160× en prefill). Repartir igualmente por count desequilibra severamente la carga real.

Token-aware load balancing suma tokens de prefill esperados (longitud del prompt) y decode esperados (max_tokens del cliente) por réplica activa, y manda la nueva request a la réplica con menor carga acumulada. Es lo que tanto vLLM Production Stack como NVIDIA Dynamo implementan como estrategia por defecto cuando se activa.

La métrica que alimenta el cálculo es —otra vez— vllm:num_requests_running y vllm:gpu_cache_usage_perc —ver Observabilidad GPU para inferencia LLM—, idealmente complementadas con un estimador de tokens del prompt entrante. Los routers maduros usan tiktoken o el tokenizer real del modelo para contar tokens del prompt antes de elegir réplica.

Comparativa de piezas concretas (mayo 2026)

PiezaAwareness LLMPrefix-awareToken-aware LBMulti-modeloSemantic cachePlug & play
LiteLLM ProxyAltaBeta (enterprise)ExcelenteSí (Redis)Muy alto
vLLM Production Stack routerEspecífico vLLMSí, nativoSolo vLLMNo (externa)Medio
NVIDIA Dynamo routerAlta + disagg-awarevLLM/SGLang/TRT-LLMNo (externa)Bajo
Envoy AI GatewayMedia (filtros)ExperimentalVía filtroMedio
Kong AI GatewayMedia (plugins)NoSí (plugin)Medio
KGatewayMediaRoadmapRoadmapBajo (CNCF gestación)
NGINX + custom LuaManualNoManualManualNoBajo (build it yourself)

LiteLLM Proxy es la opción por defecto para empezar. OpenAI-compatible, YAML simple, soporta los providers comerciales + cualquier OpenAI-compatible self-hosted. La versión OSS cubre las cuatro funciones básicas y semantic cache; el prefix-aware y la versión enterprise añaden multi-tenancy avanzado.

vLLM Production Stack router es la opción correcta si la flota es 100 % vLLM. Aware del KV cache, del prefix, del LoRA loaded por réplica. Integra mejor con métricas vLLM nativas.

NVIDIA Dynamo router es la opción production-grade más completa, especialmente si se opera disaggregated serving (prefill workers vs decode workers separados). Requiere stack NVIDIA-aligned.

Envoy AI Gateway y Kong AI Gateway son las opciones si la organización ya tiene Envoy/Kong como gateway corporativo y quiere extenderlo con LLM-awareness sin introducir otra pieza nueva.

Manifest mínimo: LiteLLM Proxy sobre cluster genérico

apiVersion: v1
kind: ConfigMap
metadata: { name: litellm-config, namespace: inference }
data:
  config.yaml: |
    model_list:
      - model_name: llama-70b
        litellm_params:
          model: openai/llama-70b
          api_base: http://vllm-llama70b-v1.inference.svc:8000/v1
          weight: 95
        model_info:
          version: v1
          lifecycle: stable
      - model_name: llama-70b
        litellm_params:
          model: openai/llama-70b
          api_base: http://vllm-llama70b-v2.inference.svc:8000/v1
          weight: 5
        model_info:
          version: v2
          lifecycle: canary
      - model_name: embedding-multilingual
        litellm_params:
          model: openai/bge-m3
          api_base: http://tei-bge-m3.inference.svc:8080
    router_settings:
      routing_strategy: least-busy   # token-aware basic
      num_retries: 1
      timeout: 60
    general_settings:
      master_key: "os.environ/LITELLM_MASTER_KEY"
      database_url: "os.environ/DATABASE_URL"
    litellm_settings:
      cache: true
      cache_params:
        type: redis
        host: redis.inference.svc
        port: 6379
        similarity_threshold: 0.97
      success_callback: ["langfuse"]
      failure_callback: ["langfuse"]    
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: litellm-router, namespace: inference }
spec:
  replicas: 3
  selector: { matchLabels: { app: litellm } }
  template:
    metadata: { labels: { app: litellm } }
    spec:
      containers:
        - name: litellm
          image: ghcr.io/berriai/litellm:v1.55.0
          args: ["--config=/config/config.yaml", "--port=4000", "--num_workers=4"]
          ports: [{ containerPort: 4000, name: http }, { containerPort: 4000, name: metrics }]
          env:
            - { name: LITELLM_MASTER_KEY, valueFrom: { secretKeyRef: { name: litellm-secret, key: master_key } } }
            - { name: DATABASE_URL, valueFrom: { secretKeyRef: { name: litellm-secret, key: db_url } } }
            - { name: LANGFUSE_PUBLIC_KEY, valueFrom: { secretKeyRef: { name: langfuse-keys, key: public } } }
            - { name: LANGFUSE_SECRET_KEY, valueFrom: { secretKeyRef: { name: langfuse-keys, key: secret } } }
          volumeMounts: [{ name: config, mountPath: /config }]
          readinessProbe: { httpGet: { path: /health, port: 4000 } }
      volumes: [{ name: config, configMap: { name: litellm-config } }]
---
apiVersion: v1
kind: Service
metadata: { name: litellm-router, namespace: inference }
spec:
  selector: { app: litellm }
  ports: [{ name: http, port: 80, targetPort: 4000 }]
---
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata: { name: litellm-metrics, namespace: inference }
spec:
  selector: { matchLabels: { app: litellm } }
  podMetricsEndpoints:
    - port: metrics
      path: /metrics
      interval: 15s

El cliente final apunta a litellm-router.inference.svc:80/v1/chat/completions, pone model=llama-70b, y el router decide en cada request si va a v1 (95 %) o v2 (5 %), aplica el rate limit, busca en semantic cache, propaga tracing a Langfuse, y traduce de OpenAI-compatible a OpenAI-compatible del vLLM de destino. Tres réplicas del router para HA y para que el propio gateway escale horizontalmente con KEDA si hace falta —ver Autoscaling LLM en Kubernetes—.

Cuatro pitfalls operacionales

Pitfall 1 — el router se convierte en SPoF si no se replica. Tres o más réplicas del propio router, detrás de un Service LoadBalancer (este sí, L4) con healthchecks. Una sola réplica del router significa que cada deploy de la configuración cierra el servicio entero unos segundos.

Pitfall 2 — la latencia del router se suma a la del modelo. Cada función añade milisegundos: parsing del body (5–10 ms), auth JWT (2–5 ms), rate limit (1–2 ms), redact PII con Presidio (20–80 ms), guardrails con Llama Guard inline (50–150 ms), prefix hash (5–10 ms), token counting con tokenizer (10–30 ms). En total 100–300 ms de overhead antes de tocar el motor. Si el TTFT del modelo es 400 ms y el del router 200 ms, el cliente ve 600 ms — vale la pena medir cuánto cuesta cada función y desactivar las no críticas en el path de baja latencia.

Pitfall 3 — el catálogo deriva del estado real del cluster. El router cree que vllm-llama70b-v2 existe porque está en su YAML, pero el deployment fue retirado hace tres días y nadie actualizó el config. El router devuelve 502 en el 5 % del tráfico. Solución: validar el catálogo contra kubectl get svc en CI; ningún endpoint del catálogo puede apuntar a un Service inexistente. O mejor: el router descubre dinámicamente los endpoints disponibles vía label selector (app=vllm,model=llama-70b) y aplica weights del catálogo sobre los que están vivos.

Pitfall 4 — semantic cache con embedding outdated. El semantic cache compara embedding del prompt nuevo contra embeddings de prompts cacheados. Si actualizas el modelo de embeddings (ver RAG corpus curation), las distancias se calculan en un espacio distinto y el cache deja de funcionar correctamente (falsos hits o falsos misses). Política: el cache se invalida al cambiar el modelo de embeddings; nunca se mezclan generaciones.

Encaje en el stack y la madurez

En el stack de siete capas, el router es la capa 1: la puerta de entrada que precede al motor de inferencia (capa 2), al KV cache + PagedAttention (capa 3) y al resto. Es la única pieza que ve todo el tráfico desde fuera; cualquier política que no se aplique aquí, se duplica N veces en los motores.

En los cinco niveles de madurez, el router aparece a partir del nivel 3 (GESTIONADO): sin OIDC + RBAC + cert-manager + NetworkPolicy default deny, el router no tiene a quien autenticar ni a quien aplicar quotas; antes del nivel 3 lo que toca es montar un proxy mínimo sin pretensión de catálogo. Plataformas que intentan tener router pulido en nivel 1 acaban con un yaml grande que nadie mantiene.

En las siete fases de despliegue, el router es lo que cierra F6: el último paso atómico que pone al cluster en producción. Sin router, F6 no termina — el catálogo, las quotas, los canaries y los failovers son condición necesaria para abrir tráfico productivo.

Aplicado a hardware on-premise típico

Para un cluster genérico de 4 nodos × 4×H100 SXM 80 GB, el router de inferencia consume recursos modestos: 3 réplicas del router-pod (CPU 2 cores, memoria 4 GiB cada una) bastan para soportar miles de RPS porque su trabajo es ligero (parsing, hashing, routing, no inferencia). El router vive en nodos no-GPU del cluster (nodos de control plane o de workload general), nunca consume nvidia.com/gpu.

Volumen de tráfico que un LiteLLM con 3 réplicas y 4 workers cada una sostiene: 2 000–5 000 RPS routing a backend vLLM, con overhead de 80–150 ms en path completo (auth + rate limit + cache check + propagación). Si se necesita más, escalar el router con KEDA sobre litellm:requests_per_second es trivial.

Para clusters más grandes (16+ nodos GPU), considerar vLLM Production Stack router o NVIDIA Dynamo router que son más complejos pero exprimen el prefix-aware routing y el token-aware LB que LiteLLM OSS no cubre. Para clusters multi-region, Envoy AI Gateway con Istio Service Mesh es la elección estándar.

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

  • Comparativa profunda LiteLLM vs vLLM PStack vs Dynamo con benchmarks de prefix-aware sobre cluster on-premise real.
  • Semantic cache con Redis Stack + RedisVL: hit rate, falsos positivos, política de TTL.
  • Multi-region routing: cómo el router decide entre clúster DC1 y DC2 según latencia, salud y carga.
  • AI Gateway specific features: token-bucket cost-based rate limiting (penaliza prompts largos), guardrails policy engine en el router.
  • Migration path: cómo introducir un router en un cluster que ya tiene clientes apuntando directo al servicio vLLM, sin downtime.

Ver también

Referencias

  • LiteLLM project — litellm.ai (documentación de Proxy, routing strategies, semantic cache).
  • vLLM Production Stack — github.com/vllm-project/production-stack (router con prefix-aware nativo).
  • NVIDIA Dynamo — developer.nvidia.com/blog/nvidia-dynamo-1-production-ready/ (router production-grade con disaggregated-aware).
  • Envoy AI Gateway — gateway.envoyproxy.io/docs/tasks/ai-gateway/ (proyecto en gestación dentro de Envoy).
  • Kong AI Gateway — konghq.com/products/kong-ai-gateway (proxy enterprise con plugin LLM).
  • KGateway — kgateway.dev (alternativa CNCF en gestación).
  • Zheng et al. — SGLang: Efficient Execution of Structured Language Model Programs (NeurIPS 2024) — RadixAttention y prefix caching.
  • vLLM project — Automatic Prefix Caching (docs.vllm.ai/en/latest/features/automatic_prefix_caching.html).
  • Patel et al. — SplitWise: Efficient Generative LLM Inference Using Phase Splitting (ISCA 2024) — base teórica del routing prefill/decode aware.