Ollama vs llama.cpp: perché lo stesso modello sembrava il doppio più veloce
- {'Ollama è costruito su llama.cpp, quindi il motore di inferenza è lo stesso': 'il divario di velocità nasce dalle impostazioni predefinite e dal disallineamento di versione, non dal modello.'}
- Un binario ricompilato con i giusti parametri per la flash attention, quantizzazione della cache KV e un numero di thread adeguato possono cambiare sensibilmente l'esperienza.
Per diletto piu' che per necessita' sto utilizzando modelli LLM in locale sul mio portatile da circa un anno. Niente di fantascientifico: una macchina Huawei con un Intel i5, 16GB di RAM e Ubuntu. Per gran parte di quell'anno lo strumento prediletto è stato Ollama. Funziona senza attriti, e la velocità e' "sufficiente" se non si hanno pretese di fare del fine-tuning.
Qualche giorno fa (un po' adesso che scrivo), ho Gemma4 — il nuovo modello di Google rilasciato ad aprile. A prescindere dalla disattivazione del thinking mode (che su llama.cpp si ottiene passando un --reasoning off all'avvio), non c'era verso di ottenere delle risposte fluide. La prima reazione: si tratta di un modello nuovo, la mia macchina e' vecchia. In realtà non è così.
Da lì è cominciata una lunga indagine. Ecco quanto ne ho ricavato.
La prima sorpresa: è lo stesso motore
Ollama non è un concorrente di llama.cpp. Ollama è costruito su llama.cpp. Sotto, lo stesso codice di inferenza. Ollama lo riveste con una CLI accogliente, un server HTTP, un registro dei modelli e una serie di impostazioni predefinite sensate.
Se il motore sottostante è lo stesso, da dove nasce la differenza di velocità? La spiegazione si riduce a tre fattori: il file eseguibile precomilato, le impostazioni predefinite e un certo disallineamento di versione.
Il binario conta più di quanto immaginassi
Quando ho eseguito brew install llama.cpp, Linuxbrew lo ha compilato dai sorgenti direttamente sulla macchina. Significa che il compilatore ha esaminato la specifica CPU e ha attivato ogni set di istruzioni supportato — AVX2, FMA, ed eventualmente altro a seconda della generazione. Di norma il binario viene inoltre collegato a una libreria matematica veloce come OpenBLAS, che incide concretamente sulla moltiplicazione matriciale (ossia gran parte del lavoro di un LLM).
Ollama, al contrario, distribuisce un binario precompilato. Per funzionare sul maggior numero possibile di macchine, è compilato con una base prudente. È probabile che la mia CPU disponga di funzionalità che quel binario, semplicemente, non può sfruttare, perché non sono state abilitate in fase di compilazione.
Questo, da solo, può giustificare una porzione significativa del divario di velocità.
Una breve digressione: cosa sono davvero AVX2 e FMA
Avendo incontrato di continuo questi acronimi, ho deciso di comprenderne - nel limite del possibile - il loro impatto.
Una CPU dispone di un vocabolario fisso di istruzioni: "somma questi due numeri", "carica questo dalla memoria", e così via. Quel vocabolario prende il nome di instruction set. Nel tempo, Intel e AMD vi hanno aggiunto nuove istruzioni per accelerare determinate operazioni — ma il software deve essere compilato per utilizzarle. Se la CPU le supporta e il programma non è stato costruito per sfruttarle, restano inutilizzate.
Gran parte delle accelerazioni moderne appartiene a una famiglia chiamata SIMD — Single Instruction, Multiple Data. Di norma una CPU esegue i calcoli un numero alla volta. La logica SIMD le consente di elaborarne molti contemporaneamente con una singola istruzione. Ed è esattamente ciò di cui una rete neurale ha bisogno: si tratta sempre di "moltiplica questa lista di numeri per quest'altra", ripetuto all'infinito.
I nomi che vale la pena conoscere:
- AVX2 (2013): elabora 256 bit per volta — 8 numeri in virgola mobile per istruzione. Standard su qualunque i5 dell'ultimo decennio.
- FMA (Fused Multiply-Add): esegue
(a × b) + cin un solo passaggio. Le reti neurali usano quell'operazione miliardi di volte, perciò l'FMA raddoppia all'incirca il throughput sul calcolo centrale. - AVX-512: il fratello maggiore — 512 bit, 16 numeri in virgola mobile per volta. Presenza incostante sui portatili consumer, perché Intel l'ha rimossa da molti chip per un certo periodo.
- AMX: ancora più recente, sui chip Intel di fascia alta più moderni, progettata specificamente per il calcolo matriciale dell'IA.
Sull'hardware ARM (Apple Silicon, Raspberry Pi) gli equivalenti si chiamano NEON e SVE.
Come verificare di cosa dispone la propria CPU
Su Linux, una riga:
cat /proc/cpuinfo | grep flags | head -1
Si ottiene una lunga sequenza di abbreviazioni. Vanno cercati avx2, fma, avx512f e simili. Sono le funzionalità supportate dal chip — quelle che un binario appena compilato sfrutterà, mentre uno precompilato generico potrebbe lasciarle inutilizzate.
Impostazioni predefinite che, in silenzio, costano prestazioni
Alcuni settaggi che in Ollama partono "disattivati" o "prudenti":
Flash attention. Un algoritmo più recente e veloce per il calcolo dell'attention. Stesso risultato, meno tempo e meno memoria. In Ollama è disattivato per impostazione predefinita; si abilita con OLLAMA_FLASH_ATTENTION=1.
Quantizzazione della cache KV. Mentre genera, il modello conserva "appunti" su tutto ciò che ha prodotto finora, per non rifare il lavoro. Tali appunti sono memorizzati in precisione a 16 bit per impostazione predefinita. È possibile comprimerli a 8 bit (OLLAMA_KV_CACHE_TYPE=q8_0) per circa metà della memoria e con una perdita di qualità trascurabile. Appunti più piccoli = letture più rapide = generazione più rapida.
Thread. llama.cpp da brew utilizza per impostazione predefinita i core fisici. Ollama è più cauto. Su un i5 a 4 core, la differenza tra 2 e 4 thread è prossima al raddoppio sul calcolo.
Dimensione del contesto. Le versioni recenti di Ollama scalano il contesto in base alla memoria disponibile: ottimo per le capacità del modello, ma può gonfiare in silenzio la cache KV e spingere una macchina al limite verso l'utilizzo della memoria swap (se presente). Con 16GB di RAM e un ambiente desktop attivo, questo e' un dettaglio che pesa.
Il disallineamento di versione
llama.cpp viene aggiornato quasi ogni giorno. Esiste una vera corsa della comunità a renderlo più veloce, in particolare per le nuove architetture di modello. Ollama, di contro, prende una versione ogni x giorni, la compila e la mette nel suo pacchetto. Perciò, compilando llama.cpp da zero, non eseguivo soltanto lo stesso codice con impostazioni diverse: eseguivo codice più recente, con ottimizzazioni non ancora arrivate a Ollama.
Per Gemma in particolare, il modello messo subito alla prova, il divario e' stato reale. Il supporto ai nuovi modelli tende ad arrivare prima in llama.cpp e a venire affinato nelle settimane successive.
Perché la sola CPU ha reso evidente il divario
Su una GPU l'hardware è così rapido che i sottodimensionamenti del wrapper e qualche ottimizzazione mancata non si notano immediatament (forse mai). Su una CPU ogni inefficienza emerge quasi immediatamente. Il calcolo è il collo di bottiglia, e qualsiasi fattore che lo rallenti si riflette direttamente sui token al secondo che vengono processati.
Ecco perché la prova con la sola CPU è stata il banco di test a convinvermi che dovevo cambiare approccio per i miei test.
La conclusione
Stesso modello. Stessa macchina. Binario diverso.
Se si usa Ollama per comodità, è una scelta legittima — è uno strumento gradevole e le impostazioni predefinite migliorano a ogni rilascio. Ma a chi si è chiesto perché il proprio LLM locale "sembri lento", nel prendere in considerazione quanto sopra, si prende in considerazione dove si nasconde il margine perduto. Poche variabili d'ambiente e un binario compilato sulla macchina locale possono cambiare completamente l'esperienza.
Cosa provare, in ordine di impatto
- Compilare llama.cpp dai sorgenti (oppure
brew installsu Linux/macOS) e confrontarlo con Ollama sullo stesso file.gguf. - In Ollama, impostare
OLLAMA_FLASH_ATTENTION=1eOLLAMA_KV_CACHE_TYPE=q8_0prima di avviare il server. - Verificare la dimensione del contesto. Se non servono 32K, è inutile allocarli — soprattutto con poca RAM.
- Tenere d'occhio il numero di thread.
- Aggiornare Ollama con regolarità.
La cosa più utile ricavata dall'intero esercizio non è stato l'aumento di velocità, ma la comprensione, finalmente, di ciò che accade realmente sotto il cofano.