Di cosa parla la fonte
Quando due richieste consecutive condividono l’inizio del prompt, il server riusa i calcoli che ha già fatto per quel tratto invece di rifarli da capo. Si chiama prefix caching, e ce l’hanno tutti i principali motori di inferenza — i programmi che fanno girare il modello e rispondono alle richieste — quasi sempre acceso di default. Per un agente il guadagno è enorme, perché a ogni passo rimanda l’intera conversazione e dopo il primo turno quasi tutto il prompt è già in cache.
Un preprint uscito il 4 settembre misura che cosa costa quel guadagno in ripetibilità. Il disegno è quello che userei io per isolare una variabile: il modello sceglie sempre la parola più probabile invece di pescarne una a caso — è la decodifica greedy, con temperatura zero — e parte ogni volta dallo stesso seme di casualità. Una richiesta alla volta, in fila, così che nessuna condivida un passaggio di calcolo con un’altra. Ottanta episodi, cioè partite intere in cui un agente usa strumenti per più turni di seguito; due motori, llama.cpp e vLLM; quattro formati di peso, dal più preciso al più compresso. Ogni configurazione eseguita due volte per intero.
Il primo numero è il controllo, ed è quello che rende leggibile tutto il resto. Con la cache spenta, la seconda esecuzione ha riprodotto la prima esattamente: 0 episodi diversi su 800. Stessi prompt a ogni passo, stessi token emessi — i pezzetti di testo in cui il modello scompone quello che scrive — stesso numero di richieste per episodio. Qualunque differenza osservata altrove nello studio non può quindi venire dal campionamento, dall’ordine in cui il server smaltisce la coda o da come raggruppa le richieste per elaborarle insieme: quelle sorgenti sono presenti anche qui, e qui non hanno prodotto nulla.
Accendendo la cache, la traiettoria dell’agente — la sequenza di passi che compie prima di arrivare in fondo — cambia sul 36,2% degli episodi a 16 bit e sul 75,0% a quattro bit. La quantizzazione, cioè la compressione dei numeri del modello per farlo stare in meno memoria, non è la causa: il fenomeno c’è già a piena precisione. È un moltiplicatore. Pesi più grossolani avvicinano fra loro i punteggi con cui il modello sceglie la parola successiva, e basta una spinta più piccola per invertirne l’ordine.
La parte migliore arriva dopo, ed è una correzione che l’autrice fa a se stessa. Ripetendo la stessa passata a cache accesa la divergenza sembrava altissima, fino al 77,5%. Ma la causa non era il prefix caching: era un secondo strato di cache di llama.cpp, che tiene in memoria stati di conversazione interi e li sceglie per somiglianza anziché per identità, attivo di default e introdotto nell’ottobre 2025. Disattivandolo e cambiando nient’altro, la divergenza fra due esecuzioni identiche passa da 38,8% a 1,2%. Con quello strato acceso, interporre una passata a cache spenta fra le due porta la divergenza da 38,8% a 77,5%: conta l’ordine in cui hai eseguito le cose.
Da cui la formulazione che regge il paper: servire le richieste con la cache accesa è deterministico dato lo stato della cache, ed è irriproducibile in pratica «perché quello stato non fa parte della richiesta, non è riportato nella risposta e non viene azzerato fra un’esecuzione e l’altra». Sugli esiti, infine, la risposta è più stretta di quanto ci si aspetterebbe: su 1300 quesiti di aritmetica elementare, 20 risposte cambiano correttezza — 13 a favore della cache, 7 del ricalcolo — e l’accuratezza complessiva non si muove. Non è peggio. È irripetibile.
Che ne penso
Il comportamento descritto qui ha un nome che nel collaudo conosciamo tutti: è un test che dipende dall’ordine di esecuzione. Passa da solo, fallisce dentro la suite, e la differenza fra 38,8% e 77,5% è esattamente la misura di quanto lo sporca ciò che è stato eseguito prima. La novità non è il fenomeno, è dove si trova lo stato condiviso: non nel codice di test, non nel database che qualcuno ha dimenticato di ripulire, ma dentro un componente che nessuno considera parte dell’input. Il server ha una memoria che non gli hai dato tu.
La distinzione fra deterministico e ripetibile sembra accademica finché non la si mette accanto alla frase che chiude più segnalazioni di qualunque altra: «non si riproduce». Un sistema deterministico dato uno stato che non ti viene né chiesto né riportato produce esattamente quel sintomo. Il paper non lo argomenta, lo misura: rimettendo la cache in uno stato noto prima di ogni passata, il percorso di ricalcolo si riproduce su 40 item su 40 e quello con cache pure — mentre i due continuano a differire fra loro su 14. Nessuna casualità residua da nessuna parte, e due risultati diversi. Se avessi questo grafico su una suite mia, smetterei di cercare una corsa fra processi paralleli e comincerei a cercare chi tiene lo stato.
Il pezzo che mi ha convinto del lavoro è però l’errore, non il risultato. La prima griglia misurava una cosa diversa da quella che l’autrice credeva di misurare, perché un secondo strato di cache lasciato al suo default gonfiava il numero. L’ha trovato testandolo — non ragionandoci — e nel paper riporta entrambe le misure invece di sostituire la buona alla cattiva. È la differenza fra una misura e un risultato, e nel nostro mestiere è la stessa differenza che passa fra un rapporto di prova e uno screenshot verde. Vale la pena aggiungere che questo ridimensiona il titolo del paper stesso: la variabile più violenta non è il prefix caching di cui parla il titolo, è un layer accessorio acceso di default da meno di un anno. L’autrice scrive che le valutazioni fatte prima dell’ottobre 2025 non ne sono state toccate e quelle fatte dopo lo sono state in silenzio — e questo, per chiunque abbia confrontato numeri pubblicati a distanza di mesi, è la frase da appuntarsi.
Ci sono poi due difetti di strumentazione che l’autrice confessa, e sono la parte più utile del paper per chi scrive test. Il primo: il programma che assegnava i punteggi accettava solo il formato di risposta chiesto dalla domanda, e i modelli rispondevano spesso in un’altra forma altrettanto chiara. Fra l'11% e il 38% delle risposte risultavano sbagliate per la sola formattazione, il che invertiva l’ordinamento apparente dell’accuratezza e gonfiava di circa quattro volte il numero di risposte che cambiavano esito. Un oracolo difettoso — oracolo è il nome che diamo alla parte del test che decide se l’esito è giusto o sbagliato — non produce un errore: produce un risultato plausibile, con l’ordine giusto delle colonne sbagliate. Ed è stato correggibile per una ragione sola — le risposte grezze erano state salvate, quindi il punteggio si è potuto rifare offline senza rieseguire una sola inferenza. Chi salva il verdetto e butta l’output non ha questa possibilità.
Il secondo è ancora più istruttivo. Nel braccio a cache accesa, vLLM dichiarava in ogni risposta di non aver riusato nulla dalla cache, perché quella versione non compila quel campo sull’indirizzo usato per le richieste — mentre il log del motore mostrava la quota di richieste servite dalla cache salire dall'87,1% al 99,1%. Chi si fosse fidato di ciò che la risposta dichiara su se stessa avrebbe concluso che l’esperimento non stava funzionando, e avrebbe buttato via dati validi. Tradotto: verificare che il test stia davvero facendo quello che credi non è pedanteria, è la precondizione perché il risultato voglia dire qualcosa. Il test che passa perché non ha eseguito nulla è lo stesso animale.
C’è una cosa che questa misura chiude, e che avevo lasciata aperta scrivendo del punteggio di un benchmark: davanti a una colonna di risultati che saliva e scendeva senza un ordine, dicevo che sarei andato a cercare la flakiness — i test che ogni tanto falliscono e ogni tanto no, senza che nulla sia cambiato — e non potevo cercarla, perché nessuno pubblica quanto un punteggio vari fra due esecuzioni. Ora c’è un ordine di grandezza, su un altro banco e su modelli molto più piccoli: il solo pezzo di infrastruttura che serve le richieste, a parità di modello, di prompt e di seme casuale, può cambiare la traiettoria di una maggioranza di episodi. Non dimostra niente su ARC-AGI-3. Ma sposta l’onere: una classifica di agenti che non dichiara motore, versione e stato della cache non sta pubblicando un numero riproducibile, e adesso non è più un’ipotesi di uno che fa test per mestiere.
Va detto con altrettanta chiarezza quanto è stretto il perimetro. Sono modelli da 7 a 14 miliardi di parametri su una sola scheda grafica da videogiocatore, due motori open source, nessun servizio ospitato da terzi, nessun modello di punta, e un preprint di un’autrice sola — cioè un lavoro pubblicato prima di passare dalla revisione di altri ricercatori. I limiti li dichiara lei invece di sfumarli, e nota che su un server condiviso fra clienti diversi, dove il contenuto della cache dipende anche dal traffico altrui, si aspetterebbe più dipendenza dalla storia e non meno: ma non l’ha misurato, e lo scrive. È l’unica cosa davvero importante che questo studio non tocca, ed è precisamente il caso di chi chiama un’API invece di servirsi il modello in casa.
La cosa che mi interessa di più, però, è quanto tempo è rimasta lì senza che nessuno la misurasse. Nella discussione del 2025 sotto il lavoro di Thinking Machines — che attribuisce la non ripetibilità al modo in cui le richieste vengono raggruppate, meccanismo che qui non si applica perché ogni richiesta gira da sola — un commentatore liquidava la questione dicendo che il non determinismo si vede solo perché non si controllano le variabili, «di solito il prompt caching». Aveva ragione sulla causa e torto sulla facilità: quella variabile non la controlli se non sai che esiste, se il server non te la riporta e se nessuno l’ha mai quantificata. Nel frattempo il fenomeno arrivava due volte nel tracker di vLLM — un calo di accuratezza con la cache attiva e una richiesta identica che restituisce testo diverso a seconda che colpisca la cache — entrambe marcate come difetto, entrambe chiuse senza che nessuno andasse a fondo. Fra il «si sa già» e il «è misurato» ci passano tre anni e un paper.
Cosa mi porto a casa
La richiesta non è più l’input. L’input è la richiesta più la storia di ciò che quel server ha servito prima, e la seconda metà non è dichiarata, non è riportata nella risposta e non viene azzerata da sola. Finché è così, «stesso input, stesso output» è una frase che non si può verificare — non falsa, proprio non controllabile.
La conseguenza pratica non è spegnere la cache, che costa parecchio e non serve: è fissarne la configurazione, azzerare lo stato al confine fra due esecuzioni, e registrare per ogni risposta quanto è stata servita dalla cache, che è l’unica cosa che rende l’irripetibilità almeno visibile dopo il fatto. E quando si scrive il rapporto, motore e versione vanno accanto a temperatura e seed. Costano una riga. Senza, fra sei mesi quel numero non lo rifà nessuno, e nemmeno chi l’ha prodotto.