Hola a tod@s,

En el artículo anterior vimos cómo desplegar y servir un LLM con vLLM en un Dell Pro Max GB10. La idea era sencilla y muy útil para laboratorio o para un entorno pequeño: Linux, GPU NVIDIA, Docker, un modelo descargado desde Hugging Face y una API compatible con OpenAI.

Pero al llevar esta misma necesidad a una organización aparecen nuevas preguntas. ¿Cómo damos servicio a varios equipos? ¿Cómo evitamos que el modelo dependa de un único equipo? ¿Cómo controlamos actualizaciones, identidades, red, secretos, capacidad y observabilidad? Aquí es donde Azure Local puede convertirse en la base de una plataforma de IA ejecutada dentro de nuestro propio centro de datos.

Azure Local no sustituye al motor de inferencia. vLLM seguirá siendo quien cargue el modelo y responda a las peticiones. Lo que aporta Azure Local es la capa de infraestructura híbrida: cómputo, almacenamiento, redes, virtualización, integración con Azure Arc y una ruta hacia Kubernetes para operar el servicio con criterios empresariales.

QUÉ VAMOS A CONSEGUIR Diseñaremos dos caminos: una máquina virtual Linux con GPU para empezar de forma controlada y una plataforma con AKS habilitado por Azure Arc para escalar. El laboratorio principal se centrará en Kubernetes porque permite separar infraestructura, runtime y aplicación.

Me viene a la cabeza una pregunta que me hizo un amigo de la comunidad tecnológica.

¿Por qué ejecutar modelos de IA en local?

  • Mantener prompts, documentos, código y respuestas dentro del perímetro definido por la organización.
  • Reducir latencia cuando las aplicaciones y los datos ya están en el datacenter o en el Edge.
  • Operar en ubicaciones con conectividad limitada o con requisitos estrictos de soberanía.
  • Controlar el ciclo de vida del runtime, del modelo y de la infraestructura.
  • Crear una API interna reutilizable por asistentes, automatizaciones, RAG y herramientas de desarrollo.
  • Predecir el coste cuando la demanda es estable y el hardware se aprovecha de forma sostenida.

Tener el modelo en local no elimina los riesgos. Al contrario, nos convierte en responsables del parcheado, la protección del endpoint, las licencias del modelo, la gestión de secretos, el filtrado de datos y la capacidad. La soberanía no consiste solo en guardar los pesos en nuestro CPD; también implica gobernar todo el servicio.

Azure Local como base para IA privada

Podemos ver la solución en cuatro capas:

CapaComponentesResponsabilidad
InfraestructuraNodos Azure Local, red, almacenamiento, GPUDisponibilidad, rendimiento y ciclo de vida del hardware.
PlataformaVM Linux o AKS habilitado por Azure ArcAislamiento, scheduling, actualizaciones y políticas.
Serving (publicar un modelo de IA como servicio para que se puedan utilizar)vLLM, imagen de contenedor, caché de modelosCarga de pesos, KV cache, batching y API.
Consumo (Quien va a utilizar el modelo y como lo va a utilizar)Aplicaciones, RAG, agentes, portales internosAutenticación, experiencia, datos y casos de uso.

Dos caminos de despliegue

OpciónCuando elegirlaVentajasLímites
VM Linux + GPUPiloto, un modelo, pocos consumidoresSencilla, cercana al artículo anterior, troubleshooting directo.Escalado y alta disponibilidad más manuales.
AKS + GPUVarios modelos o equipos, GitOps, necesidad de estandarizaciónDespliegues declarativos, servicios, rolling updates y políticas.Más componentes, curva operativa y soporte GPU dependiente de versión.

Mi recomendación es comenzar con una VM si todavía estamos validando el modelo y el patrón de consumo. Cuando la API pase a ser un servicio compartido, tengamos varios consumidores o necesitemos una operación repetible, Kubernetes empieza a aportarnos valor.

Arquitectura de referencia

La siguiente arquitectura nos muestra cómo podemos transformar un laboratorio basado en una única GPU en una plataforma empresarial gobernada desde Azure. Los usuarios consumen una API interna protegida por un gateway, mientras que vLLM ejecuta los modelos sobre nodos con GPU dentro de Kubernetes. Azure Arc proporciona gobierno, políticas y operaciones, mientras que Azure Local aporta la infraestructura de ejecución.

El endpoint de vLLM no debería publicarse directamente en Internet. En producción lo normal es situarlo detrás de una capa que termine TLS, autentique al cliente, aplique cuotas, limite el tamaño de las peticiones y genere trazabilidad.

Requisitos previos

  • Una instancia de Azure Local instalada, registrada y saludable.
  • Hardware validado para la versión desplegada, incluida la opción de GPU cuando corresponda.
  • AKS habilitado por Azure Arc operativo, o una VM Linux si elegimos el camino inicial.
  • Driver y device plug-in compatibles con la GPU y con la versión de Kubernetes.
  • Registro de contenedores autorizado y repositorio para manifiestos.
  • Almacenamiento suficiente para imagen, pesos, caché y posibles variantes del modelo.
  • Conectividad controlada hacia el registro y el repositorio del modelo, o un proceso offline de importación.
  • Licencia del modelo revisado y, si es necesario, credenciales de Hugging Face con permisos mínimos.
  • DNS, certificados y una red privada para consumidores internos.
  • Una estimación de memoria GPU que incluya pesos, KV cache, contexto y concurrencia.

DECISIÓN CRÍTICA No seleccionéis un modelo solo por su número de parámetros. La cuantización, la longitud de contexto, la la concurrencia y el runtime modifican de forma importante el consumo real de memoria.

Paso 1. Validar el clúster y localizar la GPU

Antes de desplegar vLLM comprobaremos que Kubernetes ve el recurso acelerador. Los nombres exactos de los nodos y del recurso pueden variar según el plug-in utilizado.

kubectl get nodes -o wide

kubectl describe nodes | grep -i -E «nvidia.com/gpu|gpu»

kubectl get pods -A | grep -i nvidia

El resultado esperado es que al menos un nodo publique capacidad GPU y que los componentes del device plug-in estén en estado Running. Si Kubernetes no anuncia la GPU, no merece la pena continuar con el modelo: primero debemos resolver driver, firmware, asignación y compatibilidad.

Paso 2. Preparar namespace, secretos y almacenamiento

Separaremos la carga en un namespace y así evitaremos incluir tokens directamente en el manifiesto.

kubectl create namespace ai-inference

kubectl -n ai-inference create secret generic hf-token \
  –from-literal=token=»hf_REEMPLAZAR»

Para la caché del modelo necesitamos una StorageClass disponible en el clúster. El siguiente ejemplo solicitamos 200 GiB; ajustamos el tamaño del modelo y a vuestra política de almacenamiento.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: hf-cache
  namespace: ai-inference
spec:
  accessModes:
    – ReadWriteOnce
  resources:
    requests:
      storage: 200Gi
  storageClassName: REEMPLAZAR_STORAGECLASS

Paso 3. Desplegar vLLM con GPU

Este manifiesto es una base de laboratorio. Fijaremos una imagen versionada en lugar de latest, solicitaremos una GPU y montaremos la caché. Debemos sustituir el modelo, la etiqueta de imagen y los valores de capacidad por versiones validadas en nuestro entorno.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm
  namespace: ai-inference
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm
  template:
    metadata:
      labels:
        app: vllm
    spec:
      containers:
        – name: vllm
          image: vllm/vllm-openai:REEMPLAZAR_VERSION
          args:
            – –model
            – REEMPLAZAR_MODELO
            – –max-model-len
            – «8192»
            – –gpu-memory-utilization
            – «0.80»
          env:
            – name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-token
                  key: token
          ports:
            – name: http
              containerPort: 8000
          resources:
            requests:
              cpu: «4»
              memory: 24Gi
              nvidia.com/gpu: «1»
            limits:
              cpu: «8»
              memory: 48Gi
              nvidia.com/gpu: «1»
          readinessProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
            failureThreshold: 60
          volumeMounts:
            – name: hf-cache
              mountPath: /root/.cache/huggingface
      volumes:
        – name: hf-cache
          persistentVolumeClaim:
            claimName: hf-cache

En un clúster con nodos heterogéneos añadiremos labels, nodeSelector o affinity para que el pod solo se programe donde exista la GPU adecuada. También es habitual aplicar taints a los nodos acelerados para reservarlos a las cargas de IA.

Paso 4. Exponer el servicio dentro de la red

apiVersion: v1
kind: Service
metadata:
  name: vllm
  namespace: ai-inference
spec:
  selector:
    app: vllm
  ports:
    – name: http
      port: 8000
      targetPort: http
  type: ClusterIP

ClusterIP mantiene el servicio accesible únicamente dentro de Kubernetes. Para consumidores externos al clúster utilizaremos un ingress controller, un reverse proxy o un balanceador interno con TLS y autenticación. No cambiaremos a LoadBalancer sin haber definido antes red, exposición y controles de acceso.

Paso 5. Aplicar y validar

kubectl apply -f pvc.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

kubectl -n ai-inference rollout status deployment/vllm
kubectl -n ai-inference get pods -o wide
kubectl -n ai-inference logs -f deployment/vllm

La primera carga será más lenta porque se debe descargar o leer los pesos. Esperaremos a que la readiness probe esté correcta y después haremos una prueba temporal mediante port-forward.

kubectl -n ai-inference port-forward service/vllm 8000:8000

curl http://127.0.0.1:8000/health

Paso 6. Probar la API compatible con OpenAI

curl http://127.0.0.1:8000/v1/chat/completions \
  -H «Content-Type: application/json» \
  -d ‘{
    «model»: «REEMPLAZAR_MODELO»,
    «messages»: [
      {«role»: «system», «content»: «Responde en español y de forma concisa.»},
      {«role»: «user», «content»: «Explica qué aporta Azure Local a una plataforma de IA privada.»}
    ],
    «max_tokens»: 300,
    «temperature»: 0.2
  }’

Si obtenemos un array choices y contenido en message.content, el flujo básico funciona. Esto solo valida funcionalidad, no capacidad ni seguridad.

Paso 7. Consumirlo desde Python

from openai import OpenAI

client = OpenAI(
    base_url=»https://ia.interna.ejemplo/v1″,
    api_key=»token-obtenido-del-gateway»
)

respuesta = client.chat.completions.create(
    model=»REEMPLAZAR_MODELO»,
    messages=[
        {«role»: «user», «content»: «Resume las ventajas de Azure Local.»}
    ],
    max_tokens=300,
    temperature=0.2
)

print(respuesta.choices[0].message.content)

La compatibilidad con el formato OpenAI simplifica la integración, pero no significa que todas las extensiones o comportamientos sean idénticos. Debemos probar el cliente, el modelo y el endpoint concretos.

Seguridad: lo mínimo para producción

ControlAplicación práctica
RedEndpoint privado, segmentación, firewall y acceso solo desde redes autorizadas.
IdentidadAutenticación en gateway; evitar confiar en una clave de laboratorio colocada en el cliente.
TLSCertificados internos o públicos según el escenario; rotación automatizada.
SecretosSecretos fuera de YAML y repositorios; rotación y privilegio mínimo.
ImágenesVersiones fijadas, análisis de vulnerabilidades y registro autorizado.
ModelosLicencia, procedencia, hash, versionado y proceso de aprobación.
DatosClasificación, DLP, retención y prevención de prompts con información innecesaria.
AbusoCuotas, límites de tokens, tamaño máximo de prompt, timeout y límites por consumidor.
AuditoríaRegistrar identidad, modelo, latencia, tokens y resultado técnico sin almacenar datos sensibles por defecto.

NO OLVIDEMOS Un modelo local puede inventar información, revelar contenido presente en su contexto o producir una respuesta insegura. La ubicación del modelo no sustituye la validación de salida, el diseño responsable ni los controles de aplicación.

Alta disponibilidad y escalado

Aumentar réplicas de un Deployment no siempre es suficiente. Cada réplica puede requerir una GPU completa y su propia carga de pesos. La estrategia dependerá del modelo, la memoria y el runtime.

  • Una réplica por GPU y balanceo entre réplicas cuando cada modelo cabe en una GPU.
  • Tensor parallel o pipeline parallel cuando un modelo necesita varias GPU, validando topología y rendimiento.
  • Replicas separadas por modelo para evitar que modelos con perfiles distintos compitan por memoria.
  • Escalado basado en cola o concurrencia, no solo en CPU.
  • PodDisruptionBudget, anti-affinity y pruebas reales de reinicio y mantenimiento.
  • Caché precargada o repositorio local para reducir el tiempo de recuperación.

Observabilidad y pruebas de capacidad

