You can find people online claiming Ollama runs models at half the speed of llama.cpp, and people claiming there's no difference at all. Both camps are usually benchmarking different files on different hardware with different settings, which is how you get a folklore gap instead of a measured one.

So we measured it. Same GGUF bytes, same Mac, same prompt, same context size, out-of-the-box defaults on both sides. Every command is in the methodology section so you can reproduce the whole thing in twenty minutes.

Is Ollama slower than llama.cpp on a Mac?

On Apple Silicon, no - not meaningfully. In our June 2026 testing on an M2 Max 32GB, token generation for identical Q4_K_M GGUF files came in within 2-7% between Ollama 0.23.4 and upstream llama-server b9533, and the winner changed depending on the model.

That's the headline, and it cuts both ways. Ollama's runtime held a 2.2% edge on Llama 3.2 3B and a 3.7% edge on Gemma 4 12B. Upstream llama-server held a 6.6% edge on Qwen3 4B. Nothing here resembles the 2x penalties that circulate in Reddit threads - at least not on this hardware, with these models, at these defaults.

What exactly did we benchmark?

The trick that makes this comparison airtight: Ollama stores model weights as sha256-named blobs under ~/.ollama/models/blobs/, and the bytes inside those blobs are standard GGUF. So we pointed llama-server directly at Ollama's own blob files. Not the same model re-downloaded from somewhere else - the same file on disk, byte for byte.

Three models, three size classes, all Q4_K_M:

Llama 3.2 3B Instruct - Ollama's llama3.2:latest. Ollama dispatched this one to its vendored llama.cpp runner (its repo pins build b9509).

Qwen3 4B Thinking 2507 - Ollama's qwen3:4b. Dispatched to Ollama's own Go engine, the one built directly on GGML rather than wrapping llama.cpp.

Gemma 4 12B IT - imported into Ollama from a plain GGUF with ollama create. Also dispatched to the Go engine.

That dispatch detail matters, and it is the single biggest reason to read this page as dated rather than current. Ollama at the version we tested was really three engines in a trench coat: a vendored llama.cpp runner for older architectures, the in-house Go/GGML engine for newer ones, and an MLX engine on Apple Silicon that was still a preview at 0.23.4 and so is not represented in these numbers at all. Ollama made MLX the default on Apple Silicon in v0.30. So the Ollama column below measures two engines that are no longer what a Mac gets by default. Install Ollama today and the same three models will not necessarily reach the same runners, which means the Ollama half of this comparison is not reproducible against a current install even though every command still runs. The llama-server half is. When someone says "Ollama is slow," the first question is which engine their model actually hit. We confirmed the dispatch for each model in Ollama's server logs.

The numbers

Medians of five runs after one warmup, 1,216-token prompt, 256 tokens generated, temperature 0. The tables below are the whole result.

Two things stand out. First, the generation gap never exceeds 7% in either direction. Token generation on Apple Silicon is memory-bandwidth bound, and both runtimes sit on the same GGML kernels underneath, so this is roughly what you'd expect once the folklore is stripped away. Second, Ollama's Go engine showed its only real deficit on Qwen3 4B (83.6 vs 89.1 tok/s) while beating upstream on Gemma 4 12B (34.0 vs 32.8 tok/s) - so even the engine-fork story doesn't reduce to a clean "the fork is slower."

Prompt processing was equally close: within 1% on Llama 3.2, upstream ahead 5.3% on Qwen3, Ollama ahead 5.8% on Gemma 4.

Why do people report much bigger gaps?

The dramatic Ollama-vs-llama.cpp numbers almost always come from different hardware or different settings, not from the engines themselves. The famous 20x figure was measured on an NVIDIA DGX Spark with gpt-oss-120b, where Ollama's CUDA path lagged badly - a real result, but not one that transfers to a Mac running Metal.

Four things inflate the perceived gap in practice:

Hardware path. llama.cpp's maintainer Georgi Gerganov publicly noted llama.cpp ran almost 20x faster than Ollama for gpt-oss-120b on the DGX Spark. CUDA and Metal are different codepaths with different maintenance attention. Our Metal numbers say nothing about CUDA, and vice versa.

