View a markdown version of this page

Identificación de los umbrales de métricas de escalado para la inferencia de IA - Amazon EKS

Ayude a mejorar esta página

Para contribuir a esta guía del usuario, elija el enlace Edit this page on GitHub que se encuentra en el panel derecho de cada página.

Identificación de los umbrales de métricas de escalado para la inferencia de IA

sugerencia

Regístrese en los próximos talleres de IA/ML de Amazon EKS.

En esta sección, se muestra cómo hacer una prueba de carga de un servidor de inferencia de vLLM en Amazon EKS para identificar cuándo se satura. Utilice los resultados de la prueba de carga para configurar los umbrales de profundidad de la cola y latencia para el escalado automático horizontal en la sección Escalado automático de inferencia de IA con HPA y KEDA siguiente.

En este tutorial se utilizan las siguientes herramientas:

  • k6 (Grafana k6) es una herramienta de prueba de carga de código abierto que se ejecuta como la imagen de contenedor grafana/k6 y envía solicitudes de inferencia a la réplica con frecuencias de solicitud controladas.

  • Modelo Ministral-3-8B-Instruct-2512 en una instancia g6e.4xlarge. Puede aplicar la misma metodología a cualquier modelo de código abierto y tipo de GPU.

Requisitos previos

Esta sección se basa en dos secciones anteriores de la documentación. Complételas antes de comenzar:

Confirme cada uno de los siguientes elementos antes de comenzar y complete antes la sección vinculada si alguna comprobación falla.

Si abrió un terminal nuevo, establezca el nombre y la región del clúster que utilizó anteriormente:

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

Capture el nombre del bucket del modelo de Amazon S3 que se creó en los pasos de Configuración del clúster. El generador de carga pasa este valor a vLLM.

MODEL_BUCKET=$(aws s3api list-buckets \ --query "Buckets[?starts_with(Name, '${CLUSTER_NAME}-models-')].Name | [0]" \ --output text) echo "Model bucket: ${MODEL_BUCKET}"

Confirmación de que la pila de supervisión esté en ejecución

La pila kube-prometheus-stack de la configuración de Supervisión se ejecuta en el espacio de nombres monitoring.

kubectl get pods -n monitoring

El estado de los pods de Prometheus y Grafana debería ser Running:

NAME READY STATUS RESTARTS AGE kube-prometheus-stack-grafana-6f9c8b7d5c-2xk9p 3/3 Running 0 3h kube-prometheus-stack-operator-7b8c9d6f4-q4m7n 1/1 Running 0 3h prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 3h

Si faltan, complete la configuración de Supervisión antes de continuar.

Confirmación de que el modelo de vLLM se esté ejecutando

La implementación vllm-inference-app y el servicio vllm-inference-svc de Modelos de carga y servicio se ejecutan en el espacio de nombres default.

kubectl get deployment vllm-inference-app

La implementación debería mostrar 1/1 como lista:

NAME READY UP-TO-DATE AVAILABLE AGE vllm-inference-app 1/1 1 1 3h

Si falta la implementación o no está lista, complete Modelos de carga y servicio antes de continuar.

Acceso a Grafana

Observará la prueba de carga en el panel de control de vLLM precargado, así que asegúrese de que el equilibrador de carga de Grafana que configuró en la sección Acceso a Grafana sea accesible. Imprima la URL:

echo "http://$(kubectl get ingress kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"

Abra la URL en el navegador e inicie sesión con el nombre de usuario admin y la contraseña mediante el siguiente comando:

kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo

Confirmación de que el ServiceMonitor de vLLM exista

El ServiceMonitor vllm-inference-app, creado en el paso Supervisión de vLLM, se ejecuta en el espacio de nombres default e indica a Prometheus dónde extraer las métricas de vLLM.

kubectl get servicemonitor vllm-inference-app

Resultado previsto:

NAME AGE vllm-inference-app 3h

Si falta, complete el paso Supervisión de vLLM.

Paso 1: calentamiento de la GPU

