View a markdown version of this page

Amazon EKS에서 모델 로드 및 제공 - Amazon EKS

이 페이지 개선에 도움 주기

이 사용자 가이드에 기여하려면 모든 페이지의 오른쪽 창에 있는 GitHub에서 이 페이지 편집 링크를 선택합니다.

Amazon EKS에서 모델 로드 및 제공

작은 정보

향후 예정된 Amazon EKS AI/ML 워크숍에 등록합니다.

이 섹션의 단계에서는 Amazon EKS에 대규모 언어 모델(LLM)을 배포하고 vLLM과 함께 LLM을 제공하고 추론 엔드포인트와 상호 작용합니다.

이 연습 단계에서는 다음 도구를 사용합니다.

  • vLLM - LLM 제공과 GPU 메모리 관리에 최적화된 고처리량 추론 엔진입니다.

  • Run:ai Model Streamer - 모델 가중치를 Amazon S3에서 GPU 메모리로 직접 스트리밍하여 로드 시간을 분 단위에서 초 단위로 줄입니다.

  • Open WebUI - vLLM의 OpenAI 호환 API에 연결되는 자체 호스팅 채팅 프론트엔드입니다.

이 섹션에서는 Ministral-3-8B-Instruct-2512 모델을 사용하지만 vLLM이 지원하는 모든 AI 모델을 배포할 수 있습니다. 지원되는 모델 목록은 vLLM 설명서의 Supported Models를 참조하세요.

중요

AI/ML 워크로드를 위한 Amazon EKS 클러스터 설정 섹션에서 생성한 클러스터를 사용합니다. 이 연습의 지침은 EKS 자율 모드와 자체 관리형 Karpenter 모두에 적용됩니다.

Amazon EKS에서 vLLM을 사용하는 LLM 추론 워크플로를 보여주는 아키텍처 다이어그램

아키텍처 다이어그램은 종단 간 흐름을 보여줍니다.

  1. 모델 가중치는 Hugging Face에서 Amazon S3로 다운로드됩니다.

  2. vLLM은 Run:ai Model Streamer를 사용하여 S3에서 GPU 메모리로 직접 모델을 스트리밍합니다.

  3. 사용자는 vLLM 엔드포인트에 추론 요청을 보냅니다.

이러한 단계를 완료하면 채팅 프론트엔드 애플리케이션을 통해 Ministral 모델과 상호 작용하는 데 사용할 수 있는 vLLM 추론 엔드포인트가 생깁니다.

사전 조건

클러스터 설정 섹션의 단계를 완료합니다.

새 터미널을 연 경우 CLI를 통한 클러스터 설정 섹션에서 사용한 클러스터 이름과 리전을 설정합니다.

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

모델 가중치 S3 버킷 단계에서 생성한 모델 가중치 버킷을 조회합니다.

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에서 모델 다운로드

이 단계에서는 Hugging Face에서 모델을 다운로드하여 사전 조건 섹션에서 생성한 S3 버킷에 업로드하는 Kubernetes 작업을 배포합니다.

모델을 다운로드하려면 다음 작업 매니페스트를 적용합니다.

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.4GB이며, 이 단계는 일반적으로 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.4GB)가 포함되어 있습니다. 나머지 파일은 vLLM이 모델을 제공하는 데 필요한 구성 및 Tokenizer 파일입니다.

2단계: 추론 컨테이너 배포

이 섹션에서는 Amazon S3에 업로드한 모델을 제공하기 위해 vLLM을 Kubernetes 배포로 배포합니다.

이 섹션에서는 딥 러닝 프레임워크가 사전 설치되어 AWS 인프라에서의 성능에 최적화된 Docker 이미지인 AWS 딥 러닝 컨테이너(DLC)를 사용합니다. DLC에는 보안 패치, 검증된 프레임워크 버전, 최적화된 GPU 드라이버 구성이 포함되어 있습니다.

이 배포는 SOCI 지원과 함께 vLLM 0.21.0에 다음 AWS DLC를 사용합니다.

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, EC2 기반 워크로드에 최적화된 Ubuntu 22.04, 더 빠른 컨테이너 시작을 위한 SOCI 활성화를 나타냅니다.

이 매니페스트는 GPU 노드에서 vLLM을 실행하고 Run:ai Model Streamer를 사용하여 S3에서 GPU 메모리로 직접 모델을 스트리밍하는 배포를 생성합니다. 이 매니페스트는 클러스터 내 액세스를 위해 포트 8000에 vLLM 엔드포인트를 노출하는 ClusterIP 서비스도 생성합니다.

