6 분 소요

이번에는 모델 준비를 위한 Init Container 패턴이 적용된 Pod를 사용해 EKS 클러스터에 vLLM을 배포한다. 이 배포는 S3 Model Cache, 자동 모델 컴파일 및 프로덕션 준비형 구성을 사용하는 최적화된 방식을 적용한다.

1. HF_TOKEN Secret 생성

먼저 Hugging Face Token을 안전하게 저장하기 위한 Kubernetes Secret을 생성한다.

source /home/ubuntu/workshop/.env
kubectl create secret generic hf-token-secret \
    --from-literal=HF_TOKEN="$HF_TOKEN" \
    --dry-run=client -o yaml | kubectl apply -f -

2. ConfigMap 생성

먼저 vLLM 배포에서 공유하는 모든 구성 값을 저장할 ConfigMap을 생성한다.

cat > vllm-configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: vllm-shared-config
data:
  HF_TOKEN: "$HF_TOKEN"
  MODEL_NAME: "tinyLlama/TinyLlama-1.1B-Chat-v1.0"
  S3_BUCKET: "ai-infra-summit-vllm-models-cache-$(aws sts get-caller-identity | jq -r .Account)"
  S3_PREFIX: "compiled-models"
  MAX_NUM_SEQS: "4"
  PORT: "8080"
  NEURON_COMPILED_ARTIFACTS: "/shared/model/cache"
  NEURON_COMPILE_CACHE_URL: "/shared/model/cache"
  TENSOR_PARALLEL_SIZE: "2"
  MAX_MODEL_LEN: "1024"
  NEURON_RT_VISIBLE_CORES: "0-1"
  NEURON_RT_LOG_LEVEL: "ERROR"
  NEURON_RT_ASYNC_EXEC_MAX_INFLIGHT_REQUESTS: "4"
  VLLM_NEURON_FRAMEWORK: "neuronx-distributed-inference"
EOF

kubectl apply -f vllm-configmap.yaml

3. Persistent Volume 및 Persistent Volume Claim 생성

다음으로 컴파일된 모델 Artifact를 캐싱할 S3 기반 스토리지를 위해 PV와 PVC를 생성한다.

cat > vllm-storage.yaml <<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: s3-model-cache-pv
spec:
  capacity:
    storage: 100Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  csi:
    driver: s3.csi.aws.com
    volumeHandle: ai-infra-summit-vllm-models-cache-$(aws sts get-caller-identity | jq -r .Account)
    volumeAttributes:
      bucketName: ai-infra-summit-vllm-models-cache-$(aws sts get-caller-identity | jq -r .Account)

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: s3-model-cache-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 100Gi
  volumeName: s3-model-cache-pv
EOF

kubectl apply -f vllm-storage.yaml

4. 전체 vLLM Deployment 배포

Init Container와 vLLM Server Container를 모두 포함하는 전체 Deployment를 생성한다. Init Container는 다음과 같은 핵심 작업을 수행한다.

Init Container가 수행하는 작업

  1. S3 Cache 확인: 먼저 컴파일된 모델 Artifact가 S3 Bucket에 이미 존재하는지 확인한다.
  2. Hugging Face에서 다운로드: Cache가 없으면 제공된 Token을 사용해 Hugging Face에서 모델을 다운로드한다.
  3. Neuron용 컴파일: AWS AI Chip에 맞게 모델을 컴파일한다.
  4. S3에 업로드: 이후 배포에서 사용할 수 있도록 컴파일된 Artifact를 S3에 저장한다. 이를 통해 이후 배포의 시작 시간이 크게 줄어든다.

vLLM Server Container가 수행하는 작업: Init Container가 모델 준비를 완료하면 Main vLLM Server Container가 시작되고 고성능 추론 API를 제공한다. 공유 Storage에서 사전 컴파일된 모델 Artifact를 불러오고 OpenAI 호환 REST API Endpoint를 통해 서비스한다. Server는 들어오는 요청을 처리하고 메모리를 효율적으로 관리하며, AWS Neuron Chip을 활용해 추론 성능을 최적화한다.

