Skip to content

llama.cpp vs vLLM: Which is Faster for Local LLM Inference?

I was setting up a local LLM inference server and hit a wall: should I use llama.cpp or vLLM? The Reddit threads were full of conflicting opinions, so I dug into the architecture differences and ran my own tests. Here’s what I found.

The Direct Answer

It depends on your use case:

  • vLLM is faster for batch processing and concurrent requests (up to 2x tokens/second in multi-user scenarios)
  • llama.cpp is faster for single-user low-latency inference on consumer hardware

The key insight: these tools optimize for different problems. vLLM is built for throughput; llama.cpp is built for efficiency.

Architecture Differences

The fundamental architecture difference explains why each excels in different scenarios:

Architecture comparison
llama.cpp:
+------------------+
| C++ Core Engine |
| (minimal overhead)
+------------------+
+------------------+ +------------------+
| GGUF Quantized │ | CPU/GPU/Apple |
| Models │────▶| Silicon Support │
+------------------+ +------------------+
+------------------+
| Single Request |
| Optimization |
+------------------+
vLLM:
+------------------+
| PyTorch Engine │
+------------------+
+------------------+ +------------------+
| PagedAttention │────▶| KV Cache Memory │
| (memory efficient)│ | Management │
+------------------+ +------------------+
+------------------+ +------------------+
| Continuous │────▶| Tensor |
| Batching │ | Parallelism │
+------------------+ +------------------+

llama.cpp is written in C++ and optimized for GGUF quantized models. It prioritizes minimal memory overhead and runs everywhere: CPUs, NVIDIA GPUs, AMD GPUs, and Apple Silicon.

vLLM is built on PyTorch with its signature PagedAttention algorithm. It’s designed for high-throughput serving with continuous batching and native tensor parallelism.

When Each Tool Wins

I created a decision matrix based on my testing and Reddit user reports:

Use case comparison
Scenario Winner Why
─────────────────────────────────────────────────────────────
Personal local inference llama.cpp Lower overhead, CPU fallback
Apple Silicon (M-series) llama.cpp Native Metal support
Multiple concurrent users vLLM Continuous batching
Production API endpoint vLLM Higher throughput
Memory-constrained (<16GB) llama.cpp Efficient GGUF loading
Multi-GPU setup vLLM Tensor parallelism
Edge deployment llama.cpp Portable, minimal deps
Batch prompt processing vLLM 2x+ throughput gain
GGUF model library llama.cpp Native format support
HuggingFace models vLLM Direct .bin/.safetensors

Real-World Performance

Reddit users provided benchmark data I couldn’t find in official docs:

User-reported benchmarks (Llama-2-7B, single RTX 3090)
Metric llama.cpp vLLM
─────────────────────────────────────────────────
Single request (tok/s) ~120 ~100
5 concurrent users ~60 each ~80 each
Batch of 100 prompts Sequential ~2000 tok/s total
Memory usage ~6GB ~8GB (overhead for batching)
Startup time Fast Slower (PyTorch init)

The key finding: vLLM doubles effective throughput when serving multiple users simultaneously. But for a single user, llama.cpp often matches or beats vLLM.

Setting Up llama.cpp

For single-user local inference, llama.cpp is straightforward:

llamacpp-setup.sh
# Clone and build with CUDA support
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make LLAMA_CUBLAS=1
# Download a GGUF model
wget https://huggingface.co/TheBloke/Llama-2-7B-GGUF/resolve/main/llama-2-7b.Q4_K_M.gguf
# Run inference with GPU offload (32 layers to GPU)
./main -m llama-2-7b.Q4_K_M.gguf \
-p "Explain quantum computing in one paragraph" \
-n 256 \
-ngl 32 \
-t 8

The -ngl 32 flag offloads all 32 transformer layers to GPU. For Apple Silicon, use -ngl 99 to offload everything.

For serving, llama.cpp includes a server mode:

llamacpp-server.sh
# Start OpenAI-compatible server
./server -m llama-2-7b.Q4_K_M.gguf \
--host 0.0.0.0 \
--port 8080 \
-ngl 32 \
--ctx-size 4096
# Query it like OpenAI API
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama-2-7b",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 100
}'

Setting Up vLLM

For production serving with concurrent users:

vllm-setup.sh
# Install with pip (CUDA 12.x)
pip install vllm
# Launch OpenAI-compatible server with tensor parallelism
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--tensor-parallel-size 2 \
--max-model-len 4096 \
--gpu-memory-utilization 0.9
# Server runs on port 8000 by default
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-2-7b-chat-hf",
"prompt": "Write a haiku about code",
"max_tokens": 50
}'

The key vLLM advantage is continuous batching. Here’s how it works:

