포스트

2026년 5월 기준 vLLM·TGI·Ollama 배포 실전 가이드: “로컬→프로덕션” 서빙 인프라/최적화 의사결정까지

2026년 5월 기준 vLLM·TGI·Ollama 배포 실전 가이드: “로컬→프로덕션” 서빙 인프라/최적화 의사결정까지

들어가며

2026년 5월 현재 LLM 서빙에서 가장 자주 터지는 문제는 한 가지로 요약됩니다: “첫 토큰(TTFT)은 빠른데, 동시성이 조금만 올라가면 GPU 메모리(OOM)·tail latency·처리량이 무너진다” 입니다. 특히 RAG/Agent처럼 요청 길이(컨텍스트)와 출력 길이가 들쭉날쭉한 트래픽에서는 KV cache 압력이 급증하고, 배치 전략이 나쁘면 GPU가 놀거나(underutilization) 반대로 OOM으로 쓰러집니다.

  • 언제 쓰면 좋은가
    • 사내 데이터/규제 때문에 on-prem / VPC 내 LLM이 필요
    • OpenAI-compatible API로 앱을 빠르게 붙이고, 이후에 성능·비용 최적화를 하고 싶음
    • 단일 GPU 개발 환경(로컬)에서 시작해 Kubernetes/멀티 GPU로 확장할 가능성이 큼
  • 언제 쓰면 안 되는가
    • 트래픽이 작고(동시성 1~2), 모델도 작고, “그냥 빨리 돌아가면 됨”이면: 복잡한 vLLM/K8s 튜닝은 과투자일 수 있음 → Ollama가 맞는 경우가 많습니다.
    • 최고 성능이 목표인데 NVIDIA 하드웨어 고정 + 모델별 컴파일/빌드 파이프라인을 감수할 수 있으면: vLLM보다 TensorRT-LLM류가 더 맞을 수 있습니다(대신 락인/복잡도 증가).

이 글은 “소개”가 아니라, vLLM/TGI/Ollama 중 무엇을 어떤 기준으로 택하고, 어떤 배포 형태로 가며, 어디서 병목이 생기는지를 실무 관점에서 정리합니다. (참고로 Hugging Face 문서에 따르면 TGI는 “maintenance mode”로 전환되었고, HF는 vLLM/SGLang 등을 권장하는 방향을 명확히 했습니다.) (huggingface.co)


🔧 핵심 개념

1) KV cache가 서빙 인프라를 결정한다

LLM 추론의 핵심 비용은 “가중치”가 아니라 KV cache(Attention key/value) 입니다. 요청이 길어질수록, 그리고 동시 요청이 늘수록 KV가 GPU 메모리를 잠식합니다. 서빙 엔진의 본질적 차이는 “배치/스케줄링”과 “KV를 어떻게 관리하느냐”로 갈립니다.

  • Ollama(llama.cpp 계열): 로컬 친화. 모델 관리(pull/run)와 단일 머신 운영 UX가 좋음. 대신 고동시성에서 continuous batching / KV 효율이 vLLM 계열보다 약해지는 경향.
  • TGI: continuous batching, TP(tensor parallel), SSE streaming, Prometheus 등 “프로덕션 기능”을 오래 제공. 하지만 HF 문서에서 유지보수 모드로 명시했고, 신규 최적화 흐름의 중심은 vLLM/SGLang 쪽으로 이동. (huggingface.co)
  • vLLM: 고동시성에서 강함. 핵심은 PagedAttention + continuous batching. KV를 “연속 메모리 덩어리”로 잡아두는 대신, OS 페이지처럼 paged block으로 쪼개 관리해 fragmentation과 낭비를 줄입니다(결과적으로 동시성/긴 컨텍스트에서 효율이 좋음). vLLM의 K8s 배포 가이드/production-stack도 공식 문서로 정리되어 있습니다. (docs.vllm.ai)

2) continuous batching의 ‘진짜’ 의미

