Su otto giga di VRAM non conta quanto è grande il modello
Un 27B denso gira a 2,5 token al secondo. Un MoE da 35 miliardi, sulla stessa scheda, ne fa trenta. La differenza non è la taglia.
4 min di lettura
Il piano era far girare Qwen3.8-27B su una RTX 3070 Ti da 8 GB. Ha girato: 2,5 token al secondo. Alla velocità con cui si legge una frase in quindici secondi, un agente che deve scrivere codice non è uno strumento, è una penitenza.
La reazione istintiva è mettersi a tarare. L’ho fatto, per bene, misurando tutto. Margine di VRAM più stretto, numero di thread, dimensione dei batch, speculazione da ngram, una testa MTP, un quant più piccolo.
Da 2,29 a 2,48 token al secondo. Otto per cento.
Poi ho cambiato modello. Non taglia, forma. E sono passato a trenta.
Il numero che conta non è quello sulla scatola
| modello | parametri | attivi per token | generazione |
|---|---|---|---|
| Qwen3.8-27B UD-Q3_K_XL | 27 B | 27 B | 2,5 t/s |
| Qwen3.6-35B-A3B Q4_K_M | 35 B | 3 B | ~30 t/s |
Il modello più grande va dodici volte più veloce. Non è un paradosso: è l’unica cosa che poteva succedere.
Su 8 GB di VRAM nessuno dei due ci sta. Metà dei pesi vive sulla DDR4, e la DDR4 di questa macchina rende circa 14,5 GB/s utili contro i 608 della scheda. Quarantadue volte meno. Quel bus è il collo di bottiglia, e l’unica domanda che conta diventa: quanti byte devo farci passare per ogni token?
Ventisette miliardi di pesi vanno letti tutti, ogni volta, per produrre una parola sola. Il MoE ne attiva tre su trentacinque: i pesi degli esperti stanno in RAM, ma per token se ne legge una frazione, e l’attenzione resta in GPU dove c’è la banda.
La quantità di parametri è una proprietà del file. La quantità di parametri attivi è una proprietà dell’architettura, ed è quella che il tuo bus paga.
Tre cose che davo per scontate ed erano sbagliate
I thread vanno in direzioni opposte fra le due architetture.
| modello | 6 thread | 12 thread |
|---|---|---|
| 27B denso | 2,16 t/s | 2,48 t/s |
| 35B-A3B MoE | 30,6 t/s | 22,9 t/s |
Su llama.cpp la regola tramandata è fermarsi ai core fisici. Sul denso è falsa: il lavoro è dominato dalla latenza di dequantizzazione, e l’SMT riempie le bolle. Sul MoE è vera e anche di più, perché per token si attiva poca roba, il lavoro per thread è poco e sincronizzare dodici thread costa più di quanto renda.
Avevo tarato dodici thread sul denso e me li ero portati dietro sul MoE. Mi costavano il venticinque per cento. Ora il launcher sceglie da solo: sei sul MoE, dodici sul denso.
Il quant più piccolo è più lento. Q2_K_XL pesa tre gigabyte in meno del Q3, e infatti si vede: legge il prompt a 354 token al secondo invece di 250, perché ne sta di più in GPU. Poi genera a 1,52 invece di 2,48. Il percorso di dequantizzazione a due bit costa alla CPU molto più di quanto si risparmi in byte da trasferire. Meno byte, più tempo.
La testa MTP è in perdita. 1,80 contro 2,48. Si prende 1,37 GB di VRAM, e su otto giga quella VRAM la stavi già usando per i pesi. Su una scheda più capiente il conto si ribalta, ma il conto va fatto sulla scheda che hai.
E una quarta, che è una non-scoperta e vale lo stesso: quanti esperti tenere in VRAM non cambia niente. Da 28 a 64, tutto fra 29,66 e 30,70 t/s, cioè dentro il rumore. Allora tanto vale il valore che lascia più VRAM libera per il contesto.
“Veloce” non è una risposta se il modello sbaglia
C’è un modello ancora più rapido: qwen3.5 da 9 miliardi, via Ollama, ci sta tutto in VRAM, sessantacinque token al secondo. Il doppio del MoE.
Li ho messi davanti allo stesso compito: trovare un bug non ovvio in una
merge_intervals. Il bug c’è, ed è che la funzione muta la lista del
chiamante, perché la lista in uscita contiene riferimenti agli stessi oggetti
di quella in ingresso.
Il 35B-A3B lo trova, e spiega che l’assegnamento su last[1] sta scrivendo
dentro l’oggetto originale. Corretto.
Secondo il 9b il controllo iniziale if not intervals: restituisce lista
vuota quando l’input ha un solo elemento. È falso, not [x] è False, e
non è un dettaglio: si è inventato un bug che non c’è e non ha visto quello
che c’è.
Per il codice ripetitivo e per le risposte che devono arrivare subito il 9b resta ottimo. Ma “veloce” e “utile” sono due assi diversi, e per ragionare sul codice il primo non compra il secondo.
La regola
Se la GPU non basta a tenere il modello, smetti di chiederti quanto è grande e comincia a chiederti quanto ne è attivo. Poi misura sulla macchina che hai, non su quella dei benchmark di qualcun altro: qui tre monitor accesi costavano seicentocinquanta megabyte di framebuffer, che a otto giga sono un ottavo della scheda.
Ho ancora installato il 27B. È l’unico dei due ad avere l’encoder per le immagini, e per quello va benissimo: lento, ma è l’unico che ci vede.