Aunque el modelo esté cargado en la memoria de la GPU y la réplica muestre que está lista, las primeras solicitudes tardan más que las posteriores, ya que vLLM y la GPU terminan de inicializarse en las primeras inferencias. vLLM captura gráficos CUDA, compila y ajusta automáticamente los kernels de la GPU y asigna los grupos de memoria caché KV, mientras que la GPU aumenta la velocidad del reloj desde el estado inactivo hasta su intervalo de rendimiento máximo.

Se trata de costos únicos, por lo que medir los umbrales antes del calentamiento registra el trabajo de inicio en lugar del servicio en estado estable. El siguiente calentamiento envía 100 solicitudes secuenciales para completar esta inicialización, por lo que la siguiente prueba de carga mide la capacidad en estado estable.

Primero, cree el ConfigMap que contiene el script de calentamiento (warmup.js). El script lee el modelo de destino de la variable de entorno MODEL, que el trabajo establece a partir del valor MODEL_BUCKET que capturó anteriormente:

ejemplo ConfigMap de preparación de k6
cat << 'EOF' | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: vllm-warmup-script namespace: default data: warmup.js: | import http from 'k6/http'; export default function () { const payload = JSON.stringify({ model: __ENV.MODEL, prompt: 'Write a brief technical explanation about cloud computing concepts.', max_tokens: 100, temperature: 0.7, }); const params = { headers: { 'Content-Type': 'application/json' }, timeout: '240s' }; http.post('http://vllm-inference-svc:8000/v1/completions', payload, params); } EOF

A continuación, ejecute el trabajo de calentamiento. Un solo cliente envía 100 solicitudes secuenciales:

ejemplo Trabajo de calentamiento
cat << EOF | kubectl apply -f - apiVersion: batch/v1 kind: Job metadata: name: vllm-warmup namespace: default spec: backoffLimit: 0 template: spec: restartPolicy: Never containers: - name: k6 image: grafana/k6:latest env: - name: MODEL value: "s3://${MODEL_BUCKET}/Ministral-3-8B-Instruct-2512/" args: ["run", "--vus", "1", "--iterations", "100", "/scripts/warmup.js"] volumeMounts: - name: script mountPath: /scripts volumes: - name: script configMap: name: vllm-warmup-script EOF

Espere a que termine el calentamiento (tarda unos minutos):

kubectl wait --for=condition=complete job/vllm-warmup --timeout=600s

Abra Grafana (abra el nombre de host del equilibrador de carga; consulte Acceso a Grafana para obtener más información) y vaya a Paneles > Supervisión de GPU > Pruebas de rendimiento: análisis de carga de vLLM. El panel Tasa de solicitudes de vLLM muestra la ampliación del tráfico del calentamiento. El panel Promedio de latencia de vLLM muestra que la primera solicitud es más lenta, que es el costo de calentamiento de la GPU único.

Panel de control de vLLM que muestra el efecto del calentamiento de la GPU en la latencia

Una breve ampliación del tráfico de calentamiento, con una latencia de la primera solicitud que pasa de unos 2,6 segundos a una línea base estable a medida que la GPU se calienta.

A continuación, elimine el trabajo de calentamiento:

kubectl delete job vllm-warmup

Paso 2: envío de carga con tasas de solicitudes crecientes

En este paso, ejecuta una prueba de carga que envía solicitudes a la única réplica de vLLM con tasas crecientes: 10, 20, 30, 40, 50, 60 y 70 solicitudes por segundo. Esto simula el aumento de la demanda de los usuarios. La prueba utiliza k6 en un modelo de tasa de llegada constante, donde la variable de entorno TARGET_RPS establece la tasa de solicitud objetivo. Cada tasa se ejecuta durante 60 segundos para permitir que las métricas se estabilicen, seguida de una pausa de 30 segundos antes del siguiente aumento de tasa.

El objetivo de esta prueba es identificar el punto de saturación, que es la tasa de solicitudes a la que una sola réplica ya no puede seguir el ritmo de las solicitudes entrantes. Supervise la profundidad de la cola (vllm:num_requests_waiting) y la latencia de extremo a extremo (vllm:e2e_request_latency_seconds). El aumento de la profundidad de la cola y la latencia indican que la réplica se acerca a la saturación.

Primero, cree el ConfigMap que contiene el script de prueba de carga de k6 (script.js). El script lee el modelo de destino de la variable de entorno MODEL, que cada pod establece a partir del valor MODEL_BUCKET que capturó anteriormente:

ejemplo ConfigMap de prueba de carga de k6
cat << 'EOF' | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: vllm-loadtest-script namespace: default data: script.js: | import http from 'k6/http'; // Dictionary of 10-letter words for variation (prevents prompt caching) const words = [ 'revolution', 'technology', 'javascript', 'kubernetes', 'basketball', 'strawberry', 'watermelon', 'blackberry', 'throughout', 'background', 'playground', 'understand', 'everything', 'protection', 'complexity' ]; export const options = { noConnectionReuse: true, scenarios: { constant_request_rate: { executor: 'constant-arrival-rate', rate: __ENV.TARGET_RPS || 10, // Overridden by TARGET_RPS env var timeUnit: '1s', duration: __ENV.DURATION || '60s', // Overridden by DURATION env var preAllocatedVUs: 50, maxVUs: 500, }, }, }; export default function () { const randomWord = words[Math.floor(Math.random() * words.length)]; const payload = JSON.stringify({ model: __ENV.MODEL, prompt: `Starting with the word "${randomWord}", write a brief technical explanation about cloud computing concepts. Cover containerization, orchestration, and scalability in about 80 words.`, max_tokens: 100, temperature: 0.7, }); const params = { headers: { 'Content-Type': 'application/json' }, timeout: '240s' }; http.post('http://vllm-inference-svc:8000/v1/completions', payload, params); } EOF

A continuación, ejecute la prueba de carga a cada tasa de solicitud. El siguiente bucle envía un trabajo independiente para cada tasa:

ejemplo Bucle de prueba de carga
for RPS in 10 20 30 40 50 60 70; do echo "=== Testing at ${RPS} req/s ===" cat << EOF | kubectl apply -f - apiVersion: batch/v1 kind: Job metadata: name: vllm-loadtest-${RPS} namespace: default labels: app: vllm-loadtest spec: backoffLimit: 0 template: metadata: labels: app: vllm-loadtest spec: restartPolicy: Never containers: - name: k6 image: grafana/k6:latest env: - name: K6_PROMETHEUS_RW_SERVER_URL value: "http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090/api/v1/write" - name: MODEL value: "s3://${MODEL_BUCKET}/Ministral-3-8B-Instruct-2512/" - name: TARGET_RPS value: "${RPS}" - name: DURATION value: "60s" args: ["run", "-o", "experimental-prometheus-rw", "/scripts/script.js"] volumeMounts: - name: script mountPath: /scripts volumes: - name: script configMap: name: vllm-loadtest-script EOF kubectl wait --for=condition=complete job/vllm-loadtest-${RPS} --timeout=180s sleep 30 done

Observe las métricas en Grafana a medida que se ejecuta la prueba (consulte el paso 3). Cada tasa crea su propio trabajo (de vllm-loadtest-10 a vllm-loadtest-70), para que pueda inspeccionar cualquier ejecución con kubectl get jobs. Cuando haya terminado, elimine todos los trabajos de prueba de carga:

kubectl delete jobs -l app=vllm-loadtest

Paso 3: medición de las métricas bajo carga

Mientras se ejecuta la carga, observe las dos métricas que impulsan el escalado automático:

  • Profundidad de la cola (vllm:num_requests_waiting): cuántas solicitudes están esperando el procesamiento.

  • Latencia de extremo a extremo p95 (vllm:e2e_request_latency_seconds): tiempo de respuesta del percentil 95.

Visualización de métricas en Grafana

Abra Grafana (abra el nombre de host del equilibrador de carga e inicie sesión como admin; consulte Acceso a Grafana para obtener más información) y vaya a Paneles > Supervisión de GPU > Pruebas de rendimiento: análisis de carga de vLLM. Este panel lee datos de Amazon Managed Service para Prometheus, por lo que los paneles pueden mostrar un retraso de hasta un minuto respecto al estado en vivo, ya que las métricas se escriben de forma remota en lotes.

Panel Pruebas de rendimiento: análisis de carga de vLLM durante la prueba de carga

Seis paneles muestran el uso de CPU y memoria del pod de k6 por cada trabajo de prueba de carga, el aumento de la tasa de solicitudes de vLLM en los pasos, el aumento de la latencia media una vez que la réplica se satura, los tokens generados por solicitud y el aumento de la profundidad de la cola desde cero en el momento de la saturación.

En función de los resultados de las pruebas de carga, la profundidad de la cola se mantuvo cerca de cero con 50 solicitudes simultáneas. A continuación, aumentó considerablemente con 60 solicitudes simultáneas, hasta alcanzar un máximo de entre aproximadamente 150 y 350 solicitudes en cola. Al mismo tiempo, la latencia de extremo a extremo p95 pasó de una línea de base estable de unos 2,5 segundos a entre 6 y 10 segundos. Este comportamiento indica el inicio de una sobrecarga sostenida y sugiere que el escalado automático debe comenzar antes de que la profundidad de la cola alcance niveles que hagan que la latencia se acerque a los 10 segundos. Un punto de partida práctico es activar el escalado cuando la profundidad de la cola supere las 25 solicitudes durante un plazo de entre 30 y 60 segundos. Como señal secundaria, también puede activar el escalado cuando la latencia de extremo a extremo p95 supere los 5 segundos en el mismo intervalo. Esto ayuda a tener en cuenta los patrones de carga de trabajo en los que la latencia aumenta antes de que aumente la profundidad de la cola.

Paso 4: elección de las métricas y los umbrales en los que escalar

Con las métricas observadas en el punto de saturación, determinará los umbrales utilizados para configurar el escalado automático en la siguiente sección.

  • Profundidad de la cola (vllm:num_requests_waiting): número de solicitudes en espera de procesamiento una vez que se sature una réplica. Establezca el umbral por encima del punto en el que la cola se mantiene positiva por primera vez, de modo que los picos transitorios no activen el escalado. En la prueba de carga de esta sección, la profundidad de la cola se mantuvo cerca de cero con 50 solicitudes simultáneas y aumentó considerablemente hasta alcanzar aproximadamente entre 150 y 350 solicitudes en cola con 60 solicitudes simultáneas. Un punto de partida práctico es activar el escalado cuando la profundidad de la cola supere las 25 solicitudes durante un plazo de entre 30 y 60 segundos.

  • Latencia (vllm:e2e_request_latency_seconds): el tiempo de respuesta de extremo a extremo cuando una réplica se acerca a su límite de capacidad. Establezca el umbral por debajo del SLO de la latencia para que el escalador automático escale horizontalmente antes de que la latencia visible para el usuario se vuelva inaceptable. Para la prueba de carga de esta sección, la latencia de extremo a extremo p95 se mantuvo estable en aproximadamente 2,5 segundos y aumentó a entre 6 y 10 segundos en el momento de la saturación. Como señal secundaria, un punto de partida práctico es activar el escalado cuando la latencia de extremo a extremo p95 supere los 5 segundos durante un plazo de entre 30 y 60 segundos. Esto ayuda a tener en cuenta los patrones de carga de trabajo en los que la latencia aumenta antes de que aumente la profundidad de la cola o cuando la latencia aumenta sin cola.

  • Reducción vertical: tasa a la que el escalador automático elimina las réplicas cuando la demanda disminuye. Reduzca verticalmente más despacio de lo que escala verticalmente. Para este ejemplo, reduzca verticalmente solo cuando la profundidad de la cola sea 0 y la latencia de extremo a extremo p95 sea inferior a 3 segundos durante 5 minutos. Esto evita que el escalador automático elimine las réplicas durante breves pausas en el tráfico y ayuda a evitar la oscilación.

Estos valores proporcionan un punto de partida para configurar el escalado automático en la siguiente sección. Repita este proceso para su propio modelo, tipo de GPU y patrones de solicitud a fin de determinar los umbrales adecuados para la implementación.