Same GGUF, two jackets — ollama vs llama.cpp measured
The same 2.5 GB GGUF, one box: llama.cpp served 10.5 tok/s where ollama managed 7.8 — a 35% jacket tax — because ollama's engine is llama-server, just older.
bench: 4-core / 8 GB VM, CPU-only, kernel 7.2.11-arch1-1 · ollama 0.40.15 (CPU-only mode, auto threads) · llama.cpp 0.6.0-dev build 11429 -t 4 · ctx 4096 both · num_predict 128 / -n 128, temperature 0 · last tested 2026-11-09
The compressed answer: it is one file wearing two jackets. The bytes never changed — the sha256 keys match to the letter — and neither did the hardware or the context window. What changed is the doorway: who loads the weights, which build of the loader, and which default knobs get set before your prompt arrives. The measured difference is the jacket tax: the share of your tokens-per-second that goes to the wrapper instead of the words.
Is ollama a different engine than llama.cpp?
No — and you don't have to take anyone's word for it. While both engines served the model, the process tree printed this:
46212 /usr/local/bin/ollama serve 3154608 /usr/local/lib/ollama/llama-server --model /usr/share/ollama/.ollama/models/blobs/sha256-3e4cb141…
Read the second line twice. The program actually generating tokens under ollama is llama-server — llama.cpp's own server binary, shipped inside /usr/local/lib/ollama/ — pointed at the exact blob your ollama pull downloaded. ollama is not a rival engine; it is a supervisor around one. The race below is therefore not brain against brain. It is the same brain asked to run twice: once wearing ollama's jacket (its bundled build, its defaults, its API), once in stock llama.cpp clothes.
How do you prove both engines got the same file?
Take the file out of ollama's own storage and hand it to the other engine. The blobs directory holds the GGUF under a content-addressed name; the largest blob is the weights. Copy it, and hash both copies:
verify: sha256sum /usr/share/ollama/.ollama/models/blobs/sha256-3e4cb14174460404e7a233e531675303b2fbf7749c02f91864fe311ab6344e4f /tmp/model.gguf → one hash, twice: 3e4cb14174460404e7a233e531675303b2fbf7749c02f91864fe311ab6344e4f · 2,497,280,480 bytes
Identical bytes, provably. Every number that follows is therefore about the doorway — the loader, the defaults, the plumbing — and cannot be explained away by "different model". This is the same discipline the KV-bill post used to put an exact byte price on context; here it puts an exact percentage on the wrapper.
What did the same box measure?
Three one-sentence asks, both engines, greedy decoding, the same 128-token budget. Generation speed first, because it is the one you feel while reading:
Prompt handling — how fast the engine reads what you send — separates even harder. The full matrix, sortable:
| engine | ask | prompt tokens | prompt tok/s | gen tokens | gen tok/s |
|---|---|---|---|---|---|
| llama.cpp | q1 · GGUF quantization? | 12 | 28.9 | 128 | 10.41 |
| llama.cpp | q2 · RAM for 7B Q4_K_M? | 23 | 37.5 | 128 | 10.48 |
| llama.cpp | q3 · keep_alive? | 14 | 30.5 | 42 | 10.57 |
| ollama | q1 · GGUF quantization? | 22 | 19.8 | 128 | 7.75 |
| ollama | q2 · RAM for 7B Q4_K_M? | 33 | 21.7 | 128 | 7.79 |
| ollama | q3 · keep_alive? | 24 | 22.3 | 128 | 7.83 |
Two quiet receipts hide in the prompt-token column: the identical question cost llama.cpp 12 prompt tokens and ollama 22 — the wrapper's template adds about ten tokens of overhead to every single ask — and ollama's first ask carried a 5.80-second cold load (load_duration) that the warm asks then skipped, which is the residency story the five-minute-squat post timed by hand.
Where does the jacket tax come from?
-t 4), and carries no supervisor between your prompt and the weights. On this box that was worth roughly +2.7 tok/s of generation and +11 tok/s of prompt reading.Neither number is a scandal. But the honest framing matters: this was not a showdown between engines, because there is only one engine on the track. It was the same llama-server, twice — once through a current build with tuned flags, once through an older bundled one behind default knobs. The gap is a version-and-defaults gap, and versions move.
So which one do you run?
Serving a model you will tune, benchmark, or squeeze onto specific hardware: llama.cpp, and the numbers above are the receipt. Running a small fleet of local models where you swap them daily and forget them between uses: ollama — the 5.80-second cold load and warm residency are the conveniences you are paying for, and the jacket tax is real but survivable on a box that is not token-starved. Both doors keep the weights on your machine; neither one phones home.
The honesty ledger, since receipts cut both ways: llama.cpp's cold-load time went unrecorded (its health-gated startup was measured as a wall-clock window and the log line did not survive the run — ollama's 5.80s stands alone); the RSS comparison is confounded because standalone llama-server counts mapped model pages as resident while ollama's runner accounts them differently (4.87 GiB vs 3.05 GiB as measured — read that column as "different accounting", not "different appetite"); there is no GPU in this race — CPU numbers only, the same rule every bench on this site runs under. And both engines spent nearly their whole 128-token budget explaining how they would answer a one-sentence question — brevity remains a group project.
How do you run this race yourself?
last tested 2026-11-09
ollama 0.40.15 · llama.cpp 0.6.0-dev build 11429 · qwen3:4b · CPU-only bench — throughput numbers, not VRAM claims