

 **帮助改进此页面** 

要帮助改进本用户指南，请选择位于每个页面右侧窗格中的**在 GitHub 上编辑此页面**链接。

# 在 Amazon EKS 上加载和服务模型
<a name="ml-inference-load-serve-model"></a>

**提示**  
 [注册参加](https://events.eksworkshop.com/workshops/genai/)即将举办的 Amazon EKS 人工智能/机器学习讲习会。

本节中的步骤将在 Amazon EKS 上部署一个大语言模型（LLM），使用 vLLM 服务该模型，并与推理端点进行互动。

本演练使用了以下工具：
+  [vLLM](https://docs.vllm.ai/en/latest/)：一款专为 LLM 服务和 GPU 内存管理进行优化的高吞吐量推理引擎。
+  [Run:ai Model Streamer](https://github.com/run-ai/runai-model-streamer)：将模型权重直接从 Amazon S3 流式传输到 GPU 内存，从而将加载时间从几分钟缩短到几秒。
+  [Open WebUI](https://openwebui.com/)：一种自托管聊天前端，可连接到 vLLM 兼容 OpenAI 的 API。

本节使用 [Ministral-3-8B-Instruct-2512 模型](https://huggingface.co/mistralai/Ministral-3-8B-Instruct-2512)，当然您也可以部署 vLLM 支持的任何 AI 模型。有关支持的模型列表，请参阅 vLM 文档中的 [Supported models](https://docs.vllm.ai/en/latest/models/supported_models/#text-generation)。

**重要**  
使用您在 [设置用于人工智能/机器学习工作负载的 Amazon EKS 集群](ml-cluster-setup.md) 部分中创建的集群。本演练中的说明对于 EKS 自动模式和自行管理的 Karpenter 均适用。

![显示在 Amazon EKS 上使用 vLLM 执行推理工作流的架构图](http://docs.aws.amazon.com/zh_cn/eks/latest/userguide/images/ml-inference-load-serve-model-arch.png)


该架构图显示了以下端到端的流程：

1. 模型权重从 Hugging Face 下载到 Amazon S3。

1. vLLM 使用 Run:ai Model Streamer 将模型直接从 S3 流式传输到 GPU 内存。

1. 用户向 vLLM 端点发送推理请求。

完成这些步骤后，您将获得一个可通过聊天前端应用程序与 Ministral 模型进行互动的 vLLM 推理端点。

## 先决条件
<a name="_prerequisites"></a>

完成[集群设置部分](ml-cluster-setup.md)中的步骤。

如果您打开了新终端，请在[通过 CLI 进行集群设置](ml-cluster-setup-cli.md)部分中设置您使用的集群名称和区域：

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

查找您在[模型权重 S3 存储桶](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}"
```

## 第 1 步：从 Hugging Face 下载模型
<a name="_step_1_download_the_model_from_hugging_face"></a>

在此步骤中，您将部署一个 Kubernetes 作业，以从 Hugging Face 下载模型并将其上传到您在先决条件部分中创建的 S3 存储桶。

要下载模型，请应用以下作业清单：

### 模型下载作业清单
<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
```

等待作业完成。模型权重（consolidated.safetensors）约为 10.4 GB，此步骤通常需要 3-5 分钟。

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

预期输出：

```
job.batch/model-download condition met
```

验证模型权重是否已上传到 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
```

预期输出：

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

consolidated.safetensors 文件包含模型权重（大约 10.4 GB）。其余文件是 vLLM 为模型提供服务所需的配置文件和令牌化文件。

## 第 2 步：部署推理容器
<a name="_step_2_deploy_the_inference_container"></a>

在本节中，您需要将 vLLM 作为 Kubernetes 部署进行部署，以便为您上传到 Amazon S3 的模型提供服务。

本节使用 [AWS 深度学习容器](https://github.com/aws/deep-learning-containers/tree/master)（DLC），这是一种预装了深度学习框架的 Docker 映像，并针对 AWS 基础设施进行了性能优化。DLC 包含安全补丁、经验证的框架版本和优化版 GPU 驱动程序配置。

此部署使用了以下适用于的 [vLLM 0.21.0](https://gallery.ecr.aws/deep-learning-containers/vllm) 的 AWS DLC，并且支持 SOCI：

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

映像标签指示支持 GPU 的 vLLM 0.21.0、Python 3.12、CUDA 13.0、Ubuntu 22.04、针对基于 EC2 的工作负载进行了优化、已启用 SOCI 以加快容器启动速度。

此清单创建了一个将在 GPU 节点上运行 vLLM 的部署，并使用 Run:ai Model Streamer 将模型直接从 S3 流式传输到 GPU 内存。该清单还创建了一个 ClusterIP 服务，在端口 8000 上公开 vLLM 端点以用于集群内访问。

应用清单：

### vLLM 部署和服务 YAML
<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
```

检查 vlLM 容器组是否处于就绪状态：

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

预期输出：

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

拉取容器映像以及让 vLLM 将模型权重从 S3 流式传输到 GPU 内存可能需要大约 2 分钟时间。等待容器组在 READY 列中显示 `1/1`，然后再继续操作。

组合使用 EKS、SOCI 和 Run:ai Model Streamer 可以快速启动容器组。要检查每个阶段的启动时间，请查看容器组事件：

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

预期输出：

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

在此示例中，GPU 节点在 30 秒内完成了预调配，使用 SOCI 在大约 48 秒内拉取了 8.8 GB 的容器映像。快速映像拉取缩短了大型推理容器的冷启动时间，让您可以动态扩展 GPU 容量，而不必过多预调配空闲的 GPU 容量。

然后检查 vLLM 日志以验证模型加载时间：

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

预期输出：

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

该日志证实 Run:ai Model Streamer 在大约 5 秒钟内将 10.4 GB 的模型权重直接从 S3 加载到 GPU 内存中，消耗了 9.8 GiB 的 GPU 内存。

此示例中的映像下载时间是使用 g6e.4xlarge 实例的下载时间，其持续网络带宽为 20 Gbps。其他实例类型上的映像拉取和模型加载时间可能与此不同，具体取决于可用的网络带宽。

## 第 3 步：运行推理
<a name="_step_3_run_inference"></a>

在 vLLM 部署运行后，验证推理端点并部署聊天前端以与模型进行互动。

### 运行模型验证测试
<a name="_run_a_model_validation_test"></a>

通过端口转发公开推理端点：

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

打开新终端窗口，然后验证推理容器是否响应：

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

预期输出：

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

## 第 4 步：监控 vLLM
<a name="ml-inference-load-serve-model-monitoring"></a>

vLLM 会公开若干开箱即用的 Prometheus 指标，包括请求速率、词元吞吐量、端到端延迟和 GPU KV 缓存利用率等。在本节中，您需要将这些指标与您在[集群设置](ml-cluster-setup.md)步骤中设置的监控堆栈结合使用，并在预调配的 Grafana 控制面板上查看。

**重要**  
必须首先完成[通过 CLI 设置集群](ml-cluster-setup-cli.md)一节的[监控](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring)部分，然后再继续操作。此步骤依赖要安装的 kube-prometheus-stack 以及已经在值文件中预调配的 vLLM Grafana 控制面板。

### 应用 vLLM ServiceMonitor
<a name="ml-inference-load-serve-model-monitoring-servicemonitor"></a>

ServiceMonitor 会告诉 Prometheus 从哪里抓取 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
```

验证 ServiceMonitor 是否已创建：

```
kubectl get servicemonitor vllm-inference-app
```

预期输出：

```
NAME                  AGE
vllm-inference-app    5s
```

#### 生成推理流量
<a name="ml-inference-load-serve-model-monitoring-traffic"></a>

要在控制面板中填充指标，请针对您在验证步骤中已经通过端口转发公开的 vLLM 端点生成推理流量。

发现所服务的模型名称：

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

并行发送 50 个聊天完成请求：

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

当流量流过时（或流过后即刻），直接从 vLLM `/metrics` 端点检查词元吞吐量指标：

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

`vllm:prompt_tokens_total` 和 `vllm:generation_tokens_total` 指标是单调增加所服务输入和输出词元的计数器。`vllm:avg_prompt_throughput_toks_per_s` 和 `vllm:avg_generation_throughput_toks_per_s` 指标是滚动平均吞吐量指标。这些指标也将用于支持您在下一小节中打开的 Grafana 控制面板。

### 查看 vLLM Grafana 控制面板
<a name="ml-inference-load-serve-model-monitoring-dashboard"></a>

[监控](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring)部分中的 kube-prometheus-stack 值文件已经在 **GPU Monitoring** 文件夹下预调配了社区 [vLLM 控制面板（gnetID 25263）](https://grafana.com/grafana/dashboards/25263-vllm-metrics/)，因此无需额外导入。

要访问 Grafana，请启动一个指向 Grafana 服务的端口转发：

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

在浏览器中打开 [http://localhost:3000](http://localhost:3000) 并导航到**控制面板 > GPU 监控 > vLLM 指标**。

 **vLLM Grafana 控制面板** 

![vLLM Grafana 控制面板会显示请求速率、词元吞吐量、端到端延迟和 GPU KV 缓存利用率等指标](http://docs.aws.amazon.com/zh_cn/eks/latest/userguide/images/ml-inference-load-serve-model-vllm-monitoring.png)


控制面板会显示 vLLM 推理端点的请求速率、提示和生成词元吞吐量、延迟百分位数以及 GPU KV 缓存利用率等指标。

## 第 5 步：部署聊天应用程序
<a name="_step_5_deploy_chat_application"></a>

在此步骤中，您需要将 Open WebUI 部署为聊天前端以与模型进行互动。Open WebUI 是一种开源的自托管 AI 界面，支持兼容 OpenAI 的 API，并提供包含对话历史记录和 Markdown 渲染的聊天界面。由于 vLLM 公开了兼容 OpenAI 的 API，因此 Open WebUI 将作为后端直接连接到该 API。

要部署 Open WebUI 应用程序，请应用以下清单：

### Open WebUI 部署和服务 YAML
<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
```

等待 Open WebUI 容器组准备就绪：

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

预期输出：

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

要访问该应用程序，请设置端口转发并在浏览器中打开该应用程序：

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

在浏览器中打开 [http://localhost:8080](http://localhost:8080)。

这时将显示聊天界面，让您可以在其中与 Ministral 模型互动。

完成测试后，通过运行 `kill %1 %2` 来停止后台的端口转发进程（也可运行 `jobs` 来列出这些进程以及每个进程的 `kill %<jobspec>`）。

![Open WebUI 聊天界面屏幕截图，显示了与 Ministral 模型的对话](http://docs.aws.amazon.com/zh_cn/eks/latest/userguide/images/ml-inference-load-serve-model-chatui.png)


## 清理
<a name="_clean_up"></a>

要移除您在本节中创建的工作负载资源，请删除 Open WebUI 应用程序、vLLM 推理服务器和模型下载作业：

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

有关移除基础设施资源（例如集群、节点池和 S3 存储桶）的说明，请参阅[集群设置清理](ml-cluster-setup-cli.md#cluster-setup-cli-cleanup)。