단순 배치는 “요청 N개 모아서 한 번에”지만, LLM은 요청마다 생성 길이가 다릅니다. continuous batching긴 요청이 배치를 독점하지 않게 하고, 새 요청을 틈틈이 끼워 넣어 GPU utilization을 올립니다. 이게 체감되는 지점이 보통 동시성 3 이상부터입니다(그 이하에선 체감이 작음).

3) vLLM vs TGI: “아키텍처의 무게 중심”

  • vLLM은 OpenAI-compatible 서버를 기본으로 제공하고, production-stack/Helm/K8s 패턴이 공식 문서로 점점 표준화되는 흐름입니다. (docs.vllm.ai)
  • TGI는 여전히 문서/기능이 탄탄하지만, “유지보수 모드” 선언 자체가 장기 운영 결정에 큰 변수입니다. (huggingface.co)
  • 연구/벤치에서도 고동시성 처리량에서 vLLM 우세가 반복 보고됩니다(다만 단일 사용자 인터랙티브에선 TGI tail latency가 유리한 케이스도 있다는 보고가 있습니다). (arxiv.org)

4) 로컬 배포에서 중요한 건 “성능”보다 “운영 경계”

로컬/단일 노드에서 가장 흔한 장애는 모델 자체가 아니라:

  • 모델 파일 저장소/권한/영속 볼륨
  • API 포트가 실수로 외부에 노출
  • reverse proxy(WebSocket/SSE 헤더) 설정 미스
  • “모델을 RAM/VRAM에 유지”하려다 메모리 압살

Ollama 커뮤니티에서도 OLLAMA_KEEP_ALIVE 같은 파라미터, “포트를 localhost에만 바인딩” 같은 운영 팁이 반복해서 등장합니다. (reddit.com)


💻 실전 코드

아래는 “toy”가 아니라, 실제로 많이 쓰는 형태인 (1) 로컬 단일 노드: Ollama + OpenWebUI + Nginx(선택) + 영속 스토리지(2) 프로덕션: vLLM OpenAI-compatible + K8s readiness + 리소스 튜닝의 2단 빌드업 예제입니다.

(1) 로컬/단일 노드: Ollama를 “내부망 API”로 안전하게 띄우기 (Docker Compose)

요구사항:

  • GPU 서버 1대(개발/POC)
  • 모델은 디스크에 영속 저장
  • API는 외부 노출 금지(reverse proxy만 공개)
1
2
3
4
5
# 0) 사전 준비(호스트)
# - NVIDIA 드라이버 + NVIDIA Container Toolkit 설치되어 있어야 함
# - 방화벽에서 11434 직접 오픈 금지 권장

mkdir -p ./ollama-data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# docker-compose.yaml
services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    # 핵심: 포트는 localhost에만 바인딩해서 외부 노출 차단
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ./ollama-data:/root/.ollama
    environment:
      # 모델을 메모리에 오래 유지(지연 감소) — 대신 VRAM/RAM 점유 증가 주의
      - OLLAMA_KEEP_ALIVE=30m
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: ["gpu"]

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    restart: unless-stopped
    ports:
      - "3000:8080"
    environment:
      # OpenWebUI가 같은 호스트의 Ollama로 접근(여기서는 compose 네트워크가 아니라 host 127.0.0.1 사용)
      # 필요하면 별도 reverse proxy 구성에서 변경
      - OLLAMA_BASE_URL=http://host.docker.internal:11434
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1) 실행
docker compose up -d

# 2) 모델 pull (API로 당기기보다 서버 셋업 단계에서 pull 권장 패턴이 자주 언급됨)
docker exec -it ollama ollama pull llama3.1:8b

# 3) 로컬 호출 테스트
curl -s http://127.0.0.1:11434/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama3.1:8b",
    "prompt": "우리 서비스의 장애 원인 분석을 5줄로 요약해줘",
    "stream": false,
    "options": { "temperature": 0.2 }
  }' | jq .

