콘텐츠로 이동

LLM 프로덕션 인프라 가이드

본 문서는 빈집 챗봇 프로젝트 운영 경험을 기반으로 LLM 서비스의 프로덕션 인프라 구성을 다룬다.


1. 시스템 아키텍처 개요

1.1 전체 구성도

                                    [Load Balancer]
                                          |
                    +---------------------+---------------------+
                    |                     |                     |
              [API Server 1]        [API Server 2]        [API Server N]
                    |                     |                     |
                    +---------------------+---------------------+
                                          |
                    +---------------------+---------------------+
                    |                     |                     |
              [Redis Cluster]    [Elasticsearch]         [PostgreSQL]
              (Cache/Session)      (Vector Search)         (Metadata)
                                          |
                    +---------------------+---------------------+
                    |                                           |
           [vLLM Server Pool]                        [Triton Inference Server]
           (Text Generation)                          (Embedding/Reranker)
                    |                                           |
              [GPU Cluster]                              [GPU Cluster]
              (A100/H100)                                (T4/A10G)

1.2 데이터 흐름

User Request
     |
     v
[Rate Limiter] --> [Auth] --> [Request Validator]
     |
     v
[Cache Check] --hit--> [Return Cached Response]
     |
     miss
     v
[Context Retrieval] --> [Elasticsearch/Vector DB]
     |
     v
[Prompt Construction]
     |
     v
[LLM Inference] --> [vLLM/Triton]
     |
     v
[Response Processing] --> [Cache Update] --> [Return Response]

2. 컴포넌트별 상세

2.1 API 서버

항목 권장 사양 비고
Framework FastAPI 비동기 처리, OpenAPI 자동 생성
ASGI Server Uvicorn + Gunicorn worker 수 = CPU core * 2 + 1
인스턴스 2+ (HA 구성) Auto-scaling 권장
CPU 4 vCPU 이상 전처리/후처리 연산용
Memory 8GB 이상 요청 버퍼링, 임시 데이터

주요 책임: - 인증/인가 처리 - 요청 검증 및 라우팅 - Rate Limiting - 응답 스트리밍 (SSE) - 로깅 및 메트릭 수집

2.2 vLLM 서버

항목 권장 사양 비고
GPU A100 80GB / H100 모델 크기에 따라 선택
GPU Memory 40GB+ 7B 모델 기준, KV Cache 포함
CPU 16 vCPU 토크나이징, 배치 처리
Memory 64GB+ 모델 로딩, 배치 큐
Storage NVMe SSD 500GB+ 모델 파일, 로그

핵심 설정:

# vLLM 서버 실행 예시
vllm serve meta-llama/Llama-3.1-8B-Instruct \
    --host 0.0.0.0 \
    --port 8000 \
    --tensor-parallel-size 2 \
    --max-model-len 8192 \
    --gpu-memory-utilization 0.9 \
    --enable-chunked-prefill \
    --max-num-batched-tokens 32768

성능 튜닝 포인트: - gpu-memory-utilization: 0.85-0.95 (OOM 방지 vs 처리량) - max-num-batched-tokens: 배치 크기 조절 - tensor-parallel-size: 다중 GPU 병렬화

2.3 Triton Inference Server

용도 모델 GPU 권장
Embedding bge-m3, e5-large T4, A10G
Reranker bge-reranker-v2 T4, A10G
Classifier Custom BERT T4

모델 저장소 구조:

model_repository/
├── embedding/
│   ├── config.pbtxt
│   └── 1/
│       └── model.onnx
├── reranker/
│   ├── config.pbtxt
│   └── 1/
│       └── model.plan  # TensorRT 최적화
└── classifier/
    ├── config.pbtxt
    └── 1/
        └── model.pt

config.pbtxt 예시 (Embedding):

name: "embedding"
platform: "onnxruntime_onnx"
max_batch_size: 64
input [
  {
    name: "input_ids"
    data_type: TYPE_INT64
    dims: [ -1 ]
  },
  {
    name: "attention_mask"
    data_type: TYPE_INT64
    dims: [ -1 ]
  }
]
output [
  {
    name: "embeddings"
    data_type: TYPE_FP32
    dims: [ 1024 ]
  }
]
instance_group [
  {
    count: 2
    kind: KIND_GPU
  }
]
dynamic_batching {
  preferred_batch_size: [ 16, 32, 64 ]
  max_queue_delay_microseconds: 100000
}

