SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

Perché il tuo LLM self-hosted rallenta con 5 utenti

Con un solo utente funziona, con cinque rallenta: scopri come batching, cache KV, prefill e profondità della coda fissano la capacità del server LLM.

Perché un LLM self-hosted rallenta quando arrivano più utenti?

Un LLM self-hosted si blocca con 5 utenti simultanei perché il server genera ancora una risposta alla volta e gli altri quattro restano in coda. La documentazione di Ollama è esplicita sul valore predefinito: OLLAMA_NUM_PARALLEL è «il numero massimo di richieste parallele che ogni modello elaborerà contemporaneamente, valore predefinito 1». Non c'è un guasto. Quattro delle cinque persone stanno aspettando il proprio turno.

La soluzione raramente consiste nell'usare un server più potente. Serve un motore di serving che elabori molte richieste nel modello durante lo stesso forward pass, oltre a una quantità sufficiente di memoria libera per mantenere il contesto delle conversazioni di tutti gli utenti. Entrambi gli aspetti sono importanti, ma è il secondo a stabilire effettivamente il limite massimo.

Le due fasi attraversate da ogni richiesta

Il prefill legge l’intero prompt in una sola volta e costruisce la cache dell’attenzione. Tutti i token del prompt vengono elaborati insieme dal modello, quindi il prefill consiste in una moltiplicazione di matrici di grandi dimensioni ed è limitato dalla capacità di calcolo. Il decode genera poi la risposta un token alla volta. Per ogni token è necessario leggere nuovamente dalla memoria tutti i pesi del modello, mentre i calcoli eseguiti su quel singolo token sono minimi. Il decode è quindi limitato dalla larghezza di banda della memoria.

Questa asimmetria spiega perché il batching è efficace. Il decode di un singolo utente legge, ad esempio, 5 GB di pesi per token e lascia inattiva la maggior parte delle unità di calcolo. Aggiungendo una seconda richiesta, il motore legge una sola volta gli stessi 5 GB e calcola quindi due token. Il secondo utente aggiunge un costo in termini di tempo quasi nullo. Gestire le richieste rigorosamente una dopo l’altra elimina questo vantaggio.

Due valori descrivono l’esperienza dell’utente. Il TTFT (time to first token) è dato dalla somma del tempo di attesa in coda e del prefill. L’ITL (inter-token latency) è l’intervallo tra i token trasmessi in streaming ed è determinato dal decode. Un server lento è generalmente lento in una di queste due fasi, e le correzioni da applicare non sono le stesse. Prima di modificare qualsiasi impostazione, è utile stabilire quale delle due fasi costituisce il problema; misurare separatamente prefill e decode consente di determinarlo.

Il batching statico fa attendere tutti la risposta più lenta

Il batching statico è la versione più semplice. È quello che si ottiene raggruppando manualmente le richieste nel codice dell'applicazione. Il motore raccoglie N richieste, le esegue insieme e mantiene occupato ogni slot fino al completamento della generazione più lunga del gruppo.

Una richiesta di un utente per un riepilogo di 1,200 token mantiene quattro risposte di una sola riga bloccate nel batch, perché il batch non libera alcuno slot finché non termina il membro più lento.

Ne derivano due costi. Le sequenze già completate continuano a occupare slot senza eseguire calcoli utili. Di conseguenza, il throughput effettivo diminuisce quando le lunghezze dell'output variano, come accade spesso nelle chat. Una richiesta che arriva un passo dopo la formazione del batch deve attendere lo svuotamento dell'intero batch prima ancora di iniziare il prefill. Il suo TTFT dipende quindi dal testo molto lungo richiesto da un altro utente.

Il continuous batching ammette e ritira le richieste a ogni token

