DEV Community

Cover image for Ollama vs vLLM for Concurrent Inference: How to Choose the Right Server
Aeza
Aeza

Posted on

Ollama vs vLLM for Concurrent Inference: How to Choose the Right Server

Choosing between Ollama and vLLM is not simply a matter of asking:

Which inference engine is faster?

The answer depends heavily on the workload.

A personal chat interface with one active user has very different requirements from a multi-user API handling dozens of concurrent generations.

For one workload, simple model management may matter more than maximum throughput.

For another, the deciding factors may be:

  • continuous batching
  • queue behavior
  • KV-cache utilization
  • TTFT
  • p99 latency
  • GPU saturation
  • overload handling

That means a useful Ollama vs vLLM comparison cannot be reduced to a single tokens/s number.

In this article, we'll build a practical benchmark methodology for comparing the two under real concurrent load.

By the end, you'll know:

  • when vLLM's batching can provide a measurable advantage
  • why Ollama may still be the better choice for smaller workloads
  • how to build a fair concurrency sweep
  • which latency and throughput metrics actually matter
  • how to detect queueing and saturation
  • how to choose the server that stays inside your SLO

The Short Answer

There is no universal concurrency level at which vLLM suddenly becomes better than Ollama.

vLLM begins to gain an advantage when enough requests are active or waiting for its scheduler to form useful batches while the GPU still has sufficient compute and memory capacity.

Conceptually:

Low concurrency
      ↓
Little opportunity for batching
      ↓
Ollama may be completely sufficient

Higher concurrency
      ↓
More simultaneous requests
      ↓
Continuous batching becomes useful
      ↓
vLLM can improve aggregate throughput

Too much concurrency
      ↓
Queue grows
      ↓
TTFT and p99 increase
      ↓
OOM / timeout / rejected requests
Enter fullscreen mode Exit fullscreen mode

The real question is therefore not:

Which engine reaches the highest tokens per second?

It is:

Which engine delivers the required throughput while keeping latency, errors, memory usage, and operational complexity inside acceptable limits?


Start with the Workload, Not the Inference Engine

Before testing either server, define what the service actually needs to handle.

Three common scenarios illustrate why this matters.

Personal Chat

A personal chat usually has:

  • one active user
  • low concurrency
  • occasional model loading
  • relatively tolerant startup latency
  • no large server-side request queue

In this environment, maximum aggregate GPU throughput may not matter very much.

Operational simplicity often matters more.

Small Internal API

An internal API may receive:

  • several simultaneous users
  • short request bursts
  • varying context lengths
  • occasional concurrency spikes
  • moderate latency requirements

Both Ollama and vLLM may fit this workload.

The correct choice depends on the actual traffic curve.

High-Concurrency Inference Service

A public or heavily used internal service may have:

  • many simultaneous requests
  • sustained queues
  • strict TTFT targets
  • p95/p99 latency requirements
  • high GPU utilization targets
  • overload conditions that must be handled predictably

This is where continuous batching and queue-aware scheduling become much more important.


Record the Workload Before Benchmarking

At minimum, record:

  • Input tokens — determine prefill cost.
  • Output tokens — determine how long a request occupies generation resources.
  • Average concurrency — represents normal load.
  • Peak concurrency — reveals saturation behavior.
  • Streaming ratio — affects user-perceived latency.
  • Context length — directly affects KV-cache consumption.
  • Request bursts — determine queue behavior.
  • Target SLO — defines when performance becomes unacceptable.

Long contexts consume more KV-cache.

Long generations keep execution resources occupied for longer.

Average concurrency can also hide dangerous workload patterns.

For example, imagine a service that usually has four active requests.

That sounds moderate.

But if several long conversations overlap at the same moment, the system may suddenly experience:

KV-cache pressure
+
longer queue
+
higher TTFT
+
higher p99
Enter fullscreen mode Exit fullscreen mode

even though average concurrency still looks reasonable.

Important: Higher server throughput does not automatically mean a better user experience.

