Skip to content
Friendly disclaimer: flozi00 TechHub is a solo side-project next to a full-time job โ€” personal learning notes, no official statements. Verify critical steps yourself.

llama.cpp / Ollama vs vLLM: Which One, When, and Why

A numbers-first comparison of the three dominant local LLM inference stacks: what each layer actually is, how their concurrency models differ, the GGUF question, and a worked VRAM budget for a 14B Q4_K_M model on a 24 GB card.

8 min readflozi00
aimachine-learninggpullama-cppollamavllm

Local LLM inference has converged on three stacks that are constantly confused with each other: llama.cpp, Ollama, and vLLM. They are not competitors at the same layer โ€” two of them literally wrap or complement each other. This guide pins down what each one is, where the concurrency and memory models diverge, and which to pick per scenario.

What each layer actually is

llama.cpp is the C/C++ inference engine: a GGUF-native runtime with backends for CUDA, Metal, Vulkan, ROCm, and plain CPU, plus llama-server, a built-in HTTP server with OpenAI- and Anthropic-compatible routes, continuous batching, and parallel decoding.1

Ollama is a model distribution and runtime layer, not an engine of its own (mostly). Historically it wrapped a pinned llama.cpp build โ€” the LLAMA_CPP_VERSION variable in the Ollama repo pins the exact upstream source, patched via llama/compat/.2 On top of that pin Ollama has added its own engines: a multimodal engine that runs vision models (Llama 4, Gemma 3, Qwen 2.5 VL) directly on the GGML tensor library instead of gluing separate text-decoder and vision-encoder processes together,3 and an MLX-based engine for Apple Silicon that Ollama put into preview in March 2026, replacing the llama.cpp Metal path for accelerated models.4

vLLM is a Python/CUDA serving engine built around PagedAttention: KV-cache memory managed OS-style in fixed-size blocks with logical-to-physical block tables, giving near-zero cache waste and 2โ€“4ร— throughput over the systems it was benchmarked against in the original paper.5 Since the V1 re-architecture it enables prefix caching by default (under 1% throughput loss at 0% hit rate) and reaches up to 1.7ร— higher throughput than V0.6

Layerllama.cppOllamavLLM
What it isC/C++ GGUF engine + llama-server HTTPRuntime + model registry wrapping pinned llama.cpp, own multimodal engine, MLX engine on Apple SiliconPython/CUDA serving engine, PagedAttention KV manager
Model formatGGUF (native)GGUF (+ MLX-native on Apple Silicon)HF safetensors (GGUF via experimental plugin)
Primary targetSingle box, any hardware incl. CPUEase of use, ollama runGPU server throughput
ConcurrencyFixed slots (--parallel N)OLLAMA_NUM_PARALLEL slotsContinuous batching over one paged KV pool

Concurrency models: fixed slots vs a shared pool

This is the single biggest behavioral difference, and the source of the worst gotcha.

llama-server: --parallel N fixed slots, --ctx-size split across them

llama-server -np N creates N slots. Current master defaults: -np/--parallel default is -1 (auto) and -cb/--cont-batching (continuous batching) is enabled by default; -b/--batch-size defaults to 2048 and -ub/--ubatch-size to 512. --ctx-size (default 0 = loaded from model) is the total context, and the KV budget is divided across slots.1

The gotcha: --ctx-size is the total, not per-slot. With -c 8192 -np 4, every slot gets 8192/4 = 2048 tokens of KV space โ€” your 8k prompt silently truncates or errors. If you actually want 8k per slot across 4 slots, you must pass -c 32768 -np 4. This has tripped up enough users that upstream added an issue-and-fix cycle and eventually a per-slot control (ctx-per-slot / --kv-unified-per-slot on current master) to set the per-slot context limit directly.7

vLLM: one paged KV pool, continuous batching

vLLM keeps a single shared KV pool and allocates blocks on demand; requests join and leave the batch at every decode step. Raising concurrency costs nothing when the server is idle because no per-request context is pre-reserved: a request's KV cache grows token-by-token in physical blocks, freed immediately when the request finishes.5 --max-num-seqs limits batch size; the true limit on concurrent requests is "how many sequences fit their accumulated KV into the pool."

Ollama: slots like llama.cpp, sized by defaults

Ollama inherits the slot model: OLLAMA_NUM_PARALLEL (default 1) is the number of parallel requests each model processes, and required RAM scales by OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH.8 With the default of 1, concurrent requests queue; each additional slot multiplies the KV allocation just like --parallel does in llama-server.

GGUF: native home vs experimental guest

GGUF is llama.cpp's native format โ€” the reference implementation, with the full quant family (Q2_K through Q8_0, i-quants, imatrix calibration).9

