NVIDIA L40S 1장(HAMi vGPU) 환경에서 vLLM V1 엔진의 주요 서빙 파라미터를 변경하며 처리량(Throughput)과 지연시간(TTFT·TPOT)의 트레이드오프를 실측 분석한 기록입니다. 모든 재현 명령어를 순서대로 포함했습니다.

핵심 요약

파라미터 크게(↑) 작게(↓)
--max-num-seqs (배치 상한) 처리량↑ (auto 이상은 KV캐시 한계로 무의미) 디코드 지연(TPOT)↓, 부하 시 TTFT 큐잉 폭증
--max-model-len 긴 컨텍스트 수용 (단기 성능 영향 없음) 동시 수용력(Concurrency) 대폭 증가, 긴 요청 거부
Chunked Prefill & --max-num-batched-tokens 크게/OFF → 총 처리량↑ · TPOT↓, TTFT↑ 작게/ON → 첫 토큰 지연(TTFT)↓, 처리량↓ · TPOT↑
 

0. 테스트 환경 및 GPU 상태 확인

KubeVirt VM 기반 Kubernetes 노드에서 HAMi를 통해 L40S 1장을 할당 받았습니다.

# 1. GPU 노드 및 HAMi 할당 슬롯 확인
kubectl get nodes -o custom-columns='NODE:.metadata.name,GPU:.status.capacity.nvidia\.com/gpu'

# 2. 물리 GPU 세부 사양 및 HAMi 분할 상태 확인 (44GB 메타데이터 확인)
kubectl get node <gpu-node-name> -o jsonpath='{.metadata.annotations.hami\.io/node-nvidia-register}'

1. vLLM 서버 배포

Recreate 전략의 Deployment로 배포하고, 노드 hostPath를 사용하여 모델 캐시를 재사용합니다.

# vllm.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm
  namespace: vllm-bench
  labels:
    app: vllm
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: vllm
  template:
    metadata:
      labels:
        app: vllm
    spec:
      containers:
      - name: vllm
        image: vllm/vllm-openai:latest
        args:
          - "--model=Qwen/Qwen2.5-7B-Instruct"
          - "--port=8000"
          - "--gpu-memory-utilization=0.85"
        ports:
        - containerPort: 8000
        env:
        - name: HF_HOME
          value: /root/.cache/huggingface
        resources:
          limits:
            nvidia.com/gpu: "1"
            nvidia.com/gpumem: "44000"
            nvidia.com/gpucores: "100"
            cpu: "10"
            memory: 40Gi
          requests:
            cpu: "4"
            memory: 16Gi
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 5
          failureThreshold: 240
        volumeMounts:
        - name: hf-cache
          mountPath: /root/.cache/huggingface
        - name: shm
          mountPath: /dev/shm
      volumes:
      - name: hf-cache
        hostPath:
          path: /var/lib/vllm-hf-cache
          type: DirectoryOrCreate
      - name: shm
        emptyDir:
          medium: Memory
          sizeLimit: 8Gi
# 네임스페이스 생성 및 vLLM 배포
kubectl create ns vllm-bench
kubectl apply -f vllm.yaml

# 롤아웃 대기 (최초 배포 시 모델 다운로드로 수 분 소요)
kubectl -n vllm-bench rollout status deploy/vllm --timeout=1800s

# 서빙 상태 확인
kubectl -n vllm-bench exec deploy/vllm -- curl -s localhost:8000/v1/models

트러블슈팅 Note: vLLM 0.27.1 기준 --disable-log-requests 옵션은 삭제되었으므로 제외해야 컨테이너 크래시(unrecognized arguments)를 방지할 수 있습니다.

2. 엔진 기본값 확인 명령어

vLLM CLI 도움말 및 실행 로그를 조회하여 자동 산출된 기본값을 확인합니다.

POD=$(kubectl -n vllm-bench get pod -l app=vllm -o name | head -1)