A configuration can produce more total tokens per second while making individual requests noticeably slower.


Why Ollama Is Attractive for Smaller Workloads

Ollama combines several parts of the local model lifecycle into one workflow:

  • model downloading
  • model storage
  • Modelfiles
  • configuration
  • local HTTP API
  • model loading
  • model unloading

For a developer or small team, this can make deployment and maintenance significantly easier.

You can replace a model quickly, modify the system prompt, and control how long a model remains loaded without building a large serving stack.

Cold-start performance should still be evaluated separately from warm inference.


Important Ollama Parameters

In a warmed-up environment, Ollama behavior depends on settings such as:

OLLAMA_NUM_PARALLEL
OLLAMA_MAX_QUEUE
OLLAMA_MAX_LOADED_MODELS
keep_alive
context size
Enter fullscreen mode Exit fullscreen mode

Parallel requests increase memory consumption because each active request may require additional context state.

A configuration that works perfectly with short prompts may behave very differently with longer contexts.

For example:

Short context
+
4 parallel requests
=
works normally
Enter fullscreen mode Exit fullscreen mode

may become:

Long context
+
4 parallel requests
=
larger memory usage
+
queue growth
+
possible failure
Enter fullscreen mode Exit fullscreen mode

So do not treat parallelism as an isolated setting.

Context length matters too.


Ollama's Main Advantage

For many workloads, Ollama's strongest advantage is not peak benchmark performance.

It is operational simplicity.

That can be extremely valuable for:

  • personal assistants
  • development environments
  • prototypes
  • small internal APIs
  • teams that frequently change models

If the workload does not create enough concurrency for continuous batching to matter, a more complicated serving stack may provide little practical benefit.


Why vLLM Benefits from Concurrency

vLLM is designed around server-side inference workloads.

Its scheduler can continuously adjust active batches as requests arrive and complete.

Instead of waiting for one static batch to fully finish, the engine can use newly available capacity for other requests.

This is commonly referred to as continuous batching.

Conceptually:

Request A ────────────────>
Request B      ────────────────>
Request C           ────────────────>

Scheduler continuously updates
the active batch as requests
arrive and finish.
Enter fullscreen mode Exit fullscreen mode

This becomes useful when:

  • several requests are active
  • more requests are waiting
  • the GPU supports the required backend
  • enough memory remains available
  • batching improves accelerator utilization

With one short request, there may simply be nothing useful to batch.


Important vLLM Parameters

Record the server configuration with every benchmark.

Important settings include:

--max-num-seqs
--max-model-len
--gpu-memory-utilization
--dtype
Enter fullscreen mode Exit fullscreen mode

Also record whether chunked prefill is enabled and how it is configured.

For multi-GPU deployments, record:

--tensor-parallel-size
Enter fullscreen mode Exit fullscreen mode

Tensor parallelism can increase the effective memory available for larger models, but it also introduces communication between GPUs.

That communication is not free.

So:

More GPUs
Enter fullscreen mode Exit fullscreen mode

does not automatically mean:

Linear performance scaling
Enter fullscreen mode Exit fullscreen mode

Monitor vLLM's Server-Side Behavior

In addition to normal application metrics, monitor:

  • request queue length
  • KV-cache utilization
  • GPU memory utilization
  • preemption
  • rejected requests
  • OOM events

Both Ollama and vLLM should also be monitored for:

  • queue growth
  • memory pressure
  • timeouts
  • failed requests

The important difference is that vLLM exposes more of the machinery involved in high-concurrency serving.


Which Server Is Easier for One User?

For a single user, Ollama will usually be easier to operate.

The model lifecycle and API are integrated into one workflow.

vLLM becomes more attractive when you specifically require:

  • high concurrent throughput
  • continuous batching
  • detailed production metrics
  • specific GPU backends
  • larger request queues
  • a workload expected to grow rapidly

The decision should follow the workload rather than the popularity of either tool.


Make the Benchmark Fair

