1개의 GPU에서 3개의 LLM 서빙하기 - OKE에서 Docker를 이용한 멀티 모델 추론 (Multi-Model Inference)
요약
단일 GPU 환경에서 여러 LLM을 효율적으로 서빙하기 위한 기술적 방법을 다룹니다. vLLM의 LoRA 어댑터 활용법과 Ollama를 이용한 모델 로딩/언로딩 자동화 방식을 비교 설명합니다.
핵심 포인트
- vLLM 프로세스를 개별 실행 시 GPU 메모리 점유 문제로 충돌 발생
- 동일 베이스 모델 사용 시 vLLM의 LoRA 어댑터 서빙으로 VRAM 절약 가능
- 서로 다른 베이스 모델의 경우 Ollama의 자동 로딩/언로딩 기능 활용 권장
저는 서빙하고 싶은 세 가지 작은 모델을 가지고 있었습니다: 일반 채팅을 위한 Phi-3-mini, 코드 제안을 위한 CodeLlama-7B, 그리고 문서 요약을 위해 미세 조정(Fine-tuned)된 Mistral입니다. 각 모델은 약 5-6GB의 VRAM에 들어갑니다. A10 GPU는 24GB를 보유하고 있습니다. 세 개의 모델, 하나의 GPU, 여유 공간이 충분합니다.
각각 전체 GPU를 요청하는 세 개의 별도 vLLM 배포(Deployment)를 실행하면 비용이 3배로 들고 18GB의 VRAM을 낭비하게 됩니다. 그래서 저는 하나의 컨테이너에서 세 모델을 모두 서빙하는 방법을 찾아냈습니다.
단순한 접근 방식 (그리고 그것이 작동하지 않은 이유)
저의 첫 번째 아이디어는 간단했습니다: 하나의 포드(Pod)에서 세 개의 vLLM 프로세스를 실행하고, 각각 다른 포트에 바인딩하는 것이었습니다.
# 이렇게 하지 마세요
vllm serve microsoft/Phi-3-mini-4k-instruct --port 8001 &
vllm serve codellama/CodeLlama-7b-Instruct-hf --port 8002 &
...
이 방식은 작동하지 않습니다. 왜냐하면 각 vLLM 프로세스가 GPU 전체를 점유하려고 시도하기 때문입니다. 첫 번째 프로세스가 이미 모든 VRAM을 할당했기 때문에 두 번째 프로세스는 CUDA 메모리 부족(Out-of-memory) 오류와 함께 충돌합니다.
메모리를 나누기 위해 각 프로세스에 --gpu-memory-utilization 0.30을 설정할 수는 있지만, 메모리가 제한되면 vLLM의 성능이 크게 저하됩니다. 연속 배치(Continuous batching)가 효율적으로 작동할 수 없으며, vLLM을 빠르게 만드는 KV 캐시(KV cache) 공간을 잃게 됩니다.
실제로 작동하는 방식: LoRA 어댑터를 활용한 vLLM
만약 사용 중인 모델들이 동일한 베이스 모델(Base model)의 미세 조정(Fine-tuned) 버전이라면 (또는 그렇게 구조를 재편할 수 있다면), vLLM은 단일 베이스 모델 위에서 여러 개의 LoRA 어댑터를 서빙하는 것을 지원합니다. GPU 메모리에는 하나의 베이스 모델을 두고, 여러 개의 가벼운 어댑터를 필요에 따라 로드하는 방식입니다.
docker run --gpus all -p 8000:8000 \
-v /models:/models \
vllm/vllm-openai:latest \
...
클라이언트는 model 필드에서 어떤 어댑터를 사용할지 지정합니다:
# 채팅 모델
curl http://localhost:8000/v1/chat/completions \
-d '{"model": "chat", "messages": [...]}'
...
베이스 모델(Mistral 7B)은 약 14GB의 VRAM을 사용합니다. 각 LoRA 어댑터는 50-200MB만 추가합니다. 세 개의 어댑터 모두 24GB A10에서 쉽게 수용 가능합니다.
베이스 모델이 서로 다른 경우
만약 모델들이 동일한 베이스 모델의 LoRA 변형이 아니라면 (제 모델들도 원래는 그렇지 않았습니다), 두 가지 옵션이 있습니다:
옵션 A: 여러 모델을 사용하는 Ollama
Ollama는 모델 로딩/언로딩(loading/unloading)을 자동으로 처리합니다. 모델을 요청하면 GPU 메모리에 로드합니다. 메모리가 가득 차면 가장 최근에 사용되지 않은 모델을 방출(evict)합니다.
# ollama-deployment.yaml
apiVersion: apps/v1
kind: Deployment
...
배포 후 모델을 로드합니다:
OLLAMA_IP=$(kubectl get svc ollama-multi -o jsonpath='{.spec.clusterIP}')
curl http://$OLLAMA_IP:11434/api/pull -d '{"name": "phi3:mini"}'
...
단점: 콜드 모델(cold model)을 로드해야 할 때 모델 스와핑(swapping)에 5~15초가 소요됩니다. 한 번에 주로 하나의 모델만 사용하는 팀에게는 이 방식이 괜찮습니다. 하지만 세 모델을 동시에 사용한다면, 각 모델에 대한 첫 번째 요청에서 지연 시간(latency)이 발생합니다.
옵션 B: Triton Inference Server
NVIDIA Triton은 명시적인 메모리 할당을 통해 하나의 GPU에서 여러 모델을 서빙할 수 있습니다. 설정은 더 복잡하지만 세밀한 제어(fine-grained control)가 가능합니다:
# model_repository/
# ├── phi3/
# │ ├── config.pbtxt
...
FROM nvcr.io/nvidia/tritonserver:24.01-py3
COPY model_repository /models
CMD ["tritonserver", "--model-repository=/models", "--model-control-mode=explicit"]
Triton을 시도해 보았고, ONNX/TensorRT 모델의 경우 잘 작동했습니다. 일반적인 HuggingFace transformer 모델의 경우, 변환(conversion) 단계가 번거로움을 더합니다. 저는 결국 제 사용 사례에 맞춰 LoRA 방식을 선택했습니다.
나의 OKE 배포 방식
제 세 모델 중 두 개가 어차피 파인튜닝(fine-tuned)된 Mistral 변형이었기 때문에, vLLM + LoRA 방식을 선택했습니다. 세 번째 모델(코드 모델)도 동일한 Mistral 베이스를 사용하는 LoRA로 다시 학습시켰습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
...
비용 영향
| 설정 (Setup) | 필요한 GPU 수 | 월간 비용 (OCI A10) |
|---|---|---|
| 3개의 개별 vLLM 배포 (3 separate vLLM deployments) | 3 | $3,282 |
| ... | ||
| 동일한 세 개의 모델, 동일한 추론 품질 (LoRA는 무시할 수 있는 수준의 오버헤드만 추가됨), 비용은 3분의 1 수준입니다. 트레이드오프(Trade-off)는 배포 설정이 약간 더 복잡해진다는 점과 모든 모델이 베이스(Base) 모델을 공유해야 한다는 요구 사항입니다. |
멀티 모델 설정을 탐색하는 팀이라면, Ollama(가장 단순함)로 시작하여, 모델들이 베이스를 공유한다면 vLLM + LoRA로 넘어가고, GPU 메모리 할당에 대해 최대의 제어권이 필요하다면 Triton을 사용하십시오.
Pavan Madduri - Oracle ACE Associate, CNCF Golden Kubestronaut. GitHub | LinkedIn | Website | Google Scholar | ResearchGate
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기