매니페스트를 적용합니다.

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초 내에 프로비저닝되었고, 8.8GB 컨테이너 이미지는 SOCI를 사용하여 약 48초 내에 가져왔습니다. 빠른 이미지 풀은 대규모 추론 컨테이너의 콜드 스타트 시간을 줄이므로 유휴 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가 10.4GB 모델 가중치를 약 5초 내에 S3에서 GPU 메모리로 직접 로드하고 9.8GiB의 GPU 메모리를 소비했음이 확인됩니다.

이 예제의 이미지 다운로드 시간은 네트워크 지속 대역폭이 20Gbps인 g6e.4xlarge 인스턴스를 사용하여 얻은 것입니다. 사용 가능한 네트워크 대역폭에 따라 다른 인스턴스 유형에서는 이미지 풀 및 모델 로드 시간이 달라집니다.

3단계: 추론 실행

vLLM 배포가 실행 중인 상태에서 추론 엔드포인트를 검증하고 채팅 프론트엔드를 배포하여 모델과 상호 작용합니다.

모델 검증 테스트 실행

포트 전달을 통해 추론 엔드포인트를 노출합니다.

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 모니터링

vLLM은 요청 속도, 토큰 처리량, 종단 간 지연 시간, GPU KV 캐시 사용률을 포함한 Prometheus 지표를 즉시 노출합니다. 이 섹션에서는 클러스터 설정 단계에서 설정한 모니터링 스택과 함께 이러한 지표를 사용하고 사전 프로비저닝된 Grafana 대시보드에서 해당 지표를 봅니다.

중요

계속하기 전에 CLI를 통한 클러스터 설정 섹션의 모니터링 하위 섹션을 완료해야 합니다. 이 단계는 설치 중인 kube-prometheus-stack과 값 파일에 이미 프로비저닝된 vLLM Grafana 대시보드에 따라 달라집니다.

vLLM ServiceMonitor 적용

ServiceMonitor는 vLLM 지표를 스크레이프할 위치를 Prometheus에 알려줍니다.

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

대시보드를 지표로 채우려면 검증 단계에서 포트 전달을 통해 이미 노출한 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_totalvllm:generation_tokens_total 지표는 제공된 입력 및 출력 토큰의 단조 증가하는 카운터입니다. vllm:avg_prompt_throughput_toks_per_svllm:avg_generation_throughput_toks_per_s 지표는 롤링 평균 처리량 게이지입니다. 이러한 동일한 지표는 다음 하위 섹션에서 여는 Grafana 대시보드를 구동합니다.

vLLM Grafana 대시보드 보기

모니터링 섹션의 kube-prometheus-stack 값 파일이 GPU 모니터링 폴더 아래에 커뮤니티 vLLM 대시보드(gnetId 25263)를 이미 프로비저닝하므로 추가 가져오기는 필요하지 않습니다.

Grafana에 액세스하려면 Grafana 서비스로의 포트 전달을 시작합니다.

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

브라우저에서 http://localhost:3000을 열고 대시보드 > GPU 모니터링 > vLLM 지표로 이동합니다.

vLLM Grafana 대시보드

요청 속도, 토큰 처리량, 종단 간 지연 시간, GPU KV 캐시 사용률을 보여주는 vLLM Grafana 대시보드

대시보드에는 vLLM 추론 엔드포인트에 대한 요청 속도, 프롬프트 및 생성 토큰 처리량, 지연 시간 백분위수, GPU KV 캐시 사용률이 표시됩니다.

5단계: 채팅 애플리케이션 배포

이 단계에서는 Open WebUI를 채팅 프론트엔드로 배포하여 모델과 상호 작용합니다. Open WebUI는 OpenAI 호환 API를 지원하고 대화 기록과 마크다운 렌더링이 포함된 채팅 인터페이스를 제공하는 오픈 소스 자체 호스팅 AI 인터페이스입니다. vLLM은 OpenAI 호환 API를 노출하므로 Open WebUI는 vLLM에 백엔드로 직접 연결됩니다.

Open WebUI 애플리케이션을 배포하려면 다음 매니페스트를 적용합니다.

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을 엽니다.

Minstral 모델과 상호 작용할 수 있는 채팅 인터페이스가 나타납니다.

테스트를 마치면 kill %1 %2를 실행하여 백그라운드 포트 전달 프로세스를 중지합니다(또는 jobs를 실행하여 해당 프로세스를 나열하고 kill %<jobspec>을 실행하여 각각 중지합니다).

Ministral 모델과의 대화를 보여주는 Open WebUI 채팅 인터페이스의 스크린샷

정리

이 섹션에서 생성한 워크로드 리소스를 제거하려면 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

클러스터, NodePool, S3 버킷 등 인프라 리소스 제거에 대한 지침은 클러스터 설정 정리를 참조하세요.