A benchmark only makes sense if both systems are processing equivalent work.

This sounds obvious.

In practice, it is easy to get wrong.

Try to keep the following equivalent:

  • model weights
  • model architecture
  • precision
  • quantization
  • tokenizer
  • chat template
  • generation parameters

Different weight formats may require different inference paths.

For example:

GGUF
Enter fullscreen mode Exit fullscreen mode

and:

safetensors
Enter fullscreen mode Exit fullscreen mode

do not automatically represent identical runtime behavior.

If exactly the same model build cannot be used by both engines, document that clearly.

The benchmark conclusion then applies only to the tested configurations.


Record Model Sources and Hashes

For reproducibility, save:

Model name
Model version
Weight source
File hash
Quantization
dtype
Enter fullscreen mode Exit fullscreen mode

Otherwise, a future benchmark may use slightly different weights and produce results that appear inconsistent.


Keep Generation Parameters Identical

Use the same values for parameters such as:

max_tokens
temperature
top_p
stop
stream
Enter fullscreen mode Exit fullscreen mode

For deterministic benchmark workloads, you may use:

temperature: 0
Enter fullscreen mode Exit fullscreen mode

But matching generation settings is still not enough.


Verify Tokenization and Chat Templates

The same chat payload can produce different token sequences if the two servers use different tokenizers or templates.

For example:

{
  "messages": [
    {
      "role": "user",
      "content": "Explain TCP slow start."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

may be transformed differently before reaching the model.

That means two systems may appear to be benchmarking the same request while actually processing different input lengths.

Compare input-token counts before trusting the results.

If one server sees:

312 input tokens
Enter fullscreen mode Exit fullscreen mode

and another sees:

356 input tokens
Enter fullscreen mode Exit fullscreen mode

the workload is not identical.


Record the Benchmark Environment

Every run should include:

Inference server version
Model name
Model hash
GPU model
VRAM
CPU
RAM
Operating system
Driver version
Context length
Generation settings
Enter fullscreen mode Exit fullscreen mode

A result such as:

8,200 output tokens/s
Enter fullscreen mode Exit fullscreen mode

has little value without the environment that produced it.


Separate Cold and Warm Performance

Cold-start performance and steady-state inference answer different questions.

Measure them separately.

Cold Test

A cold test includes:

  • model loading
  • initialization
  • first request latency

This matters for workloads where models are frequently unloaded.

Warm Test

A warm test measures:

  • steady-state throughput
  • TTFT
  • token generation
  • latency
  • concurrency scaling

Do not combine cold and warm measurements into one average.

They describe different behavior.


Handle Prompt Caching Consistently

Prompt caching can significantly change benchmark results.

Either:

disable prompt caching on both systems
Enter fullscreen mode Exit fullscreen mode

or:

use it consistently on both systems
Enter fullscreen mode Exit fullscreen mode

Do not allow one server to reuse cached prompt state while the other processes every prompt from scratch.


Make Sure the Load Generator Is Not the Bottleneck

Your client must have enough:

  • CPU
  • network bandwidth
  • connection capacity

to generate the intended load.

Otherwise, you may accidentally benchmark the load generator rather than the inference server.

Monitor the client machine during heavy tests too.


Build a Concurrency Sweep

A simple benchmark matrix could look like this:

input_tokens:
  - 256
  - 2048

output_tokens: 128

concurrency:
  - 1
  - 2
  - 4
  - 8
  - 16
  - 32

repeats: 5
stream: true
temperature: 0
Enter fullscreen mode Exit fullscreen mode

The exact values should match your real workload.

If production requests usually have:

8k input tokens
+
1k output tokens
Enter fullscreen mode Exit fullscreen mode

then a benchmark using:

256 input tokens
+
64 output tokens
Enter fullscreen mode Exit fullscreen mode

may tell you very little about production behavior.


Increase Concurrency Until Saturation

Start at:

concurrency = 1
Enter fullscreen mode Exit fullscreen mode

Then increase gradually.

For every level:

  1. warm up the server
  2. execute several repeated runs
  3. collect latency
  4. collect throughput
  5. collect errors
  6. collect queue metrics
  7. record GPU utilization
  8. record GPU memory usage

Continue until you reach the first unacceptable condition.

Examples include:

OOM
Timeout
HTTP 503
Rejected request
p99 > SLO
GPU memory exhaustion
Queue growth without recovery
Enter fullscreen mode Exit fullscreen mode

That point is much more useful than the absolute maximum throughput.


Closed-Loop vs Open-Loop Testing

There are two common ways to generate concurrent load.

They measure different things.

Closed-Loop Load

A closed-loop generator maintains a fixed number of active requests.

For example:

Concurrency = 16
Enter fullscreen mode Exit fullscreen mode

Whenever one request finishes, another starts.

This answers questions such as:

How does the server behave with 16 continuously active clients?


Open-Loop Load

An open-loop generator sends requests at a defined arrival rate.

For example:

20 requests/second
Enter fullscreen mode Exit fullscreen mode

Requests continue arriving regardless of how quickly previous requests finish.

This is useful for exposing queueing and overload behavior.

If the server can process:

15 requests/second
Enter fullscreen mode Exit fullscreen mode

while receiving:

20 requests/second
Enter fullscreen mode Exit fullscreen mode

then approximately:

5 requests/second
Enter fullscreen mode Exit fullscreen mode

are being added to the queue.

The queue may continue growing until:

  • latency becomes unacceptable
  • requests time out
  • requests are rejected
  • memory is exhausted

Do not combine closed-loop and open-loop measurements into a single curve.

They answer different questions.


Throughput Alone Is Not Enough

Aggregate tokens per second describes server capacity.

It does not fully describe the user experience.

For every concurrency level, collect:

  • requests per second
  • output tokens per second
  • TTFT
  • ITL
  • TPOT
  • end-to-end latency
  • successful-request rate
  • queue length
  • memory usage

You need both throughput and latency.


TTFT: Time to First Token

TTFT measures:

Request sent
      ↓
First actual generated token
Enter fullscreen mode Exit fullscreen mode

Do not automatically treat the first streaming chunk as the first token.

Some APIs may send metadata or other fields before actual generated text appears.

Measure the first real generated output.

TTFT strongly affects perceived responsiveness in chat applications.

A user may tolerate a long generation if text begins appearing quickly.

A long silent wait before the first token often feels much worse.


ITL: Inter-Token Latency

ITL describes the delay between generated tokens.

Conceptually:

Token 1
   ↓ 40 ms
Token 2
   ↓ 43 ms
Token 3
Enter fullscreen mode Exit fullscreen mode

This describes how smoothly the response streams after generation begins.

If the client measures network chunks rather than actual tokens, call the measurement:

inter-chunk latency
Enter fullscreen mode Exit fullscreen mode

instead.


TPOT: Time per Output Token

TPOT can be represented conceptually as:

generation time after first token
---------------------------------
remaining output tokens
Enter fullscreen mode Exit fullscreen mode

It helps separate initial responsiveness from steady-state generation speed.


Measure End-to-End Latency

End-to-end latency measures:

Request sent
      ↓
Queueing
      ↓
Prefill
      ↓
Generation
      ↓
Final response
Enter fullscreen mode Exit fullscreen mode

This is often the metric most directly connected to the total user wait time.


Always Measure Tail Latency

Do not report only the average.

At minimum, report:

p50
p95
p99
Enter fullscreen mode Exit fullscreen mode

Why?

Because batching and queueing may affect a minority of requests much more severely than the median request.

Suppose:

p50 = 1.8 s
p95 = 4.2 s
p99 = 12.7 s
Enter fullscreen mode Exit fullscreen mode

An average of:

2.4 s
Enter fullscreen mode Exit fullscreen mode

would hide an important part of the user experience.

For production inference APIs, p99 often matters much more than the mean.


Understand the Throughput-Latency Trade-Off

Continuous batching can improve aggregate throughput.

But that does not mean every request becomes faster.

Consider:

Concurrency 4
Throughput: 3,000 tok/s
p99 TTFT: 400 ms
Enter fullscreen mode Exit fullscreen mode

and:

Concurrency 32
Throughput: 7,500 tok/s
p99 TTFT: 4.8 s
Enter fullscreen mode Exit fullscreen mode

The second configuration produces much more aggregate throughput.

But it may violate the application's SLO.

If your requirement is:

p99 TTFT < 2 seconds
Enter fullscreen mode Exit fullscreen mode

then the higher-throughput operating point is not acceptable.

The best benchmark result is therefore not necessarily the highest point on the throughput curve.

It is the highest useful operating point inside the SLO.


Measure Queueing Explicitly

Queueing is one of the most important parts of concurrent inference.

Imagine requests arriving faster than they can be processed:

Arrival rate
     ↓
[Request]
[Request]
[Request]
[Request]
     ↓
Server
Enter fullscreen mode Exit fullscreen mode

If the server cannot keep up:

Queue length ↑
TTFT ↑
p99 ↑
timeouts ↑
Enter fullscreen mode Exit fullscreen mode

This is why a large or unlimited queue does not solve overload.

It only changes:

rejected request
Enter fullscreen mode Exit fullscreen mode

into:

very slow request
Enter fullscreen mode Exit fullscreen mode

Backpressure and overload behavior should therefore be part of the benchmark.


Test Fairness Between Short and Long Requests

A realistic workload rarely contains identical prompts.

Create a mixed workload with:

  • short inputs
  • long inputs
  • short outputs
  • long outputs

Then check whether short requests are excessively delayed by long ones.

For example:

Request A:
256 input tokens
64 output tokens

Request B:
8,000 input tokens
2,000 output tokens
Enter fullscreen mode Exit fullscreen mode

A scheduler should not allow a large request to make every small request unreasonably slow.

Also observe whether large prefill operations create visible pauses for requests already generating tokens.


Store Raw Benchmark Data

A professional benchmark should produce reproducible artifacts.

For example:

benchmark/
├── requests.jsonl
├── run.json
├── results.csv
├── server.log
└── gpu.csv
Enter fullscreen mode Exit fullscreen mode

A minimal result schema could look like:

server,run,concurrency,input_tokens,output_tokens,ttft_ms,itl_ms,e2e_ms,status,error
Enter fullscreen mode Exit fullscreen mode

You may also want:

timestamp,gpu_utilization,gpu_memory_used,queue_length
Enter fullscreen mode Exit fullscreen mode

Screenshots alone are not sufficient.

Raw data allows you to:

  • recalculate percentiles
  • change aggregation windows
  • inspect outliers
  • compare future server versions
  • reproduce the analysis

Graph Throughput and Latency Together

A useful benchmark graph uses concurrency on the X-axis.

Then plot metrics such as:

Requests/s
Output tokens/s
TTFT p50
TTFT p95
TTFT p99
End-to-end p99
Queue length
Enter fullscreen mode Exit fullscreen mode

The important shape often looks like:

Throughput
    /
   /
  /
 /_____
       \
        saturation

Latency
        /
       /
______/ 
Enter fullscreen mode Exit fullscreen mode

At low concurrency:

  • throughput increases
  • latency remains stable

Near saturation:

  • throughput growth slows
  • queueing increases
  • p99 rises rapidly

That knee in the curve is often more important than the absolute maximum.


Compare API Compatibility Before Migrating

Both Ollama and vLLM may expose OpenAI-compatible interfaces.

But:

OpenAI-compatible
Enter fullscreen mode Exit fullscreen mode

does not mean:

behaviorally identical
Enter fullscreen mode Exit fullscreen mode

Verify the features your application actually uses.

For example:

  • Chat Completions
  • streaming
  • structured output
  • embeddings
  • tool calling
  • generation parameters
  • stop sequences
  • error responses
  • token limits

Do not discover incompatibilities after production traffic has already been switched.


Production Readiness Is More Than the Inference Engine

A working HTTP endpoint is not automatically a production-ready inference service.

The surrounding platform may need to provide:

TLS
Authentication
Rate limiting
Logging
Monitoring
Health checks
Enter fullscreen mode Exit fullscreen mode

A health check should also distinguish between:

Server process is running
Enter fullscreen mode Exit fullscreen mode

and:

Model is loaded and ready to serve
Enter fullscreen mode Exit fullscreen mode

A listening TCP port does not guarantee that the next request will meet the latency SLO.


Compare Observability

For concurrent production workloads, observability becomes part of the product decision.

Useful vLLM signals may include:

  • queue state
  • KV-cache utilization
  • TTFT
  • request latency
  • memory pressure
  • preemption

Ollama also exposes timing information around:

  • model loading
  • prompt evaluation
  • generation

Regardless of the server, collect the same client-side metrics.

Client-side instrumentation provides the most direct view of what users actually experience.


Test Upgrades Before Production

Inference-server updates can change:

  • memory consumption
  • scheduler behavior
  • inference backends
  • configuration defaults
  • chat templates
  • model compatibility

Before upgrading production, replay a realistic workload against the new version.

Keep the previous:

Container image or package
Configuration
Model hash
Enter fullscreen mode Exit fullscreen mode

available for rollback.

A benchmark that is valid for one version should not automatically be assumed valid for the next.


Use a Shared API Layer to Keep Migration Easy

If possible, avoid coupling application logic directly to one inference engine.

Keep:

  • model names
  • generation parameters
  • timeout logic
  • error handling

behind an internal adapter.

Then make the server endpoint configurable.

Conceptually:

Application
     ↓
Internal inference adapter
     ↓
Ollama OR vLLM
Enter fullscreen mode Exit fullscreen mode

This makes it much easier to:

  • benchmark alternatives
  • canary a new server
  • roll back
  • migrate later

Canary the Migration

Do not send 100% of production traffic to the new server immediately.

Start with a small percentage.

For example:

5% new server
95% old server
Enter fullscreen mode Exit fullscreen mode

Measure:

  • latency
  • errors
  • throughput
  • queue growth
  • memory usage

Then increase gradually if the results remain inside the SLO.

Keep the old server available for rapid rollback.


A Practical Benchmark Workflow

Here is a reusable process.

Step 1: Define the Real Workload

Record:

Input length
Output length
Average concurrency
Peak concurrency
Request rate
Streaming ratio
SLO
Enter fullscreen mode Exit fullscreen mode

Step 2: Normalize the Models

Match:

Weights
Precision
Quantization
Tokenizer
Chat template
Enter fullscreen mode Exit fullscreen mode

Step 3: Match Generation Settings

Use identical:

max_tokens
temperature
top_p
stop
stream
Enter fullscreen mode Exit fullscreen mode

Step 4: Record the Environment

Save:

Server version
Model hash
GPU
VRAM
CPU
RAM
OS
Driver
Enter fullscreen mode Exit fullscreen mode

Step 5: Warm Up the Server

Separate cold-start measurements from steady-state results.

Step 6: Run the Concurrency Sweep

For example:

1
2
4
8
16
32
Enter fullscreen mode Exit fullscreen mode

Step 7: Measure Latency and Throughput

Collect:

Requests/s
Tokens/s
TTFT
ITL
TPOT
E2E latency
Enter fullscreen mode Exit fullscreen mode

Step 8: Measure Tail Latency

Calculate:

p50
p95
p99
Enter fullscreen mode Exit fullscreen mode

Step 9: Watch Queue and GPU State

Collect:

Queue length
GPU utilization
GPU memory
KV-cache
OOM events
Enter fullscreen mode Exit fullscreen mode

Step 10: Stop at the SLO Boundary

The benchmark should stop being considered "better" once:

p99 > SLO
Enter fullscreen mode Exit fullscreen mode

even if tokens per second continue increasing.


Starting Recommendation by Scenario

Single User

Start with:

Ollama
Enter fullscreen mode Exit fullscreen mode

Confirm:

  • the model is supported
  • cold start is acceptable
  • memory consumption is acceptable
  • latency is acceptable

The simplicity may be worth more than extra concurrent throughput.


Small Internal API

Start with:

Ollama or vLLM
Enter fullscreen mode Exit fullscreen mode

Benchmark:

  • request bursts
  • moderate concurrency
  • queue behavior
  • p95/p99
  • overload handling

Either system may be the better fit.


High-Concurrency GPU Service

Start with:

vLLM
Enter fullscreen mode Exit fullscreen mode

Then confirm that:

  • throughput improves with concurrency
  • p99 remains inside the SLO
  • TTFT remains acceptable
  • KV-cache remains healthy
  • queue growth remains controlled
  • OOM does not occur

Do not assume the answer before measuring it.


Practical Checklist

Before declaring one server faster than the other:

  • [ ] Use comparable model weights
  • [ ] Match precision and quantization
  • [ ] Match tokenizer behavior
  • [ ] Match chat templates
  • [ ] Match generation parameters
  • [ ] Record software versions
  • [ ] Record GPU and VRAM
  • [ ] Separate cold and warm tests
  • [ ] Use realistic input lengths
  • [ ] Use realistic output lengths
  • [ ] Sweep concurrency
  • [ ] Test open-loop and closed-loop load separately
  • [ ] Measure requests per second
  • [ ] Measure output tokens per second
  • [ ] Measure TTFT
  • [ ] Measure ITL or TPOT
  • [ ] Measure end-to-end latency
  • [ ] Calculate p50/p95/p99
  • [ ] Monitor queue length
  • [ ] Monitor GPU utilization
  • [ ] Monitor GPU memory
  • [ ] Record OOM and rejected requests
  • [ ] Preserve raw benchmark data
  • [ ] Evaluate results against the SLO

Final Takeaway

Ollama and vLLM solve overlapping problems, but they optimize for different priorities.

Ollama is attractive when you value:

  • fast setup
  • simple model management
  • a straightforward local lifecycle
  • moderate concurrency
  • lower operational complexity

vLLM becomes more attractive when you need:

  • continuous batching
  • higher concurrent throughput
  • efficient KV-cache management
  • high GPU utilization
  • larger request queues
  • detailed production observability

The correct comparison is not:

Which server produces the most tokens per second?

It is:

Which server delivers the throughput you need while keeping TTFT, p99 latency, queueing, errors, memory usage, and operational complexity inside acceptable limits?

If vLLM produces higher aggregate throughput but pushes p99 beyond your SLO, that is not a win.

If Ollama already handles your real traffic comfortably, migrating to a more complex serving stack may provide little practical benefit.

Benchmark the workload you actually have.

Find the saturation point.

Measure the queue.

Measure the tails.

Then choose the simplest system that satisfies the SLO.

Top comments (1)

Collapse
 
max_quimby profile image
Max Quimby

The framing of "start with the workload, not the engine" is the part most benchmarks skip, and it's the part that actually decides the outcome. One thing I'd add from running both in production: the crossover point isn't just about concurrency count, it's about arrival-time distribution. vLLM's continuous batching shines when requests arrive close enough together that the scheduler can keep partial batches full, but a bursty workload with idle gaps can leave it looking no better than Ollama because the batches never fill. We ended up plotting p99 against sustained RPS rather than raw concurrency, because "50 concurrent" from a load tester and "50 concurrent" from real traffic behave very differently once you factor in generation-length variance. The other thing that bit us: KV-cache pressure from long-context requests silently caps your effective batch size well before you hit GPU compute limits, so an OOM at "low" concurrency is usually a context-length problem in disguise. Curious whether your sweep held output length fixed or sampled it — that variable alone can flip the conclusion.