vLLM's GGUF support is a different story, in its own docs' words: "GGUF support in vLLM is highly experimental and under-optimized at the moment, it might be incompatible with other features." GGUF loading has migrated out of core into the OOT vllm-gguf-plugin package (uv pip install vllm-gguf-plugin), only single-file GGUFs are supported, and the docs recommend passing the base model's tokenizer explicitly, e.g.10:

sh
vllm serve unsloth/Qwen3-0.6B-GGUF:Q4_K_M --tokenizer Qwen/Qwen3-0.6B

Practical rule: on vLLM, GGUF is a memory-footprint reduction, not a performance path. For high-throughput serving use safetensors with a first-class quantization scheme (AWQ, GPTQ, FP8); use GGUF only when that model exists only as GGUF.

Defaults that bite

Ollama context defaults are VRAM-tiered. Current docs: < 24 GiB VRAM โ†’ 4k context; 24โ€“48 GiB โ†’ 32k; โ‰ฅ 48 GiB โ†’ 256k, overridable with OLLAMA_CONTEXT_LENGTH (older releases used a flat 4096 default, and 4096 is still what the FAQ's override examples show).11 If your 24 GB card picks a 32k default you didn't ask for, KV cache grows accordingly โ€” check ollama ps (its CONTEXT column) and set num_ctx per request or the env var globally.

Ollama KV cache type defaults to f16. OLLAMA_KV_CACHE_TYPE accepts f16 (default), q8_0 (~half the KV memory, tiny quality loss), or q4_0 (~quarter, more noticeable loss) โ€” and requires flash attention to be enabled to take effect.8

OLLAMA_NUM_PARALLEL defaults to 1 โ€” simultaneous requests queue rather than run concurrently, and raising it multiplies KV memory by N.8

vLLM gpu_memory_utilization defaults to 0.9 โ€” vLLM claims up to 90% of the card's total memory at startup (per-instance, not per-process-coordinated), then subtracts weights, activation peak, and CUDA-graph pools; the KV cache is the residual. On a 24 GB card shared with a display or other processes, 0.9 will OOM at init โ€” lower it (0.80โ€“0.85) or pin --max-model-len.12

Worked example: 14B Q4_K_M, 4 slots ร— 8k, on a 24 GB card

Take Qwen2.5-14B (48 layers, GQA with 8 KV heads, hidden size 5120 across 40 heads โ†’ head dim 128, 14.7B total parameters).13 We compute the budget using the site's KV formula (see the KV cache guide and inference math), plugging in llama.cpp's measured Q4_K_M โ‰ˆ 4.85 bits-per-weight for this weight class.9

Weights at 4.85 bpw:

W=14.7ร—109ร—4.858=8.912โ€‰GBโ‰ˆ8.30โ€‰GiBW = \frac{14.7 \times 10^9 \times 4.85}{8} = 8.912 \,\text{GB} \approx 8.30 \,\text{GiB}

KV cache per token (f16, 2 bytes/value), using 2ร—Lร—HKVร—dheadร—bdtypeร—s2 \times L \times H_{KV} \times d_{head} \times b_{dtype} \times s:

k=2ร—48ร—8ร—128ร—2=196,608โ€‰B/tokenk = 2 \times 48 \times 8 \times 128 \times 2 = 196{,}608 \,\text{B/token}

Per slot at 8192 tokens, and total for 4 slots:

KVslot=8,192ร—196,608=1.611โ€‰GB=1.5โ€‰GiBKV_{slot} = 8{,}192 \times 196{,}608 = 1.611 \,\text{GB} = 1.5 \,\text{GiB} KVtotal=4ร—1.5โ€‰GiB=6.0โ€‰GiBKV_{total} = 4 \times 1.5 \,\text{GiB} = 6.0 \,\text{GiB}
ComponentSize
Weights (Q4_K_M, 4.85 bpw)8.30 GiB
KV cache, 4 ร— 8k slots, f166.00 GiB
Subtotal14.30 GiB
Headroom on a 24 GB card (24 decimal GB โˆ’ the 14.30 GiB subtotal = 15.35 GB)~8.65 GB

It fits โ€” with room to spare, which is the point: on a 24 GB card a 14B Q4_K_M with 4 concurrent 8k conversations is comfortably inside budget, and the ~8.65 GB headroom โ€” 24 decimal GB on the sticker minus the 14.30 GiB subtotal (15.35 decimal GB) โ€” covers compute scratch (dependent on batch size and backend) plus the CUDA context. Two levers if you need more slots or context: -ctk q8_0 -ctv q8_0 halves KV to 3.0 GiB (llama-server flags, f16 default1), the same trade Ollama's OLLAMA_KV_CACHE_TYPE=q8_0 makes. For a full sizing workflow see the VRAM calculator explained and quantization guide; the interactive tool is at LLM inference calculator.

And remember the gotcha from above: to actually get 8k per slot you'd start llama-server -m model-Q4_K_M.gguf -c 32768 -np 4, not -c 8192.

Decision table

ScenarioPickWhy
Laptop / desktop, one user at a timeOllamaOne command to a running model; VRAM-tiered defaults, model registry, auto GPU split via llama.cpp under the hood
Mac on Apple Silicon, fastest decodeOllama (MLX engine)MLX path built for unified memory; preview requires >32 GB unified memory and currently accelerates specific models4
Max control: KV quant, slot tuning, odd hardware, CPU inferencellama.cpp (llama-server)Direct access to every flag (-ctk, -np, -c, --override-tensor), GGUF-native, runs on CPU where vLLM GGUF does not
Many concurrent users, one GPU or many, latency SLOvLLMPaged KV pool + continuous batching scale with load; prefix caching on by default6
Production API with structured output / tool use at high QPSvLLMFirst-class serving features, tensor parallelism across GPUs, FP8/AWQ quantization support
Multimodal (vision) locallyOllama or llama-serverOllama's multimodal engine makes vision first-class3; llama-server's --mmproj covers GGUF vision models1
Serving an HF-only model as GGUFllama.cpp or OllamavLLM's GGUF path is 'highly experimental', single-file only, plugin-based10

Rule of thumb: llama.cpp when you need to touch the knobs or fit weird hardware; Ollama when you want zero-friction local use; vLLM when other people or services are hitting the endpoint and throughput per GPU matters.

Footnotes

  1. ggml-org/llama.cpp, tools/server/README.md (master): https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md โ€” -np/--parallel default -1 (auto), -cb (continuous batching) default enabled, -b default 2048, -ub default 512, -c/--ctx-size default 0 (loaded from model). โ†ฉ โ†ฉ2 โ†ฉ3 โ†ฉ4

  2. ollama/ollama, llama/README.md: LLAMA_CPP_VERSION pins Ollama's llama.cpp source: https://github.com/ollama/ollama/blob/main/llama/README.md โ†ฉ

  3. Ollama blog, "Ollama's new engine for multimodal models" (May 15, 2025): https://ollama.com/blog/multimodal-models โ†ฉ โ†ฉ2

  4. Ollama blog, "Ollama is now powered by MLX on Apple Silicon in preview" (March 2026): https://ollama.com/blog/mlx โ†ฉ โ†ฉ2

  5. Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention", SOSP 2023, arXiv:2309.06180: https://arxiv.org/abs/2309.06180 โ†ฉ โ†ฉ2

  6. vLLM blog, "vLLM V1: A Major Upgrade to vLLM's Core Architecture" (2025-01-27): up to 1.7ร— throughput vs V0; prefix caching on by default with \<1% throughput loss at 0% hit rate: https://blog.vllm.ai/2025/01/27/v1-alpha-release.html โ†ฉ โ†ฉ2

  7. llama.cpp issue #11681 ("--ctx-size is divided by --parallel") and PR #24124 (commit 1844325) / release b10662 (ctx-per-slot, --kv-unified-per-slot): https://github.com/ggml-org/llama.cpp/issues/11681 โ†ฉ

  8. Ollama FAQ: OLLAMA_NUM_PARALLEL default 1, RAM scales with OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH; OLLAMA_KV_CACHE_TYPE default f16 with q8_0/q4_0 options: https://docs.ollama.com/faq โ†ฉ โ†ฉ2 โ†ฉ3

  9. ggml-org/llama.cpp, tools/quantize/README.md: Q4_K_M = 4.8944 bpw on Llama-3.1-8B (โ‰ˆ4.85โ€“4.9 bpw weight class): https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md โ†ฉ โ†ฉ2

  10. vLLM docs, GGUF feature page: "highly experimental and under-optimized", migrated to OOT vllm-gguf-plugin, single-file GGUF only, base-model tokenizer recommended: https://github.com/vllm-project/vllm/blob/main/docs/features/quantization/gguf.md โ†ฉ โ†ฉ2

  11. Ollama docs, Context length: <24 GiB VRAM โ†’ 4k, 24โ€“48 GiB โ†’ 32k, โ‰ฅ48 GiB โ†’ 256k, OLLAMA_CONTEXT_LENGTH override: https://docs.ollama.com/context-length ; older flat 4096 default per https://docs.ollama.com/faq โ†ฉ

  12. vLLM EngineArgs: gpu_memory_utilization default 0.9, "fraction of GPU memory to be used for the model executor", per-instance: https://docs.vllm.ai/en/latest/api/engine_args.html (vllm/config/cache.py, default=0.9) โ†ฉ

  13. Qwen2.5-14B-Instruct model card (48 layers, GQA 40 Q / 8 KV heads, 14.7B params) and config.json (hidden_size 5120, num_key_value_heads 8): https://huggingface.co/Qwen/Qwen2.5-14B-Instruct โ†ฉ