자기 하드웨어에서 오픈 모델을 실행하고 싶다면 도구가 난립한 것처럼 보인다. 하지만 소스를 읽어보면 어느 계층을 직접 운영하고 싶은지에 따라 나뉘는 세 진영으로 정리된다. llama.cpp는 원시 엔진이다. 모델 파일을 불러와 가지고 있는 어떤 하드웨어에서든 실행하는 독립 실행형 바이너리다. Ollama는 이 엔진을 더 쓰기 좋은 패키지로 감싼 친근한 관리자다. vLLM은 엔진을 버리고 자체 엔진을 구축한 처리량 서버다. 나는 llama.cpp의 2026-07-01 master (a6647b1), Ollama의 v0.31.1, vLLM의 v0.24.0rc2 소스를 읽었다. 이 축을 알고 나면 비교가 명확해진다. 모든 주장은 근거가 되는 코드 줄을 가리킨다.
단 하나의 질문
어느 계층을 직접 운영하고 싶은가? 이 선택 하나가 사용할 수 있는 하드웨어 범위, 동시성, 도구가 대신 해주는 일의 범위를 결정한다.
- 원시 엔진 — 추론 라이브러리를 직접 다룬다. 이식성과 제어력은 최대지만, 친절한 안내는 최소다. (llama.cpp)
- 관리자 — 엔진을 감싸고 모델 레지스트리, API, 수명주기 관리를 더한다. 한 대의 머신을 편리하게 운영할 수 있다. (Ollama)
- 처리량 서버 — 다수의 동시 요청을 처리하도록 최적화한 전용 엔진이다. 제대로 된 GPU가 필요한 대신 처리량을 얻는다. (vLLM)
진영 1 — 원시 엔진: llama.cpp
llama.cpp는 독립 실행형 C/C++ 추론 엔진이다. 작은 단일 목적 바이너리인 llama-cli와 llama-server HTTP 서버(CMakeLists.txt:57)를 빌드하며, 이들은 GGUF 파일, 즉 ggml 자체의 양자화 모델 형식(llama-model-loader.cpp:550)을 불러와 실행한다. llama.cpp의 강점은 이식성이다. 연산 백엔드(CPU, CUDA, Metal, Vulkan, ROCm 외 십여 종)는 C vtable 뒤에 있고, 하나의 빌드가 런타임에 이를 동적으로 불러올 수 있다(ggml-backend-dl.cpp:35). Raspberry Pi에서도 H100에서도 실행된다.
-ngl로 GPU 분할을 제어할 수 있다(arg.cpp:2482). 기본값은 auto다. auto는 실제로 여유 VRAM을 측정하고 들어갈 수 있는 만큼의 레이어를 배치하며, 들어갈 수 있는 수까지 레이어 수를 줄인다(fit.cpp:375). 이 메모리 맞춤은 llama.cpp 자체에 들어 있는 진짜 엔지니어링이고, 다음 진영을 이해할 때 중요하다. 이 진영은 제어력과 도달 범위가 최대지만 친절한 안내는 가장 적다. 모델 레지스트리도, 데몬도 없어서 파일과 플래그를 직접 관리해야 한다.
진영 2 — 친근한 관리자: Ollama
Ollama는 추론을 다시 구현하지 않는다. llama.cpp의 llama-server를 서브프로세스로 감싸고(server.go:107) 그 주변의 모든 것을 더한다. ollama pull을 지원하는 모델 레지스트리, REST API, 유휴 모델을 다섯 분 뒤 언로드하는 keep-alive 스케줄러가 그 예다. Ollama 티어다운에서 확인했듯, Ollama는 GPU 레이어 분할까지 llama.cpp에 위임한다. 기본적으로 -ngl을 아예 전달하지 않고(llama_server.go:396) llama.cpp의 auto 맞춤이 결정하게 한 뒤, 서버 로그에서 최종 레이어 수를 다시 읽는다. 그래서 두 진영은 포개진다. Ollama는 편의 계층이고, 그 아래 엔진은 정확히 진영 1이다. 돈을 내는 대상은 패키징이다. 한 명이 쓰는 머신을 위한 pull-and-run, API, 수명주기 관리다.
진영 3 — 처리량 서버: vLLM
vLLM은 정반대의 선택을 한다. vLLM은 그 자체가 여러 요청을 동시에 처리하도록 전용으로 만든 엔진이다. llama.cpp가 어디서든 모델 하나를 실행하는 데 최적화한다면, vLLM은 GPU를 쉬지 않게 유지하는 데 최적화한다. vLLM 티어다운에서 확인했듯, KV 캐시를 고정된 16토큰 블록으로 페이징해 메모리를 촘촘하게 채우고 공유할 수 있게 한다(cache.py:46). 또한 스케줄러에는 prefill/decode 단계 구분이 없다. 토큰 예산을 기준으로 매 단계 배치를 다시 만든다(scheduler.py:391). 새 프롬프트와 진행 중인 생성을 섞어 아무것도 유휴 상태로 두지 않는다. 이것이 연속 배칭이며, 동시 부하에서 vLLM의 처리량이 다른 둘을 앞서는 이유다. 대신 모델 전체를 올려둘 수 있는 제대로 된 GPU를 요구한다.
비교
| llama.cpp — 원시 엔진 | Ollama — 관리자 | vLLM — 처리량 서버 | |
|---|---|---|---|
| 직접 운영하는 계층 | 추론 라이브러리 | llama.cpp를 감싼 래퍼 | 전용 서빙 엔진 |
| 모델 형식 | GGUF (양자화) | GGUF (llama.cpp 경유) | HF 가중치 (다양한 양자화) |
| 하드웨어 범위 | 무엇이든: CPU → 어떤 GPU든 | llama.cpp와 동일 | 제대로 된 GPU, 모델 전체 상주 |
| 동시성 | 기본적 | 단순한 모델별 스케줄링 | 연속 배칭 (높음) |
| VRAM 맞춤 | -ngl auto가 VRAM에 맞춤 |
Ollama의 추정 + llama.cpp | PagedAttention 블록 |
| 편의성 | 낮음 — 직접 다뤄야 함 | 높음 — pull과 REST API | 서버 운영용, 노트북 도구가 아님 |
| 관계 | 엔진 | 엔진을 감쌈 | 엔진을 대체함 |
이 구분이 모든 것을 결정하는 이유
포개지는 구조를 눈여겨봐야 한다. 이것이 진짜 이야기이기 때문이다. Ollama와 llama.cpp는 경쟁자가 아니다. Ollama는 llama.cpp에 리본을 묶은 것이다. 둘 중 하나를 고른다면 같은 엔진과 GGUF 모델을 두고 편의성과 제어력 중 무엇을 택할지 고르는 셈이다. vLLM이 진짜 갈림길이다. 엔진도, 모델 형식도, 최적화 목표도 다르다. 따라서 이 결정은 대칭적인 삼자 구도가 아니다. 먼저 “서빙 처리량이 필요한가?“를 묻고(vLLM과 llama.cpp 계열 중 선택), 그다음 “관리자를 원하는가, 원시 엔진을 원하는가?“를 묻는 구조다(Ollama와 llama.cpp 중 선택).
나는 어디에 해당할까?
- 모델 하나, 머신 한 대에서 그냥 작동하기를 원한다 → Ollama. 아래에는 llama.cpp가 있고 그 위에 레지스트리, API, 유휴 시 언로드 기능이 있다. 엔진에서는 아무것도 포기하지 않고 모든 패키징을 얻는다.
- 특이한 하드웨어, 세밀한 제어, 또는 자체 앱에 추론을 내장해야 한다 → llama.cpp를 직접 쓴다. 다른 어떤 것도 실행되지 않는 곳(CPU 전용, Metal, 엣지 기기)에서도 실행되고, 어떤 레이어를 GPU에 올릴지를 포함해 모든 조절 장치를 직접 쥔다.
- 다수의 동시 사용자를 서빙하고 처리량이 기준이다 → vLLM. PagedAttention과 연속 배칭은 바로 그 부하를 위해 만들어졌고, 요청이 쌓이기 시작하면 단일 스트림 엔진을 멀찍이 따돌린다. 단, GPU는 준비해야 한다.
범위와 정직함
나는 master a6647b1의 llama.cpp (2026-07-01), Ollama v0.31.1 (710292f), vLLM v0.24.0rc2 (6d37570)를 읽었다. Ollama와 vLLM의 메커니즘은 링크한 각 티어다운에서 소스와 대조해 검증했다. llama.cpp의 엔진 구조(독립 실행형 바이너리, GGUF 로딩, 동적 ggml 백엔드)는 이 커밋에서 반증을 시도하는 claim-verifier를 거쳤고 확인됐다.
정직하게 밝혀둘 점이 두 가지 있다. 첫째, 읽는 도중 바로잡아야 했던 세부 사항이 있다. llama.cpp의 -ngl auto는 여유 VRAM을 실제로 측정하고 들어맞도록 레이어 수를 줄인다. 이 로직은 common/fit.cpp에 있고, 모델을 불러오기 전에 호출되며, auto에서만 작동한다(all이나 명시적인 수에서는 작동하지 않는다). 처음에는 코어 모델 로더만 검색해서 이 로직을 놓쳤고 “맞춤 기능이 없다”고 결론 내렸다. claim-verifier가 이 페이지에 실리기 전에 오류를 잡았다. 이 맞춤 기능이야말로 Ollama의 “그냥 llama.cpp에 위임한다”는 전략이 작동하게 해주는 요소다. 둘째, llama.cpp는 변화가 빠르다. 여기서는 다른 어디보다 날짜를 고정하는 것이 중요하고, Ollama는 자체 빌드를 번들로 제공하기 때문에 upstream master보다 뒤처지거나 앞설 수 있다.


