[워크삽] AWS 트레니엄 워크삽(12)-성능 테스트
이번에는 vLLM 배포에 대한 종합적인 성능 테스트와 성능 검증을 수행한다. 실제와 유사한 워크로드를 시뮬레이션하고 성능 특성을 측정하기 위해 다양한 도구를 사용한다. 여기에는 여러 플랫폼과 구성에서 LLM 추론 성능을 평가하는 업계 표준 벤치마킹 도구인 llmperf가 포함된다.
1. 사전 요구 사항
이전 실습에서 모니터링을 활성화한 vLLM 배포가 실행 중인지 확인한다.
2. 환경변수 설정
export AWS_REGION=us-west-2
export CLUSTER_NAME=vllm-trn1-eks-cluster
export MONITORING_NAMESPACE=monitoring
# Get the vLLM service endpoint
export VLLM_ENDPOINT=$(kubectl get service vllm-service -n default -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
export VLLM_NAMESPACE=default
export VLLM_URL="http://$VLLM_ENDPOINT:8080/v1"
echo "vLLM Endpoint: $VLLM_URL"
echo "vLLM Namespace: $VLLM_NAMESPACE"
3. 성능 테스트 도구 설치
전용 네임스페이스를 생성하고 성능 테스트 도구를 설치한다.
# Create namespace for testing
kubectl create namespace performance-testing
# Create a performance testing pod with necessary tools
cat > performance-test-pod.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: performance-test-runner
namespace: performance-testing
spec:
containers:
- name: performance-tester
image: python:3.10-slim
command: ["sleep", "infinity"]
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 4Gi
volumeMounts:
- name: test-scripts
mountPath: /scripts
volumes:
- name: test-scripts
emptyDir: {}
restartPolicy: Never
EOF
kubectl apply -f performance-test-pod.yaml
# Wait for pod to be ready
kubectl wait --for=condition=ready pod/performance-test-runner -n performance-testing --timeout=120s
4. 테스트 종속성 설치
성능 테스트 Pod에 필요한 Python 패키지를 설치한다.
kubectl exec -it performance-test-runner -n performance-testing -- bash -c "
pip install requests asyncio aiohttp numpy matplotlib pandas locust
"
5. 기본 부하 테스트 스크립트 생성
기본 기능을 검증하기 위한 간단한 부하 테스트 스크립트를 생성한다.
cat > basic_load_test.py <<'EOF'
#!/usr/bin/env python3
import requests
import time
import concurrent.futures
import statistics
import json
from datetime import datetime
class VLLMLoadTester:
def __init__(self, base_url, max_workers=10):
self.base_url = base_url.rstrip('/')
self.max_workers = max_workers
self.results = []
def single_request(self, request_id):
"""Send a single completion request"""
start_time = time.time()
try:
response = requests.post(
f"{self.base_url}/completions",
headers={"Content-Type": "application/json"},
json={
"model": "tinyLlama/TinyLlama-1.1B-Chat-v1.0",
"prompt": f"Request {request_id}: Tell me about artificial intelligence",
"max_tokens": 100,
"temperature": 0.7
},
timeout=60
)
end_time = time.time()
if response.status_code == 200:
return {
"request_id": request_id,
"status": "success",
"latency": end_time - start_time,
"tokens": len(response.json().get("choices", [{}])[0].get("text", "").split()),
"timestamp": datetime.now().isoformat()
}
else:
return {
"request_id": request_id,
"status": "error",
"latency": end_time - start_time,
"error_code": response.status_code,
"timestamp": datetime.now().isoformat()
}
except Exception as e:
end_time = time.time()
return {
"request_id": request_id,
"status": "exception",
"latency": end_time - start_time,
"error": str(e),
"timestamp": datetime.now().isoformat()
}
def run_load_test(self, total_requests=100, duration_seconds=None):
"""Run load test with specified parameters"""
print(f"Starting load test with {self.max_workers} workers")
print(f"Target: {self.base_url}")
start_time = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
if duration_seconds:
# Duration-based testing
request_id = 0
futures = []
while time.time() - start_time < duration_seconds:
future = executor.submit(self.single_request, request_id)
futures.append(future)
request_id += 1
time.sleep(0.1) # Small delay between request submissions
# Wait for all requests to complete
for future in concurrent.futures.as_completed(futures):
self.results.append(future.result())
else:
# Request count-based testing
futures = [executor.submit(self.single_request, i) for i in range(total_requests)]
for future in concurrent.futures.as_completed(futures):
self.results.append(future.result())
if len(self.results) % 10 == 0:
print(f"Completed {len(self.results)}/{total_requests} requests")
self.analyze_results()
def analyze_results(self):
"""Analyze and print test results"""
if not self.results:
print("No results to analyze")
return
successful_requests = [r for r in self.results if r["status"] == "success"]
failed_requests = [r for r in self.results if r["status"] != "success"]
if successful_requests:
latencies = [r["latency"] for r in successful_requests]
tokens_per_request = [r.get("tokens", 0) for r in successful_requests if "tokens" in r]
print("\n=== LOAD TEST RESULTS ===")
print(f"Total Requests: {len(self.results)}")
print(f"Successful: {len(successful_requests)} ({len(successful_requests)/len(self.results)*100:.1f}%)")
print(f"Failed: {len(failed_requests)} ({len(failed_requests)/len(self.results)*100:.1f}%)")
print(f"\n=== LATENCY STATISTICS ===")
print(f"Average Latency: {statistics.mean(latencies):.2f}s")
print(f"Median Latency: {statistics.median(latencies):.2f}s")
print(f"95th Percentile: {sorted(latencies)[int(len(latencies)*0.95)]:.2f}s")
print(f"99th Percentile: {sorted(latencies)[int(len(latencies)*0.99)]:.2f}s")
print(f"Min Latency: {min(latencies):.2f}s")
print(f"Max Latency: {max(latencies):.2f}s")
if tokens_per_request:
print(f"\n=== TOKEN STATISTICS ===")
print(f"Average Tokens per Response: {statistics.mean(tokens_per_request):.1f}")
total_tokens = sum(tokens_per_request)
total_time = sum(latencies)
print(f"Tokens per Second: {total_tokens/total_time:.1f}")
if failed_requests:
print(f"\n=== FAILURE ANALYSIS ===")
error_types = {}
for req in failed_requests:
error_type = req.get("error_code", req.get("error", "unknown"))
error_types[error_type] = error_types.get(error_type, 0) + 1
for error, count in error_types.items():
print(f"{error}: {count} requests")
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python basic_load_test.py <vllm_url> [requests] [workers]")
sys.exit(1)
base_url = sys.argv[1]
total_requests = int(sys.argv[2]) if len(sys.argv) > 2 else 50
max_workers = int(sys.argv[3]) if len(sys.argv) > 3 else 5
tester = VLLMLoadTester(base_url, max_workers)
tester.run_load_test(total_requests=total_requests)
EOF
# Copy the script to the performance testing pod
kubectl cp basic_load_test.py performance-testing/performance-test-runner:/scripts/
6. 모니터링용 Grafana 대시보드 열기
부하 테스트를 실행하기 전에 Grafana 대시보드를 열어 vLLM 지표를 실시간으로 모니터링한다.
이 대시보드에는 다음 항목이 표시된다.
- 요청 처리량과 성공률
- 토큰 생성 지표
- Neuron 캐시 사용량
- 실행 중인 요청과 대기 중인 요청
부하 테스트를 실행하는 동안 이 대시보드를 열어 두고 성능 지표를 실시간으로 관찰한다.
7. 기본 부하 테스트 실행
기능을 검증하기 위한 기본 부하 테스트를 실행한다.
echo "Running basic load test..."
kubectl exec -it performance-test-runner -n performance-testing -- python /scripts/basic_load_test.py $VLLM_URL 30 5
8. llmperf를 사용한 업계 성능 벤치마킹
종합적인 성능 평가를 위해 llmperf로 업계 표준 벤치마크를 실행한다.
8-1. 성능 테스트 Pod에 llmperf 설치
성능 테스트 Pod에 llmperf와 관련 종속성을 설치한다.
# Install llmperf in the performance testing pod
kubectl exec -it performance-test-runner -n performance-testing -- bash -c "
pip install --upgrade pip && \
apt-get update && apt-get install -y git && \
cd /tmp && \
git clone https://github.com/ray-project/llmperf.git && \
cd llmperf && \
pip install ray && \
pip install -e .
"
8-2. llmperf 토큰 벤치마크 테스트 실행
실제와 유사한 매개변수로 토큰 벤치마크 테스트를 실행한다.
# Run llmperf token benchmark test
echo "Running llmperf token benchmark..."
kubectl exec -it performance-test-runner -n performance-testing -- bash -c "
cd /tmp/llmperf && \
export OPENAI_API_KEY=EMPTY && \
export OPENAI_API_BASE=$VLLM_URL && \
python token_benchmark_ray.py \
--model 'tinyLlama/TinyLlama-1.1B-Chat-v1.0' \
--mean-input-tokens 256 \
--stddev-input-tokens 50 \
--mean-output-tokens 100 \
--stddev-output-tokens 20 \
--max-num-completed-requests 50 \
--timeout 600 \
--num-concurrent-requests 5 \
--results-dir 'result_outputs' \
--llm-api openai \
--additional-sampling-params '{\"temperature\": 0.7}'
"
8-3. llmperf 벤치마크 결과 확인
벤치마크 결과를 표시하고 분석한다.
# Display results
echo "llmperf benchmark results:"
kubectl exec -it performance-test-runner -n performance-testing -- find /tmp/llmperf/result_outputs -name "*.json" -exec cat {} \;
9. 성능 테스트 리소스 정리
성능 테스트 네임스페이스를 정리한다.
echo "Cleaning up performance testing resources..."
kubectl delete namespace performance-testing
echo "performance testing cleanup completed!"
10. 성능 테스트 요약
이 성능 테스트 섹션에서는 다음 항목을 검증했다.
- 기본 기능: 단일 요청의 지연 시간과 정확성
- 업계 표준 벤치마킹: llmperf를 사용한 성능 검증
- 리소스 사용률: CPU, 메모리 및 Neuron 디바이스 사용량
- 모니터링 통합: Prometheus, Grafana 및 CloudWatch 지표 수집
- 엔드투엔드 관측 가능성: 부하와 모니터링 데이터 간 상관관계
11. 모니터링할 주요 지표
- 지연 시간(Latency): P50, P95 및 P99 응답 시간
- 처리량(Throughput): 초당 요청 수와 초당 토큰 수
- 오류율(Error Rate): 실패한 요청의 비율
- 리소스 사용량: CPU, 메모리 및 Neuron 사용률
- 스케일링 동작: 스케일링에 걸리는 시간과 효과
- 모니터링 상태: Prometheus, Grafana 및 CloudWatch 상태
- 알림 상태: 현재 발생한 알림과 알림 시스템 상태
12. 다음 단계
- 성능 검증을 완료했으면 HPA 실습으로 이동해 vLLM 배포의 자동 스케일링을 구성한다.
댓글남기기