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 스크립트 및 성능 체크 결과
'LLM Serving and Optimization Study' 카테고리의 다른 글
| [LLMSO 4주차] 쿠버네티스 위 TensorRT-LLM 실습기 — 설치부터 FP8 양자화, 그리고 vLLM 대조 (0) | 2026.08.30 |
|---|---|
| [LLM 3.5주차] 양자화 실습 (0) | 2026.08.23 |
| [LLMSO 2주차] Ray Serve로 LLM(Qwen2.5) 서빙하기 (0) | 2026.08.15 |
| [LLMSO 1주차] 기본 개념과 PagedAttention 최적화 메커니즘 (0) | 2026.08.09 |