Defaults. Ollama historically shipped a small default context and silent reload-on-overflow. A model that quietly re-processes its context reads as "slow" in a way no tokens-per-second number captures. That one has largely been fixed: the server now sizes context from available VRAM rather than pinning a single number, although Ollama's own FAQ still tells readers the default is 4,096, which is a good reminder to check the code rather than the docs. We pinned both sides to 4,096 explicitly, which takes the question off the table either way.

Version skew. Both projects move fast. Ollama 0.23.4 vendors llama.cpp b9509, and the upstream build we tested is b9533, released days apart. A six-month-old comparison is a comparison of six-month-old software.

This post is now on the wrong side of its own observation, and by a wider margin than a version bump. Ollama is at 0.32.14 as of August 2026, roughly a release every one to two days. Our embedded engine moved to b10485, which is 535 upstream commits past the build measured here. We have not re-run these three models at the new pins. The numbers below stay exactly where they were, with the versions they were taken against attached to them, because restating an old measurement under a new version number is precisely how the folklore in the first paragraph gets manufactured.

Different files. "Same model" often isn't. A Q4_K_M from one uploader and a Q4_0 from Ollama's registry can differ by double-digit percentages in speed and quality. Same-bytes testing is the only version of this comparison that means anything.

How we ran it (full methodology)

Hardware: MacBook Pro, Apple M2 Max, 32GB unified memory, macOS 26.5. Software: Ollama 0.23.4 (Metal, flash attention auto-enabled per its logs), llama-server build b9533, commit c4a278d68, built for arm64. That was ToolPiper's embedded engine at benchmark time. It moved to b10485 on 2026-07-27, after these runs. One other model sat loaded but idle in a separate process throughout - identical conditions for both engines, and no inference ran on it during any measurement.

Protocol, in full:

1. The prompt is a fixed ~1,200-token passage. Each run prepends a unique numeric prefix of identical token shape, so neither engine can serve a cached prefix. llama-server additionally gets cache_prompt: false.

2. llama-server: llama-server --model <blob> -ngl 99 -c 4096, then POST /completion with n_predict: 256, temperature: 0. Speeds read from the response's timings object.

3. Ollama: stock ollama serve, then POST /api/generate with raw: true (bypasses chat templating, so both engines see the identical text), num_predict: 256, temperature: 0, num_ctx: 4096. Speeds computed from prompt_eval_count/duration and eval_count/eval_duration.

4. One warmup, five measured runs per model per engine, medians reported. Models benchmarked sequentially, never two loaded by the same engine at once, and each Ollama model explicitly unloaded before the next.

5. The Gemma 4 12B GGUF was imported into Ollama with a one-line Modelfile (FROM /path/to/gemma-4-12b-it-Q4_K_M.gguf) and ollama create. Importing a GGUF into Ollama is easy. Getting one out is not - there is no ollama export, and the long-standing issue asking for one was closed "not planned" in April 2026. We have a separate guide on that.

What should you actually pick a runner on?

Not speed. That's the practical conclusion of this whole exercise: on a Mac, in mid-2026, the engines are close enough that tokens per second is the wrong axis to decide on. The axes with real daylight between them are storage layout (named GGUF files you can point any tool at, versus sha256 blobs behind a manifest), interface (a native app versus a CLI plus env vars for the tuning), memory defaults (a KV cache type chosen per model that logs its fallbacks, versus one global switch that drops back to f16 without telling you), and where the project is headed (on-device features versus a cloud tier).

Flash attention is not on that list, though it was on ours until recently. Both runtimes turn it on by themselves where the hardware supports it, and Ollama has done so since October 2025. Any comparison table still scoring that row as a win for either side is out of date, including the earlier version of this one.

ToolPiper embeds upstream llama-server unmodified, with the build number public and tracked release to release. Being precise about which build matters on a benchmark page: the llama-server column above is b9533, which is what we shipped in June. We moved to b10485 on 2026-07-27 and have not re-run these three models against it. The whole runner is free: unlimited GGUF downloads, multi-model, the local OpenAI-compatible API, embeddings, and an MCP server with over 420 tools. No account, no caps, no terminal. The full comparison covers everything beyond the tokens.

Download ToolPiper at modelpiper.com/download and run the same benchmark against it - the commands above work unchanged against its embedded engine.

This post is part of our local LLM performance series. See Local LLM Benchmarks on Apple Silicon for cross-device numbers and OLLAMA_KV_CACHE_TYPE for the memory side of the tuning story.