View a markdown version of this page

Carregar e servir modelos no Amazon EKS - Amazon EKS

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.

Carregar e servir modelos no Amazon EKS

dica

Registre-se para os próximos workshops do Amazon EKS AI/ML.

As etapas desta seção implantam um grande modelo de linguagem (LLM) no Amazon EKS, o servem com o vLLM e interagem com o endpoint de inferência.

A demonstração usa as seguintes ferramentas:

  • vLLM: um mecanismo de inferência de alto throughput otimizado para servir LLM e gerenciar memória de GPU.

  • Streamer de modelo Run:ai: transmite os pesos do modelo diretamente do Amazon S3 para a memória da GPU, reduzindo o tempo de carregamento de minutos para segundos.

  • Open WebUI: um frontend de chat auto-hospedado que se conecta à API compatível com OpenAI do vLLM.

Esta seção usa o modelo Ministral-3-8B-Instruct-2512, mas você pode implantar qualquer modelo de IA compatível com vLLM. Para ver uma lista dos modelos compatíveis, consulte Supported models na documentação do vLLM.

Importante

Selecione o cluster criado na seção Configurar cluster do Amazon EKS para workloads de IA/ML. Essas instruções passo a passo funcionam tanto para o Modo Automático do EKS quanto para o Karpenter autogerenciado.

Diagrama da arquitetura mostrando o fluxo de trabalho de inferência de LLM com vLLM no Amazon EKS

O diagrama da arquitetura mostra o fluxo completo:

  1. Os pesos do modelo são baixados do Hugging Face para o Amazon S3.

  2. O vLLM transmite o modelo diretamente do S3 para a memória da GPU usando o streamer de modelo Run:ai.

  3. Os usuários enviam as solicitações de inferência para o endpoint do vLLM.

Quando conclui essas etapas, você tem um endpoint de inferência do vLLM que pode ser usado para interagir com um modelo Ministral por meio de uma aplicação de frontend de chat.

Pré-requisitos

Conclua as etapas na seção Configuração de cluster.

Se você abriu um novo terminal, defina o nome e a região do cluster usados na seção Configuração de cluster via CLI:

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

Consulte o bucket de pesos do modelo que você criou na etapa Bucket do S3 de pesos do modelo:

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

Etapa 1: baixar o modelo do Hugging Face

Nesta etapa, você implanta um Job do Kubernetes que baixa o modelo do Hugging Face e o carrega no bucket do S3 que você criou na seção de pré-requisitos.

Para baixar o modelo, aplique o seguinte manifesto de Job:

cat << EOF | kubectl apply -f - apiVersion: batch/v1 kind: Job metadata: name: model-download namespace: default labels: guide: ai-eks-docs spec: backoffLimit: 10 activeDeadlineSeconds: 3600 ttlSecondsAfterFinished: 86400 template: spec: restartPolicy: Never serviceAccountName: model-storage-sa containers: - name: downloader image: python:3.11-slim command: ["/bin/bash", "-c"] args: - | set -e pip install -q huggingface_hub boto3 echo "Downloading Ministral-3-8B-Instruct-2512 from Hugging Face..." python3 -c "from huggingface_hub import snapshot_download; snapshot_download('mistralai/Ministral-3-8B-Instruct-2512', local_dir='/tmp/mistral', allow_patterns=['*.json', '*.txt', '*.md', 'consolidated.safetensors'], ignore_patterns=['model-*.safetensors', 'model.safetensors.index.json'])" echo "Uploading to S3 bucket: \${MODEL_BUCKET}" python3 << 'PYTHON' import boto3 import os from pathlib import Path s3 = boto3.client('s3') bucket = os.environ.get('MODEL_BUCKET') local_dir = Path("/tmp/mistral") for file_path in local_dir.rglob("*"): if file_path.is_file(): if '.cache' in file_path.parts: continue s3_key = f"Ministral-3-8B-Instruct-2512/{file_path.relative_to(local_dir)}" print(f"Uploading {file_path.name}...") s3.upload_file(str(file_path), bucket, s3_key) print("Upload complete!") PYTHON env: - name: MODEL_BUCKET value: "${MODEL_BUCKET}" - name: HF_HUB_DISABLE_XET value: "1" resources: requests: memory: "2Gi" cpu: "1" limits: memory: "4Gi" cpu: "2" EOF

Aguarde a conclusão do Job. Os pesos do modelo (consolidated.safetensors) são aproximadamente 10,4 GB, e essa etapa normalmente leva de 3 a 5 minutos.

kubectl wait --for=condition=complete job/model-download --timeout=600s

Saída esperada:

job.batch/model-download condition met

Verifique se os pesos do modelo foram carregados no S3:

aws s3 ls s3://$(kubectl get job model-download -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="MODEL_BUCKET")].value}')/Ministral-3-8B-Instruct-2512/ --recursive

