
Ollama와 vLLM 중 어느 것이 더 빠를까? Gemma 4로 실측 비교해 보았다
요약
Ollama와 vLLM 엔진의 추론 성능을 Gemma 4 모델을 통해 실측 비교한 글입니다. H200 GPU 환경에서 단일 요청에 대한 생성 속도를 BF16 정밀도로 공정하게 측정하여 두 엔진의 특성을 분석했습니다.
핵심 포인트
- Ollama는 로컬 검증 및 개인용으로 적합한 간편한 실행 환경 제공
- vLLM은 PagedAttention 등을 활용한 고처리량 추론 서버에 최적화
- Gemma 4의 특수 구조로 인해 vLLM에서 TRITON_ATTN 백엔드 사용 확인
- 단일 요청 생성 속도 측면에서의 엔진별 성능 차이 실측
평소 GPU 클라우드 제안을 하다 보면, "추론 기반(Inference Infrastructure)에는 무엇을 사용하면 좋을까요?"라는 상담을 받을 때가 있습니다.
그럴 때 자주 언급되는 것이 바로 Ollama와 vLLM입니다.
Ollama는 명령어 한 줄로 모델을 구동할 수 있는 간편함 덕분에 검증이나 개인용으로 자주 사용됩니다.
반면 vLLM은 PagedAttention이나 지속적 배치 처리(continuous batching)와 같은 메커니즘을 갖추고 있어, 본업 운영의 처리량(throughput)을 중시하는 추론 서버로서 채택되는 사례가 늘고 있습니다.
"결국 어느 쪽이 더 빠를까"라는 점은 이전부터 궁금했던 의문이었습니다.
그래서 이번에는 지난번 CPU VM/GPU VM 비교와 동일한 FPT CLOUD의 H200 GPU VM 위에서, Gemma 4를 사용하여 Ollama와 vLLM의 추론 성능을 실측 비교해 보았습니다.
여기서 소개하는 수치는 어디까지나 개인의 검증 환경에서 측정한 것입니다.
모델 크기나 프롬프트, VM 스펙이 바뀌면 수치도 달라집니다. 어디까지나 하나의 기준으로 봐주시기 바랍니다.
- Ollama와 vLLM의 포지셔닝 차이
- 단일 요청(single request)의 응답 속도를 정밀도, API, 측정 조건을 맞춘 상태에서 공정하게 비교하는 방법
- "동일한 BF16 정밀도·단일 요청"이라는 조건 하에서 실제로 어느 정도의 속도 차이가 발생하는지
매우 간단히 말하자면, "vLLM은 본업의 처리량(throughput) 중시"라는 평판의 일부분인, 단일 요청의 생성 속도만을 조건을 맞춰 실측해 본 기사입니다 (동시 요청 시의 비교는 이번 범위 밖입니다).
이번에는 FPT CLOUD의 GPU VM(H200 1기)을 사용하여, 추론 엔진만 Ollama와 vLLM으로 교체하여 비교합니다. FPT CLOUD는 FPT Smart Cloud(FPT 스마트 클라우드 재팬 주식회사)가 제공하는 GPU 클라우드 기반으로, 이번 검증에 사용한 것은 H200을 1기 탑재한 GPU VM입니다. 또한, FPT CLOUD에서는 이 외에 Metal Cloud(베어메탈, H200×8기 구성)의 할당도 가능합니다.
| 항목 | 내용 |
|---|---|
| 타입 | NVIDIA H200 SXM5 |
| ... |
Ollama와 vLLM 모두 Gemma 4 12B를 사용합니다. 정밀도를 맞추는 이유는 후술하겠지만, 결론부터 말씀드리면 두 엔진 모두 BF16(16bit)으로 맞추었습니다.
| 엔진 | 모델 지정 |
|---|---|
| Ollama | gemma4:12b-it-bf16 |
| vLLM | google/gemma-4-12B-it(순수 가중치, BF16) |
Gemma 4는 head_dim=256과 global_head_dim=512가 혼재하는 특수한 구조이기 때문에, vLLM 측에서는 FlashAttention이나 FlashInfer를 사용할 수 없어 TRITON_ATTN 백엔드로의 강제 폴백(fallback)이 발생합니다. 이는 모델 측의 제약 사항으로 환경 측에서 변경할 수 없습니다. 이번 수치는 이 TRITON_ATTN 백엔드에서의 결과입니다. 향후 vLLM 측의 최적화가 진행되면 이 숫자도 바뀔 가능성이 있습니다.
간단히 정리해 두겠습니다.
| 항목 | Ollama | vLLM |
|---|---|---|
| 포지셔닝 | 로컬·검증용의 간편한 실행 환경 | 본업 운영을 의식한 고처리량(high throughput) 추론 서버 |
| ... |
일반적으로 "Ollama는 간편함, vLLM은 처리량(throughput)"이라고 말합니다. 이 "처리량"이라는 평판은 본래 다수의 동시 요청 시의 이야기입니다. 이번에 검증하는 것은 단일 요청의 생성 속도뿐이므로, 이 정설을 전면적으로 검증하는 것은 아니라는 점을 미리 말씀드립니다.
솔직히 말하면, 처음에는 "Ollama와 기본 설정의 vLLM을 돌려서 빠른 쪽을 쓰면 되겠지" 정도의 가벼운 마음이었습니다. 이번에는 그 가벼운 마음이 어떻게 무너져 내렸는지에 대한 이야기를 먼저 하겠습니다. 이 부분을 건너뛰고 숫자만 내놓는다면 아마 누구의 마음에도 남지 않을 것이기 때문입니다.
vLLM을 pip로 처음부터 빌드하려고 했더니, 의존성 문제로 버그가 발생하여 추론이 제대로 작동하지 않았습니다. CUDA 버전, PyTorch 버전, 이 조합을 찾는 데만 하루가 사라졌습니다. 결국 이날은 성과 제로였습니다.
다음 날, 방침을 바꾸어 공식 Docker 이미지(vllm/vllm-openai)
)로 전환했습니다. 빌드(Build)로 힘을 빼느니, 동작이 확인된 이미지를 그대로 사용하는 것이 빠릅니다. 당연한 말이지만, 환경 구축에 고집을 피웠던 하루는 정말 낭비였습니다.
Docker로 구동하게 된 후, 처음 vLLM으로 측정한 수치를 Ollama와 나란히 놓았을 때, 순간 '어라, 생각보다 차이가 나는데?'라고 생각했습니다. 하지만 냉정하게 다시 살펴보니, 비교하던 두 모델은 내용물이 완전히 다른 것이었습니다.
- Ollama에서 아무 생각 없이
ollama pull gemma4:12b를 하면 다운로드되는 것은 Q4_K_M (4bit 양자화 (Quantization)) - vLLM에서
google/gemma-4-12B-it를 그대로 지정하면 가져오는 것은 BF16 (16bit)
이것은 빠른 쪽이 양자화된 모델인 것이 당연합니다. 엔진의 구현 차이 같은 것이 아니라, 단순히 계산량이 4배 가까이 차이 나는 것을 비교하고 있었을 뿐이었습니다. 이를 깨닫지 못하고 그대로 기사를 썼다면 나중에 망신을 당할 뻔했습니다.
대책으로서 Ollama 측도 기본값인 Q4_K_M이 아닌 gemma4:12b-it-bf16을 명시적으로 지정하여, 양쪽 엔진 모두 BF16으로 통일했습니다. 마침 Google에서 공식적으로 BF16 태그를 배포하고 있었기에 직접 양자화하는 수고도 들지 않았습니다.
BF16으로 맞춘 뒤, 다음으로 Ollama 측의 측정을 진행하려 했을 때의 이야기입니다. 'vLLM 컨테이너는 아직 돌아가고 있고, H200은 약 140GB나 되니까 메모리는 아직 남아있겠지'라고 가볍게 생각했습니다.
이것이 안일했습니다. 이번에 사용한 H200은 nvidia-smi로 확인하면 143,771MiB(약 140GB)이지만, vLLM은 실행 옵션에 --gpu-memory-utilization 0.90을 지정했습니다. 이것은 '모델의 가중치(Weights)가 사용하는 양'이 아니라, 실행하는 순간 GPU 메모리의 90%(약 126GB)를 KV 캐시(KV Cache)용으로 통째로 확보해 버리는 설정입니다.
즉 남은 용량은 약 14GB뿐이며, 거기에 Ollama의 24GB 모델을 올리려 한다면 거의 확실하게 메모리 부족(Out of Memory) 에러로 종료됩니다. 이를 깨닫지 못하고 단계를 진행했다면, 환경 구축에 쏟은 하루에 이어 또다시 꼬박 하루를 측정 에러의 원인 조사에 허비했을 것입니다.
--gpu-memory-utilization은 '현재 사용 중인 메모리 양'이 아니라 '확보할 상한선'입니다. 모델 자체가 작더라도 이 비율만큼은 실행 시점에 점유되므로 주의해야 합니다.
애초에 두 추론 엔진이 같은 GPU를 두고 다투는 상태에서 측정해도 서로의 수치가 불안정해질 뿐 공정한 비교가 되지 않습니다. 그래서 측정 중에는 한쪽 컨테이너를 멈추고, 한 엔진이 GPU를 독점한 상태에서 측정하기로 했습니다.
# vLLM 측정 중에는 Ollama를, Ollama 측정 중에는 vLLM을 멈춤
docker stop gemma4-vllm
vLLM 측에서 /v1/completions (생 텍스트 보완용 API)를 호출했을 때 출력이 깨지는 경우가 있었습니다. Gemma 4 12B IT는 인스트럭션 튜닝 (Instruction-tuned)된 모델이기에, 순수 보완 API와는 궁합이 좋지 않은 듯합니다. /v1/chat/completions로 바꾸니 안정되었습니다. Ollama 측도 이에 맞춰 /api/chat을 사용하여, 양쪽 엔진 모두 '채팅 형식'의 API로 통일했습니다.
모두 작성한 뒤 선배에게 검토를 받았더니, "생성 AI의 속도 비교라면 TTFT (Time to First Token) · ITL (Inter Token Latency) · TPS (Tokens per Second)로 보는 것이 더 공정합니다"라는 지적을 받았습니다.
듣고 보니 당연한 말이었습니다. 그때까지 보고 있던 '생성 토큰 수 ÷ 전체 시간'이라는 값은 프롬프트를 읽어들이는 시간과 실제로 문장을 생성하는 시간이 하나로 묶인 거친 평균이었습니다. 게다가 vLLM은 약 260토큰, Ollama는 500토큰으로 생성량 자체도 달랐기에, 이 거친 평균 방식으로는 공정한 비교가 되지 않았습니다.
TTFT · ITL · TPS를 측정하기 위해, 스트리밍 (Streaming)으로 응답을 받으면서 청크 (Chunk) 단위의 시각을 기록하는 Python 스크립트를 작성했습니다. 구현하는 도중에 깨달은 것이지만, Ollama의 chat API에는 think라는 옵션이 있어 이를 명시적으로 false
로 설정하지 않으면 Gemma 4는 사고 (reasoning) 모드로 동작하는 것 같습니다. 지금까지의 측정에서는 이 옵션을 지정하지 않았기 때문에, Ollama 측에서만 생성 토큰 수가 매번 정확히 500개(상한선에 걸림)가 되었던 수수께끼의 답이 여기서 드디어 밝혀졌습니다.
두 엔진 모두, 독립된 워밍업 (warm-up) 명령어를 한 번 실행한 후 3회 측정하였습니다.
따라서 조건이 하나 더 추가됩니다.
{"model":"gemma4:12b-it-bf16","messages":[{"role":"user","content":"..."}],"stream":true,"think":false,"options":{"num_predict":500}}
이렇게 함으로써 Ollama도 vLLM과 마찬가지로, 생성 도중이라도 문장의 끝을 나타내는 토큰에 도달하면 자발적으로 정지하게 되었습니다.
지금까지와 비교하면 사소하지만, 다음 사항들도 통일했습니다.
- 프롬프트는
生成AIについて500文字で説明してください(생성 AI에 대해 500자로 설명해 주세요)로 공통 적용 - 생성 토큰 수의 상한 (
max_tokens/num_predict)은 둘 다 500으로 설정 (사고 모드를 껐기 때문에, 실제로는 EOS에서 일찍 멈추는 경우가 많음) - 첫 번째 요청은 모델 로드 오버헤드 (overhead)가 발생하므로, 워밍업을 1회 거친 후 본 측정을 3회 실시
- 스트리밍 (streaming)으로 응답을 받고, 청크 (chunk)별 시각으로부터 TTFT (첫 번째 청크까지의 시간) · ITL (청크 간 평균 간격) · TPS (decode 단계의 토큰/초)를 산출
최종적으로 통일한 조건 요약
- 정밀도: 두 엔진 모두 BF16
- GPU: 측정 중에는 한쪽 컨테이너를 정지시켜 전용 상태로 측정
- API: 두 엔진 모두 채팅 형식의 엔드포인트 (endpoint) 사용 (스트리밍)
- Ollama의 사고 (think) 모드: 명시적으로 비활성화
- 프롬프트 ·
max_tokens/num_predict: 공통 (500) - 워밍업 1회 → 본 측정 3회의 평균
- TTFT · ITL · TPS의 3가지 지표로 비교
Ollama의 기본값 (Q4_K_M)과 vLLM의 기본값 (BF16)을 있는 그대로 비교했을 때 어떻게 될지도 궁금하지만, 그것은 조건이 다른 별개의 비교가 되므로 본 기사에서는 다루지 않습니다.
우선 가공 전의 생데이터 (raw data)입니다.
vLLM (BF16) 측정 스크립트와 생데이터
# /tmp/bench_vllm.py
import requests, time, json
url = "http://localhost:8000/v1/chat/completions"
...
# 워밍업
python3 /tmp/bench_vllm.py > /dev/null
# 본 측정 3회
...
=== 시도 1 ===
TTFT: 27.5 ms
Total tokens (chunks): 281
...
Ollama (BF16, think 비활성화) 측정 스크립트와 생데이터
# /tmp/bench_ollama.py
import requests, time, json
url = "http://localhost:11434/api/chat"
...
스크립트를 작성하고 처음 실행한 1회는 실질적인 워밍업으로 취급하고 있었으나, vLLM 측과 완전히 동일한 절차로 맞추기 위해 독립된 워밍업 명령어를 삽입하여 다시 측정했습니다.
# 워밍업
python3 /tmp/bench_ollama.py > /dev/null
# 본 측정 3회
...
=== 시도 1 ===
TTFT: 544.3 ms
Total tokens (chunks): 252
...
두 엔진 모두, 독립된 워밍업 명령어를 1회 실행한 후 3회 측정하여 그 평균값을 채택하고 있습니다.
| 지표 | vLLM (BF16) | Ollama (BF16, think 비활성화) |
|---|---|---|
| TTFT | 약 27.2ms (3회 평균) | 약 547.0ms (3회 평균) |
| ... |
TTFT는 약 20배, TPS는 약 1.16배의 차이가 납니다. 그래프로 그리면 TTFT 차이의 크기를 한눈에 알 수 있습니다.
TTFT = first_token_time - start
(첫 번째 청크를 수신할 때까지의 시간), ITL = (end - first_token_time) / (tokens - 1)
(토큰 간의 평균 간격), TPS = (tokens - 1) / (end - first_token_time)
(디코딩 (decode) 페이즈의 토큰/초)로서, 두 스크립트 모두 공통된 로직으로 산출하고 있습니다.
TTFT, ITL, TPS로 나누어 비교했을 때, 가장 큰 차이가 난 것은 TTFT였습니다. 약 27.2ms (vLLM) 대 약 547.0ms (Ollama)로, 약 20배의 차이가 납니다. 그래프로 그리면 TTFT 차이의 크기를 한눈에 알 수 있습니다.
반면 디코딩 (decode) 페이즈의 TPS는 약 102.3 tok/s (vLLM) 대 약 88.0 tok/s (Ollama)로, 약 1.16배 차이였으며 TTFT만큼의 큰 차이는 없었습니다.
즉, 단일 요청 (single request) 시 체감 속도의 차이는 '문장을 생성하는 속도'보다 '첫 글자가 나오기까지의 지연'이 더 지배적이었다는 뜻입니다. vLLM은 PagedAttention을 통한 프롬프트 처리 최적화가 단일 요청에서도 TTFT에 효과적으로 작용하는 반면, Ollama (llama.cpp 백엔드)는 이 부분의 최적화가 상대적으로 약하다고 보는 것이 타당할 것 같습니다.
이번 측정에서도 워밍업 (warm-up)이 없는 첫 번째 요청은 TTFT가 1.8초 가까이 걸렸습니다 (CUDA 그래프 캡처나 커널 컴파일이 실행되기 때문입니다). 이는 항상 구동하며 요청을 계속 받는 서버 용도라면 무시할 수 있는 비용이지만, 요청할 때마다 엔진을 실행하는 방식이라면 오히려 vLLM이 불리해집니다. 이번 결과는 어디까지나 '이미 구동 중인 서버에 단발성 요청을 보냈을 경우'의 수치라는 점을 명시해 둡니다.
이전 측정에서는 Ollama 측만 생성 토큰 수가 매번 500(상한선)에 걸쳐 나타나고, vLLM은 248267 사이에서 자발적으로 멈추는 차이가 있어 그 원인을 미결 상태로 두었습니다. 이번에 261 토큰에서 자발적으로 멈추게 되었습니다. Gemma 4는 Ollama 상에서 기본적으로 사고 모드 (thought mode)가 활성화되어 있어, 그만큼 생성이 길어졌을 가능성이 높습니다. 이 점은 선배의 지적이 없었다면 깨닫지 못했을 것입니다.think 옵션을 명시적으로 비활성화하자, Ollama도 253
솔직히 말하자면, 이 결과는 vLLM이 본래 평가받는 상황(다수의 동시 요청 시의 지속적 배치 처리 (continuous batch processing))을 검증한 것이 아닙니다. 이번에 측정한 것은 단일 요청뿐이며, 지속적 배치 처리와 같은 vLLM의 강점은 동시 요청 수가 늘어날 때 비로소 발휘되는 메커니즘입니다.
'검증이나 개인 용도라면 Ollama, 프로덕션(본番)이라면 vLLM'이라는 구분은 이번 단일 요청 수치만 보더라도 속도 측면에서 뒤집히지 않았습니다. 오히려 Ollama가 검증 용도로 선택되는 이유는 속도가 아닌 편리함 그 자체이며, vLLM이 프로덕션에 채택되는 이유는 이번에 측정하지 않은 동시 요청 시의 처리량 (throughput)에 있다는 기존의 구분은 이번 결과와도 모순되지 않습니다.
환경 구축에 하루를 허비하고, 양자화 (quantization)의 차이를 알아채지 못해 한 번은 이상한 수치를 내보낼 뻔하고, GPU 메모리 계산을 잘못하여 또 하루를 허비할 뻔하고, 지표를 잘못 파악한 것을 깨닫고, 선배의 지적으로 think 모드를 놓친 것을 깨닫고, 마지막으로 워밍업의 비대칭성까지 깨닫는 과정. 이번 검증은 수치에 도달하기까지의 과정이 더 길었습니다.
그럼에도 최종적으로는 동일한 BF16 정밀도와 동일한 프롬프트라는 조건을 맞춘 상태에서 TTFT, ITL, TPS라는 세 가지 지표로 살펴볼 수 있었습니다. TTFT는 약 27.2ms (vLLM) 대 약 547.0ms (Ollama)로 약 20배, 디코딩 TPS는 약 102.3 tok/s 대 약 88.0 tok/s로 약 1.16배라는 결과가 나왔습니다. 단일 요청의 체감 속도는 생성 자체의 속도보다 TTFT의 차이에 크게 좌우되었습니다.
동시 요청 시의 비교는 시간 관계상 이번에는 보류했습니다. 이 부분은 vLLM이 본래 강점을 발휘하는 영역이므로, 다음에 기회를 만들어 다시 검증해 보겠습니다.
이번 검증은 FPT CLOUD의 GPU VM (H200 1기) 상에서 실시했습니다. GPU 1기를 독점하여 사용할 수 있어, 이번처럼 Ollama와 vLLM을 교체하며 검증하거나 GPU 메모리를 최대한 사용하는 추론 엔진 비교에 적합합니다. 더 많은 GPU가 탑재된 환경이 필요하다면 Metal Cloud (베어메탈, H200 × 8기 구성)라는 선택지도 있습니다.
GPU 인프라 선정이나 추론 엔진 활용법으로 고민하고 계신 분들은 FPT AI FACTORY도 꼭 선택지 중 하나로 검토해 보시기 바랍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기