cat > vllm-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-deployment
  labels:
    app.kubernetes.io/name: vllm-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: vllm-server
  template:
    metadata:
      labels:
        app.kubernetes.io/name: vllm-server
    spec:
      restartPolicy: Always
      schedulerName: my-scheduler
      nodeSelector:
        alpha.eksctl.io/nodegroup-name: neuron-trn1-2x
      tolerations:
        - key: "node.kubernetes.io/disk-pressure"
          operator: "Exists"
          effect: "NoSchedule"
      # Volumes for compiled models
      volumes:
        - name: model-storage
          persistentVolumeClaim:
            claimName: s3-model-cache-pvc
      # Init container that downloads, compiles, and uploads model to S3
      initContainers:
        - name: model-prep
          image: public.ecr.aws/neuron/pytorch-inference-vllm-neuronx:0.9.1-neuronx-py310-sdk2.25.0-ubuntu22.04
          imagePullPolicy: Always
          envFrom:
            - configMapRef:
                name: vllm-shared-config
            - secretRef:
                name: hf-token-secret                
          command: ["/bin/bash", "-c"]
          args:
            - |
              set -e
              echo "Starting model prep for \$MODEL_NAME..."
              huggingface-cli login --token "\$HF_TOKEN"
              mkdir -p /tmp/cache /shared/model/cache
            
              if [ ! "\$(ls -A /shared/model/cache 2>/dev/null)" ]; then
                export NEURON_COMPILED_ARTIFACTS=/tmp/cache NEURON_COMPILE_CACHE_URL=/tmp/cache
                python3 -c "
              import os
              from vllm import LLM
              LLM(model=os.environ['MODEL_NAME'], max_num_seqs=int(os.environ['MAX_NUM_SEQS']), 
                  max_model_len=int(os.environ['MAX_MODEL_LEN']), tensor_parallel_size=int(os.environ['TENSOR_PARALLEL_SIZE']),
                  device='neuron', override_neuron_config={'enable_bucketing': False})
              print('Model compiled successfully!')"
                cp -r /tmp/cache/* /shared/model/cache/ 2>/dev/null || true
              else
                echo "Model cache exists, skipping compilation"
              fi
          resources:
            limits:
              aws.amazon.com/neuron: 1
              ephemeral-storage: 50Gi
            requests:
              aws.amazon.com/neuron: 1
              ephemeral-storage: 50Gi
          volumeMounts:
            - name: model-storage
              mountPath: /shared/model
      containers:
        - name: vllm-server
          image: public.ecr.aws/neuron/pytorch-inference-vllm-neuronx:0.9.1-neuronx-py310-sdk2.25.0-ubuntu22.04
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
              name: http-vllm
          envFrom:
            - configMapRef:
                name: vllm-shared-config
            - secretRef:
                name: hf-token-secret              
          command: ["/bin/bash", "-c"]
          args:
            - |
              python -m vllm.entrypoints.openai.api_server \\
                --model="\$MODEL_NAME" \\
                --max-num-seqs=\$MAX_NUM_SEQS \\
                --max-model-len=\$MAX_MODEL_LEN \\
                --tensor-parallel-size=\$TENSOR_PARALLEL_SIZE \\
                --port=\$PORT \\
                --device=neuron \\
                --override-neuron-config='{"enable_bucketing":false}'
          volumeMounts:
            - name: model-storage
              mountPath: /shared/model
              readOnly: true
          resources:
            limits:
              aws.amazon.com/neuron: 1
              ephemeral-storage: 50Gi
              cpu: "8000m"
            requests:
              aws.amazon.com/neuron: 1
              ephemeral-storage: 50Gi
              cpu: "4000m"
EOF

kubectl apply -f vllm-deployment.yaml

5. LoadBalancer Service 배포

마지막으로 vLLM Server Endpoint를 노출하기 위한 LoadBalancer Service를 생성한다.

cat > vllm-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: vllm-service
spec:
  selector:
    app.kubernetes.io/name: vllm-server
  ports:
    - protocol: TCP
      port: 8080
      targetPort: http-vllm
  type: LoadBalancer
EOF

kubectl apply -f vllm-service.yaml

6. 모든 구성 요소의 배포 확인

모든 구성 요소가 성공적으로 배포되었는지 확인한다.

echo "Checking deployment status..."
kubectl get configmap vllm-shared-config
kubectl get pv s3-model-cache-pv
kubectl get pvc s3-model-cache-pvc
kubectl get deployment vllm-deployment
kubectl get service vllm-service
kubectl get secrets hf-token-secret 

7. Deployment가 준비될 때까지 대기

전체 Deployment가 준비될 때까지 기다린다. 이 단계는 최초 실행 시에만 전체 과정에 약 8분이 걸린다. 이후 실행에서는 S3 Model Cache를 사용하므로 Pod 시작에 약 20초만 걸린다.

  • Image Pull 및 Scheduling: 약 4분
  • 모델 컴파일: 약 4분
  • vLLM API Server 시작: 약 20초
echo "Waiting for vLLM deployment to be ready..."
kubectl wait --for=condition=Available deployment/vllm-deployment --timeout=1800s

echo "vLLM deployment is ready!"

Init Container Log를 실시간으로 확인

echo -e "\nChecking init container logs (model preparation)..."
kubectl logs -l app.kubernetes.io/name=vllm-server -c model-prep -f

8. 배포 상태 및 로그 모니터링

Deployment의 상세 상태를 확인한다.

echo "=== Deployment Status ==="
kubectl get deployment vllm-deployment -o wide

echo -e "\n=== Pod Status ==="
kubectl get pods -l app.kubernetes.io/name=vllm-server -o wide

echo -e "\n=== Pod Description ==="
kubectl describe pods -l app.kubernetes.io/name=vllm-server

echo -e "\n=== Init Container Logs ==="
kubectl logs -l app.kubernetes.io/name=vllm-server -c model-prep --tail=50

echo -e "\n=== Main Container Logs ==="
kubectl logs -l app.kubernetes.io/name=vllm-server -c vllm-server --tail=50

echo -e "\n=== Service Status ==="
kubectl get service vllm-service

echo -e "\n=== ConfigMap ==="
kubectl get configmap vllm-shared-config -o yaml

9. S3 Model Caching 확인

모델 Artifact가 S3에 성공적으로 캐싱되었는지 확인한다.

BUCKET_NAME=$(kubectl get pv s3-model-cache-pv -o jsonpath='{.spec.csi.volumeAttributes.bucketName}')
aws s3 ls $BUCKET_NAME --recursive

10. vLLM Server 테스트

vLLM API가 정상적으로 작동하는지 테스트한다.

# Start port-forwarding in background
echo "Setting up port-forward to vLLM service..."
kubectl port-forward svc/vllm-service 8080:8080 &
PORT_FORWARD_PID=$!

# Wait for port-forward to be ready
sleep 3

export VLLM_ENDPOINT="http://localhost:8080"

echo "Testing vLLM API with curl..."
curl -X POST "$VLLM_ENDPOINT/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tinyLlama/TinyLlama-1.1B-Chat-v1.0",
    "messages": [{"role": "user", "content": "Hello, how are you?"}],
    "max_tokens": 100,
    "temperature": 0.7
  }' | jq -r '.choices[0].message.content'

echo -e "\n\nBasic API tests completed!"

11. 대화형 Chat Terminal

# Create the test script
cat > test-vllm-pod.py <<'EOF'
from openai import OpenAI
import sys
import os

def main():
    # Setup client
    try:
        base_endpoint = os.getenv("VLLM_ENDPOINT")
        if not base_endpoint:
            print("Error: VLLM_ENDPOINT environment variable is not set")
            sys.exit(1)
      
        vllm_endpoint = f"{base_endpoint}/v1"
        client = OpenAI(api_key="EMPTY", base_url=vllm_endpoint)
        model_name = client.models.list().data[0].id
        print(f"Connected! Using model: {model_name}")
    except Exception as e:
        print(f"Connection failed: {e}")
        sys.exit(1)
  
    # Chat loop
    print("Chat (type 'exit' to quit):")
    while True:
        user_input = input("\nYou: ").strip()
      
        if user_input.lower() in ['exit', 'quit', 'bye'] or not user_input:
            break
          
        try:
            response = client.chat.completions.create(
                model=model_name,
                messages=[{"role": "user", "content": user_input}],
                max_tokens=900,
                temperature=1.0,
                extra_body={'top_k': 50}
            )
            print("AI:", response.choices[0].message.content)
        except Exception as e:
            print(f"Error: {e}")

if __name__ == "__main__":
    main()
EOF

echo "test-vllm-pod.py created!"

12. Python Client를 사용한 대화형 테스트

대화형 Python Test Client를 실행한다.

echo "Installing required Python packages..."
pip install openai

echo "Running interactive test client..."
python3 test-vllm-pod.py

13. 문제 해결

문제가 발생하면 다음 명령어를 사용해 디버깅한다.

# Check pod events
kubectl get events --sort-by=.metadata.creationTimestamp

# Check init container logs for compilation issues
kubectl logs -l app.kubernetes.io/name=vllm-server -c model-prep

# Check main container logs for server issues
kubectl logs -l app.kubernetes.io/name=vllm-server -c vllm-server

# Check node resources
kubectl describe nodes -l alpha.eksctl.io/nodegroup-name=neuron-trn1-2x

# Check Neuron device allocation
kubectl describe pods -l app.kubernetes.io/name=vllm-server | grep -A 10 "Requests"

# Check service endpoint
kubectl get endpoints vllm-service

14. 배포 아키텍처

vLLM 배포는 다음 요소로 구성된다.

  1. ConfigMap: vLLM 구성과 환경변수를 중앙에서 관리한다.
  2. Deployment: Init Container 패턴이 적용된 vLLM Pod를 관리한다.
  3. Init Container: 모델을 다운로드하고 컴파일한 후 S3에 캐싱한다.
  4. Main Container: 사전 컴파일된 모델로 vLLM Server를 실행한다.
  5. LoadBalancer Service: vLLM API에 대한 외부 접근을 제공한다.
  6. S3 Model Cache: 재사용을 위해 컴파일된 모델 Artifact를 저장한다.
  7. Neuron Resource 관리: 최적의 성능을 위해 NeuronCore 2개를 할당한다.

Init Container 패턴을 사용하는 배포는 다음을 보장한다.

  • 모델을 한 번 컴파일하고 재사용할 수 있도록 캐싱한다.
  • 이후 배포의 시작 시간을 최적화한다.
  • S3가 컴파일된 Artifact를 위한 영구 스토리지를 제공한다.
  • Main Container가 사전 컴파일된 모델을 사용해 빠르게 시작할 수 있다.

15. 다음 단계

최적화된 Pod 패턴으로 vLLM을 성공적으로 배포한 후 Ingress 및 Load Balancing 섹션으로 이동한다. 이 섹션에서는 vLLM 배포를 위한 외부 접근과 Load Balancing을 설정한다.

댓글남기기