Il continuous batching pianifica l'elaborazione a livello del singolo passaggio di decodifica. Dopo ogni passaggio, lo scheduler rimuove le sequenze che hanno appena prodotto il token di arresto, quindi ammette le richieste in attesa negli slot liberi. Una risposta che termina al passaggio 40 libera il relativo slot al passaggio 40, non alla fine del batch.

Non è un comportamento particolare. llama-server documenta -cb, --cont-batching come «se abilitare il continuous batching (detto anche dynamic batching) (impostazione predefinita: abilitato)» e vLLM si basa su questo principio. Anche Ollama gestisce richieste parallele. Per impostazione predefinita, però, il numero massimo è uno. Per questo molte persone concludono che il proprio hardware non supporti la concorrenza, quando è la configurazione a disabilitarla.

I risultati pubblicati sul continuous batching vengono generalmente misurati su schede per datacenter che dispongono sia di capacità di calcolo inutilizzata sia di decine di gigabyte per la cache. L'andamento di questi risultati si applica anche al tuo sistema. I valori assoluti no: il motivo è spiegato nella sezione sulla memoria qui sotto.

Il prefill compete con il decode per le stesse risorse di calcolo

Quando arriva una nuova richiesta mentre quattro risposte sono in streaming, il relativo prompt deve essere elaborato prima con il prefill, che richiede molte risorse di calcolo. Se lo scheduler assegna a questo prefill uno step dedicato, durante quello step i quattro utenti che stanno ricevendo la risposta non ottengono alcun token. Con un prompt lungo, ogni finestra aperta mostra una pausa evidente. È il rallentamento a scatti che si verifica quando il server ha un blocco ogni volta che un altro utente invia una richiesta.

Il prefill suddiviso in blocchi divide un prompt lungo in parti e inserisce ogni parte nello stesso step dei decode già in esecuzione. La guida al tuning di vLLM indica direttamente il compromesso: budget di dimensioni inferiori «ottengono un ITL migliore perché ci sono meno prefill che rallentano i decode», mentre valori più alti «ottengono un time to first token (TTFT) migliore perché consentono di elaborare più token di prefill in un batch». Devi scegliere quale esperienza proteggere: quella della persona che aspetta l'inizio della risposta oppure quella delle persone che osservano il testo mentre viene trasmesso.

La lunghezza del prompt determina l'impatto del problema. Un prompt di 6,000 token con una risposta di 200 token richiede 6,000 token di lavoro di prefill a fronte di 200 step di decode. Le chat con retrieval-augmented generation e i prompt di sistema lunghi portano entrambe a questo scenario. Il prefill smette quindi di essere un dettaglio trascurabile e diventa l'operazione che gli utenti devono attendere. Il prefix caching è utile quando la parte lunga si ripete: vLLM espone --enable-prefix-caching, che riutilizza la cache per un prefisso di prompt condiviso invece di ricalcolarlo per ogni richiesta.

La memoria che si esaurisce per prima è la cache KV

Ogni token di ogni conversazione attiva lascia un vettore key e un vettore value in ogni layer del modello. Questa è la cache KV (cache key/value) e consente alla fase di decode di evitare di ricalcolare l'intero prompt per ogni nuovo token. La dimensione per token è determinata dalla struttura del modello: 2 (una key e un value) moltiplicato per il numero di layer, per il numero di head key/value, per la dimensione della head e per i byte per valore. Leggi questi numeri dal config.json del modello.

Calcola il valore una volta e il limite diventa chiaro. Un modello tipico da 8B con 36 layer, 8 head key/value e una dimensione della head pari a 128, con la cache memorizzata a 16 bit, richiede 2 36 8 128 2 byte per token. Sono 147,456 byte, circa 144 KiB. Una conversazione da 8,192 token richiede quindi circa 1.2 GB di cache. Cinque conversazioni richiedono circa 6 GB, oltre ai pesi, e questa è la risposta concreta a quanti utenti possono essere gestiti.