# CLI 기본 도움말 조회
kubectl -n vllm-bench exec $POD -- sh -c 'vllm serve --help=max-num-seqs'
kubectl -n vllm-bench exec $POD -- sh -c 'vllm serve --help=max-num-batched-tokens'
kubectl -n vllm-bench exec $POD -- sh -c 'vllm serve --help=max-model-len'
kubectl -n vllm-bench exec $POD -- sh -c 'vllm serve --help=enable-chunked-prefill'

# 기동 로그에서 실제 산출된 값 확인
kubectl -n vllm-bench logs $POD | grep -iE "max_seq_len|enable_chunked_prefill|enable_prefix_caching|GPU KV cache size|Maximum concurrency"

 

확인된 기본 산출 값:

  • max_model_len: 32,768 (모델 Config 기반 자동)
  • max_num_seqs: auto
  • max_num_batched_tokens: auto (~8,192 수준)
  • enable_chunked_prefill: True (V1 Engine 기본 ON)
  • GPU KV Cache Size: 374,704 tokens (동시성 11.44x)

3. 벤치마크 실행 명령어

컨테이너 내부에서 내장 vllm bench serve를 실행하여 런 간 공정한 실측 데이터를 수집합니다.

POD=$(kubectl -n vllm-bench get pod -l app=vllm -o name | head -1)

kubectl -n vllm-bench exec $POD -- sh -c 'vllm bench serve \
  --model Qwen/Qwen2.5-7B-Instruct --host localhost --port 8000 \
  --dataset-name random --random-input-len 1024 --random-output-len 256 \
  --num-prompts 300 --max-concurrency 128 \
  --ignore-eos --temperature 0 --seed 12345 \
  --percentile-metrics ttft,tpot,itl,e2el --metric-percentiles 90,99'

4. 과제 1: max_num_seqs 및 max_model_len 실측

Deployment의 args를 패치하여 재배포 후 실측합니다. (input 1024 / output 256 / concurrency 128)

# 예시: max-num-seqs=32 설정 패치 명령
kubectl -n vllm-bench patch deploy vllm --type=json -p \
 '[{"op":"replace","path":"/spec/template/spec/containers/0/args",
    "value":["--model=Qwen/Qwen2.5-7B-Instruct","--port=8000","--gpu-memory-utilization=0.85","--max-num-seqs=32"]}]'

kubectl -n vllm-bench rollout status deploy/vllm --timeout=420s

실측 결과

Config req/s out tok/s TTFT (중앙) TTFT (p99) TPOT (중앙) 최대 동시성
baseline (auto) 5.96 1,525 951ms 10.8s 71.9ms 11.44x
--max-num-seqs=32 3.41 (-43%) 873 27,470ms 29.3s 34.0ms 12.09x
--max-num-seqs=512 5.96 (=) 1,526 943ms 10.8s 71.9ms 11.34x
--max-model-len=8192 5.96 (=) 1,525 946ms 10.8s 71.9ms 45.74x
 

분석:

  • max-num-seqs=32: 동시 요청 128개 중 96개가 큐 대기 상태로 밀려나며 TTFT가 27초로 폭증하고 전체 처리량이 43% 급감합니다.
  • max-model-len=8192: 긴 입력이 없는 워크로드에서는 성능 변화가 없으나, 엔진 내부 KV 캐시를 적게 할당하여 최대 동시 수용력이 11.44x에서 45.74x로 4배 급증합니다.

5. 과제 2: Chunked Prefill 및 max_num_batched_tokens 실측

Prefill 부하를 높여 청크 효과를 명확히 측정합니다. (input 4096 / output 256 / concurrency 64)

# 1. Chunked Prefill ON & batched tokens=2048 패치
kubectl -n vllm-bench patch deploy vllm --type=json -p \
 '[{"op":"replace","path":"/spec/template/spec/containers/0/args",
    "value":["--model=Qwen/Qwen2.5-7B-Instruct","--port=8000","--gpu-memory-utilization=0.85","--max-num-batched-tokens=2048"]}]'

