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
Skip to the full article
Watch Β· explore Β· understand

Choose a serving layer, then budget its requests

Ollama, llama.cpp and vLLM overlap, but package management and execution are different jobs.

AI-assisted learning visualization Β· schematic article explanation

Step 1 / 4Distinguish management from execution

Ollama adds model management and a runtime interface. llama.cpp is an execution engine with its own server; Ollama also has other engine paths.

ComparisonCurrent stepSelect a block to explain it
Hardware and workload
Model format, context and concurrent requests.
Ollama runtime layer
Model management and an accessible local interface.
llama.cpp / llama-server
GGUF execution with explicit backend and slot controls.
  1. Hardware and workload Ollama runtime layer
    local workflow
  2. Hardware and workload llama.cpp / llama-server
    direct control
  3. Ollama runtime layer llama.cpp / llama-server
    one backend path
What to remember

Choose by the actual execution path and workload, then verify memory and latency together.

The graph is not a universal speed ranking. Ollama has multiple backends; format support and default settings are version-dependent.

Terms and the complete walkthrough
GGUF
A model-file format used natively by llama.cpp.
Slot
A server position for an active sequence, with configuration-dependent capacity.
Paged KV pool
Cache storage allocated in blocks as sequences grow.
  1. Distinguish management from execution

    Ollama adds model management and a runtime interface. llama.cpp is an execution engine with its own server; Ollama also has other engine paths.

  2. Look at how requests share memory

    llama-server can use slots and a unified pool; vLLM allocates KV blocks through its serving scheduler. Both need enough capacity for the accumulated context.

  3. Add context across concurrent users

    Four full-length requests do not fit into one request’s context budget merely because memory is shared. Count the total stored tokens and the runtime headroom.

  4. Measure your actual path

    Model format support and engine defaults can change. Verify the installed version and benchmark the chosen model, context and concurrency together.

All connections

  • Hardware and workload β†’ Ollama runtime layer: local workflow
  • Hardware and workload β†’ vLLM serving engine: serving workload
  • Hardware and workload β†’ llama.cpp / llama-server: direct control
  • Ollama runtime layer β†’ llama.cpp / llama-server: one backend path
  • llama.cpp / llama-server β†’ Context allocation: slots or pool
  • vLLM serving engine β†’ Context allocation: paged pool
  • Context allocation β†’ Measure the chosen configuration: full workload
Set your own pace. Pause at any time or select a step directly.

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
ConcurrencyParallel slots; unified KV pool is optionalOLLAMA_NUM_PARALLEL slotsContinuous batching over one paged KV pool

Concurrency models: slots and KV allocation

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

llama-server: --parallel N slots, with optional unified KV

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) sets the total KV capacity. With --no-kv-unified, it is divided across slots; with --kv-unified, slots share one pool. The current auto-slot default enables unified KV.1

The gotcha applies to non-unified mode: with -c 8192 -np 4 --no-kv-unified, each slot gets 8192/4 = 2048 tokens of KV capacity. For four simultaneous 8k slots in that mode, use -c 32768 -np 4 --no-kv-unified. With --kv-unified, the 8192-token pool is shared rather than fixed into four 2048-token partitions; four full 8k sequences would still require capacity for about 32k tokens. --kv-unified-per-slot can set an independent per-slot cap.7

vLLM: one paged KV pool, continuous batching

vLLM keeps a paged KV pool and allocates blocks on demand; llama-server can also use a unified pool, but its allocator and scheduling differ. 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 parallel request can raise KV memory demand.

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), 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 support remains less optimized in vLLM, so performance needs measurement. For high-throughput serving, also compare safetensors with supported quantization schemes (AWQ, GPTQ, FP8).

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 payload memory), or q4_0 (~quarter the payload; both need quality checks on your workload) β€” 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.92 β€” vLLM claims up to 92% 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 card shared with a display or other processes, this may leave too little free memory. Adjust the fraction or --max-model-len based on measured use.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.

For four fixed 8k slots in non-unified mode, start llama-server -m model-Q4_K_M.gguf -c 32768 -np 4 --no-kv-unified. A unified pool can also support them if its total capacity is sufficient.

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', 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, 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.92, "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.92) ↩

  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 ↩