Saída esperada:

2026-05-18 10:29:53 20311 Ministral-3-8B-Instruct-2512/README.md 2026-05-18 10:29:53 2361 Ministral-3-8B-Instruct-2512/SYSTEM_PROMPT.txt 2026-05-18 10:29:53 1903 Ministral-3-8B-Instruct-2512/config.json 2026-05-18 10:29:54 10420633176 Ministral-3-8B-Instruct-2512/consolidated.safetensors 2026-05-18 10:29:53 131 Ministral-3-8B-Instruct-2512/generation_config.json 2026-05-18 10:29:53 1185 Ministral-3-8B-Instruct-2512/params.json 2026-05-18 10:29:53 976 Ministral-3-8B-Instruct-2512/processor_config.json 2026-05-18 10:29:53 16753777 Ministral-3-8B-Instruct-2512/tekken.json 2026-05-18 10:29:53 17077402 Ministral-3-8B-Instruct-2512/tokenizer.json 2026-05-18 10:29:53 21168 Ministral-3-8B-Instruct-2512/tokenizer_config.json

O arquivo consolidades.safetensors contém os pesos do modelo (aproximadamente 10,4 GB). Os demais arquivos são os arquivos de configuração e os tokenizadores que o vLLM requer para servir o modelo.

Etapa 2: implantar o contêiner de inferência

Nesta seção, você implanta o vLLM como uma implantação do Kubernetes para servir o modelo que você carregou no Amazon S3.

Esta seção usa AWS Deep Learning Containers (DLCs), que são imagens do Docker pré-instaladas com estruturas de aprendizado profundo otimizadas para performance na infraestrutura da AWS. Os DLCs incluem correções de segurança, versões validadas da estrutura e configurações, otimizadas de drivers de GPU.

Essa implantação usa o seguinte DLC da AWS para o vLLM 0.21.0 compatível com SOCI:

public.ecr.aws/deep-learning-containers/vllm:0.21.0-gpu-py312-cu130-ubuntu22.04-ec2-v1.0-soci

A tag de imagem indica o vLLM 0.21.0 com suporte de GPU, Python 3.12, CUDA 13.0, Ubuntu 22.04, otimizado para workloads baseadas no EC2 e habilitado para SOCI para permitir uma inicialização mais rápida de contêineres.

Esse manifesto cria uma implantação que executa o vLLM em um nó de GPU e transmite o modelo diretamente do S3 para a memória da GPU usando o streamer de modelo Run:ai. O manifesto também cria um serviço de ClusteriP que expõe o endpoint do vLLM na porta 8.000 para acesso dentro do cluster.

Aplique o manifesto:

cat << EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: vllm-inference-app labels: guide: ai-eks-docs spec: replicas: 1 selector: matchLabels: app: vllm-inference-app template: metadata: labels: app: vllm-inference-app guide: ai-eks-docs spec: serviceAccountName: model-storage-sa tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule nodeSelector: karpenter.sh/nodepool: gpu-inf containers: - name: vllm-inference image: public.ecr.aws/deep-learning-containers/vllm:0.21.0-gpu-py312-cu130-ubuntu22.04-ec2-v1.0-soci ports: - containerPort: 8000 args: - "--model=s3://${MODEL_BUCKET}/Ministral-3-8B-Instruct-2512/" - "--host=0.0.0.0" - "--port=8000" - "--tensor-parallel-size=1" - "--gpu-memory-utilization=0.9" - "--max-model-len=8192" - "--max-num-seqs=1" - "--load-format=runai_streamer" - "--enforce-eager" - "--tokenizer_mode=mistral" - "--config_format=mistral" - "--enable-auto-tool-choice" - "--tool-call-parser=mistral" resources: limits: nvidia.com/gpu: 1 requests: memory: "40Gi" cpu: "8" --- apiVersion: v1 kind: Service metadata: name: vllm-inference-svc namespace: default labels: app: vllm-inference-app spec: selector: app: vllm-inference-app ports: - name: http port: 8000 targetPort: 8000 protocol: TCP EOF

Verifique se o pod vLLM está no estado Pronto:

kubectl get pod -l app=vllm-inference-app -w

Saída esperada:

NAME READY STATUS RESTARTS AGE vllm-inference-app-65df5fddc8-5kmjm 1/1 Running 0 86s

Pode levar cerca de 2 minutos para a imagem do contêiner ser extraída e o vLLM transmitir os pesos do modelo do S3 para a memória da GPU. Espere até que o pod tenha 1/1 na coluna PRONTO para continuar.

A combinação EKS, SOCI e streamer de modelo Run:ai permite a inicialização rápida do pod. Para verificar a hora da inicialização de cada estágio, visualize os eventos do pod:

kubectl describe pod -l app=vllm-inference-app | grep -A 20 "Events:"

Saída esperada:

Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 86s default-scheduler 0/2 nodes are available: 2 node(s) had untolerated taint(s). Normal Nominated 85s eks-auto-mode/compute Pod should schedule on: nodeclaim/gpu-inf-kqkq6 Normal Scheduled 55s default-scheduler Successfully assigned default/vllm-inference-app-d9d54586d-csmd7 to i-04f8792414384d2d3 Normal Pulling 52s kubelet Pulling image "public.ecr.aws/deep-learning-containers/vllm:0.21.0-gpu-py312-cu130-ubuntu22.04-ec2-v1.0-soci" Normal Pulled 4s kubelet Successfully pulled image "public.ecr.aws/deep-learning-containers/vllm:0.21.0-gpu-py312-cu130-ubuntu22.04-ec2-v1.0-soci" in 48.376s (48.376s including waiting). Image size: 8802823997 bytes. Normal Created 4s kubelet Created container vllm-inference Normal Started 4s kubelet Started container vllm-inference

Neste exemplo, o nó de GPU foi provisionado em 30 segundos e a imagem do contêiner de 8,8 GB foi extraída em aproximadamente 48 segundos usando o SOCI. A coleta rápida de imagens reduz os tempos de inicialização a frio dos grandes contêineres de inferência, o que permite escalar os pods GPU dinamicamente em vez de provisionar a capacidade ociosa da GPU em excesso.

Em seguida, verifique os logs do vLLM para verificar o tempo de carregamento do modelo:

kubectl logs $(kubectl get pod -l app=vllm-inference-app -o jsonpath='{.items[0].metadata.name}') | grep -i 'Model loading took'

Saída esperada:

INFO 05-18 18:41:49 [gpu_model_runner.py:4959] Model loading took 9.81 GiB memory and 5.023344 seconds

O log confirma que o streamer de modelo Run:ai carregou os pesos do modelo de 10,4 GB diretamente do S3 para a memória da GPU em aproximadamente 5 segundos, consumindo 9,8 GiB de memória da GPU.

O tempo de download da imagem neste exemplo foi usando uma instância g6e.4xlarge, que tem uma largura de banda da rede sustentada de 20 Gbps. Os tempos das extrações de imagens e carregamento do modelo variam em outros tipos de instância, dependendo da largura de banda da rede disponível.

Etapa 3: executar inferência

Com a implantação do vLLM em execução, valide o endpoint de inferência e implante uma aplicação de frontend de chat para interagir com o modelo.

Executar um teste de validação de modelo

Exponha o endpoint de inferência via port-forward:

kubectl port-forward svc/vllm-inference-svc 8000:8000

Abra uma nova janela de terminal e confirme que o contêiner de inferência está respondendo:

curl -sI -X GET http://localhost:8000/health

Saída esperada:

HTTP/1.1 200 OK date: Fri, 18 May 2026 00:39:23 GMT server: uvicorn content-length: 0

Etapa 4: monitorar o vLLM

O vLLM expõe as métricas do Prometheus prontas para uso, incluindo taxa de solicitações, throughput de tokens, latência de ponta a ponta e utilização do cache de KV da GPU. Nesta seção, você usa essas métricas com a pilha de monitoramento configurada nas etapas de Configuração do cluster e as visualiza em um painel pré-provisionado do Grafana.

Importante

Você deve concluir a subseção Monitorar da seção Configuração do cluster via CLI antes de continuar. Essa etapa depende do kube-prometheus-stack que está sendo instalado e do painel do vLLM do Grafana já provisionado no arquivo de valores.

Aplicar o ServiceMonitor do vLLM

Um ServiceMonitor informa ao Prometheus onde extrair as métricas do vLLM.

cat << EOF | kubectl apply -f - apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: vllm-inference-app namespace: default labels: release: kube-prometheus-stack spec: selector: matchLabels: app: vllm-inference-app endpoints: - port: http path: /metrics interval: 15s EOF

Verifique se o ServiceMonitor foi criado:

kubectl get servicemonitor vllm-inference-app

Saída esperada:

NAME AGE vllm-inference-app 5s

Para preencher o painel com as métricas, gere tráfego de inferência em relação ao endpoint do vLLM que você já expôs via port-forward na etapa de validação.

Descubra o nome do modelo servido:

MODEL_NAME=$(curl -s http://localhost:8000/v1/models | jq -r '.data[0].id') echo "Using model: $MODEL_NAME"

Envie 50 solicitações de conclusão de chat em paralelo:

for i in $(seq 1 50); do curl -s -X POST http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d "{\"model\": \"$MODEL_NAME\", \"messages\": [{\"role\": \"user\", \"content\": \"Write a short poem about Kubernetes.\"}], \"max_tokens\": 128}" \ > /dev/null & done wait

Enquanto o tráfego estiver fluindo (ou imediatamente depois), verifique as métricas de throughput de tokens diretamente do endpoint de /metrics do vLLM:

curl -s http://localhost:8000/metrics | grep -E '^vllm:(prompt_tokens_total|generation_tokens_total|avg_generation_throughput_toks_per_s|avg_prompt_throughput_toks_per_s)' | head

As métricas vllm:prompt_tokens_total e vllm:generation_tokens_total aumentam monotonicamente os contadores de tokens de entrada e saída servidos. As métricas vllm:avg_prompt_throughput_toks_per_s e vllm:avg_generation_throughput_toks_per_s são medidores de throughput por média móvel. Essas mesmas métricas alimentam o painel do Grafana que você abrirá na próxima subseção.

Visualizar o painel do vLLM do Grafana

O arquivo de valores kube-prometheus-stack da seção Monitorar já provisiona o painel do vLLM (gnetId 25263) da comunidade na pasta Monitoramento da GPU, assim, não é necessária nenhuma importação adicional.

Para acessar o Grafana, inicie um port-forward para o serviço do Grafana:

kubectl port-forward svc/kube-prometheus-stack-grafana 3000:80 -n monitoring

Abra http://localhost:3000 no navegador e navegue até Dashboards > GPU Monitoring > vLLM Metrics.

Painel de controle do vLLM do Grafana

Painel do vLLM do Grafana mostrando a taxa de solicitações, o throughput de tokens, a latência de ponta a ponta e a utilização do cache de KV da GPU

O painel exibe a taxa de solicitações, o throughput de prompts e de tokens de geração, os percentis de latência e a utilização do cache de KV da GPU para o endpoint de inferência do vLLM.

Etapa 5: implantar uma aplicação de chat

Nesta etapa, você implanta o Open WebUI como um frontend de chat para interagir com o modelo. O Open WebUI é uma interface de IA de código aberto auto-hospedada que aceita APIs compatíveis com OpenAI e fornece uma interface de chat com histórico de conversa e renderização de markdown. Como o vLLM expõe uma API compatível com OpenAI, o Open WebUI se conecta a ela diretamente como um backend.

Para implantar a aplicação Open WebUI, aplique o seguinte manifesto:

cat << 'EOF' | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: open-webui namespace: default labels: app: open-webui guide: ai-eks-docs spec: replicas: 1 selector: matchLabels: app: open-webui template: metadata: labels: app: open-webui guide: ai-eks-docs spec: containers: - name: open-webui image: ghcr.io/open-webui/open-webui:v0.9.2 ports: - containerPort: 8080 resources: requests: cpu: "500m" memory: "500Mi" limits: cpu: "1000m" memory: "1Gi" env: - name: OPENAI_API_BASE_URLS value: "http://vllm-inference-svc:8000/v1" - name: OPENAI_API_KEY value: "dummy" - name: WEBUI_AUTH value: "False" - name: ENABLE_OLLAMA_API value: "False" - name: ENABLE_EVALUATION_ARENA_MODELS value: "False" volumeMounts: - name: webui-volume mountPath: /app/backend/data volumes: - name: webui-volume emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: open-webui namespace: default labels: app: open-webui spec: type: ClusterIP selector: app: open-webui ports: - protocol: TCP port: 80 targetPort: 8080 EOF

Aguarde o pod Open WebUI ficar pronto:

kubectl wait --for=condition=ready pod -l app=open-webui --timeout=300s

Saída esperada:

pod/open-webui-6cbfc9867f-jf9w9 condition met

Para acessar a aplicação, configure o processo de port-forward e abra a aplicação no navegador:

kubectl port-forward svc/open-webui 8080:80 & sleep 5 echo "Open WebUI: http://localhost:8080"

Abra o http://localhost:8080 no navegador.

A interface de chat é exibida, permitindo que você interaja com o modelo Ministral.

Ao terminar o teste, interrompa a execução dos processos de port-forward em segundo plano executando kill %1 %2 (ou execute jobs para listá-los e kill %<jobspec> para cada um deles).

Captura de tela da interface de chat Open WebUI mostrando uma conversa com o modelo Ministral

Fazer a limpeza.

Para remover os recursos de workload criados nesta seção, exclua a aplicação Open WebUI, o servidor de inferência do vLLM e o Job de download de modelo:

kubectl delete deployment open-webui kubectl delete service open-webui kubectl delete deployment vllm-inference-app kubectl delete service vllm-inference-svc kubectl delete servicemonitor vllm-inference-app kubectl delete job model-download

Para obter instruções sobre a remoção de recursos de infraestrutura, como o cluster, o NodePool e o bucket do S3, consulte Limpeza da configuração do cluster.