# 2. Chunked Prefill OFF 패치
kubectl -n vllm-bench patch deploy vllm --type=json -p \
 '[{"op":"replace","path":"/spec/template/spec/containers/0/args",
    "value":["--model=Qwen/Qwen2.5-7B-Instruct","--port=8000","--gpu-memory-utilization=0.85","--no-enable-chunked-prefill"]}]'

실측 결과

Config req/s 총 tok/s TTFT (중앙) TTFT (p99) TPOT (중앙)
baseline (chunked ON, auto) 1.73 7,534 2,310ms 23.5s 132ms
--max-num-batched-tokens=2048 1.70 7,410 1,404ms 24.3s 138ms
--max-num-batched-tokens=16384 1.80 7,855 4,333ms 21.8s 116ms
--no-enable-chunked-prefill 1.83 7,971 7,297ms 21.3s 104ms
 

분석:

  • TTFT 개선: 청크 크기를 2048로 줄이면 Prefill을 잘게 쪼개어 스케줄링하므로 첫 토큰 지연이 1404ms로 가장 우수해집니다.
  • 처리량 최적화: Chunked Prefill을 끄거나(--no-enable-chunked-prefill) 청크를 대형화(16384)하면 GPU Batching 효율이 높아져 총 처리량이 최고(1.83 req/s)가 되며 TPOT(104ms)이 가장 빠릅니다.

6. 전체 테스트 자동화 스크립트 및 cleanup

반복 테스트를 진행한 전체 스크립트 흐름 및 cleanup 명령어입니다.

# 튜닝 자동화 스크립트 예시
apply_cfg() {
  ARG_STR=$1
  kubectl -n vllm-bench patch deploy vllm --type=json -p \
   "[{\"op\":\"replace\",\"path\":\"/spec/template/spec/containers/0/args\",
      \"value\":[\"--model=Qwen/Qwen2.5-7B-Instruct\",\"--port=8000\",\"--gpu-memory-utilization=0.85\" ${ARG_STR:+, $ARG_STR}]}]"
  kubectl -n vllm-bench rollout status deploy/vllm --timeout=420s
}

bench() {
  LABEL=$1; IN_LEN=$2; OUT_LEN=$3; NUM_P=$4; CONC=$5
  POD=$(kubectl -n vllm-bench get pod -l app=vllm -o name | head -1)
  echo "=== Running $LABEL ==="
  kubectl -n vllm-bench exec $POD -- sh -c "vllm bench serve \
    --model Qwen/Qwen2.5-7B-Instruct --host localhost --port 8000 \
    --dataset-name random --random-input-len $IN_LEN --random-output-len $OUT_LEN \
    --num-prompts $NUM_P --max-concurrency $CONC \
    --ignore-eos --temperature 0 --seed 12345 \
    --percentile-metrics ttft,tpot,itl,e2el --metric-percentiles 90,99"
}

# 과제 1 실행
apply_cfg ""
bench C1_baseline 1024 256 300 128

apply_cfg '"--max-num-seqs=32"'
bench C1_seqs32 1024 256 300 128

apply_cfg '"--max-num-seqs=512"'
bench C1_seqs512 1024 256 300 128

apply_cfg '"--max-model-len=8192"'
bench C1_modellen8192 1024 256 300 128

# 과제 2 실행
apply_cfg ""
bench C2_baseline 4096 256 200 64

apply_cfg '"--max-num-batched-tokens=2048"'
bench C2_mnbt2048 4096 256 200 64

apply_cfg '"--max-num-batched-tokens=16384"'
bench C2_mnbt16384 4096 256 200 64

apply_cfg '"--no-enable-chunked-prefill"'
bench C2_nochunk 4096 256 200 64

# 자원 자원 해제 (네임스페이스 삭제)
kubectl delete ns vllm-bench

 

### bench 스크립트 및 성능 체크 결과

run_vllm_bench.sh
0.00MB
vllm-bench-results.txt
0.05MB

 

+ Recent posts