vllm_batch.py
from vllm import LLM, SamplingParams
# Initialize vLLM
llm = LLM(model="meta-llama/Llama-2-7b-chat-hf")
# Process 100 prompts concurrently
prompts = [
"Translate to French: Hello",
"Summarize: Long text here...",
# ... 98 more prompts
]
# All prompts processed in parallel with shared KV cache
sampling_params = SamplingParams(max_tokens=100)
outputs = llm.generate(prompts, sampling_params)
# vLLM batches these internally and processes them together
# Result: much higher total throughput than sequential processing

Compare this to llama.cpp’s approach:

llamacpp_batch.py
# llama.cpp processes requests sequentially
# (via server or llama-cpp-python)
from llama_cpp import Llama
llm = Llama(model_path="llama-2-7b.Q4_K_M.gguf", n_gpu_layers=32)
prompts = ["Prompt 1", "Prompt 2", "Prompt 3"]
for prompt in prompts:
# Each request is processed independently
output = llm(prompt, max_tokens=100)
print(output)

The difference: vLLM processes all prompts in a single batched forward pass, while llama.cpp handles them one at a time.

Hardware Requirements

Your hardware choice matters:

Hardware compatibility
Hardware llama.cpp vLLM
──────────────────────────────────────────────
NVIDIA GPU (CUDA) Yes Yes (required)
AMD GPU (ROCm) Yes Limited
Apple Silicon (Metal) Yes No
CPU only Yes No (GPU required)
Multiple GPUs Basic Full tensor parallelism

llama.cpp runs anywhere. I’ve used it on:

  • MacBook Pro M3 Max (128GB unified memory)
  • Desktop with RTX 4090
  • Old laptop with just a CPU
  • Raspberry Pi (for tiny models)

vLLM requires NVIDIA GPUs. If you’re on Apple Silicon or AMD, vLLM isn’t an option.

Memory Management

The memory strategies differ fundamentally:

Memory handling comparison
llama.cpp:
┌─────────────┐
│ GGUF Model │ ──▶ Load entire file into memory
└─────────────┘
┌─────────────┐ ┌─────────────┐
│ GPU VRAM │ ◀─▶ │ System RAM │
│ (optional) │ │ (fallback) │
└─────────────┘ └─────────────┘
Offload layers dynamically based on availability
vLLM:
┌─────────────┐
│ Model │ ──▶ Split across GPUs (tensor parallel)
└─────────────┘
┌─────────────────────────────────────┐
│ PagedAttention KV Cache │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Page │ │Page │ │Page │ │Page │ │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ Efficient memory for concurrent reqs │
└─────────────────────────────────────┘

llama.cpp’s memory model is simple: load what fits in VRAM, offload the rest to RAM. This works well for single-user scenarios where you load once and query many times.

vLLM’s PagedAttention is sophisticated: it manages KV cache like virtual memory, allocating pages on demand. This allows batching many concurrent requests without running out of memory mid-generation.

My Decision Framework

I use this simple flowchart:

Decision flowchart
Need to serve multiple users?
┌────────────┴────────────┐
YES NO
│ │
┌─────────┴─────────┐ ┌──────┴──────┐
│ Have NVIDIA GPU? │ │ Apple Silicon│
└─────────┬─────────┘ │ or AMD? │
┌────┴────┐ └──────┬──────┘
YES NO │
│ │ llama.cpp
vLLM llama.cpp

For my personal development workflow (single user, MacBook), llama.cpp is the clear choice. For the API server I run for my team (multiple users, RTX 4090), vLLM delivers better throughput.

There’s a third option worth knowing about. Reddit users consistently mentioned exllamav2:

exllamav2 positioning
Engine Best For Model Format
──────────────────────────────────────────────────────────────
llama.cpp Single-user, CPU fallback GGUF
vLLM Production serving, multi-GPU HF/Safetensors
exllamav2 Fastest single-GPU inference EXL2, GPTQ

exllamav2 can outperform both llama.cpp and vLLM for specific model formats (EXL2, GPTQ) on NVIDIA GPUs. But it lacks vLLM’s batching sophistication and llama.cpp’s hardware flexibility.

Summary

In this post, I compared llama.cpp and vLLM for local LLM inference. The key point is that each tool optimizes for a different problem: vLLM maximizes throughput for concurrent users with continuous batching and PagedAttention, while llama.cpp maximizes efficiency for single-user scenarios with broad hardware support.

Choose vLLM if you’re building an API endpoint, serving multiple users, have NVIDIA GPUs, and need maximum throughput. Choose llama.cpp if you’re running personal local inference, using Apple Silicon or AMD GPUs, working with GGUF models, or need CPU fallback options.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments