vLLM Kubernetes: Guía Completa de Despliegue
Tabla de contenidos
Parte 2 de 6. En la Parte 1 cubrimos la arquitectura y los prerrequisitos. Aquí desplegamos vLLM en Kubernetes paso a paso. Continúa en Parte 3: Paralelismo de Tensores y Cuantización.
Paso a paso: Ejecutar vLLM en Kubernetes
Estos seis pasos numerados te llevan de un setup de desarrollo con una GPU a un servidor de inferencia multi-GPU completo en Kubernetes.
Paso 1: Seleccionar la imagen de contenedor de vLLM
Siempre ancla a una versión específica. latest no tiene lugar en vLLM production.
docker pull vllm/vllm-openai:v0.8.4Para despliegues con hardening de seguridad:
FROM vllm/vllm-openai:v0.8.4USER rootRUN pip install --no-cache-dir transformers==4.48.0 accelerate==1.3.0USER vllmPaso 2: Crear el manifiesto de Deployment con recursos GPU
Este despliegue de una sola GPU es adecuado para modelos como Mistral 7B o Llama 3 8B:
apiVersion: apps/v1kind: Deploymentmetadata: name: vllm-inference namespace: llm-serving labels: app: vllm model: llama-3-8bspec: replicas: 1 selector: matchLabels: app: vllm template: metadata: labels: app: vllm annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" prometheus.io/path: "/metrics" spec: nodeSelector: nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: vllm image: vllm/vllm-openai:v0.8.4 command: - python - -m - vllm.entrypoints.openai.api_server args: - --model - meta-llama/Meta-Llama-3-8B-Instruct - --dtype - bfloat16 - --max-model-len - "8192" - --gpu-memory-utilization - "0.9" - --max-num-seqs - "256" - --port - "8000" ports: - containerPort: 8000 name: http resources: limits: nvidia.com/gpu: "1" memory: "48Gi" cpu: "8" requests: nvidia.com/gpu: "1" memory: "32Gi" cpu: "4" env: - name: HF_HOME value: "/models" - name: VLLM_LOGGING_LEVEL value: "INFO" volumeMounts: - name: model-cache mountPath: /models livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 60 periodSeconds: 10 volumes: - name: model-cache persistentVolumeClaim: claimName: vllm-model-pvcBanderas clave explicadas:
| Bandera | Valor | Propósito |
|---|---|---|
--dtype bfloat16 | bfloat16 | Precisión y memoria balanceadas. Usa float16 para GPUs antiguas sin soporte bf16. |
--max-model-len 8192 | 8192 | Límite estricto en la longitud total de secuencia (input + output). |
--gpu-memory-utilization 0.9 | 0.9 | Reserva 90% de la VRAM GPU para vLLM. Deja espacio para el scratch space de CUDA. |
--max-num-seqs 256 | 256 | Secuencias concurrentes máximas. Este es tu techo de batch size. |
Aplica el manifiesto:
kubectl create namespace llm-servingkubectl apply -f vllm-deployment.yamlPaso 3: Configurar multi-GPU con tensor parallelism
Definición de entidad: Tensor parallelism: Una técnica que divide capas individuales de transformers entre múltiples GPUs. Cada GPU procesa un shard de las capas de atención y MLP mientras NCCL all-reduce sincroniza las activaciones.
Cuando tu modelo excede la memoria de una sola GPU, agrega --tensor-parallel-size. Este valor debe coincidir con nvidia.com/gpu en resources.limits:
apiVersion: apps/v1kind: Deploymentmetadata: name: vllm-inference-70b namespace: llm-servingspec: replicas: 1 selector: matchLabels: app: vllm-70b template: metadata: labels: app: vllm-70b spec: nodeSelector: node-type: gpu containers: - name: vllm image: vllm/vllm-openai:v0.8.4 command: - python - -m - vllm.entrypoints.openai.api_server args: - --model - meta-llama/Meta-Llama-3-70B-Instruct - --tensor-parallel-size - "4" - --dtype - bfloat16 - --max-model-len - "32768" - --gpu-memory-utilization - "0.92" - --max-num-seqs - "128" - --port - "8000" ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: "4" memory: "384Gi" cpu: "32" requests: nvidia.com/gpu: "4" memory: "256Gi" cpu: "16" env: - name: NCCL_IB_DISABLE value: "1" - name: HF_HOME value: "/models"Crítico:
--tensor-parallel-sizedebe ser exactamente igual anvidia.com/gpuenresources.limits. Cualquier discrepancia produce errores de inicialización de NCCL que te harán perder horas de debugging.
Paso 4: Montar modelos desde almacenamiento local o HuggingFace
Opción A: HuggingFace Hub (solo desarrollo)
kubectl create secret generic hf-token \ --from-literal=token=$HF_TOKEN -n llm-servingOpción B: PV local (recomendado para producción)
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: vllm-model-pvc namespace: llm-servingspec: accessModes: [ReadWriteOnce] resources: requests: storage: 500Gi storageClassName: fast-local-nvmePre-descarga vía un Kubernetes Job:
kubectl apply -f - <<EOFapiVersion: batch/v1kind: Jobmetadata: name: model-downloader namespace: llm-servingspec: template: spec: containers: - name: dl image: vllm/vllm-openai:v0.8.4 command: [huggingface-cli, download, meta-llama/Meta-Llama-3-8B-Instruct, --local-dir, /models/llama-3-8b] env: - name: HF_TOKEN valueFrom: secretKeyRef: name: hf-token key: token volumeMounts: [{name: cache, mountPath: /models}] volumes: [{name: cache, persistentVolumeClaim: {claimName: vllm-model-pvc}}] restartPolicy: OnFailureEOFPaso 5: Exponer el servidor de API compatible con OpenAI
apiVersion: v1kind: Servicemetadata: name: vllm-service namespace: llm-servingspec: selector: app: vllm ports: - port: 8000 targetPort: 8000---apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: vllm-ingress namespace: llm-serving annotations: nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" cert-manager.io/cluster-issuer: "letsencrypt-prod"spec: ingressClassName: nginx tls: - hosts: [llm-api.yourdomain.com] secretName: vllm-tls rules: - host: llm-api.yourdomain.com http: paths: - path: / pathType: Prefix backend: service: name: vllm-service port: number: 8000Nota: Configura los timeouts de proxy a 3600 segundos. Los timeouts por defecto de NGINX son de 60s, lo que mata requests de LLM de larga duración a mitad de generación.
Prueba la conectividad:
kubectl port-forward svc/vllm-service 8000:8000 -n llm-serving
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model": "meta-llama/Meta-Llama-3-8B-Instruct", "messages": [{"role": "user", "content": "Hello"}], "max_tokens": 50}'Paso 6: Configurar Horizontal Pod Autoscaler con métricas personalizadas
HPA basado en CPU no sirve para inferencia de LLM. La utilización de GPU no tiene correlación con las métricas de CPU. Debes escalar en las métricas Prometheus de vLLM.
Instala Prometheus Adapter:
helm repo add prometheus-community https://prometheus-community.github.io/helm-chartshelm upgrade --install prometheus-adapter prometheus-community/prometheus-adapter \ --namespace monitoring --set prometheus.url=http://prometheus.monitoring.svcConfigura una regla de métrica personalizada:
apiVersion: v1kind: ConfigMapmetadata: name: prometheus-adapter namespace: monitoringdata: config.yaml: | rules: - seriesQuery: 'vllm_num_requests_running' resources: overrides: namespace: {resource: namespace} pod: {resource: pod} metricsQuery: 'avg(vllm_num_requests_running{<<.LabelMatchers>>}) by (<<.GroupBy>>)'Crea el HPA:
apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: vllm-hpa namespace: llm-servingspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: vllm-inference minReplicas: 1 maxReplicas: 5 metrics: - type: Pods pods: metric: name: vllm_num_requests_running target: type: AverageValue averageValue: "50" behavior: scaleUp: stabilizationWindowSeconds: 60 policies: [{type: Pods, value: 1, periodSeconds: 120}] scaleDown: stabilizationWindowSeconds: 300 policies: [{type: Pods, value: 1, periodSeconds: 300}]Tip de producción: La estabilización de scale-down es de 5 minutos por una buena razón: iniciar en frío un modelo de 70B toma 3–5 minutos. Un scale-down agresivo destruirá tu latencia durante picos de tráfico.
Continúa en Parte 3: Paralelismo de Tensores y Cuantización para la configuración de producción.