Ollama vs vLLM: quando usi cosa e perché la scelta sbagliata costa 19x in latenza
Ollama serve token a 41 TPS. vLLM arriva a 793 TPS. Stessa GPU - un'NVIDIA A100 nel benchmark Red Hat 2025 - stesso modello Llama 3.1 8B Instruct, stessa quantizzazione. Il delta è 19x. Non è un'ottimizzazione marginale: è la differenza tra un sistema che regge 5 utenti e uno che ne serve 500 simultaneamente con p99 latency sotto i 100ms.
Il problema è che la maggior parte degli ingegneri sceglie lo strumento sbagliato, non per ignoranza tecnica, ma perché Ollama è talmente semplice da avviare che sembra "abbastanza buono". E lo è - finché non lo metti in produzione con traffico reale. In questo episodio analizziamo le differenze architetturali tra i due motori, i benchmark aggiornati al 2026, e un decision framework operativo per sapere esattamente quando usare uno e quando l'altro. Nel mio portfolio ho deployato Ollama su VPS Hetzner CCX23 con Llama 3.1 8B come provider AI primario: è la scelta giusta per quel contesto. Ma se domani il traffico cresce, so esattamente quando e come migrare a vLLM.
Cosa Imparerai
- Differenza architetturale tra Ollama (llama.cpp wrapper) e vLLM (PagedAttention engine)
- Dati di benchmark reali: TPS, TTFT, latency p99 a diversi livelli di concorrenza
- Decision framework: quale motore scegliere in base a req/sec, SLA e budget ingegneria
- Setup pratico di entrambi con configurazioni ottimali commentate
- Calcolo del costo per token e break-even vs provider cloud
- Come migrare da Ollama a vLLM senza modificare il codice applicativo
- Quando Ollama è la scelta corretta anche in produzione
Prerequisiti
- Python 3.10+ e Docker installati
- Familiarità con REST API e JSON
- Accesso a GPU NVIDIA (consumer o cloud, minimo 8GB VRAM per 8B models)
- Conoscenza base di systemd o Docker Compose
- Nozioni base su cosa sono gli LLM e come generano token
Sezione 1 - Architettura a confronto: llama.cpp wrapper vs PagedAttention engine
Per capire il gap di performance non basta guardare i numeri: bisogna capire perché esistono. Ollama e vLLM risolvono lo stesso problema - servire inferenza da un Large Language Model - con filosofie radicalmente diverse.
Ollama è fondamentalmente un wrapper intorno a llama.cpp, la libreria C++ di
Georgi Gerganov che porta i modelli transformer su hardware consumer. Il suo punto di forza è la semplicità
operativa: packaging dei pesi in formato GGUF, API OpenAI-compatible out of the box, supporto per CPU (con
quantizzazione aggressiva), Apple Silicon via Metal, e GPU NVIDIA via CUDA. Ollama gestisce le richieste in
modo sequenziale o con parallelismo limitato (massimo 4 richieste simultane per default). Non c'è batching
dinamico: ogni richiesta occupa la GPU per tutta la sua durata prima che la successiva inizi.
vLLM nasce come sistema di serving ad alta concorrenza, sviluppato da Woosuk Kwon e team a UC Berkeley e pubblicato come paper peer-reviewed all'SOSP 2023. Il contributo centrale è PagedAttention: un algoritmo che gestisce la KV cache (Key-Value cache dell'attention mechanism) come la memoria virtuale del sistema operativo - con pagine logiche non-contigue e allocazione on-demand. Il risultato è che vLLM riduce lo spreco di memoria da 60–80% (sistemi tradizionali) a meno del 4%, permettendo batch size molto più grandi e quindi throughput superiore. Sopra questo, vLLM implementa continuous batching: le richieste vengono aggregate dinamicamente in batch uniformi, massimizzando l'utilizzo della GPU ad ogni ciclo di forward pass.
La differenza pratica è questa: Ollama processa una richiesta alla volta (o al massimo un piccolo pool fisso). vLLM processa decine o centinaia di richieste in parallelo, aggregandole in batch ottimizzati. A singolo utente la differenza è minima. Con 50 utenti simultanei, Ollama soffoca mentre vLLM scala linearmente.
| Caratteristica | Ollama | vLLM |
|---|---|---|
| Engine base | llama.cpp (C++) | PyTorch custom kernels + CUDA |
| Gestione memoria KV | Allocazione contigua statica | PagedAttention (pagine non-contigue, on-demand) |
| Batching | Nessuno (sequenziale o pool fisso) | Continuous batching dinamico |
| Formati modello | GGUF (quantizzato), llama.cpp | HuggingFace (FP16, BF16, AWQ, GPTQ, FP8) |
| Hardware supportato | CPU, GPU NVIDIA, Apple Silicon, AMD ROCm | GPU NVIDIA (CUDA 11.8+), AMD ROCm sperimentale |
| Multi-GPU | Non supportato | Tensor parallelism nativo |
| Setup time | 5 minuti | 30–60 minuti (prima volta) |
| API compatibile | OpenAI-compatible | OpenAI-compatible |
| Caso d'uso primario | Sviluppo locale, prototipazione, single-user | Production serving, alta concorrenza, SLA stringenti |
Sezione 2 - Benchmark TPS, latency e memoria: i numeri reali
I benchmark che seguono sono basati sul test Red Hat Developer pubblicato ad agosto 2025, con vLLM 0.9.1 e Ollama 0.9.2 su NVIDIA A100-PCIE-40GB con Llama 3.1 8B Instruct. Sono i dati più rigorosi disponibili pubblicamente con metodologia trasparente (tool di load testing, concorrenza incrementale 1–256 utenti).
| Metrica | Ollama | vLLM | Rapporto |
|---|---|---|---|
| Peak throughput (TPS) | 41 token/sec | 793 token/sec | vLLM 19x superiore |
| TTFT a 1 utente | ~45 ms | ~82 ms | Ollama più veloce (-45%) |
| TTFT a 50 utenti | ~3200 ms | ~145 ms | vLLM 22x più veloce |
| P99 latency a peak | 673 ms | 80 ms | vLLM 8x più basso |
| Throughput a 10 utenti | ~150 token/sec totale | ~485 token/sec totale | vLLM 3x superiore |
| Spreco KV cache memoria | 60–80% | <4% | PagedAttention >15x più efficiente |
Nota il dato sul TTFT (Time To First Token) a singolo utente: Ollama è più veloce di vLLM - 45ms contro 82ms. Questo è controintuitivo ma ha senso: vLLM ha overhead di startup per il batching engine e non "guadagna" nulla quando c'è una sola richiesta. Ollama è leggero e diretto. Il punto di crossover arriva intorno a 3–5 utenti concorrenti, dopo di che vLLM domina in modo crescente.
Il dato più importante per le decisioni di produzione non è il throughput massimo, ma il comportamento sotto carico reale. Con 50 utenti simultanei, Ollama accumula una coda che porta il tempo di attesa a 3.2 secondi prima che la generazione inizi. In un chatbot, questo è inaccettabile. In un batch processor offline, potrebbe essere ok. Questo è il core del decision framework.
Sezione 3 - Setup pratico: Ollama in 5 minuti
Partiamo da Ollama perché è il punto di partenza naturale. L'installazione su Linux richiede un singolo comando. Il seguente script bash installa Ollama, scarica Llama 3.1 8B e avvia il server, con commenti su ogni step per capire cosa succede sotto il cofano.
# Installazione Ollama su Linux (Ubuntu/Debian)
# Il curl pipe-a bash scarica e installa il binario Ollama via script ufficiale
curl -fsSL https://ollama.com/install.sh | sh
# Ollama viene installato come servizio systemd automaticamente
# Verifica che il daemon sia attivo e in ascolto sulla porta 11434
systemctl status ollama
# Scarica il modello Llama 3.1 8B (quantizzazione Q4_K_M di default, ~5GB)
# Usa ollama run invece di ollama pull per scaricare E testare subito
ollama run llama3.1:8b
# Verifica che l'API REST sia attiva (compatibile con specifiche OpenAI)
curl http://localhost:11434/api/tags
# Output atteso: {"models":[{"name":"llama3.1:8b",...}]}
# Test veloce dell'endpoint /v1/chat/completions (formato OpenAI)
curl http://localhost:11434/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama3.1:8b",
"messages": [{"role": "user", "content": "Ciao, chi sei?"}],
"stream": false
}'
La semplicità è reale: in meno di 5 minuti hai un LLM che risponde via API. Nel mio portfolio ho deployato Ollama su un Hetzner CCX23 (8 vCPU, 16GB RAM, nessuna GPU dedicata) con Llama 3.1 8B in quantizzazione Q4_K_M - circa 5GB di VRAM/RAM. Funziona, è stabile da mesi, e per il traffico del mio sito (meno di 50 chiamate AI al giorno) è più che sufficiente.
Per configurare Ollama in modo più granulare, la configurazione si passa tramite variabili d'ambiente o il file di systemd unit. Ecco la unit file annotata con le opzioni più rilevanti per produzione:
# /etc/systemd/system/ollama.service
# Unit systemd per Ollama in produzione con parametri ottimizzati
[Unit]
Description=Ollama LLM Server
After=network-online.target
Wants=network-online.target
[Service]
# Utente dedicato senza privilegi di root (sicurezza)
User=ollama
Group=ollama
ExecStart=/usr/local/bin/ollama serve
# Variabili d'ambiente per tuning performance
# OLLAMA_HOST: bind su tutte le interfacce (default: 127.0.0.1)
Environment="OLLAMA_HOST=0.0.0.0:11434"
# OLLAMA_NUM_PARALLEL: richieste concorrenti (default: 4)
# Aumentare solo se hai VRAM sufficiente per più copie del KV cache
Environment="OLLAMA_NUM_PARALLEL=4"
# OLLAMA_MAX_QUEUE: dimensione coda in attesa (default: 512)
# Ridurre per rifiutare prima le richieste invece di accumularle
Environment="OLLAMA_MAX_QUEUE=64"
# OLLAMA_KEEP_ALIVE: tempo di idle prima di scaricare il modello (default: 5m)
# Aumentare su server dedicato per evitare cold start
Environment="OLLAMA_KEEP_ALIVE=24h"
# OLLAMA_FLASH_ATTENTION: abilita Flash Attention 2 per modelli supportati
Environment="OLLAMA_FLASH_ATTENTION=1"
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Dopo aver modificato la unit file, ricarica systemd e riavvia il servizio con
systemctl daemon-reload && systemctl restart ollama. Il parametro
OLLAMA_NUM_PARALLEL è la leva più importante: aumentarlo significa più richieste
simultanee, ma ogni richiesta parallela usa una porzione separata di VRAM per il KV cache.
Con un modello 8B in Q4_K_M e 8GB VRAM, puoi permetterti al massimo 2–3 parallele.
Sezione 4 - Setup vLLM con Docker: il modo corretto
vLLM in produzione si deploya quasi sempre via Docker o su Kubernetes. Creare il container da zero
comporta la gestione di dipendenze CUDA complesse. Il modo consigliato è usare l'immagine ufficiale
vllm/vllm-openai che include già CUDA, PyTorch e tutti i kernel ottimizzati.
# Dockerfile per deployment vLLM con modello pre-scaricato nella immagine
# Base image ufficiale vLLM con CUDA 12.1 e PyTorch
FROM vllm/vllm-openai:latest
# Impostazioni ambiente per evitare prompt interattivi durante il build
ENV DEBIAN_FRONTEND=noninteractive
ENV HF_HOME=/models
ENV TRANSFORMERS_CACHE=/models
# Directory per i pesi del modello (va montata come volume esterno in produzione)
RUN mkdir -p /models
# Porta esposta: 8000 è il default di vLLM (diverso da Ollama su 11434)
EXPOSE 8000
# Comando di avvio con parametri ottimizzati per RTX 3090 / A100
# --model: path o HuggingFace model ID
# --tensor-parallel-size: numero di GPU (1 per single-GPU)
# --max-model-len: lunghezza massima del contesto in token
# --gpu-memory-utilization: percentuale VRAM usata (0.90 = 90%, lascia buffer)
# --dtype: auto sceglie BF16 su Ampere+, FP16 su GPU precedenti
CMD ["python", "-m", "vllm.entrypoints.openai.api_server", \
"--model", "meta-llama/Llama-3.1-8B-Instruct", \
"--tensor-parallel-size", "1", \
"--max-model-len", "8192", \
"--gpu-memory-utilization", "0.90", \
"--dtype", "auto", \
"--host", "0.0.0.0", \
"--port", "8000"]
Per lanciare il container con accesso alla GPU e al volume dei modelli:
# Avvio vLLM via Docker con GPU passthrough
# --gpus all: passa tutte le GPU disponibili al container
# --shm-size: shared memory necessaria per PyTorch multiprocessing (aumentare se OOM)
# -v: mount del volume con i pesi HuggingFace (evita di riscaricarli ad ogni restart)
docker run -d \
--name vllm-server \
--gpus all \
--shm-size 8g \
-v ~/.cache/huggingface:/models \
-p 8000:8000 \
vllm/vllm-openai:latest \
--model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 1 \
--max-model-len 8192 \
--gpu-memory-utilization 0.90
# Verifica che il server sia pronto (impiega 1-3 minuti al primo avvio)
# Il server è pronto quando risponde con status 200
until curl -s http://localhost:8000/health | grep -q "{}"; do
echo "Attesa avvio vLLM..."
sleep 5
done
echo "vLLM pronto!"
# Test endpoint modelli disponibili
curl http://localhost:8000/v1/models
Il primo avvio richiede il download dei pesi da HuggingFace Hub (circa 15GB per Llama 3.1 8B in BF16) e la compilazione dei kernel CUDA custom di vLLM (5–10 minuti). I run successivi sono molto più veloci perché entrambi vengono cachati. Nota: per scaricare Llama 3.1 devi avere un token HuggingFace e aver accettato la licenza Meta su huggingface.co.
Sezione 5 - Client Python unificato: stesso codice, motore intercambiabile
Uno dei vantaggi chiave di entrambi i motori è l'API OpenAI-compatible. Questo significa che puoi
scrivere il codice cliente una volta sola e passare da Ollama a vLLM cambiando solo il
base_url. Ecco un client Python che gestisce entrambi con una configurazione a runtime:
"""
Client LLM unificato per Ollama e vLLM.
Entrambi espongono API compatibili OpenAI: cambio motore = cambio base_url.
"""
import os
import time
from openai import OpenAI
from typing import Literal
# Tipo per il motore: "ollama" o "vllm"
TipoMotore = Literal["ollama", "vllm"]
def crea_client(motore: TipoMotore = "ollama") -> tuple[OpenAI, str]:
"""
Crea un client OpenAI configurato per Ollama o vLLM.
Args:
motore: "ollama" per sviluppo locale, "vllm" per produzione
Returns:
Tuple (client, nome_modello) pronto all'uso
"""
if motore == "ollama":
# Ollama: porta 11434, nessuna autenticazione necessaria
client = OpenAI(
base_url=os.getenv("OLLAMA_URL", "http://localhost:11434/v1"),
api_key="ollama", # Ollama accetta qualsiasi stringa come API key
)
modello = os.getenv("OLLAMA_MODEL", "llama3.1:8b")
else:
# vLLM: porta 8000, supporta token Bearer per autenticazione opzionale
client = OpenAI(
base_url=os.getenv("VLLM_URL", "http://localhost:8000/v1"),
api_key=os.getenv("VLLM_API_KEY", "vllm-no-auth"),
)
modello = os.getenv("VLLM_MODEL", "meta-llama/Llama-3.1-8B-Instruct")
return client, modello
def genera_risposta(
prompt: str,
motore: TipoMotore = "ollama",
temperatura: float = 0.7,
max_token: int = 512,
) -> dict:
"""
Genera una risposta LLM e misura le metriche di latency.
Returns:
Dict con: testo, latency_ms, tokens_per_sec
"""
client, modello = crea_client(motore)
inizio = time.perf_counter()
risposta = client.chat.completions.create(
model=modello,
messages=[{"role": "user", "content": prompt}],
temperature=temperatura,
max_tokens=max_token,
)
fine = time.perf_counter()
latency_ms = (fine - inizio) * 1000
testo = risposta.choices[0].message.content
token_generati = risposta.usage.completion_tokens if risposta.usage else 0
# Calcolo tokens per secondo (TPS)
tps = token_generati / (latency_ms / 1000) if latency_ms > 0 else 0
return {
"testo": testo,
"latency_ms": round(latency_ms, 2),
"tokens_generati": token_generati,
"tps": round(tps, 1),
"motore": motore,
"modello": modello,
}
# Esempio di utilizzo: stesso codice, motore configurabile via env var
if __name__ == "__main__":
motore_attivo = os.getenv("LLM_ENGINE", "ollama") # "ollama" o "vllm"
risultato = genera_risposta(
prompt="Spiega PagedAttention in 3 frasi",
motore=motore_attivo,
)
print(f"Motore: {risultato['motore']}")
print(f"Latency: {risultato['latency_ms']} ms")
print(f"Throughput: {risultato['tps']} TPS")
print(f"Risposta: {risultato['testo'][:200]}...")
La migrazione da Ollama a vLLM si riduce a impostare LLM_ENGINE=vllm nella tua configurazione
di produzione. Zero modifiche al codice applicativo. Questo è il vantaggio concreto dell'API
OpenAI-compatible: vendor portability garantita.
Sezione 6 - Benchmark con carico reale: come misurare il tuo stack
I benchmark dei vendor sono utili come riferimento ma non sostituiscono la misurazione sul tuo hardware
con il tuo workload reale. Lo strumento più semplice per testare un server LLM HTTP è
wrk combinato con uno script Python per generare richieste realistiche. Ecco una pipeline
di benchmark riproducibile:
#!/bin/bash
# Script benchmark per confrontare Ollama vs vLLM
# Richiede: wrk (apt install wrk) e Python 3.10+
# Configurazione test
CONCORRENZA_LIVELLI="1 5 10 25 50"
DURATA="30s"
PROMPT_FILE="benchmark_prompt.json"
# Crea il file con la richiesta di test (prompt realistico da ~50 token input)
cat > "$PROMPT_FILE" << 'EOF'
{
"model": "llama3.1:8b",
"messages": [
{
"role": "user",
"content": "Scrivi una spiegazione tecnica di 100 parole su come funziona l'attention mechanism nei Transformer."
}
],
"max_tokens": 150,
"temperature": 0.7,
"stream": false
}
EOF
echo "=== BENCHMARK OLLAMA (porta 11434) ==="
for CONC in $CONCORRENZA_LIVELLI; do
echo "--- Concorrenza: $CONC ---"
wrk -t"$CONC" \
-c"$CONC" \
-d"$DURATA" \
-s wrk_llm.lua \
--latency \
http://localhost:11434/v1/chat/completions
sleep 5
done
echo "=== BENCHMARK vLLM (porta 8000) ==="
for CONC in $CONCORRENZA_LIVELLI; do
echo "--- Concorrenza: $CONC ---"
wrk -t"$CONC" \
-c"$CONC" \
-d"$DURATA" \
-s wrk_llm.lua \
--latency \
http://localhost:8000/v1/chat/completions
sleep 5
done
# Nota: wrk_llm.lua è uno script Lua che imposta Content-Type e invia il body JSON
# Crea wrk_llm.lua con:
# wrk.method = "POST"
# wrk.headers["Content-Type"] = "application/json"
# wrk.body = io.open("benchmark_prompt.json"):read("*a")
Per benchmark più sofisticati (percentili di latency, distribuzione TTFT, throughput in token/sec)
il team vLLM mantiene il benchmark ufficiale vllm/benchmarks/benchmark_throughput.py
nel repo ufficiale che misura direttamente i token generati, non solo le richieste HTTP. Ti suggerisco
di usare quello come strumento di verifica dopo il tuo benchmark applicativo con wrk.
Sezione 7 - Decision matrix: il framework definitivo
Abbastanza teoria. Ecco il decision framework in forma di codice - letteralmente. Questo script Python prende come input le tue metriche operative e restituisce una raccomandazione motivata:
"""
Decision framework Ollama vs vLLM.
Inserisci i parametri del tuo caso d'uso e ottieni una raccomandazione motivata.
"""
from dataclasses import dataclass
from typing import Literal
@dataclass
class ParametriCasoUso:
"""Descrive il tuo caso d'uso LLM con parametri quantitativi."""
# Richieste medie al secondo (peak hour)
req_per_secondo: float
# SLA massimo accettabile per TTFT (time to first token) in millisecondi
sla_ttft_ms: int
# Numero di sviluppatori che gestiscono l'infrastruttura
n_sviluppatori: int
# Hai a disposizione una GPU NVIDIA per vLLM?
gpu_nvidia_disponibile: bool
# Il modello è sotto 13B di parametri? (modelli > 13B richiedono multi-GPU)
modello_sotto_13b: bool
# Stai facendo prototyping o è un sistema di produzione?
fase: Literal["prototipo", "produzione"]
# Budget mensile per infrastruttura in euro
budget_mensile_eur: float
def raccomanda_motore(params: ParametriCasoUso) -> dict:
"""
Applica il decision framework e restituisce raccomandazione con motivazioni.
Il framework usa soglie derivate dai benchmark Red Hat 2025:
- Soglia req/sec: 5 req/s è il punto dove vLLM supera nettamente Ollama
- Soglia TTFT: sotto 200ms richiede vLLM con >5 utenti concorrenti
"""
motore = "ollama"
punteggio_vllm = 0
motivazioni = []
# Regola 1: throughput (più importante)
if params.req_per_secondo > 10:
punteggio_vllm += 3
motivazioni.append(
f"Traffico elevato ({params.req_per_secondo} req/s): "
"vLLM è l'unica scelta praticabile oltre 10 req/s."
)
elif params.req_per_secondo > 5:
punteggio_vllm += 2
motivazioni.append(
f"Traffico medio ({params.req_per_secondo} req/s): "
"vLLM consigliato per SLA stabili."
)
else:
motivazioni.append(
f"Traffico basso ({params.req_per_secondo} req/s): "
"Ollama è sufficiente con OLLAMA_NUM_PARALLEL=4."
)
# Regola 2: SLA latency
if params.sla_ttft_ms < 200 and params.req_per_secondo > 3:
punteggio_vllm += 2
motivazioni.append(
f"SLA aggressivo ({params.sla_ttft_ms}ms TTFT) con >3 req/s: "
"Ollama degrada a 3200ms TTFT a 50 utenti."
)
# Regola 3: complessità operativa
if params.n_sviluppatori < 2:
motivazioni.append(
"Team piccolo: considera il costo operativo di vLLM "
"(tuning, monitoring, upgrade CUDA)."
)
# Regola 4: prerequisiti hardware
if not params.gpu_nvidia_disponibile:
motore = "ollama" # vLLM senza GPU NVIDIA è impraticabile in produzione
motivazioni.append(
"Senza GPU NVIDIA: vLLM non è un'opzione pratica. "
"Ollama con CPU+quantizzazione GGUF è l'alternativa."
)
return {"motore": motore, "motivazioni": motivazioni, "punteggio_vllm": 0}
# Regola 5: fase di sviluppo
if params.fase == "prototipo":
motivazioni.append(
"Fase prototipo: Ollama permette iterazione rapida. "
"Puoi migrare a vLLM al momento del go-live."
)
# Decisione finale
if punteggio_vllm >= 3:
motore = "vllm"
return {
"motore": motore,
"motivazioni": motivazioni,
"punteggio_vllm": punteggio_vllm,
}
# Esempio: chatbot interno aziendale con 20 utenti concorrenti
caso_aziendale = ParametriCasoUso(
req_per_secondo=8.0,
sla_ttft_ms=300,
n_sviluppatori=2,
gpu_nvidia_disponibile=True,
modello_sotto_13b=True,
fase="produzione",
budget_mensile_eur=200.0,
)
risultato = raccomanda_motore(caso_aziendale)
print(f"Raccomandazione: {risultato['motore'].upper()}")
for m in risultato['motivazioni']:
print(f" - {m}")
La logica del decision framework è trasparente: non ci sono soglie arbitrarie. I valori derivano dai benchmark (5 req/s è il punto dove il degrado di Ollama diventa misurabile, 10 req/s è dove diventa critico). Il punteggio permette di considerare trade-off: un sistema con 7 req/s e SLA di 500ms può ancora usare Ollama se il team è di una persona sola.
Sezione 8 - Cost analysis: quanto ti costa sbagliare motore
La scelta del motore ha impatto diretto sui costi. Analizziamo tre scenari reali con un calcolo rigoroso del costo per token e del break-even rispetto ai provider cloud.
| Scenario | Volume | Motore scelto | GPU necessaria | Costo mensile stimato | Alternative cloud |
|---|---|---|---|---|---|
| Portfolio personale / blog AI | 500 chiamate/giorno | Ollama | Nessuna (CPU con GGUF) | €0 (CPU inclusa nel VPS) | OpenAI: ~€1.50/mese - non vale la pena cloud |
| Startup SaaS, chatbot prodotto | 50k token output/ora | vLLM | 1x RTX 3090 (~$0.17/h) | ~€90/mese (GPU on-demand) | OpenAI GPT-4o: ~€250/mese - self-hosted vince a 2.7x |
| Enterprise, RAG su documenti interni | 500k token output/ora | vLLM | 2x A100 40GB | ~€600/mese | OpenAI: ~€2500/mese - self-hosted vince a 4x |
| Prototipo / developer locale | 200 chiamate/giorno | Ollama | Laptop con GPU consumer | €0 (hardware proprio) | N/A - nessun senso usare cloud per sviluppo |
Il calcolo del costo per token self-hosted si basa su: costo GPU (€/h) / throughput (token/h) = €/token. Con una RTX 3090 a $0.17/h e vLLM a 793 TPS, il costo per token output è circa $0.17 / (793 × 3600) = $0.000000060 per token, ovvero $0.06 per milione di token. OpenAI GPT-4o output: $15 per milione di token. Il rapporto è 250x a favore del self-hosted.
Anche con un modello più capace come Llama 3.3 70B (che richiede 2–4 GPU) i costi self-hosted rimangono 20–50x inferiori al cloud. Il punto di rottura non è il volume: è la complessità operativa e il rischio. Se hai 1 sviluppatore e non puoi permetterti downtime, il cloud ha ancora senso per volumi bassi.
Regola empirica costo self-hosted
- Sotto 100k token/giorno: non vale setup GPU dedicata. Ollama su CPU o cloud.
- 100k–1M token/giorno: RTX 3090 on-demand ($0.17/h) con vLLM batte il cloud.
- Sopra 1M token/giorno: hardware dedicato o GPU riservata. ROI chiaro in 3–6 mesi.
Sezione 9 - Quando Ollama è la scelta giusta anche in produzione
L'articolo ha parlato molto di quando scegliere vLLM, ma è importante chiarire che Ollama non è solo per prototipi. Esistono scenari di produzione in cui Ollama è la scelta tecnicamente corretta, non un compromesso.
Scenario 1 - Inferenza su dispositivi edge e hardware eterogeneo. Ollama supporta nativamente CPU (con GGUF quantizzato), Apple Silicon via Metal, e AMD GPU via ROCm. Se il tuo deployment target non è NVIDIA, Ollama è spesso l'unica opzione pratica. vLLM su CPU esiste ma le performance sono talmente degradate da renderlo inutile per produzione.
Scenario 2 - Single-tenant applications con SLA non stringenti. Un tool interno usato da una singola persona o da un team di 5, con richieste sequenziali e nessun requisito di latency sotto i 500ms, non ha nessun bisogno di vLLM. L'overhead operativo di vLLM (setup, tuning, monitoring) non si giustifica. Ollama in questo contesto è over-engineered nella direzione giusta: semplice, stabile, zero manutenzione.
Scenario 3 - RAG pipeline con embedding + generazione locale.
Se la tua pipeline esegue embedding (con un modello come nomic-embed-text) e generazione
nello stesso processo, Ollama gestisce entrambi senza configurazioni speciali. L'integrazione con
pgvector per RAG su PostgreSQL è documentata e stabile. vLLM gestisce solo la generazione, non
l'embedding: richiederebbe un secondo servizio separato.
Scenario 4 - Rapid iteration su modelli diversi.
Ollama permette di passare da Llama 3.1 a Mistral a Phi-3 con un solo comando
(ollama run phi3). vLLM richiede di riavviare il container con una nuova configurazione
per ogni modello (o di usare multi-model serving con overhead di VRAM significativo). Per team
che sperimentano attivamente con architetture diverse, Ollama è operativamente superiore.
FAQ
Ollama e vLLM sono compatibili con la stessa API?
Sì. Entrambi espongono endpoint compatibili con le specifiche OpenAI: /v1/chat/completions,
/v1/completions e /v1/models. Puoi passare da Ollama a vLLM cambiando solo
il base_url nel client senza modificare una riga di codice applicativo. Questo è uno
dei vantaggi pratici più importanti di entrambi gli strumenti.
Quante richieste al secondo regge Ollama prima di degradare?
Ollama gestisce fino a 4 richieste in parallelo per default (configurabile con
OLLAMA_NUM_PARALLEL). Con concorrenza superiore a 5 req/sec le richieste accodate
aumentano il TTFT in modo non lineare: nei benchmark Red Hat 2025, a 50 utenti simultanei il TTFT
sale da ~45ms a ~3200ms. Questo non è un bug ma un limite architetturale del modello sequenziale
di llama.cpp.
PagedAttention funziona anche su GPU consumer come RTX 3090?
Sì. vLLM con PagedAttention è compatibile con qualsiasi GPU NVIDIA con CUDA 11.8+, incluse le RTX 3090 (24GB VRAM). La RTX 3090 è sufficiente per servire Llama 3.1 8B in BF16 con throughput significativamente superiore a Ollama. Per modelli 70B+ sono necessarie configurazioni multi-GPU con tensor parallelism.
Qual è il costo reale di una GPU RTX 3090 su cloud?
Su provider come Vast.ai e RunPod il prezzo on-demand è $0.12–0.17/ora per RTX 3090, circa $87–122/mese a utilizzo continuo. Confrontato con OpenAI GPT-4o output ($15/1M token), il break-even avviene già con 1–2 milioni di token al mese. A volumi maggiori il risparmio diventa sostanziale: 5M token/mese = $75 OpenAI vs ~$6 self-hosted con vLLM su RTX 3090.
vLLM supporta modelli multimodali e function calling?
Sì. vLLM dalla versione 0.4.x supporta modelli multimodali (vision) come LLaVA e InternVL, e function calling/tool use compatibile con la specifica OpenAI. Ollama ha aggiunto supporto sperimentale per tool calling a partire dalla versione 0.3.x, ma con limitazioni sui modelli supportati e sulla conformità completa alla specifica.
Posso usare vLLM su CPU senza GPU?
vLLM supporta inferenza CPU tramite backend OpenVINO o llama.cpp ma le performance sono drasticamente inferiori: 5–10 token/sec contro i 700+ della GPU. Per deployment CPU-only, Ollama con GGUF quantizzato (Q4_K_M o Q5_K_M) è la scelta più pratica, con throughput di 10–30 token/sec su CPU server moderno.
Come si migra da Ollama a vLLM senza downtime?
Entrambi espongono la stessa API OpenAI-compatible, quindi la migrazione è:
(1) deployare vLLM in parallelo su porta diversa (es. 8000 vs 11434),
(2) validare le risposte con un test suite automatizzato,
(3) aggiornare il base_url nel codice o nel load balancer (nginx upstream),
(4) monitorare per 30 minuti, poi spegnere Ollama.
Zero modifiche al codice applicativo, downtime zero se usi un load balancer.
Conclusione
Il titolo dell'articolo parla di "19x in latenza": il benchmark reale è ancora più netto. A 50 utenti concorrenti, vLLM ha un TTFT di 145ms contro i 3200ms di Ollama. Non è una questione di quale sia il motore "migliore" in assoluto: è una questione di abbinare lo strumento al contesto.
Il framework decisionale si riduce a tre domande. Prima: quante richieste simultanee servono? Sotto 5 req/sec, Ollama è sufficiente. Sopra 10, vLLM è necessario. Tra 5 e 10, dipende dal tuo SLA. Seconda: hai GPU NVIDIA disponibile? Senza CUDA, Ollama è l'unica scelta pratica per produzione. Terza: quanto costa operativamente il sistema? Se hai un ingegnere DevOps dedicato, l'overhead di vLLM è trascurabile. Se sei un solopreneur, Ollama ti risparmia settimane di tuning.
Nel mio portfolio uso Ollama su Hetzner CCX23 senza GPU: il traffico AI giornaliero non giustifica
il costo di una GPU dedicata. Ma ho già il codice cliente scritto con base_url
configurabile via env var. Quando il traffico crescerà, la migrazione sarà un cambio di variabile
d'ambiente, non un refactoring.
Prossimo episodio: Ollama setup production: quantization, memory, concurrency e cost per inference. Approfondiamo GGUF, il sweet spot tra accuracy e velocità, e come Ollama si integra con PostgreSQL + pgvector per RAG.

Commenti
Caricamento commenti...
Accedi per commentare