2.4 Redis Cluster

용도 데이터 구조 TTL
응답 캐시 String (JSON) 1-24시간
세션 관리 Hash 30분
Rate Limit Sorted Set 1분
대화 히스토리 List 24시간

클러스터 구성:

[Master 1] --- [Replica 1]
[Master 2] --- [Replica 2]
[Master 3] --- [Replica 3]

캐시 키 설계:

# 응답 캐시
chat:response:{hash(prompt + context)}

# 세션
session:{user_id}:{session_id}

# Rate Limit
ratelimit:{user_id}:{window}

# 대화 히스토리
history:{conversation_id}

항목 권장 값 비고
노드 수 3+ HA 구성
Shard 수 인덱스당 3-5 데이터 크기에 따라
Replica 1 가용성 확보
Heap 32GB 이하 JVM GC 최적화

인덱스 매핑 예시:

{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "nori"
      },
      "embedding": {
        "type": "dense_vector",
        "dims": 1024,
        "index": true,
        "similarity": "cosine"
      },
      "metadata": {
        "type": "object",
        "properties": {
          "source": { "type": "keyword" },
          "category": { "type": "keyword" },
          "timestamp": { "type": "date" }
        }
      }
    }
  }
}


3. Docker Compose 구성

3.1 개발/스테이징 환경

version: '3.8'

services:
  # API Server
  api:
    build:
      context: .
      dockerfile: Dockerfile.api
    ports:
      - "8080:8080"
    environment:
      - VLLM_URL=http://vllm:8000
      - TRITON_URL=triton:8001
      - REDIS_URL=redis://redis:6379
      - ES_URL=http://elasticsearch:9200
    depends_on:
      - redis
      - elasticsearch
      - vllm
      - triton
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '4'
          memory: 8G

  # vLLM Server
  vllm:
    image: vllm/vllm-openai:latest
    ports:
      - "8000:8000"
    volumes:
      - ./models:/models
      - ./cache:/root/.cache
    environment:
      - CUDA_VISIBLE_DEVICES=0,1
    command: >
      --model /models/llama-3.1-8b-instruct
      --tensor-parallel-size 2
      --max-model-len 8192
      --gpu-memory-utilization 0.9
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 2
              capabilities: [gpu]

  # Triton Inference Server
  triton:
    image: nvcr.io/nvidia/tritonserver:24.01-py3
    ports:
      - "8001:8001"  # gRPC
      - "8002:8002"  # Metrics
    volumes:
      - ./model_repository:/models
    command: >
      tritonserver
      --model-repository=/models
      --strict-model-config=false
      --log-verbose=1
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

  # Redis
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru

  # Elasticsearch
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    ports:
      - "9200:9200"
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms4g -Xmx4g"
    volumes:
      - es_data:/usr/share/elasticsearch/data

  # Prometheus
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus

  # Grafana
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards

volumes:
  redis_data:
  es_data:
  prometheus_data:
  grafana_data:

3.2 프로덕션 환경 (Kubernetes 권장)

프로덕션에서는 Docker Compose 대신 Kubernetes를 권장한다.

핵심 리소스: - Deployment: API 서버, 무상태 워커 - StatefulSet: Elasticsearch, Redis Cluster - DaemonSet: 로그 수집기 (Fluent Bit) - HPA: API 서버 오토스케일링 - PDB: 가용성 보장


4. 스케일링 전략

4.1 수평 확장 (Horizontal Scaling)

컴포넌트 스케일링 방식 트리거 조건
API Server HPA CPU > 70%, RPS > threshold
vLLM 수동/예약 GPU 사용률 > 80%
Triton HPA (GPU) 큐 대기 시간 > 100ms
Redis 샤딩 추가 메모리 > 80%
ES 노드 추가 디스크 > 75%

4.2 수직 확장 (Vertical Scaling)

vLLM 성능 향상 경로:
A10G (24GB) -> A100 (40GB) -> A100 (80GB) -> H100 (80GB)
     |              |              |              |
   7B 모델      13B 모델       70B 모델      70B + 긴 컨텍스트

4.3 배치 최적화

# 동적 배치 설정
BATCH_CONFIG = {
    "max_batch_size": 32,
    "max_wait_time_ms": 50,
    "preferred_batch_sizes": [8, 16, 32],
}

