vLLM vs llama.cpp vs Ollama: 모델이 24GB VRAM에 들어가지 않을 때 발생하는 현상
요약
RTX 3090(24GB VRAM) 환경에서 vLLM, llama.cpp, Ollama의 성능을 비교 분석했습니다. 24GB 이내에서는 vLLM이 압도적인 처리량을 보였으나, VRAM 초과 시 vLLM은 즉시 OOM 오류를 발생시키는 반면 llama.cpp와 Ollama는 RAM 스필을 통해 동작을 유지함을 확인했습니다.
핵심 포인트
- vLLM은 Paged Attention을 통해 동시성 증가 시 처리량이 최대 5.4배 확장됨
- VRAM 용량 초과 시 vLLM은 즉시 OOM 오류가 발생하며 실행이 중단됨
- llama.cpp와 Ollama는 RAM 스필을 통해 성능 저하를 감수하며 실행 지속 가능
- RAM 스필 상황에서 llama.cpp의 레이어 오프로드 튜닝이 Ollama보다 TTFT 측면에서 우수함
요약 (TL;DR)
HomeLab Monitor를 통해 가격을 확인한 RTX 3090 (24GB) + 128GB RAM 홈랩 환경에서 **5개의 모델 (1B ~ 116.8B 파라미터)**을 대상으로 llama.cpp, Ollama, vLLM을 벤치마크했습니다. 24GB 이내의 환경에서는 vLLM의 연속 배치 (continuous batching) 기술 덕분에 동시성 (concurrency) 1에서 8로 증가할 때 총 처리량 (aggregate throughput)이 3.9배~5.4배까지 확장되었습니다 (llama.cpp는 -np 8을 명시적으로 설정하여 맞추려 했음에도 1.2배~1.9배에 그쳤습니다). 24GB를 초과하는 경우 — RAM 스필 (RAM-spill)을 강제하기 위해 의도적으로 선택된 두 모델의 경우 — llama.cpp와 Ollama는 모두 한 자릿수 tok/s로 성능이 저하되면서도 계속 생성을 이어갔습니다. 반면 vLLM은 양자화 (quantization) 방식에 관계없이, 약 22.1~22.2GB 사용 / 700MB 미만 여유라는 동일한 한계치에서 두 모델 모두 즉시 OOM (Out of Memory) 오류가 발생했습니다. 부차적인 결과로, RAM 스필 발생 시 llama.cpp의 수동으로 튜닝된 레이어 오프로드 (layer offload)가 Ollama의 자동 분할 (automatic split)보다 첫 토큰 생성 시간 (time-to-first-token) 측면에서 37배 더 뛰어난 성능을 보였으며, 안정적인 상태의 디코딩 속도는 거의 동일했습니다.
라인업 (The roster)
| 모델 | 제조사 | 유형 | 24GB에 적합한가? |
|---|---|---|---|
| Gemma 3 1B | dense | 예 | |
| ... | |||
| (Gemma 4는 실존합니다 — 이 글을 쓰는 시점 기준 Google의 최신 출시작이며, Gemma 3의 오타가 아닙니다.) |
3단계 프롬프트 계층 (짧음/중간/길음), 동시성 1 및 8, 셀당 2회 반복, 총 15개의 백엔드×모델 쌍을 테스트했습니다. 사전에 주의사항을 밝힙니다: 처음 세 모델은 저의 실제 운영용 Ollama (OLLAMA_NUM_PARALLEL=1, 기본적으로 직렬화된 실제 일상 사용 설정)를 대상으로 실행되었습니다. GLM과 GPT-OSS는 깨끗한 볼륨이 필요했기에 별도의 격리된 인스턴스 (OLLAMA_NUM_PARALLEL=4)를 대상으로 실행되었습니다. 처음 세 모델에 대한 Ollama의 동시성=8 수치는 Ollama의 동시성 한계치가 아니라, 실제 기본 운영 동작 방식입니다.
동시성, 24GB 이내 환경
총 디코딩 tok/s, 동시성 1 → 동시성 8:
| 모델 | Ollama | llama.cpp | vLLM |
|---|---|---|---|
| Gemma 3 1B | 125.6 → 71.4 | 294.1 → 400.6 | 235.5 → 1172.1 |
| ... |
vLLM 자체의 c1→c8 스케일링: 3.9x-5.4x (Paged Attention (페이지드 어텐션) 사용, 요청을 유휴 사이클에 슬롯화함). llama.cpp의 경우, 동시성 수준에 맞춰 -np 8을 설정하더라도: 1.2x-1.9x — 서버 시작 전 병렬 슬롯당 고정된 KV-캐시 (KV-cache) 예약을 미리 선언하기 때문에, 동시성은 런타임 결정이 아닌 설정(config)의 결정 사항입니다. c8에서의 정면 승부: vLLM은 llama.cpp를 2.9x-3.7x 차이로 앞서며, Ollama의 직렬화된 기본 설정보다 6.3x-16.4x 앞섭니다 (위의 주의 사항 적용).
절벽, 그리고 vLLM의 벽
GLM-4.5-Air (llama.cpp 튜닝 하에서 레이어의 약 52%가 시스템 RAM으로 유출됨)와 GPT-OSS-120B (약 67% 유출)는 모델이 들어가지 않도록 의도적으로 선정되었습니다. llama.cpp와 Ollama는 두 모델 모두 실행했습니다 — 속도는 느리고 한 자릿수 tok/s였지만, 충돌 없이 실제 생성이 이루어졌습니다. 반면 vLLM은 두 모델 모두에서 완전히 실패했습니다:
# GPT-OSS-120B, native MXFP4, --cpu-offload-gb 45
OutOfMemoryError: CUDA out of memory. Tried to allocate 1.08 GiB.
GPU 0 has a total capacity of 23.56 GiB of which 533.69 MiB is free.
...
# GLM-4.5-Air, pre-quantized AWQ, --cpu-offload-gb 36
OutOfMemoryError: CUDA out of memory. Tried to allocate 1.16 GiB.
GPU 0 has a total capacity of 23.56 GiB of which 685.69 MiB is free.
...
동일한 형태, 다른 모델, 다른 양자화 (Quantization) 경로입니다. GLM을 --gpu-memory-utilization 0.78로 다시 시도했습니다 (더 많은 여유 공간을 강제하기 위해 0.90에서 낮춤) — 결과는 바이트 단위까지 동일한 오류가 발생했습니다: 22.12 GiB 사용 중, 685.69 MiB 여유, 1.16 GiB 요청됨. 이는 활용도(utilization) 조절 노브가 해결책이 아님을 의미합니다. 기본 가중치 + 오프로드 (offload) 점유율이 프로파일링 시작 전 이미 천장에 고정되어 있습니다. 두 모델, 두 가지 양자화 방식, 동일한 약 22GB의 벽 — 이는 특정 모델의 특이 사항이 아니라, 이 스택에서 24GB 카드 한 장으로 100B 이상의 파라미터를 가진 MoE를 처리할 때 발생하는 vLLM의 CPU-오프로드 경로의 실제 한계로 읽힙니다.
TTFT: 정상 상태(Steady-state)에서는 보이지 않는 37배의 격차
모든 환경에서 실행 가능한 모델들의 경우, 예열(warm-up)이 완료된 후의 정상 상태(steady-state) 디코딩 속도는 거의 대등합니다. GPT-OSS-120B의 가장 긴 티어(tier)를 보면 **7.65 tok/s (llama.cpp) vs 7.6 tok/s (Ollama)**입니다. GLM의 경우 4.58 vs 4.59입니다. 하지만 첫 번째 토큰 생성 시간(Time-to-first-token, TTFT)은 이야기가 다릅니다:
| 모델 | Ollama TTFT | llama.cpp TTFT | 격차 |
|---|---|---|---|
| GLM-4.5-Air | 13.6s | 8.1s | 1.7x |
| GPT-OSS-120B | 274.0s | 7.3s | 37x |
lama.cpp의 -ngl 값은 모델의 실제 config.json(레이어 수, 레이어당 크기)을 바탕으로 제가 직접 계산한 수치입니다. GPT-OSS의 경우 약 21GB를 의도적으로 오프로딩(offloading)하기 위해 -ngl 12를 설정했습니다. Ollama는 로드 시점에 분할(split)을 자동으로 계산하며, 새로 다운로드하여 RAM에 일부 상주하는 65GB 모델의 경우 이러한 자동 경로는 비용이 많이 듭니다. 목적지는 같지만, 그곳에 도달하는 경로는 매우 다릅니다.
발생하는 비용 (출력 토큰 100만 개당 BGN, 실제 GPU 에너지 소비)
| 모델 | Ollama | llama.cpp | vLLM |
|---|---|---|---|
| Gemma 3 1B | 0.19 | 0.05 | ~0* |
| ... | |||
| *vLLM의 Gemma 3 1B 실행은 6초 만에 완료되어 전력 샘플러(power sampler)가 측정치를 포착하기에는 너무 빨랐으며, 거의 0에 가깝게 기록되었습니다. 이는 짧은 버스트(burst) 상황에서의 샘플링 한계일 뿐, 실제로 비용이 들지 않는다는 의미는 아닙니다. |
Ollama에서 GPT-OSS-120B를 실행하는 것은 동일한 모델을 사용하는 llama.cpp보다 백만 토큰당 실제 전기 사용량이 약 7배 더 많이 듭니다. 이는 앞서 언급한 TTFT의 편의성 세금(convenience tax)이 화폐 단위로 다시 나타난 결과입니다.
공개된 세 가지 vLLM 체크포인트 교체 사례
원래 계획은 모든 vLLM 단계에서 bitsandbytes 4-bit 양자화(quantization)를 즉석에서(on-the-fly) 사용하는 것이었습니다. 하지만 모든 MoE(Mixture-of-Experts) 모델에서 실패했으며, 그 이유는 세 가지의 서로 다른 검증된 원인 때문이었습니다. 단순히 동일한 오류가 세 번 복사되어 붙여넣기 된 것이 아닙니다.
- Qwen3-Coder-30B:
ValueError: BitsAndBytes quantization with padded hidden_size ... Parameter shape (786432, 1) != checkpoint shape (2048, 768)— BitsAndBytes (bnb)가 이 MoE(Mixture of Experts)의 패딩된 전문가 레이아웃(padded expert layout)을 역양자화(dequantize)할 수 없습니다. 해결책: 사전 양자화된 AWQ 체크포인트 사용. 이후 정상 작동함 (c8 환경에서 총합 677.9 tok/s). - Gemma 4 26B-A4B:
AttributeError: MoE Model Gemma4ForConditionalGeneration does not support BitsAndBytes quantization yet.새로운 아키텍처로, bnb 경로가 아직 연결되지 않았습니다. 해결책: 다른 사전 양자화된 체크포인트를 사용했으나, 리포지토리 이름에도 불구하고config.json에 AWQ가 아닌compressed-tensors라고 명시되어 있어 pydantic 오류가 발생했습니다.--quantization플래그를 완전히 제거하고 vLLM이 자동 감지하도록 하여 해결했습니다. - GLM-4.5-Air: 실패가 아니라 실용적인 결정이었습니다. vLLM 커뮤니티에서 이미 불안정하다고 지적한 bnb+MoE+CPU 오프로드(CPU-offload) 조합을 테스트하기 위해 212GB의 네이티브 bf16 다운로드를 건너뛰고, 동일한 질문을 테스트할 수 있는 약 63GB의 사전 양자화된 AWQ 체크포인트로 바로 진행했습니다.
위의 모든 근본 원인은 이전 모델의 실패 사례를 그대로 적용한 것이 아니라, 실제 컨테이너 로그에서 도출된 것입니다.
테스트되지 않은 사항
OOM(Out of Memory)을 최종 결과로 받아들이기 전, --cpu-offload-gb를 전수 조사하는 대신 단 두 가지의 --gpu-memory-utilization 값만을 테스트했습니다. 멀티 GPU / 텐서 병렬(tensor-parallel) vLLM 경로는 테스트하지 않았습니다. 이는 "단일 카드의 CPU 오프로드가 작동하는가"라는 질문과는 다른 문제입니다. 처음 세 모델에 대한 Ollama의 c8 수치는 프로덕션 기본값이며, 동시성 한계치가 아닙니다. 그리고 llama.cpp의 개별 요청 타이밍 중 하나(Gemma 4, medium tier, c8)에서 거의 0에 가까운 완료 시간으로부터 불가능한 250,024 tok/s가 보고되었습니다. 본문 전체에서 사용된 집계 수치는 전체 시간 대비 총 토큰 수(total-tokens-over-wall-time)이므로 해당 오류의 영향을 받지 않지만, 이는 개별 요청 로그에서 발생하는 알려진 거친 측면(rough edge)입니다.
RAM 유출(RAM-spill) 메커니즘과 편집된 대시보드 스크린샷이 포함된 전체 상세 버전은 Medium에서 확인할 수 있습니다.
위의 모든 수치는 HomeLab Monitor — 오픈 소스, MIT 라이선스 — 를 통해 RTX 3090의 실제 전력 소비량과 비교하여 산출되었습니다.
만약 여러분이 이미 이 세 가지 백엔드 (backends) 중 하나를 실행하고 있다면: 여러분의 시스템은 용량이 맞지 않는 모델을 로드하려고 시도한 적이 있나요? 그리고 그때 명시적인 오류를 내며 실패했나요, 아니면 조용히 실패했나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기