La concorrenza moltiplica il contesto, come mostrano chiaramente gli strumenti. Le FAQ di Ollama riportano: "Parallel request processing for a given model results in increasing the context size by the number of parallel requests. For example, a 2K context with 4 parallel requests will result in an 8K context and additional memory allocation." La RAM richiesta cresce in proporzione a OLLAMA_NUM_PARALLEL moltiplicato per OLLAMA_CONTEXT_LENGTH. In llama-server, il contesto richiesto con -c viene distribuito tra gli slot -np, quindi aumentare da solo il numero di slot riduce la quantità di contesto disponibile per ogni richiesta. Leggi il contesto per slot dal log di avvio invece di presumerlo.

vLLM esegue invece una preallocazione. --gpu-memory-utilization (valore predefinito 0.92) è "the fraction of GPU memory to be used for the model executor". La memoria rimanente dopo l'allocazione dei pesi diventa il pool KV paginato. Quando il pool si esaurisce, lo scheduler espelle una richiesta invece di causare un errore:

WARNING 05-09 00:49:33 scheduler.py:1057] Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.

Nel motore V1 di vLLM, la modalità di preemption predefinita è RECOMPUTE. Una richiesta espulsa elimina quindi la propria cache e ripete il prefill quando viene riammessa. Questa operazione viene eseguita due volte. La documentazione avverte che "preemption and recomputation can adversely affect end-to-end latency". Questa riga di log è la spiegazione più utile del motivo per cui un utente può attendere molto più a lungo degli altri mentre la latenza media resta nella norma. Imposta disable_log_stats=False per registrare il conteggio cumulativo oppure leggi il contatore di preemption dalle metriche Prometheus esposte da vLLM.

Cosa cambia con 2, 5 e 20 utenti simultanei

Due utenti. Su una GPU con cache disponibile, l'impatto è quasi impercettibile, perché il secondo flusso di generazione procede insieme al primo con un aumento minimo del tempo complessivo. Su una VPS con solo CPU e da 4 a 8 GB di RAM, invece, il costo è reale: entrambi i flussi condividono gli stessi pochi vCPU e la stessa larghezza di banda della RAM. Ogni utente ottiene quindi circa la metà dei token al secondo, mentre il consumo della cache raddoppia a fronte di un budget molto più ridotto.

Cinque utenti. In questo caso i valori predefiniti non bastano più e il problema iniziale è la coda. Con OLLAMA_NUM_PARALLEL impostato su 1, quattro persone devono attendere chi ha richiesto la risposta più lunga e, quando arriva il loro turno, vedono una velocità normale. Aumentando il numero di richieste parallele, il problema cambia: cinque slot con un contesto di 8K token richiedono una cache da 40K token. Se lo spazio non è sufficiente nella VRAM, il motore trasferisce alcuni layer nella RAM di sistema. Se anche la RAM non è sufficiente, il server usa lo swap e la velocità scende drasticamente.

Venti utenti. Venti persone in un'interfaccia di chat non equivalgono di norma a venti richieste simultanee. È l'aspetto più importante da capire prima di acquistare l'hardware. Una persona legge la risposta e attende da 20 a 60 secondi prima del turno successivo, quindi la maggior parte della sessione resta inattiva. Venti agenti o venti processi di riepilogo di documenti generano invece venti flussi reali senza alcun periodo di inattività. Si tratta di una macchina diversa. Uno sviluppatore che ha indirizzato un agente di programmazione al proprio server Ollama è più vicino al secondo caso che al primo, perché l'agente continua a inviare richieste per tutta la durata dell'attività e non lascia le pause di lettura tipiche di una persona.

Gli utenti lavorano in concorrenza o sono soltanto autenticati?

Calcola prima il numero di richieste in corso, poi dimensiona il sistema. Il calcolo è semplice: richieste in corso = utenti × secondi necessari per generare una risposta per turno ÷ secondi tra un turno e l'altro.

  1. Misura prima la velocità del tuo singolo flusso, sia per il prefill sia per il decode. Non usare il valore ottenuto dalla scheda di qualcun altro: misura i token al secondo sul tuo server e usa il risultato.
  2. Stima il duty cycle. Venti utenti della chat, 12 secondi di generazione per turno e un turno ogni 90 secondi producono 20 * 12 / 90, cioè circa 2.7 richieste in corso.
  3. Imposta il numero di slot leggermente al di sopra di questo valore, quindi confrontalo con la memoria disponibile: il numero di slot moltiplicato per il contesto per richiesta deve rientrare nei token della cache realmente disponibili.
  4. Mantieni breve la coda, in modo che il superamento della capacità generi rapidamente un errore visibile.

I token della cache disponibili corrispondono alla memoria libera dopo il caricamento dei pesi, divisa per il costo per token indicato nella sezione precedente. Una scheda da 24 GB che esegue un modello 8B a 16 bit usa circa 16 GB per i pesi e dispone di circa 6 GB di cache utilizzabile con l'utilizzo predefinito, sufficienti per circa cinque conversazioni da 8K. Per gestirne di più, riduci il contesto per richiesta oppure memorizza la cache a 8 bit (llama-server richiede --cache-type-k q8_0). Entrambe le opzioni aumentano la concorrenza in cambio di una rinuncia a qualcosa. Prima di investire nell'hardware, conviene leggere con attenzione questo compromesso: quando un GPU VPS raggiunge il punto di pareggio rispetto ai token API.

Quando i valori predefiniti di Ollama non sono più sufficienti

Aumenta il numero di richieste parallele tramite l'unità di servizio, perché un export eseguito nella shell non raggiunge un demone gestito da systemd.

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_MAX_QUEUE=64"
sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment
ollama ps

systemctl show deve stampare le tre variabili appena impostate. In caso contrario, il drop-in non è stato salvato e tutte le operazioni successive saranno inutili. ollama ps elenca quindi il modello caricato con una dimensione superiore a quella dei soli pesi, perché quattro slot da 8,192 token riservano altri 32,768 token di cache. Se la colonna PROCESSOR mostra una parte del modello sulla CPU quando ti aspettavi che fosse tutto sulla GPU, significa che hai richiesto più cache di quanta ne fosse disponibile sulla scheda. Riduci uno dei due valori. Di solito è più sicuro ridurre il contesto, ma una finestra troppo piccola tronca silenziosamente i prompt lunghi invece di restituire un errore. Conviene quindi dimensionare deliberatamente num_ctx invece di ridurlo finché il modello rientra nella memoria disponibile.

Il valore predefinito della coda merita un'ulteriore verifica. Ollama accoda fino a OLLAMA_MAX_QUEUE richieste e "il valore predefinito è 512". Oltre questo limite risponde "con un errore 503 che indica che il server è sovraccarico". Una coda lunga 512 richieste su un host che ne gestisce quattro alla volta rappresenta una promessa che non puoi mantenere, perché il client in posizione 300 va in timeout molto prima del proprio turno. Una coda breve restituisce un errore che l'applicazione può ritentare o segnalare. È preferibile a un indicatore di caricamento che non termina mai.

Esegui un test reale. Invia due richieste nello stesso momento da due terminali e monitora entrambe. Se la seconda non produce alcun risultato finché la prima non termina, l'impostazione delle richieste parallele non è stata applicata.

Quando un motore di serving completo inizia a ripagare la complessità

vLLM ripaga la configurazione aggiuntiva quando si dispone di una GPU con risorse disponibili e di più di circa quattro richieste effettivamente in elaborazione contemporaneamente. Il suo scheduler opera per token, la cache è paginata per riutilizzare i frammenti liberi e converte la VRAM inutilizzata in concorrenza invece di lasciarla inattiva. Ad agosto 2026, l'installazione e l'avvio documentati richiedono due comandi:

uv pip install vllm --torch-backend=auto
vllm serve Qwen/Qwen2.5-1.5B-Instruct
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "Qwen/Qwen2.5-1.5B-Instruct",
        "messages": [{"role": "user", "content": "Say hello."}]
    }'

Una risposta contenente un array choices indica che il server è attivo e il modello è stato caricato. Sotto carico, i due parametri più importanti sono --max-num-seqs, il "numero massimo di sequenze da elaborare in una singola iterazione", e --max-num-batched-tokens, il "numero massimo di token che possono essere elaborati in una singola iterazione". Il primo limita la concorrenza. Il secondo definisce il budget di prefill suddiviso in blocchi descritto in precedenza.

Con meno di circa quattro richieste contemporaneamente, o su qualsiasi macchina priva di una GPU supportata, vLLM aggiunge complessità e offre vantaggi limitati. Richiede una scheda di classe CUDA e all'avvio si riserva gran parte della memoria, un compromesso inadatto a una VPS con 4-8 GB. In questo caso è preferibile un modello più piccolo con un contesto più breve e una coda sotto il proprio controllo. come differiscono Ollama e vLLM come motori di serving analizza in dettaglio la scelta, mentre eseguire Qwen 3 8B su una VPS mostra quali risorse richiede un modello di dimensioni medie prima di aggiungere anche un solo utente.

Il compromesso che la tradizione omette

Il batching continuo aumenta il throughput complessivo e di solito migliora anche la latenza mediana, perché una richiesta in coda inizia prima. La latenza di coda si comporta al contrario, ma questo aspetto viene menzionato raramente.

Ogni sequenza aggiuntiva in uno step introduce un piccolo carico di lavoro, quindi l’ITL aumenta per tutti man mano che il batch si riempie. Il prefill di una nuova richiesta occupa una parte dello step che altrimenti sarebbe stata disponibile per le richieste già in streaming. Quando la cache è sotto pressione, lo scheduler effettua il preemption e riporta una richiesta parzialmente generata all’inizio del prefill.

Un’interfaccia chat mostra i valori di coda, non le medie. Uno stream che si interrompe per due secondi nel mezzo di una frase sembra non funzionare, anche quando il tempo totale di completamento è buono. Misura p95 TTFT e p95 ITL sotto il carico previsto e considera la media dei token al secondo un valore di capacità, non una descrizione dell’esperienza utente.

Da questo deriva l’impostazione pratica. Limita la concorrenza a un valore leggermente inferiore a quello consentito dalla memoria, in modo che il motore non debba mai effettuare il preemption. Una coda breve e prevedibile è preferibile a un batch profondo soggetto a thrashing, perché un utente che attende quattro secondi e poi riceve uno stream fluido è più soddisfatto di un utente che inizia subito ma subisce due interruzioni.

Cosa controllare quando è lento

Ogni richiesta procede normalmente, ma l'attesa è lunga. È una coda, non un problema di velocità. Controlla prima l'impostazione della concorrenza. Il modello sta elaborando correttamente le richieste, una alla volta.

Ollama restituisce HTTP 503. La coda è piena. Il server è realmente al limite della capacità oppure OLLAMA_MAX_QUEUE è impostato volutamente su un valore basso per ridurre il carico, che è esattamente il comportamento desiderato.

I token al secondo crollano sotto carico su un server con CPU. Esegui vmstat 1 mentre il problema si verifica. Valori diversi da zero nelle colonne si e so indicano che la macchina sta usando lo swap, quindi i pesi vengono letti dal disco per ogni token. Nessuna modifica alla configurazione può risolvere il problema. Riduci le dimensioni del modello o il numero di slot.

Un utente su dieci attende molto più a lungo degli altri. Cerca preempted nel log di vLLM. La preemption e il ricalcolo associato sono la causa più comune. Indicano che la cache è sovrascritta rispetto alla lunghezza del contesto consentita.