# 요청 큐잉 전략
async def batch_inference(requests: list[Request]):
    """
    요청을 배치로 모아 처리.
    latency vs throughput 트레이드오프 조절.
    """
    batch = []
    deadline = time.time() + BATCH_CONFIG["max_wait_time_ms"] / 1000

    while len(batch) < BATCH_CONFIG["max_batch_size"]:
        if time.time() > deadline:
            break
        try:
            req = await asyncio.wait_for(
                request_queue.get(),
                timeout=deadline - time.time()
            )
            batch.append(req)
        except asyncio.TimeoutError:
            break

    return await process_batch(batch)

5. 모니터링 구성

5.1 메트릭 수집 체계

[Application] --> [Prometheus] --> [Grafana]
     |                 |
     |            [AlertManager]
     |                 |
     v                 v
[OpenTelemetry] --> [Jaeger]    [PagerDuty/Slack]

5.2 핵심 메트릭

카테고리 메트릭 임계값 알림 레벨
Latency p50, p95, p99 p95 > 3s Warning
Throughput requests/sec < 10 RPS Critical
Error Rate 5xx/total > 1% Critical
GPU utilization, memory > 95% Warning
Cache hit rate < 70% Warning
Queue depth, wait time depth > 100 Warning

5.3 Prometheus 설정

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alerts/*.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["alertmanager:9093"]

scrape_configs:
  - job_name: 'api'
    static_configs:
      - targets: ['api:8080']
    metrics_path: /metrics

  - job_name: 'vllm'
    static_configs:
      - targets: ['vllm:8000']
    metrics_path: /metrics

  - job_name: 'triton'
    static_configs:
      - targets: ['triton:8002']
    metrics_path: /metrics

  - job_name: 'redis'
    static_configs:
      - targets: ['redis-exporter:9121']

  - job_name: 'elasticsearch'
    static_configs:
      - targets: ['es-exporter:9114']

5.4 알림 규칙

# alerts/llm-alerts.yml
groups:
  - name: llm-service
    rules:
      - alert: HighLatency
        expr: histogram_quantile(0.95, rate(request_duration_seconds_bucket[5m])) > 3
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High latency detected"
          description: "P95 latency is {{ $value }}s"

      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.01
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High error rate"
          description: "Error rate is {{ $value | humanizePercentage }}"

      - alert: GPUMemoryHigh
        expr: nvidia_gpu_memory_used_bytes / nvidia_gpu_memory_total_bytes > 0.95
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "GPU memory usage high"

      - alert: VLLMQueueBacklog
        expr: vllm_num_requests_waiting > 50
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "vLLM request queue backlog"

5.5 Grafana 대시보드 구성

권장 패널:

  1. Overview
  2. 총 요청 수 (24h)
  3. 평균 응답 시간
  4. 에러율
  5. 활성 사용자 수

  6. Inference Performance

  7. Token throughput (tokens/sec)
  8. Time to First Token (TTFT)
  9. Time Per Output Token (TPOT)
  10. 배치 크기 분포

  11. Resource Utilization

  12. GPU 사용률 (per device)
  13. GPU 메모리 (used/total)
  14. CPU/Memory (API 서버)
  15. 네트워크 I/O

  16. Cache & Search

  17. Redis hit/miss rate
  18. ES query latency
  19. ES indexing rate

6. 운영 체크리스트

6.1 배포 전

  • [ ] 부하 테스트 완료 (목표 RPS의 2배)
  • [ ] 장애 복구 시나리오 테스트
  • [ ] 롤백 절차 문서화
  • [ ] 모니터링 알림 설정
  • [ ] 보안 점검 (인증, 암호화, 접근 제어)

6.2 운영 중

  • [ ] 일일 메트릭 리뷰
  • [ ] 주간 용량 계획 점검
  • [ ] 월간 비용 최적화 검토
  • [ ] 분기별 아키텍처 리뷰

6.3 장애 대응

1. 탐지 (Alert)
   |
2. 분류 (Severity 판단)
   |
3. 대응 (Runbook 실행)
   |
4. 복구 (서비스 정상화)
   |
5. 분석 (RCA 작성)
   |
6. 개선 (재발 방지)

부록: 참고 자료

주제 링크
vLLM Documentation https://docs.vllm.ai
Triton Inference Server https://github.com/triton-inference-server
Prometheus Best Practices https://prometheus.io/docs/practices
Kubernetes Patterns https://k8spatterns.io

문서 버전: 1.0 최종 수정: 2025-01