Ajudar a melhorar esta página
Para contribuir com este guia de usuário, escolha o link Editar esta página no GitHub, disponível no painel direito de cada página.
Identificar limites de métricas de escalabilidade para inferência de IA
dica
Registre-se
Esta seção mostra como testar a carga de um servidor de inferência do vLLM no Amazon EKS para identificar quando ele fica saturado. Use os resultados do teste de carga para configurar os limites de profundidade de fila e latência para escalabilidade automática horizontal na seção Escalabilidade automática de inferência de IA com HPA e KEDA subsequente.
A demonstração usa as seguintes ferramentas:
-
k6
(Grafana k6) é uma ferramenta de teste de carga de código aberto, executada como imagem de contêiner grafana/k6, que envia requisições de inferência para a réplica com taxas de requisição controladas. -
Modelo Ministral-3-8B-Instruct-2512
em uma instância g6e.4xlarge. Você pode aplicar a mesma metodologia a qualquer modelo de código aberto e tipo de GPU.
Pré-requisitos
Esta seção baseia-se em duas seções anteriores da documentação. Conclua as duas antes de começar:
-
Configuração do cluster fornece um cluster do EKS com um NodePool de GPU, a pilha de monitoramento kube-prometheus-stack (Prometheus e Grafana) e um bucket do Amazon S3 para pesos do modelo.
-
Carregar e disponibilizar modelos implanta o servidor de inferência do vLLM e cria o ServiceMonitor vllm-inference-app.
Confirme cada item abaixo antes de começar e conclua primeiro a seção vinculada se a verificação falhar.
Se você abriu um novo terminal, defina a região e o nome do cluster usados anteriormente:
export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2
Capture o nome do bucket do modelo do Amazon S3 que foi criado nas etapas de Configuração do cluster. O gerador de carga passa esse valor para o vLLM.
MODEL_BUCKET=$(aws s3api list-buckets \ --query "Buckets[?starts_with(Name, '${CLUSTER_NAME}-models-')].Name | [0]" \ --output text) echo "Model bucket: ${MODEL_BUCKET}"
Confirmar se a pilha de monitoramento está em execução
O kube-prometheus-stack da configuração do Monitoramento é executado no namespace monitoring.
kubectl get pods -n monitoring
Os pods do Prometheus e do Grafana devem 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
Se eles estiverem ausentes, conclua a configuração do Monitoramento antes de continuar.
Confirmar se o modelo do vLLM está em execução
A implantação vllm-inference-app e o serviço vllm-inference-svc de Carregar e disponibilizar modelos são executados no namespace default.
kubectl get deployment vllm-inference-app
A implantação deve indicar o status de 1/1 pronta:
NAME READY UP-TO-DATE AVAILABLE AGE vllm-inference-app 1/1 1 1 3h
Se a implantação estiver ausente ou não estiver pronta, conclua a etapa Carregar e disponibilizar modelos antes de continuar.
Acessar o Grafana
Você monitora o teste de carga no painel pré-carregado do vLLM, portanto, certifique-se de que o balanceador de carga do Grafana configurado na seção Acessar Grafana esteja acessível. Exiba seu URL:
echo "http://$(kubectl get ingress kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
Abra o URL no seu navegador e faça login com o nome de usuário admin e a senha do seguinte comando:
kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
Confirmar se o ServiceMonitor do vLLM existe
O ServiceMonitor vllm-inference-app, criado na etapa Monitorar o vLLM, é executado no namespace default e informa ao Prometheus onde extrair as métricas do vLLM.
kubectl get servicemonitor vllm-inference-app
Saída esperada:
NAME AGE vllm-inference-app 3h
Se não houver, conclua a etapa Monitorar o vLLM.
Etapa 1: aquecer a GPU
Embora o modelo esteja carregado na memória da GPU e a réplica informe Pronto, as primeiras requisições demoram mais do que as posteriores porque o vLLM e a GPU terminam de ser inicializados nas primeiras inferências. O vLLM captura grafos CUDA, compila e ajusta automaticamente os kernels da GPU e aloca seus grupos de memória de cache KV, enquanto a GPU eleva suas frequências de clock do modo ocioso para a faixa de aceleração máxima.
Estes são custos únicos, portanto, medir os limites antes do aquecimento registra o trabalho de inicialização em vez do serviço em estado estável. O aquecimento a seguir envia cem requisições sequenciais para concluir essa inicialização, portanto, o teste de carga a seguir mede a capacidade em estado estável.
Primeiro, crie o ConfigMap que contém o script de aquecimento (warmup.js). O script lê o modelo de destino da variável de ambiente MODEL, que o Job define com base no valor MODEL_BUCKET que você capturou anteriormente:
exemplo ConfigMap de aquecimento 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
Em seguida, execute o Job de aquecimento. Um único cliente envia cem requisições sequenciais:
exemplo Job de aquecimento
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
Aguarde o término do aquecimento (leva alguns minutos):
kubectl wait --for=condition=complete job/vllm-warmup --timeout=600s
Abra o Grafana (abra o nome do host do balanceador de carga; consulte Acessar o Grafana para obter detalhes) e navegue até Painéis > Monitoramento de GPU > Teste de desempenho - Análise de carga do vLLM. O painel Taxa de requisição do vLLM mostra o pico do tráfego de aquecimento. O painel Latência média do vLLM mostra que a primeira requisição é mais lenta, que é o custo único de aquecimento da GPU.
Painel do vLLM mostrando o efeito de aquecimento da GPU na latência
Em seguida, exclua o Job de aquecimento:
kubectl delete job vllm-warmup
Etapa 2: enviar carga com taxas crescentes de requisição
Nesta etapa, você executa um teste de carga que envia requisições para a única réplica do vLLM em taxas crescentes: 10, 20, 30, 40, 50, 60 e 70 requisições por segundo. Isso simula o aumento da demanda dos usuários. O teste usa k6 em um modelo constant-arrival-rate, em que a variável de ambiente TARGET_RPS define a taxa de requisições alvo. Cada taxa dura 60 segundos para permitir que as métricas se estabilizem, seguida por uma pausa de 30 segundos antes do próximo aumento da taxa.
O objetivo deste teste é identificar o ponto de saturação, que é a taxa de requisição na qual uma única réplica não consegue mais acompanhar as requisições recebidas. Você monitora a profundidade da fila (vllm:num_requests_waiting) e a latência de ponta a ponta (vllm:e2e_request_latency_seconds). O aumento da profundidade e da latência da fila indica que a réplica está se aproximando da saturação.
Primeiro, crie o ConfigMap que contém o script k6 load-test (script.js). O script lê o modelo de destino da variável de ambiente MODEL, que cada pod define com base no valor MODEL_BUCKET que você capturou anteriormente:
exemplo ConfigMap de teste de carga 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
Em seguida, execute o teste de carga em cada taxa de requisição. O seguinte loop envia um Job separado para cada taxa:
exemplo Loop de teste 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
Monitore as métricas no Grafana à medida que o teste é executado (consulte a Etapa 3). Cada taxa cria seu próprio Job (vllm-loadtest-10 a vllm-loadtest-70) para que você possa inspecionar qualquer execução com kubectl get jobs. Quando terminar, exclua todos os Jobs de teste de carga:
kubectl delete jobs -l app=vllm-loadtest
Etapa 3: medir métricas sob carga
Enquanto a carga é executada, monitore as duas métricas que impulsionam a escalabilidade automática:
-
Profundidade da fila (
vllm:num_requests_waiting): quantas requisições estão aguardando para serem processadas. -
Latência p95 de ponta a ponta (
vllm:e2e_request_latency_seconds): o tempo de resposta do 95.º percentil.
Visualizar as métricas no Grafana
Abra o Grafana (abra o nome do host do balanceador de carga e faça login como admin; consulte Acessar o Grafana para obter detalhes) e navegue até Painéis > Monitoramento de GPU > Teste de desempenho - Análise de carga do vLLM. Esse painel lê dados do Amazon Managed Service for Prometheus, então os painéis podem atrasar o estado ativo em até um minuto porque as métricas são gravadas remotamente em lotes.
Teste de desempenho: painel de análise de carga do vLLM durante o teste de carga
Com base nos resultados do teste de carga, a profundidade da fila permaneceu próxima de zero em 50 requisições simultâneas. Em seguida, aumentou acentuadamente para 60 requisições simultâneas, atingindo um pico entre aproximadamente 150 e 350 requisições na fila. Ao mesmo tempo, a latência p95 de ponta a ponta aumentou de uma linha de base estável de cerca de 2,5 segundos para 6 a 10 segundos. Esse comportamento indica o início de uma sobrecarga sustentada e sugere que o ajuste de escala automático deve começar antes que a profundidade da fila atinja níveis que façam com que a latência se aproxime de dez segundos. Um ponto de partida prático é acionar a escalabilidade quando a profundidade da fila exceder 25 requisições por 30 a 60 segundos. Como um sinal secundário, você também pode acionar a escalabilidade quando a latência p95 de ponta a ponta exceder cinco segundos no mesmo intervalo. Isso ajuda a considerar os padrões das workloads em que a latência aumenta antes que a profundidade da fila aumente.
Etapa 4: escolher as métricas e os limites para escalar
Usando as métricas observadas no ponto de saturação, você determina os limites usados para configurar o ajuste de escala automático na próxima seção.
-
Profundidade da fila (
vllm:num_requests_waiting): o número de requisições esperando para serem processadas quando uma réplica ficar saturada. Defina o limite acima do ponto em que a fila primeiro permanece positiva, para que picos transitórios não acionem a escalabilidade. Para o teste de carga nesta seção, a profundidade da fila permaneceu próxima de zero em 50 requisições simultâneas e aumentou acentuadamente para aproximadamente 150 a 350 requisições em fila em 60 requisições simultâneas. Um ponto de partida prático é acionar a escalabilidade quando a profundidade da fila exceder 25 requisições por 30 a 60 segundos. -
Latência (
vllm:e2e_request_latency_seconds): o tempo de resposta de ponta a ponta quando uma réplica se aproxima do limite de capacidade. Defina o limite abaixo do SLO de latência para que o autoescalador aumente a escala horizontalmente antes que a latência visível ao usuário se torne inaceitável. Para o teste de carga nesta seção, a latência p95 de ponta a ponta permaneceu estável em aproximadamente 2,5 segundos e aumentou para 6 a 10 segundos na saturação. Como um sinal secundário, um ponto de partida prático é acionar a escalabilidade quando a latência p95 de ponta a ponta exceder 5 segundos por 30 a 60 segundos. Isso ajuda a considerar os padrões das workloads em que a latência aumenta antes que a profundidade da fila aumente ou quando a latência aumenta sem enfileiramento. -
Redução vertical da escala: a taxa na qual o autoescalador remove as réplicas após a queda da demanda. Reduza a escala verticalmente de forma mais lenta do que a aumente verticalmente. Neste exemplo, reduza a escala verticalmente somente quando a profundidade da fila for
0e a latência p95 de ponta a ponta estiver abaixo de três segundos por cinco minutos. Isso evita que o autoescalador remova réplicas durante breves pausas no tráfego e ajuda a evitar oscilações.
Esses valores fornecem um ponto de partida para configurar o ajuste de escala automático na próxima seção. Repita esse processo para seu próprio modelo, tipo de GPU e padrões de requisição para determinar os limites apropriados para sua implantação.