Guida AI locale 3: modelli, quantizzazione e VRAM — quanto modello ti serve davvero

Prima di scaricare un modello fai il conto: pesi + KV cache + margine. Un 8B in Q4_K_M sta intorno ai 4,6 GiB, ma a 32k di contesto la cache ne aggiunge 4. Q4 o Q8 si decide da cosa sbaglia il modello sul tuo compito, non dalla dimensione del file. Terza puntata della Guida AI locale.

Francesco Pio Nocerino9 minAI
PercorsoAI Engineering: gli articoliParte 15 di 24
Guida AI locale 3: modelli, quantizzazione e VRAM — quanto modello ti serve davvero
Indice

Terza puntata della serie sull'AI locale. Nella puntata 1 abbiamo deciso se ha senso uscire dal cloud, nella puntata 2 quale runtime usare. Qui si sceglie il modello e il file da scaricare: quanti parametri, quanti bit, quanta VRAM.

Il percorso completo è nell'hub AI Engineering. I termini tecnici (GGUF, KV cache, quantizzazione) hanno una voce breve nel glossario.

In breve: La VRAM che serve è la somma di tre voci: pesi, KV cache e un margine. I pesi li decide il file: un 8B in Q4_K_M occupa circa 4,6 GiB, in Q8_0 circa 8. La KV cache la decidi tu, con contesto e richieste parallele: su Llama 3.1 8B in f16 vale circa 128 KiB per token, cioè 4 GiB a 32k. Q4_K_M è un punto di partenza sensato. Sali di precisione solo se, sul tuo compito, vedi errori che a Q6 o Q8 spariscono.

La domanda sbagliata e quella giusta

Non "qual è il modello più grande che entra nella mia GPU?". È: quanto contesto mi serve, quante richieste servo insieme, e quali errori posso tollerare.

La prima domanda porta a riempire la VRAM di pesi e a scoprire dopo che non resta spazio per il contesto. La seconda parte dal lavoro che il modello deve fare.

La quantizzazione è la tecnica che salva i pesi del modello con meno bit, per esempio 4 o 8 invece di 16. Riduce la memoria e spesso velocizza l'inferenza. Il prezzo è una perdita di precisione, che llama.cpp misura con perplexity e KL divergence rispetto al modello a 16 bit (README di llama-quantize).

Il conto in tre voci

Prima di scaricare un modello fai questo conto:

  • Pesi — fissi per quel file. Dipendono da parametri e livello di quantizzazione.
  • KV cache — variabile. Cresce con la lunghezza del contesto e con il numero di richieste in parallelo.
  • Margine — buffer di calcolo, runtime, eventuale desktop sulla stessa GPU.

Il margine non ha un valore universale. Un riferimento concreto: llama-server, con l'opzione --fit attiva di default, regola i parametri per lasciare circa 1024 MiB liberi per dispositivo (README del server llama.cpp). Conta almeno 1 GiB e non progettare sul 100% della scheda.

Voce 1: i pesi

La stima rapida è: parametri × bit per peso ÷ 8. Il risultato è in byte.

Attenzione al nome del file. Q4_K_M non vuol dire "4 bit esatti". I k-quants mescolano tipi diversi tra i tensori. Nella tabella ufficiale di llama.cpp per Llama 3.1 8B, Q4_K_M vale 4,89 bit per peso e Q8_0 8,50 (README di llama-quantize). La pagina GGUF di Hugging Face elenca i bit per peso di ogni tipo.

Lo scoreboard di llama.cpp su Llama 3 8B mette in fila dimensione e distanza dal modello a 16 bit. La colonna KLD è la KL divergence media rispetto a FP16: più è bassa, più le previsioni restano vicine all'originale. I valori sono quelli pubblicati nel README di llama-perplexity, su Wikitext-2 e senza imatrix salvo dove indicato.

Tipo

Dimensione (GiB)

KLD vs FP16

F16

14,97

0,0006

Q8_0

7,96

0,0014

Q6_K

6,14

0,0055

Q5_K_M

5,33

0,0108

Q4_K_M

4,58

0,0313 (0,0282 con imatrix)

Q3_K_M

3,74

0,1019 (0,0844 con imatrix)

Q2_K

2,96

0,4451 (0,33 con imatrix)

Come leggerla. Da Q8_0 a Q4_K_M la memoria scende di oltre il 40% e la distanza cresce, ma resta lontana dai valori dei gradini sotto. Sotto Q4 la curva cambia pendenza: Q3_K_M si allontana circa tre volte più di Q4_K_M, Q2_K più di dieci volte. Risparmi meno di un gigabyte a ogni gradino e paghi sempre di più.

L'imatrix (importance matrix) è un file che dice al quantizzatore quali pesi contano di più. A parità di dimensione abbassa la distanza, come si vede nelle righe "con imatrix". Spesso chi pubblica un GGUF lo dichiara nella model card: a parità di livello, preferisci quelli.

