

 **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
<a name="ml-inference-load-serve-model"></a>

**dica**  
 [Registre-se](https://events.eksworkshop.com/workshops/genai/) 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](https://docs.vllm.ai/en/latest/): um mecanismo de inferência de alto throughput otimizado para servir LLM e gerenciar memória de GPU.
+  [Streamer de modelo Run:ai](https://github.com/run-ai/runai-model-streamer): 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](https://openwebui.com/): 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](https://huggingface.co/mistralai/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](https://docs.vllm.ai/en/latest/models/supported_models/#text-generation) na documentação do vLLM.

**Importante**  
Selecione o cluster criado na seção [Configurar cluster do Amazon EKS para workloads de IA/ML](ml-cluster-setup.md). 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](http://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-inference-load-serve-model-arch.png)


O diagrama da arquitetura mostra o fluxo completo:

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

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

1. 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
<a name="_prerequisites"></a>

Conclua as etapas na [seção Configuração de cluster](ml-cluster-setup.md).

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:](ml-cluster-setup-cli.md)

```
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](ml-cluster-setup-cli.md#cluster-setup-cli-model-bucket):

```
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
<a name="_step_1_download_the_model_from_hugging_face"></a>

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:

### Manifesto do Job de download do modelo
<a name="ml-inference-step1-model-download-yaml"></a>

```
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
<a name="_step_2_deploy_the_inference_container"></a>

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](https://github.com/aws/deep-learning-containers/tree/master) (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](https://gallery.ecr.aws/deep-learning-containers/vllm) 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:

### Implantação de vLLM e YAML do serviço
<a name="ml-inference-step2-vllm-deployment-yaml"></a>

```
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
<a name="_step_3_run_inference"></a>

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
<a name="_run_a_model_validation_test"></a>

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
<a name="ml-inference-load-serve-model-monitoring"></a>

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](ml-cluster-setup.md) e as visualiza em um painel pré-provisionado do Grafana.

**Importante**  
Você deve concluir a subseção [Monitorar](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring) da seção [Configuração do cluster via CLI](ml-cluster-setup-cli.md) 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
<a name="ml-inference-load-serve-model-monitoring-servicemonitor"></a>

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
```

#### Gerar tráfego de inferência
<a name="ml-inference-load-serve-model-monitoring-traffic"></a>

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
<a name="ml-inference-load-serve-model-monitoring-dashboard"></a>

**O arquivo de valores kube-prometheus-stack da seção [Monitorar](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring) já provisiona o [painel do vLLM (gnetId 25263)](https://grafana.com/grafana/dashboards/25263-vllm-metrics/) 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](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](http://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-inference-load-serve-model-vllm-monitoring.png)


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
<a name="_step_5_deploy_chat_application"></a>

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:

### Implantação do Open WebUI e YAML do serviço
<a name="ml-inference-step5-open-webui-yaml"></a>

```
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](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](http://docs.aws.amazon.com/pt_br/eks/latest/userguide/images/ml-inference-load-serve-model-chatui.png)


## Fazer a limpeza.
<a name="_clean_up"></a>

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](ml-cluster-setup-cli.md#cluster-setup-cli-cleanup).