예상 포인트:

  • 127.0.0.1 바인딩으로 Ollama API가 외부로 직접 노출되지 않음(실무에서 가장 중요한 기본기).
  • OLLAMA_KEEP_ALIVE는 지연을 줄이지만, 트래픽/모델 크기에 따라 메모리 압박이 커집니다(“항상 -1로 고정” 같은 접근은 위험).

(이 “localhost 바인딩 + reverse proxy로만 공개” 패턴은 커뮤니티의 production-ready 가이드에서도 반복적으로 강조됩니다.) (reddit.com)

(2) 프로덕션/확장: vLLM OpenAI-compatible 서버를 K8s로 배포

요구사항:

  • 앱이 OpenAI SDK를 이미 사용 중 → drop-in 교체
  • readiness/liveness로 롤링 업데이트 안정성 확보
  • KV cache OOM 방지를 위한 max-model-len / gpu-memory-utilization 튜닝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# vllm-deployment.yaml (핵심만 발췌)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-openai
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-openai
  template:
    metadata:
      labels:
        app: vllm-openai
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model"
            - "Qwen/Qwen2.5-32B-Instruct"
            - "--host"
            - "0.0.0.0"
            - "--port"
            - "8000"
            # 실무 튜닝 포인트 1) 컨텍스트 제한(무한정 늘리면 KV로 죽음)
            - "--max-model-len"
            - "8192"
            # 실무 튜닝 포인트 2) VRAM을 100% 다 쓰지 말고 헤드룸 확보
            - "--gpu-memory-utilization"
            - "0.90"
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: "1"
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 20
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-openai-svc
spec:
  selector:
    app: vllm-openai
  ports:
    - name: http
      port: 80
      targetPort: 8000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 적용
kubectl apply -f vllm-deployment.yaml

# OpenAI-compatible 호출(서비스 IP/Ingress에 맞게 BASE_URL만 바꾸면 됨)
export VLLM_BASE_URL="http://$(kubectl get svc vllm-openai-svc -o jsonpath='{.spec.clusterIP}')"
curl -s "$VLLM_BASE_URL/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
    "model":"Qwen/Qwen2.5-32B-Instruct",
    "messages":[
      {"role":"system","content":"You are a senior SRE."},
      {"role":"user","content":"장애 대응 runbook을 7개 체크리스트로 만들어줘."}
    ],
    "temperature":0.2,
    "stream":false
  }' | jq .
  • vLLM의 K8s 배포(health probe/production-stack 흐름)는 공식 문서에 정리되어 있고, “NOT_SERVING” 등 상태 기반 운영을 전제로 합니다. (docs.vllm.ai)
  • 프로덕션에서는 latest 대신 이미지 태그 pinning이 기본(재현성/롤백). 또한 --max-model-len, --gpu-memory-utilization은 “OOM vs 동시성”의 직접 레버입니다. (실무 가이드에서도 이 두 옵션을 핵심으로 꼽습니다.) (sitepoint.com)

⚡ 실전 팁 & 함정

Best Practice (2~3개)

1) 동시성 기준으로 엔진을 고르기

  • 동시성 1~2, 로컬 UX/간편 배포가 중요: Ollama
  • 동시성 3+, RAG/Agent로 요청 패턴이 다양: vLLM
  • 이미 TGI를 운영 중이고 기능/모델 호환이 문제 없으면 유지 가능하지만, 장기 신규 투자에는 “maintenance mode” 리스크를 반영하세요. (huggingface.co)

2) KV cache 관측이 없으면 튜닝이 아니다 CPU/GPU utilization만 보면 “왜 느린지”가 안 나옵니다. 최소한 다음을 대시보드로 보세요:

  • TTFT p95/p99
  • tokens/sec (prefill vs decode 분리 가능하면 더 좋음)
  • queue depth(요청 대기열)
  • KV cache 사용률/여유(엔진별 노출 방식은 다름)