Per i modelli grandi lo stesso conto diventa un muro. Nella tabella di llama.cpp, Llama 3.1 70B in Q4_K_M pesa 43,1 GB. Non entra in una singola scheda da 24 GB, nemmeno prima di parlare di contesto.

Voce 2: la KV cache

La KV cache conserva chiavi e valori dell'attenzione per ogni token già visto, così il modello non li ricalcola a ogni passo (documentazione di Transformers). È la voce che si dimentica, ed è quella che cresce.

La formula per token, descritta anche nel blog tecnico di NVIDIA, è:

byte per token = 2 × layer × KV heads × head_dim × byte per elemento

Il 2 sta per K e V. Con la grouped-query attention, usata dalla maggior parte dei modelli recenti, conti le KV heads, non tutte le teste di attenzione. I valori sono nel config.json del modello.

Esempio su carta, con il config.json di Llama 3.1 8B: 32 layer, 8 KV heads, head_dim 128, cache in f16 (2 byte). Fa 131.072 byte, cioè 128 KiB per token.

Contesto

KV cache f16

KV cache q8_0 (circa)

4k token

0,5 GiB

0,25 GiB

8k token

1 GiB

0,5 GiB

32k token

4 GiB

2 GiB

128k token

16 GiB

8 GiB

A 128k la cache pesa più di tre volte i pesi in Q4_K_M. Ecco perché un modello che "entra" va in out of memory quando allunghi il contesto.

Due moltiplicatori da non perdere:

  • Richieste parallele. Nelle FAQ di Ollama la memoria richiesta scala con OLLAMA_NUM_PARALLEL × OLLAMA_CONTEXT_LENGTH. Quattro richieste da 8k sono, per la cache, un contesto da 32k.
  • Il default che cambia. Ollama sceglie il contesto in base alla VRAM: 4k sotto i 24 GiB, 32k fino a 48, 256k sopra. La stessa pagina consiglia almeno 64.000 token per agenti e coding tool (Context length). Su un 8B come quello dell'esempio, 64k in f16 sono 8 GiB di sola cache.

Non tutti i modelli seguono la formula piena. Con sliding window attention (Mistral, Gemma2) la cache di quei layer si ferma alla dimensione della finestra, come nota la documentazione di Transformers. Leggi il config prima di fare il conto.

Un esempio di conto, prima di scaricare

Scenario: una GPU con 12 GiB di VRAM, Llama 3.1 8B, un solo utente, contesto da 32k. È un conto su carta con i numeri delle tabelle sopra, non una misura.

  • Q4_K_M: 4,58 + 4 (cache f16) + 1 (margine) ≈ 9,6 GiB. Entra.
  • Q8_0: 7,96 + 4 + 1 ≈ 13 GiB. Non entra a 32k. Entra a 8k (circa 10 GiB).
  • Q4_K_M con due richieste parallele da 32k: 4,58 + 8 + 1 ≈ 13,6 GiB. Non entra, a meno di quantizzare la cache.

Il compromesso è esplicito: con la stessa scheda scegli tra più precisione nei pesi e più contesto. Non esiste una risposta giusta in astratto. Dipende dal lavoro.

Lo stesso conto in poche righe di Python, partendo dal config.json del modello:

import json

