Ridurre la latenza dei LLM è una delle sfide più critiche per gli ingegneri che creano applicazioni IA reattive. Mentre i grandi modelli linguistici (LLM) continuano a crescere in capacità, la loro generazione token per token può creare colli di bottiglia frustranti per gli utenti finali, e i lunghi tempi di attesa portano direttamente a un minore coinvolgimento e all’abbandono delle applicazioni. Ottimizzare le pipeline di inferenza per la velocità è quindi un requisito fondamentale per gli sviluppatori. Questa guida illustra come configurare il prompt caching, implementare lo streaming delle risposte, strutturare il routing di rete edge e utilizzare configurazioni serverless per ridurre i ritardi di elaborazione.
[!TIP] Consiglio sulle metriche di prestazione: Quando misuri i ritardi delle API, isola il Time to First Token (TTFT) dalla velocità di generazione complessiva. Un TTFT basso fa percepire all’utente un’applicazione istantanea, anche se la generazione completa dell’output richiede diversi secondi, perché il testo inizia a comparire subito.
Punti chiave:
- Prompt caching: Riutilizza i prefissi statici per aggirare le fasi di parsing e ridurre il TTFT dell'80%.
- Streaming delle risposte: Invia i token tramite Server-Sent Events (SSE) affinché gli utenti vedano una generazione di testo istantanea.
- Edge Workers: Esegui l’autorizzazione e il routing delle richieste in centri edge regionali vicini agli utenti.
- Routing dei modelli: Dirotta le richieste semplici verso modelli leggeri per ottimizzare le velocità.
Le componenti della latenza delle API LLM
Per ridurre i tempi di risposta, devi prima capire quali elementi determinano il ritardo complessivo dell’API. La latenza complessiva è la somma cumulativa di tre variabili distinte.
In primo luogo, il tempo di transito di rete misura quanto impiega una richiesta a viaggiare dal client al tuo server, e poi all’API del fornitore del modello. Questo rende la distanza di transito un collo di bottiglia importante.
In secondo luogo, il Time to First Token (TTFT) rappresenta la durata tra la ricezione della richiesta da parte del modello e la generazione del suo primo token di output, ed è per questo che il prompt caching conta così tanto.
Infine, la velocità di generazione dei token misura il ritmo con cui l’hardware produce i token successivi. I vincoli hardware determinano la velocità di generazione, ma gli sviluppatori mantengono il pieno controllo sul tempo di transito e sul TTFT, quindi un routing e un caching intelligenti possono ridurre drasticamente la latenza dei LLM.
Accelera la tua integrazione LLMPrerequisiti
Prima di iniziare a impostare le ottimizzazioni descritte di seguito, assicurati di avere a disposizione i seguenti elementi. Nessuno di essi è esotico, ma saltarne uno tende a causare errori sconcertanti in seguito.
- Un middleware o un runtime edge che controlli. Gli esempi usano Cloudflare Workers, ma va bene qualsiasi piattaforma serverless in grado di inoltrare una richiesta come proxy. Ti serve un punto collocato tra il browser e il fornitore del modello.
- Credenziali API di un fornitore che supporta streaming e caching. OpenAI e Anthropic li supportano entrambi. Conserva la chiave come secret (un secret di Wrangler o una variabile d’ambiente), mai nel codice lato client.
- Node.js 18 o versione successiva se vuoi testare i Worker in locale con
wrangler dev. Le API globalifetcheReadableStreamusate in tutta la guida sono disponibili in quel runtime e nei browser moderni. - Una misurazione di riferimento. Rileva il tuo attuale Time to First Token e il tempo di risposta totale prima di modificare qualsiasi cosa, così potrai dimostrare che ogni ottimizzazione ha davvero aiutato. La sezione di benchmarking più avanti mostra come.
- Familiarità con i Server-Sent Events (SSE). Le risposte in streaming arrivano come una sequenza di righe
data:, che analizzerai sul client.
Implementare il prompt caching
Il prompt caching è il modo più efficace per ottimizzare il TTFT nelle applicazioni costruite attorno a grandi prompt di sistema. Quando una richiesta contiene un lungo blocco di istruzioni statiche (come il prompt di sistema di un agente o un documento di riferimento RAG), il fornitore del modello deve analizzare e codificare quei token a ogni esecuzione. Sia Anthropic sia OpenAI supportano il prompt caching, che conserva in memoria gli stati dei token analizzati. Le richieste successive che condividono lo stesso prefisso aggirano quindi la fase di parsing, riducendo il TTFT fino all'80%.
La durata della cache varia da un fornitore all’altro. Anthropic mantiene la cache per circa cinque minuti di inattività, mentre OpenAI utilizza un modello di decadimento dinamico. Programmare regolari ping di fetch in background può quindi mantenere attive nella memoria del server le istruzioni di sistema critiche.
Il meccanismo differisce leggermente tra i due fornitori, e strutturare correttamente la richiesta è ciò che determina se la cache si attiva davvero. Con Anthropic, contrassegni esplicitamente un breakpoint della cache utilizzando cache_control. Tutto ciò che precede il breakpoint viene memorizzato, quindi il contenuto stabile e statico deve venire per primo e il contenuto volatile specifico di ogni richiesta deve venire per ultimo:
1import Anthropic from "@anthropic-ai/sdk";
2
3const anthropic = new Anthropic();
4
5const response = await anthropic.messages.create({
6 model: "claude-opus-4-8",
7 max_tokens: 1024,
8 system: [
9 {
10 type: "text",
11 text: SYSTEM_INSTRUCTIONS // small, sent on every request
12 },
13 {
14 type: "text",
15 text: KNOWLEDGE_BASE, // large, static reference block
16 cache_control: { type: "ephemeral" }
17 }
18 ],
19 messages: [
20 { role: "user", content: userQuestion } // volatile — after the breakpoint
21 ]
22});
23
24// Confirm the cache is working
25console.log(response.usage.cache_read_input_tokens);
L’errore in assoluto più comune è collocare un timestamp, un ID di richiesta o qualsiasi stringa specifica della richiesta prima del blocco memorizzato in cache. Poiché il caching si basa su una corrispondenza di prefisso, un solo byte modificato in qualsiasi punto prima del breakpoint invalida tutto ciò che segue, e la cache non si attiva mai in modo silenzioso. Verifica che funzioni leggendo usage.cache_read_input_tokens nella risposta: se questo valore resta a zero tra richieste identiche, qualcosa di dinamico si è insinuato nel prefisso. Nota inoltre che il prefisso memorizzato in cache deve superare una lunghezza minima (nell’ordine di 1.024-4.096 token a seconda del modello) prima che il caching si attivi.
OpenAI adotta un approccio più semplice: il caching è automatico per i prompt superiori a circa 1.024 token, senza alcun flag cache_control da impostare. La stessa disciplina vale comunque. Mantieni il blocco di istruzioni statiche all’inizio dell’array dei messaggi e aggiungi l’input utente variabile alla fine, così che il prefisso riutilizzabile resti identico byte per byte tra le richieste.
Per consultare le strutture di prezzo e di parametri del prompt caching, fai riferimento alla guida di Anthropic al prompt caching .
Edge compute e routing serverless
Elaborare le richieste LLM su un unico server centralizzato introduce enormi salti di rete per gli utenti di tutto il mondo. Distribuire il middleware delle API su reti edge serverless (come Cloudflare Workers) accorcia drasticamente quei percorsi.
L’edge worker riceve la richiesta del client, autorizza la sessione e la instrada al datacenter del fornitore del modello più vicino. Questa struttura serverless consegna i token allo schermo dell’utente nel momento stesso in cui vengono calcolati, così l’interfaccia risulta estremamente reattiva. Il middleware JavaScript qui sotto mostra come configurare risposte in streaming direttamente da un runtime edge:
1export default {
2 async fetch(request, env) {
3 const payload = await request.json();
4
5 // Call the streaming LLM endpoint
6 const response = await fetch("https://api.openai.com/v1/chat/completions", {
7 method: "POST",
8 headers: {
9 "Authorization": `Bearer ${env.OPENAI_API_KEY}`,
10 "Content-Type": "application/json"
11 },
12 body: JSON.stringify({
13 model: "gpt-4o-mini",
14 messages: payload.messages,
15 stream: true
16 })
17 });
18
19 // Forward the stream directly to the client browser
20 return new Response(response.body, {
21 headers: { "Content-Type": "text/event-stream" }
22 });
23 }
24};
Questa struttura serverless consegna i token allo schermo dell’utente istantaneamente, man mano che vengono calcolati. Per imparare a costruire backend ottimizzati per l’edge, leggi la nostra guida su come creare un’API serverless con Cloudflare Workers .
Analizzare lo stream sul client
Inoltrare lo stream dall’edge è solo metà del lavoro. Il browser deve comunque leggere quei chunk man mano che arrivano e renderizzare ogni token, altrimenti la risposta si accumula in un buffer e compare tutta insieme, vanificando lo scopo. Questo è il passaggio che la maggior parte dei tutorial salta, ed è qui che la prestazione percepita si vince o si perde davvero.
Il corpo della risposta è un ReadableStream di byte grezzi. I frame SSE arrivano come righe data:, ma un singolo chunk di rete può contenere più frame, oppure dividere un frame su due chunk, quindi devi bufferizzare le righe parziali anziché presumere che ogni chunk sia un messaggio completo:
1async function streamCompletion(messages, onToken) {
2 const response = await fetch("/api/chat", {
3 method: "POST",
4 headers: { "Content-Type": "application/json" },
5 body: JSON.stringify({ messages })
6 });
7
8 const reader = response.body.getReader();
9 const decoder = new TextDecoder();
10 let buffer = "";
11
12 while (true) {
13 const { value, done } = await reader.read();
14 if (done) break;
15
16 buffer += decoder.decode(value, { stream: true });
17 const lines = buffer.split("\n");
18 buffer = lines.pop(); // keep the trailing partial line
19
20 for (const line of lines) {
21 if (!line.startsWith("data: ")) continue;
22 const payload = line.slice(6).trim();
23 if (payload === "[DONE]") return;
24
25 try {
26 const json = JSON.parse(payload);
27 const token = json.choices?.[0]?.delta?.content;
28 if (token) onToken(token);
29 } catch {
30 // ignore keep-alive comments and malformed partial frames
31 }
32 }
33 }
34}
Il buffer.split("\n") seguito da lines.pop() è il dettaglio importante: conserva ogni riga incompleta finché il chunk successivo non la completa. Racchiudere JSON.parse in un try/catch mantiene vivo il ciclo quando arriva un commento keep-alive o un frame ricevuto a metà. Il callback onToken aggiunge quindi ogni frammento al DOM, così il testo compare nell’istante stesso in cui il modello lo produce.
Misurare e valutare la latenza
Non puoi ottimizzare ciò che non hai misurato. Prima e dopo ogni modifica, rileva il Time to First Token e il tempo totale di generazione così da attribuire un miglioramento alla causa giusta. Il modo più rapido per campionare il TTFT è con curl, usando time_starttransfer come stretta approssimazione dell’arrivo del primo byte al client:
1curl -w "TTFT: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
2 -X POST https://your-worker.example.com/chat \
3 -H "Content-Type: application/json" \
4 -d '{"messages":[{"role":"user","content":"Hello"}]}' \
5 -o /dev/null -s
Per valori a livello di applicazione, strumenta direttamente il parser sul client. Marca l’orologio quando la richiesta parte e di nuovo quando arriva il primo token:
1const start = performance.now();
2let firstTokenAt = null;
3
4await streamCompletion(messages, (token) => {
5 if (firstTokenAt === null) firstTokenAt = performance.now();
6 render(token);
7});
8
9console.log(`TTFT: ${Math.round(firstTokenAt - start)}ms`);
Esegui ogni misurazione più volte e prendi la mediana anziché un singolo campione, poiché la varianza di rete e i cold start possono distorcere letture occasionali. La tabella seguente fornisce intervalli indicativi su dove tipicamente va il tempo in una richiesta globale ben gestita; consideralo come uno schema di confronto, non come valori fissi, perché i tuoi numeri dipenderanno da regione, modello e dimensione del prompt.
| Fase | Contributo tipico | Sotto il tuo controllo? |
|---|---|---|
| Transito di rete (client verso edge) | 10–60 ms | Sì — il routing edge lo accorcia |
| Elaborazione del middleware all’edge | 1–15 ms | Sì — mantieni il worker snello |
| Time to First Token (prompt a freddo) | 400–1.200 ms | In parte — il caching lo riduce nettamente |
| Time to First Token (prefisso in cache) | 100–400 ms | Sì — tramite prompt caching |
| Generazione per token | 10–50 ms/token | No — determinato dal modello e dall’hardware |
Le due righe che vale la pena osservare sono i valori del TTFT con e senza cache. Quel divario è il singolo guadagno più grande disponibile per la maggior parte delle applicazioni, ed è per questo che il prompt caching è in cima alla lista delle ottimizzazioni. La velocità di generazione, al contrario, è fissata dal fornitore, quindi instradare le richieste più semplici verso un modello più piccolo è l’unica leva disponibile.
Flusso di ottimizzazione passo dopo passo
Per ottimizzare la velocità della tua applicazione software, inizia separando le istruzioni di sistema statiche dagli input utente dinamici. Questa divisione ti consente di individuare in modo pulito i punti di ingresso della cache.
Successivamente, attiva i flag di prompt caching all’interno dei payload delle API per garantire che il fornitore del modello conservi i tuoi token di testo in memoria.
Configura sempre lo streaming delle risposte utilizzando endpoint SSE standard. Scrivendo parser frontend leggeri che elaborano i chunk man mano che arrivano, migliori la prestazione percepita dall’utente. Stabilisci poi percorsi di fallback tra modelli: instrada gli input basilari dei clienti verso modelli più piccoli, riservando i modelli di ragionamento più grandi ai compiti avanzati. Infine, profila i salti di rete per confermare che i worker serverless riducano i ritardi di transito. Per dettagli sull’ottimizzazione dei database, consulta la nostra guida su WordPress a confronto con lo sviluppo web personalizzato .
Errori comuni e risoluzione dei problemi
Una manciata di guasti si ripresenta di continuo quando i team introducono per la prima volta streaming e caching. Riconoscere il sintomo fa risparmiare ore di tentativi alla cieca.
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
cache_read_input_tokens resta a zero | Un timestamp, un UUID o un ID di sessione si trova prima del breakpoint della cache, quindi il prefisso cambia a ogni richiesta | Sposta tutto il contenuto dinamico dopo il blocco statico; serializza qualsiasi JSON in modo deterministico |
| I token arrivano tutti insieme, non in modo incrementale | Un proxy intermedio o un CDN sta bufferizzando la risposta | Invia Cache-Control: no-transform e X-Accel-Buffering: no; assicurati che il content type text/event-stream sia impostato |
| Lo stream si interrompe a metà | Il worker è tornato prima che il corpo upstream terminasse, oppure è stato raggiunto max_tokens | Restituisci direttamente response.body anziché attendere il testo completo; aumenta max_tokens |
| Il primo token è lento nonostante il caching | Il prefisso statico è al di sotto della lunghezza minima memorizzabile in cache del fornitore | Consolida le istruzioni così che il blocco in cache superi la soglia di circa 1.024 token |
| Il parser del client genera un errore su alcuni chunk | Un frame è stato diviso su due chunk di rete | Bufferizza le righe parziali come mostrato sopra e racchiudi JSON.parse in un try/catch |
Una trappola più sottile è la bufferizzazione all’edge stesso. Se all’interno del worker chiami await response.text() prima di restituire, hai silenziosamente riconvertito una risposta in streaming in una bloccante. Passa sempre il corpo dello stream direttamente. Allo stesso modo, tieni d’occhio i limiti di CPU del worker: un lavoro pesante per richiesta nel middleware si somma direttamente al TTFT, quindi mantieni minima la logica di autorizzazione e routing e rimanda tutto ciò che è costoso.
Considerazioni per la produzione
Far funzionare una demo in streaming in un browser è semplice; eseguirla in modo affidabile sotto traffico reale richiede qualche protezione in più.
Imposta un timeout di richiesta ragionevole sulla chiamata upstream così che una connessione bloccata del fornitore non possa tenere un worker aperto a tempo indeterminato, e abbinalo a un retry che ricade su un secondo fornitore o su un modello più piccolo quando il principale va in timeout. Poiché il prompt caching applica un piccolo sovrapprezzo sulle scritture in cache e un forte sconto sulle letture, conviene solo quando un prefisso viene riutilizzato; un ping in background ogni pochi minuti mantiene residente un prompt di sistema attivo senza pagare per riscriverlo a ogni richiesta utente.
Strumenta in modo continuo anziché solo al lancio. Registra il TTFT e i token al secondo per richiesta e attiva un avviso quando la mediana deriva, poiché una regressione lato fornitore o una variazione nella dimensione del prompt si manifesterà lì per prima. Infine, rispetta i limiti di frequenza del fornitore: una raffica di stream concorrenti può farli scattare, quindi metti in coda o alleggerisci il carico con eleganza invece di lasciare che le richieste falliscano in silenzio. Queste misure trasformano un prototipo veloce in un’applicazione che resta veloce quando conta.
Punti chiave
- Mira al Time to First Token (TTFT) e al tempo di transito per ridurre la latenza dei LLM.
- Sfrutta il prompt caching sulle API dei modelli per aggirare l’overhead sistemico di parsing delle istruzioni.
- Usa lo streaming delle risposte per consegnare i token in tempo reale, migliorando la velocità percepita.
- Distribuisci il middleware delle API su runtime edge serverless per accorciare i percorsi di rete globali.
- Instrada le query utente più semplici verso modelli leggeri per ottimizzare le velocità di esecuzione.
- Configura strumenti di monitoraggio delle prestazioni per analizzare e ridurre la latenza in modo continuo in condizioni d’uso reali.
Domande frequenti (FAQ)
Come riduco la latenza dei LLM in produzione? Per ridurre la latenza dei LLM in produzione, dovresti implementare il prompt caching per le istruzioni statiche, abilitare lo streaming dei token e distribuire edge worker per ottimizzare il routing delle richieste. Distribuendo runtime di orchestrazione serverless più vicini ai client di tutto il mondo, gli sviluppatori aggirano molteplici salti di routing di rete e consegnano il primo token di risposta in tempo reale.
Che cos’è il prompt caching? Il prompt caching è una funzionalità delle API che memorizza gli stati di testo analizzati nella memoria del server, consentendo alle richieste successive che usano lo stesso prefisso di eseguirsi molto più velocemente. Aggirando il ciclo di parsing sistemico per grandi set di istruzioni, questa ottimizzazione riduce il Time to First Token (TTFT) fino all’ottanta percento.
La dimensione del modello influisce sulla latenza? Sì, i modelli più piccoli hanno velocità di generazione dei token molto più rapide, il che li rende ideali per compiti semplici in cui la latenza è una preoccupazione primaria. Instradare semplici richieste di classificazione o estrazione verso modelli edge specializzati garantisce tempi di risposta rapidi, riservando i modelli densi ai compiti di ragionamento.
In che modo lo streaming Server-Sent Events (SSE) aiuta a ridurre la latenza percepita? Lo streaming SSE invia i token di output testuale dall’host del modello allo schermo del client in tempo reale, man mano che vengono compilati. Sebbene questo non riduca la durata totale di esecuzione, minimizza il Time to First Token (TTFT) e offre all’utente un’interfaccia applicativa reattiva e attiva.
Come metto in cache le risposte LLM dinamiche all’edge? Puoi mettere in cache le risposte dinamiche all’edge usando database KV o istanze Redis con brevi limiti di TTL (Time to Live). Il caching delle risposte dinamiche è efficace per query utente ripetitive o intenti comuni del servizio clienti, evitando del tutto le chiamate di rete al fornitore del modello.
Commenti