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
Ollama adds model management and a runtime interface. llama.cpp is an execution engine with its own server; Ollama also has other engine paths.
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
| Layer | llama.cpp | Ollama | vLLM |
|---|---|---|---|
| What it is | C/C++ GGUF engine + llama-server HTTP | Runtime + model registry wrapping pinned llama.cpp, own multimodal engine, MLX engine on Apple Silicon | Python/CUDA serving engine, PagedAttention KV manager |
| Model format | GGUF (native) | GGUF (+ MLX-native on Apple Silicon) | HF safetensors (GGUF via experimental plugin) |
| Primary target | Single box, any hardware incl. CPU | Ease of use, ollama run | GPU server throughput |
| Concurrency | Parallel slots; unified KV pool is optional | OLLAMA_NUM_PARALLEL slots | Continuous 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:
vllm serve unsloth/Qwen3-0.6B-GGUF:Q4_K_M --tokenizer Qwen/Qwen3-0.6BPractical 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:
KV cache per token (f16, 2 bytes/value), using :
Per slot at 8192 tokens, and total for 4 slots:
| Component | Size |
|---|---|
| Weights (Q4_K_M, 4.85 bpw) | 8.30 GiB |
| KV cache, 4 Γ 8k slots, f16 | 6.00 GiB |
| Subtotal | 14.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
| Scenario | Pick | Why |
|---|---|---|
| Laptop / desktop, one user at a time | Ollama | One command to a running model; VRAM-tiered defaults, model registry, auto GPU split via llama.cpp under the hood |
| Mac on Apple Silicon, fastest decode | Ollama (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 inference | llama.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 SLO | vLLM | Paged KV pool + continuous batching scale with load; prefix caching on by default6 |
| Production API with structured output / tool use at high QPS | vLLM | First-class serving features, tensor parallelism across GPUs, FP8/AWQ quantization support |
| Multimodal (vision) locally | Ollama or llama-server | Ollama's multimodal engine makes vision first-class3; llama-server's --mmproj covers GGUF vision models1 |
| Serving an HF-only model as GGUF | llama.cpp or Ollama | vLLM'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
-
ggml-org/llama.cpp, tools/server/README.md (master): https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md β
-np/--paralleldefault -1 (auto),-cb(continuous batching) default enabled,-bdefault 2048,-ubdefault 512,-c/--ctx-sizedefault 0 (loaded from model). β© β©2 β©3 β©4 -
ollama/ollama, llama/README.md:
LLAMA_CPP_VERSIONpins Ollama's llama.cpp source: https://github.com/ollama/ollama/blob/main/llama/README.md β© -
Ollama blog, "Ollama's new engine for multimodal models" (May 15, 2025): https://ollama.com/blog/multimodal-models β© β©2
-
Ollama blog, "Ollama is now powered by MLX on Apple Silicon in preview" (March 2026): https://ollama.com/blog/mlx β© β©2
-
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
-
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
-
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 β© -
Ollama FAQ:
OLLAMA_NUM_PARALLELdefault 1, RAM scales withOLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH;OLLAMA_KV_CACHE_TYPEdefault f16 with q8_0/q4_0 options: https://docs.ollama.com/faq β© β©2 β©3 -
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
-
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
-
Ollama docs, Context length: <24 GiB VRAM β 4k, 24β48 GiB β 32k, β₯48 GiB β 256k,
OLLAMA_CONTEXT_LENGTHoverride: https://docs.ollama.com/context-length ; older flat 4096 default per https://docs.ollama.com/faq β© -
vLLM EngineArgs:
gpu_memory_utilizationdefault 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) β© -
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 β©