cfg = json.load(open("config.json"))
layers = cfg["num_hidden_layers"]
kv_heads = cfg.get("num_key_value_heads", cfg["num_attention_heads"])
head_dim = cfg.get("head_dim", cfg["hidden_size"] // cfg["num_attention_heads"])

def kv_gib(ctx, parallel=1, byte_per_el=2):
    return 2 * layers * kv_heads * head_dim * byte_per_el * ctx * parallel / 2**30

pesi_gib = 4.58   # dimensione del file GGUF che vuoi scaricare
margine_gib = 1.0
print(round(pesi_gib + kv_gib(32768) + margine_gib, 1))

Dopo il primo avvio, verifica. ollama ps mostra quanto occupa il modello, il contesto allocato e la colonna PROCESSOR. Se non leggi 100% GPU, una parte è finita in RAM di sistema e la generazione rallenta (FAQ di Ollama).

Ridurre la cache senza cambiare modello

Prima di scendere sotto Q4 con i pesi, guarda la cache. È una leva separata.

  • Ollama: OLLAMA_KV_CACHE_TYPE accetta f16 (default), q8_0 e q4_0. Secondo le FAQ, q8_0 usa circa metà memoria con una perdita di solito non percepibile. q4_0 circa un quarto, con una perdita più visibile a contesti lunghi. Serve Flash Attention, e l'opzione vale per tutti i modelli.
  • llama.cpp: -ctk e -ctv impostano il tipo di K e V, default f16. Flash Attention è in auto di default (README del server).
  • vLLM: --kv-cache-dtype fp8. La documentazione raccomanda di calibrare le scale con llm-compressor e permette di escludere i layer più sensibili (Quantized KV Cache).
  • Transformers: QuantizedCache, oppure l'offload della cache su CPU. Con contesti corti e memoria sufficiente, quantizzare la cache può peggiorare la latenza.

Un avvertimento delle FAQ di Ollama: i modelli con un GQA count alto, come Qwen2, possono risentire di più della cache quantizzata. Anche qui si prova sul proprio compito.

Nell'esempio sopra, Q4_K_M con due richieste da 32k e cache q8_0 scende intorno ai 10 GiB e torna dentro i 12.

Q4 o Q8? Dipende da cosa sbaglia il modello

Perplexity e KL divergence dicono quanto il modello quantizzato si allontana dall'originale, in media, su testo generico. Il README di llama.cpp avverte anche che la perplexity non è comparabile tra modelli diversi. Nessuna delle due ti dice se il tuo compito regge.

Il costo di un token sbagliato non è uguale ovunque. In un riassunto o in una bozza una parola diversa passa. In un JSON per il tool calling, in codice da compilare o in un numero estratto da un documento, un token sbagliato rompe l'output.

Per decidere davvero:

  • Prepara un set di prova. Venti o trenta prompt presi dal tuo lavoro reale, con l'output atteso o un criterio di correttezza.
  • Fai girare due livelli. Per esempio Q4_K_M e Q6_K o Q8_0, stesso contesto, stessi parametri di sampling.
  • Classifica gli errori. Formato rotto, fatto sbagliato, istruzione ignorata, risposta troncata. Conta per categoria, non a sensazione.
  • Decidi. Se gli errori sono gli stessi, tieni Q4 e spendi la VRAM in contesto. Se Q8 elimina proprio gli errori che ti costano, sali.

C'è anche una terza strada. A parità di memoria, un modello più grande a bit più bassi può battere uno più piccolo a bit più alti. Il conto: 14 miliardi × 4,89 bit ÷ 8 fa circa 8,6 GB, quasi quanto un 8B in Q8_0. Quale dei due sbaglia meno sul tuo set lo dice solo il set.

Se vuoi un numero oggettivo sul tuo testo, llama-perplexity calcola la KL divergence contro il modello a 16 bit. Il file dei logit di riferimento però è grande: 37 GiB per Llama 3 su Wikitext-2, secondo il README.

Errori comuni

  • Scegliere dalla dimensione del file. Il file dice quanto pesano i pesi, non quanto costa il contesto che ti serve.
  • Alzare il contesto o il parallelismo senza rifare il conto. È la causa più comune dell'out of memory "improvviso".
  • Quantizzare un modello già quantizzato. llama.cpp lo permette solo con --allow-requantize e avverte che la qualità può peggiorare molto. Parti sempre da un file a 16 o 32 bit.
  • Dare per gratis la cache quantizzata. Spesso costa poco, ma dipende da modello e compito.
  • Dimenticare il disco. Oltre alla VRAM conta lo spazio per i file. Con Ollama 0.40, per esempio, durante la conversione i modelli occupano per un po' circa il doppio: i dettagli sono in Ollama 0.40.

Cosa conviene fare adesso

  • Apri il config.json del modello: layer, KV heads, head_dim, eventuale sliding window.
  • Scrivi contesto e richieste parallele reali, non quelle massime.
  • Fai il conto: file GGUF + KV cache + almeno 1 GiB.
  • Parti da Q4_K_M, con imatrix se disponibile.
  • Se la VRAM non basta, nell'ordine: riduci il contesto, quantizza la cache a q8_0, scegli un modello più piccolo. Solo dopo, scendi sotto Q4.
  • Prova Q4 contro Q6/Q8 sul tuo set di prompt e decidi dagli errori.
  • Verifica con ollama ps (o con i log del runtime) che tutto stia in GPU.

Se poi esponi il modello ai tuoi tool tramite un endpoint compatibile, i parametri da controllare sono in API OpenAI-compatible in locale.

Domande rapide

Q4_K_M è il default giusto? È il punto di partenza più diffuso, ed è il livello che la documentazione di llama.cpp usa negli esempi. Non è una garanzia per il tuo compito: il set di prova serve a quello.

Su un Mac con memoria unificata vale lo stesso conto? Sì, le tre voci sono le stesse. La differenza è che la memoria è condivisa con sistema e app, quindi il margine va tenuto più largo.

E i modelli MoE? Per la memoria dei pesi conta il numero totale di parametri, perché il file contiene tutti gli esperti. I parametri attivi per token incidono soprattutto sulla velocità.

Prossima puntata: fine-tuning con Unsloth, cioè quando ha senso adattare un modello piccolo invece di comprarne uno più grande in VRAM. Il percorso completo resta sull'hub AI Engineering.

Condividi