Il TTFT è elevato anche quando il server è inattivo. Si tratta del prefill, non della concorrenza. I prompt lunghi richiedono tempo prima che venga restituito il primo token. Controlla quindi le dimensioni del prompt e il caching dei prefissi prima di esaminare l'hardware. Se l'attesa lunga riguarda solo il primo utente dopo un periodo di inattività e tutti gli utenti successivi non hanno lo stesso problema, non è un problema di prefill. In questo caso Ollama ha scaricato il modello e sta leggendo di nuovo i pesi dal disco. Conviene escludere questa causa mantenendo il modello residente tra le richieste.

FAQ

Perché il mio LLM self-hosted rallenta quando lo usa una seconda persona?

Nella maggior parte dei casi non rallenta affatto. Le richieste vengono messe in coda. Ollama viene distribuito con OLLAMA_NUM_PARALLEL impostato su 1, quindi la seconda richiesta attende che la prima generi il token finale. Per distinguere i due casi, misura i tempi dello stream di un utente mentre un altro attende: se, una volta avviato, il primo stream mantiene un numero normale di token al secondo, hai una coda e aumentare il numero di richieste parallele risolve il problema. Se entrambi gli stream procedono a metà velocità, stai realmente condividendo la larghezza di banda della memoria e questo è un limite hardware.

Quanti utenti simultanei può gestire una singola GPU di fascia bassa?

Calcola la memoria, non il numero di utenti. Prima considera i pesi, poi la KV cache, che per ogni token e per ogni conversazione attiva richiede 2 volte il numero di layer per il numero di key/value heads per la dimensione della head per il numero di byte. Un modello tipico da 8B con 36 layer, 8 key/value heads e una head dimension di 128 richiede circa 144 KiB per token in 16 bit. Una conversazione di 8,192 token richiede quindi circa 1.2 GB. Una scheda da 24 GB che contiene il modello in 16 bit ha circa 6 GB disponibili per la cache: sono circa cinque conversazioni con il contesto completo, o più conversazioni se riduci il contesto.

Il continuous batching rende più lenta la risposta per ogni utente?

La latenza mediana di solito migliora, perché le richieste non devono più attendere il completamento di un intero batch. La latenza di coda peggiora. Ogni sequenza aggiuntiva aumenta il lavoro di ogni fase di decoding, l'operazione di prefill di una nuova richiesta sottrae parte di una fase agli utenti che ricevono lo stream e una richiesta interrotta deve eseguire il prefill due volte. Misura la latenza inter-token p95, non la media, perché una finestra di chat rende evidenti le pause in un modo che le medie possono nascondere.

Devo aumentare OLLAMA_NUM_PARALLEL o passare a vLLM?

Aumenta prima il numero di richieste parallele. Non comporta costi e richiede un solo file drop-in; inoltre risolve il caso comune in cui quattro persone attendono in coda dietro una risposta lunga. Il limite è la memoria: le richieste parallele moltiplicano il contesto che devi mantenere, quindi controlla se alcuni layer vengono spostati sulla CPU. Passa a vLLM quando hai una GPU con VRAM disponibile e più di circa quattro richieste realmente in esecuzione, perché da quel punto la cache paginata e la pianificazione per token offrono vantaggi superiori al relativo costo.

Più core della CPU risolvono la lentezza di un server LLM?

Non per la parte più evidente agli utenti. Il decoding legge l'intero modello dalla memoria per ogni token, quindi è limitato dalla larghezza di banda della RAM. I core aggiuntivi smettono di essere utili quando la larghezza di banda è saturata. Il prefill scala invece con il numero di core, quindi più core riducono il tempo al primo token per i prompt lunghi. Su una VPS da 4 a 8 GB il vincolo principale è di solito la capacità della memoria. La soluzione efficace consiste quindi nell'usare un modello più piccolo o un contesto più breve, non nell'aggiungere vCPU.