MétricaQué nos diceSeñal de alerta
TTFTTiempo hasta el primer tokenCola, prompt largo o modelo saturado.
Tokens/sVelocidad de generaciónCaídas al aumentar concurrencia.
ConcurrenciaSesiones atendidasErrores, timeouts o esperas largas.
Memoria GPUMargen para pesos y KV cacheOOM, evictions o reinicios.
Utilización GPUAprovechamiento del aceleradorMuy baja con cola, o sostenida al 100 %.
Latencia totalExperiencia extremo a extremoGateway, red, retrieval o inferencia.
Errores HTTPSalud del servicio429, 5xx y timeouts crecientes.
Tamaño de promptPresión de contextoUsuarios enviando documentos completos sin control.

Las pruebas se deben utilizar prompts y contextos representativos. Una pregunta corta desde curl no predice cómo funcionará un asistente RAG con documentos extensos y varios usuarios simultáneos.

Operación desconectada y soberanía

Si el entorno debe operar con conectividad limitada, prepararemos un flujo de suministro controlado: imágenes copiadas a un registro local, pesos descargados y validados previamente, dependencias congeladas, certificados disponibles y documentación para renovar o importar artefactos. También debemos distinguir entre ejecutar la carga sin Internet y administrar toda la plataforma de forma completamente desconectada; no son el mismo requisito y pueden implicar capacidades, licencias y procedimientos diferentes.

Errores comunes y troubleshooting

SíntomaCausa probableComprobación
Pod PendingNo hay GPU disponible o selector incorrectokubectl describe pod y capacidad del nodo.
CrashLoopBackOffModelo incompatible, argumentos erróneos u OOMLogs del contenedor y eventos del pod.
CUDA no disponibleDriver o device plug-in no operativoDaemonSet, runtime y recurso nvidia.com/gpu.
Descarga interminableToken, licencia, DNS o salida bloqueadaSecret, acceso al repositorio y proxy.
Readiness no pasaCarga lenta o ruta de salud incorrectaLogs, /health e initialDelay/failureThreshold.
OOM con pocas peticionesContexto o KV cache sobredimensionadosReducir max-model-len y medir concurrencia.
Respuesta lentaModelo grande, almacenamiento lento o colaTTFT, tokens/s, GPU y latencia de caché.
Servicio inaccesibleDNS, ingress, firewall o Service mal definidoEndpoints, NetworkPolicy y ruta completa.

¿Cuándo no elegir esta arquitectura?

  • Si nuestra demanda es muy variable y no justifica mantener GPU propia.
  • Si necesitamos modelos propietarios disponibles únicamente como servicio cloud.
  • Si el equipo no puede operar Kubernetes, GPU, seguridad y observabilidad de forma sostenida.
  • Si el objetivo es una prueba rápida y una VM aislada cubre el caso.
  • Si no existe un requisito real de latencia, soberanía o integración local que compense la complejidad.

La decisión no es local contra cloud como si fueran opciones excluyentes. Muchas organizaciones terminarán con un enfoque híbrido: modelos locales para información sensible o baja latencia y servicios cloud para capacidades específicas, picos de demanda o modelos que no pueden alojar por sí mismas.

Conclusión

En el artículo anterior demostramos que un Dell Pro Max GB10 se puede convertir en un laboratorio de inferencia muy capaz con vLLM. Con Azure Local damos el siguiente paso: transformamos ese patrón técnico en una plataforma que puede integrarse con la infraestructura, la red, el almacenamiento y el gobierno de una organización.

La pieza clave es separar responsabilidades. Azure Local aporta la base híbrida; AKS habilitado por Azure Arc organiza la ejecución; la GPU acelera la inferencia; vLLM sirve el modelo; y la capa de acceso protege y gobierna el consumo. Cuando cada capa tiene límites y propietarios claros, la IA local deja de ser un experimento conectado a una GPU y empieza a comportarse como un servicio de plataforma.

Mi recomendación es avanzar por etapas: primero un modelo y un caso de uso medible, después seguridad y observabilidad, y finalmente alta disponibilidad y automatización. Y, como siempre, probadlo primero en laboratorio antes de llevarlo a producción.

Saludos, nos vemos en el próximo artículo.

por David Rivera

Me llamo David Rivera, con más de 30 años de experiencia en el área Tecnología de la información, los últimos 15 años dedicados a la arquitectura de soluciones de Microsoft, preventas y gerencia de proyectos.