3) 컨텍스트를 ‘제품 정책’으로 관리 “유저가 긴 문서를 넣는다”는 요구가 있으면, 무작정 max-model-len을 올리는 게 아니라:

  • 업로드/프롬프트를 서버에서 chunking
  • RAG로 대체
  • long-context 전용 모델/풀로 분리(다른 GPU에 격리) 같은 아키텍처 레벨로 푸는 게 비용/안정성 측면에서 유리합니다.

흔한 함정/안티패턴

  • Ollama API(11434)를 공인망에 그대로 노출
    • 인증/레이트리밋/감사로그가 빈약한 상태에서 바로 뚫립니다.
    • 최소한 localhost 바인딩 + reverse proxy(+auth)로 경계를 세우세요(커뮤니티 production-ready 글에서도 반복). (reddit.com)
  • vLLM에서 gpu-memory-utilization을 1.0에 가깝게
    • “벤치 점수”는 올라가도, 실서비스에선 burst/긴 컨텍스트에서 OOM로 바로 장애가 납니다.
    • 0.85~0.92 사이에서 안전마진을 두고, 부족하면 scale-out/모델 축소/quantization을 먼저 검토하는 편이 낫습니다. (프로덕션 가이드에서도 이 옵션을 핵심 레버로 언급) (sitepoint.com)
  • TGI 장기 운영에서 로드맵을 무시
    • 당장 잘 돌아가도, 유지보수 모드 선언은 “새 최적화/신기능/핫픽스”의 속도에 영향을 줍니다. 신규 표준이 vLLM/SGLang 쪽으로 이동 중이라는 시그널로 해석해야 합니다. (huggingface.co)

비용/성능/안정성 트레이드오프(현실적인 결론)

  • Ollama: “사람 시간(운영 단순성)” 절약 ↔ “고동시성 처리량” 손해
  • vLLM: “GPU당 처리량/동시성” 이득 ↔ “튜닝/관측/배포 복잡도” 증가
  • TGI: “성숙한 기능” 이득 ↔ “유지보수 모드”로 장기 투자 리스크

🚀 마무리

핵심 정리:

  • 2026년 5월 기준, 프로덕션 LLM 서빙의 병목은 KV cache와 배치/스케줄링이고, 이 축에서 vLLM이 가장 강한 선택지가 되는 경우가 많습니다. (docs.vllm.ai)
  • Ollama는 로컬/단일 노드에서 “안전한 경계(포트/프록시) + 영속 스토리지”를 먼저 갖추면 개발·POC 생산성이 매우 좋습니다. (reddit.com)
  • TGI는 유지보수 모드이므로, 지금 새로 표준을 잡는다면 vLLM 중심으로 설계하고, 필요하면 일부 워크로드만 TGI로 남기는 식의 현실적인 절충이 가능합니다. (huggingface.co)

도입 판단 기준(바로 적용용): 1) 동시성 3 이상(혹은 향후 올라갈 계획) + RAG/Agent + tail latency 민감 → vLLM 2) 내부 툴/개발환경/개인 GPU 워크스테이션 + “설치/모델 관리가 제일 중요” → Ollama 3) 이미 TGI 기반 운영 중이고, 가시성/TP/모델 호환이 잘 맞고, 당장 마이그레이션 비용이 크다 → TGI 유지 + vLLM로 신규 워크로드부터 분리

다음 학습 추천:

  • vLLM 공식 K8s 배포 가이드와 production-stack(Helm) 흐름을 먼저 훑고(프로브/리소스/업데이트 전략), (docs.vllm.ai)
  • TGI를 쓴다면 “maintenance mode” 전제에서 어디까지 가져갈지(새 기능 의존도를 낮추기) 전략을 세우세요. (huggingface.co)

원하면 다음 단계로, 당신의 환경(모델 크기, GPU 종류/개수, 예상 QPS, 평균 입력/출력 토큰, RAG 여부)을 기준으로 vLLM 파라미터(텐서 병렬/컨텍스트/메모리/스케줄러)와 K8s 오토스케일 지표(큐 깊이·TTFT·KV 압력)까지 포함한 “실제 배포 설계안” 형태로 구체화해드릴게요.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.