KV Cache su GPU da 16 GB: far funzionare davvero i contesti lunghi
Perché un contesto di 128K fallisce con 16 GB
Un modello può pubblicizzare una finestra di contesto da 128K eppure fallire a 40K token su una GPU da 16 GB. Il limite architetturale non ha mai promesso che pesi, KV cache, buffer di calcolo e il compositor desktop potessero coesistere sulla stessa scheda.
La KV cache è solitamente il punto dove i piani per contesti lunghi incontrano quel limite fisico. Cresce con ogni token attivo e sequenza, quindi una configurazione che sembra comoda all’avvio può rallentare bruscamente, riversarsi nella memoria di sistema o fallire durante un prefill di grandi dimensioni.

Questa guida trasforma il problema in un budget di VRAM. Copre la formula della cache, tabelle di dimensioni riproducibili da 32K a 128K e configurazioni funzionanti per --cache-type-k e --cache-type-v di llama.cpp, caching paginato e di prefisso di vLLM, e i controlli del contesto di Ollama — oltre ai fork sperimentali a cache adattiva che meritano interesse ma non fiducia acritica. Per il contesto più ampio su throughput, latenza e benchmark dietro questi numeri, iniziare con l’hub sulle Prestazioni LLM.
La Risposta Breve per una GPU da 16 GB
Iniziare con una sola sequenza, un massimo realistico del contesto, Flash Attention e una KV cache a 8 bit. Misurare quella configurazione prima di tentare cache a 4 bit, offload su CPU, slot paralleli multipli o un fork sperimentale.
| Obiettivo | Primo tentativo sensato su 16 GB | Rischio principale |
|---|---|---|
| 32K | Pesi Q4 o Q5, KV Q8, una sequenza | I pesi del modello lasciano troppo poco spazio buffer |
| 64K | Modello più piccolo o quantizzazione pesi aggressiva, KV Q8 | Latenza prefill e banda della cache |
| 128K | Piccolo modello GQA, KV Q8 o Q4 testata, una sequenza | La sola cache potrebbe consumare gran parte della VRAM |
| Due sessioni concorrenti da 64K | Trattarla come un budget di cache di circa 128K | La capacità parallela è scambiata per throughput gratuito |
La mia opinione è semplice: una configurazione stabile a 64K è solitamente più utile di una configurazione nominale a 128K che opera al limite di un guasto out-of-memory. La capacità del contesto non è un trofeo; è una decisione su latenza, qualità e concorrenza.
Cosa Conserva la KV Cache
Durante la generazione autoregressiva, ogni layer di attenzione produce tensori chiave e valore per ogni token elaborato. Il runtime mantiene quei tensori in modo che il token successivo possa fare attenzione ai token precedenti senza ricalcolare l’intero prefisso.
La cache risparmia enormi quantità di calcolo, ma consuma memoria in proporzione al numero di token trattenuti. Per un trasformatore convenzionale con grouped-query attention, una baseline utile è:
Byte KV = sequenze * token * layer * 2 * teste KV * dimensione testa * byte per valore
Il fattore due rappresenta chiavi e valori. L’attenzione multi-testa usa tante teste KV quante sono le teste query, la grouped-query attention usa meno teste KV, e l’attenzione multi-testa latente o le architetture ricorrenti ibride necessitano di calcoli diversi.
Perché il Numero di Parametri Non è Sufficiente
Due modelli 8B possono avere costi di KV-cache molto diversi. Uno può usare 32 layer e otto teste KV, mentre l’altro può usare meno teste KV, layer KV condivisi, attenzione a finestra scorrevole o stati latenti compressi.
Il numero di parametri prevede principalmente la memoria per i pesi. La geometria della KV deriva dall’architettura dell’attenzione, quindi leggere i metadati del modello piuttosto che indovinare da 8B, 27B o dalla dimensione del file GGUF. L’esempio più chiaro è quanto il design dell’attenzione si sia spinto oltre l’attenzione multi-testa pura (MHA):
- Multi-Query Attention (MQA) condivide una singola testa K/V tra tutte le teste query — risparmi massimali sulla cache, ma è il compromesso sulla qualità più aggressivo e raramente è usato da solo nei modelli frontier correnti.
- Grouped-Query Attention (GQA) raggruppa le teste query in cluster che condividono ciascuno una testa K/V — il compromesso mainstream usato dalla maggior parte dei modelli densi aperti, e la geometria che la formula sopra presuppone.
- Multi-Head Latent Attention (MLA), introdotta in DeepSeek-V2 e portata in DeepSeek-V3 e Kimi K2, adotta un approccio interamente diverso: invece di condividere K/V tra le teste, proietta chiavi e valori in un vettore latente compresso a basso rango e ricostruisce i K/V a piena risoluzione su richiesta in fase di attenzione. DeepSeek ha riportato una riduzione della KV-cache di circa il 93% rispetto a un modello denso MHA di dimensioni equivalenti, mantenendo una qualità competitiva — talvolta superiore — a GQA a parità di budget di memoria.
La conseguenza pratica è che un “modello 27B GQA” e un “modello 27B MLA” possono avere impronte di KV-cache che differiscono di un ordine di grandezza per la stessa lunghezza di contesto. Non dare per scontato che la formula sopra si applichi a un modello che si documenta come uso di attenzione latente, stato DeltaNet-style o layer a finestra scorrevole — controllare prima la sezione architettura della model card.
Con Ollama, ollama show MODEL --verbose espone metadati del modello inclusa la conteggi di layer, teste di attenzione, teste KV e lunghezza del contesto, ove il formato li fornisca. Con llama.cpp, l’output del model-loader stampato all’avvio include solitamente metadati GGUF equivalenti e l’allocazione cache effettiva del runtime.
Limite del Modello, Contesto Allocato e Contesto Usato
Questi sono tre numeri separati. Il limite del modello è il massimo supportato dal suo training e dal posizionamento encoding, il contesto allocato è ciò che il runtime riserva o permette, e il contesto usato sono i token attualmente trattenuti per una sequenza.
Alzare un flag dell’engine non può estendere in modo sicuro un modello oltre il suo schema di posizione supportato. Il RoPE scaling può estendere alcune architetture, ma è un esperimento sulla qualità del modello, non un’ottimizzazione della memoria KV.
Tabella Dimensione KV Cache: Budget di Contesto 32K, 64K e 128K
Considerare un modello GQA rappresentativo con 32 layer, otto teste KV e una dimensione testa di 128. Queste dimensioni producono 65.536 elementi chiave e valore per token prima di moltiplicare per la dimensione di storage di ciascun elemento.
La tabella usa GiB binari e le dimensioni di blocco fisiche comunemente associate a f16, q8_0 e q4_0 di llama.cpp. È un calcolo di baseline, non una promessa sulla memoria totale del processo; allineamento, metadati, layer ibridi e workspace del backend aggiungono overhead.
| Tipo cache | Byte appross. per valore memorizzato | Contesto 32K | Contesto 64K | Contesto 128K |
|---|---|---|---|---|
| F16 | 2.0000 | 4.00 GiB | 8.00 GiB | 16.00 GiB |
| Q8_0 | 1.0625 | 2.13 GiB | 4.25 GiB | 8.50 GiB |
| Q4_0 | 0.5625 | 1.13 GiB | 2.25 GiB | 4.50 GiB |
| K Q8_0 e V Q4_0 | Misto | 1.63 GiB | 3.25 GiB | 6.50 GiB |
Ora raddoppiare la conteggi di layer a 64 mantenendo invariate le altre dimensioni. La cache FP16 diventa 8 GiB a 32K, 16 GiB a 64K e 32 GiB a 128K, il che dimostra perché un’unica raccomandazione di contesto non può coprire ogni modello.
L’Equazione Reale da 16 GB
Un budget pratico è più ampio della formula KV:
VRAM utilizzabile = VRAM totale - riserva desktop e driver
Budget KV = VRAM utilizzabile
- pesi modello residenti sulla GPU
- buffer grafo e attivazioni
- workspace runtime
- stato speculative-decoding
- margine di sicurezza
Su una scheda da 16 GB con display collegato, non pianificare contando su tutti i 16 GiB disponibili. Riservare almeno diverse centinaia di MiB per il desktop e il driver, poi lasciare un altro margine per buffer dipendenti dal workload; 1.0 a 1.5 GiB di respiro totale è un’assunzione iniziale ragionevole, ma i tuoi log sono l’autorità.
Supponiamo che un modello GGUF occupi 10.8 GiB sulla GPU e l’overhead runtime raggiunga picchi vicino a 1.2 GiB. Dopo un margine di sicurezza di 1 GiB, restano solo circa 3 GiB per la KV, quindi il modello rappresentativo contiene approssimativamente 45K token con Q8_0 o 87K con Q4_0, prima dell’overhead specifico dell’engine.
Questo non rende automaticamente Q4_0 la scelta giusta. Se l’accuratezza del lungo contesto diminuisce sul tuo workload, un modello più piccolo o più aggressivamente quantizzato con cache Q8_0 potrebbe essere migliore di pesi più grandi accoppiati a una cache fragile. Ancoraggi misurati per esattamente questa aritmetica si trovano nelle tabelle benchmark llama.cpp per VRAM 16 GB, dove la VRAM per modello è registrata a contesto 19K, 32K e 64K. Per un’indagine più ampia su quali dimensioni di modello e livelli di quantizzazione si comportino bene con Ollama sulla stessa classe di scheda, vedere Confronto delle prestazioni LLM su Ollama con GPU 16GB VRAM.
Calcolare il Budget KV Cache per il Tuo Modello
Il seguente snippet Python stima una cache GQA full-attention convenzionale. Sostituire la geometria con i valori dalla configurazione del modello o dai metadati GGUF.
def kv_gib(tokens, layers, kv_heads, head_dim, bytes_per_value, sequences=1):
total = (
sequences
* tokens
* layers
* 2
* kv_heads
* head_dim
* bytes_per_value
)
return total / (1024 ** 3)
model = {
"layers": 32,
"kv_heads": 8,
"head_dim": 128,
}
types = {
"f16": 2.0,
"q8_0": 34 / 32,
"q4_0": 18 / 32,
}
for tokens in (32768, 65536, 131072):
row = {
name: round(kv_gib(tokens=tokens, bytes_per_value=size, **model), 2)
for name, size in types.items()
}
print(tokens, row)
I rapporti Q8_0 e Q4_0 includono metadati di blocco semplici, motivo per cui sono leggermente maggiori di esattamente un byte e mezz’ byte per valore. Il report di avvio del runtime rimane più accurato perché conosce i layout cache specifici del modello.
Quando Questa Formula è Sbagliata: Architetture Ibride e a Finestra Scorrevole
Non forzare architetture ibride nell’equazione GQA convenzionale. I layer a finestra scorrevole trattengono solo una finestra recente, i layer KV condivisi riducono la duplicazione, i layer ricorrenti possono portare stati a dimensione fissa, e l’attenzione multi-testa latente memorizza una rappresentazione compressa invece di tensori K/V per testa — il caso MLA sopra essendo l’esempio più drammatico.
I engine moderni gestiscono sempre più questi layout misti esplicitamente. Usare la formula per spiegare i termini dominanti, poi confermare l’allocazione riportata dall’esatto build dell’engine e dal backend che si intende deployare.
llama.cpp: Controllo Diretto della Precisione K e V
llama.cpp espone opzioni separate --cache-type-k e --cache-type-v nel suo parser di argomenti corrente. Questa è l’interfaccia di inferenza locale più utile quando si deve scambiare precisione della cache contro capacità del contesto invece di accettare un preset globale unico. Se è necessario prima il setup di installazione e serving circostante, la guida llama.cpp copre llama-cli, llama-server e i flag chiave VRAM.
Una configurazione conservativa a 64K per singolo utente sembra così:
./llama-server \
--model /models/model.gguf \
--n-gpu-layers 999 \
--ctx-size 65536 \
--parallel 1 \
--flash-attn on \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
--batch-size 1024 \
--ubatch-size 256
La sintassi dei flag e il supporto del backend cambiano rapidamente, quindi eseguire llama-server --help per il build installato. Più importante, ispezionare il log di avvio: dovrebbe mostrare il contesto previsto, i tipi di cache, l’offload GPU e i buffer K e V allocati.
Quali Tipi di Cache llama.cpp Provare
Iniziare con Q8_0 sia per K che per V. Riduce approssimativamente la memoria KV a metà rispetto a F16, e test indipendenti di perplexity su modelli 20B e superiori (Qwen3.6-27B, Nemotron-30B) mostrano che la differenza di qualità aggregata rispetto a F16 è entro il rumore di misura — una scommessa molto meno drammatica rispetto al passaggio diretto a Q4_0, che gli stessi test hanno mostrato collassare velocità di decodifica e accuratezza a lungo contesto su modelli più piccoli.
Se Q8_0 non entra, testare chiavi Q8_0 con valori Q4_0 prima di quantizzare entrambi i lati a Q4_0. Questo ordine ha un supporto di ricerca, non solo folklore: studi controllati sull’allocazione dei bit su checkpoint Llama, Phi-4, Qwen3 e Mistral hanno scoperto che i tensori chiave sono consistentemente da due a dieci volte più sensibili all’errore di quantizzazione rispetto ai tensori valore, e che dare alle chiavi un budget di bit maggiore (per esempio chiavi a 4 bit con valori a 2 bit) recupera fino al 94–98% dell’accuratezza a piena precisione — mentre la suddivisione inversa (chiavi a 2 bit, valori a 4 bit) può perdere 30 punti percentuali su task come GSM8K. Le chiavi determinano quali token precedenti l’attenzione effettivamente abbina, quindi proteggerle per prime è la scelta architetturalmente sana, non solo quella che sembra più sicura.
| Configurazione | Memoria | Rischio qualità | Raccomandazione |
|---|---|---|---|
| K e V F16 | Più alta | Più bassa | Baseline quando entra |
| K e V Q8_0 | Circa metà di F16 | Bassa ma non zero | Punto di partenza predefinito per 16 GB |
| K Q8_0, V Q4_0 | Tra Q8 e Q4 | Moderata | Secondo passo utile |
| K e V Q4_0 | Circa un quarto di F16 | Più alta | Validare alla profondità target |
Un caveat importante da interiorizzare: “basso rischio qualità” su benchmark aggregati non significa zero rischio a livello di token. Un test controllato che ha tenuto Flash Attention costante e ha cambiato solo la precisione KV sotto decodifica greedy (deterministica) ha trovato che la cache Q8_0 ha cambiato il testo generato esatto sulla grande maggioranza dei prompt, e Q4_0 l’ha cambiato praticamente su tutti — una volta che un token cambia, il resto della continuazione può divergere. Perplexity e punteggi su task downstream possono sembrare ragionevoli in media mentre gli output individuali differiscono ancora dalla baseline F16. Se la tua applicazione richiede riproducibilità byte-per-byte (test di regressione, risposte cacheate, agenti deterministici), trattare ogni quantizzazione KV come un cambio comportamentale, non solo un’ottimizzazione di memoria, e validare contro il proprio set di prompt fissi.
La cache V quantizzata potrebbe richiedere Flash Attention o un percorso backend compatibile. Un server che cade silenziosamente su un altro tipo invalida l’esperimento, motivo per cui i log di avvio contano più delle righe di comando copiate.
Contesto, Slot Paralleli e Cache Unificata
--ctx-size descrive una capacità dell’engine, non una garanzia che ogni slot parallelo riceva quei token indipendentemente. La gestione della cache si è evoluta in llama.cpp, includendo il comportamento unified-cache, quindi testare l’esatto build invece di affidarsi a una regola più vecchia che semplicemente divide il contesto per il numero di slot.
L’equazione di capacità sopravvive comunque ai cambi di implementazione: i token unici simultanei necessitano di storage da qualche parte. Se due sessioni agente possono raggiungere ciascuna 48K, budgetizzare per quasi 96K token live a meno che il workload non condivida prefissi o tolleri evizione e ricalcolo.
La Batch Size Non Riduce la KV Memorizzata
--batch-size e --ubatch-size influenzano l’elaborazione del prompt e la memoria temporanea. Ridurli può salvare un grande prefill da un picco di memoria attivazione, ma non cambia i byte persistenti richiesti per ogni token trattenuto.
Questa distinzione spiega un comune modello di fallimento: il modello parte e una richiesta vuota funziona, ma un prompt da 60K fallisce durante l’ingestione. Ridurre la micro-batch per diagnosticare il picco transiente; ridurre contesto, precisione cache, parallelismo o residenza pesi per cambiare la capacità persistente.
vLLM: La Capacità Paginata è Ancora Capacità
vLLM affronta il problema come engine di serving. Profila la memoria disponibile, riserva un pool KV-cache e alloca la cache in blocchi in modo che sequenze concorrenti non richiedano ciascuna una singola regione contigua grande. Se si sta decidendo se migrare a vLLM in primo luogo, la guida alla migrazione da Ollama a vLLM copre i segnali del workload; qui la domanda è puramente quanto cache il pool può contenere, e il quickstart vLLM copre installazione e flag generali di serving oltre alle leve di capacità qui sotto.
PagedAttention riduce frammentazione e spreco attorno a lunghezze di sequenza variabili — l’allocazione paginata rimuove la frammentazione, non il costo di storage per token, quindi una singola richiesta unica da 128K ha ancora bisogno di blocchi sufficienti per il suo stato KV.
La guida ufficiale alla conservazione della memoria vLLM raccomanda di limitare max_model_len e max_num_seqs quando la memoria è stretta, e nota che i grafi CUDA consumano memoria GPU aggiuntiva. Su una scheda da 16 GB, entrambe le impostazioni dovrebbero essere intenzionali piuttosto che ereditate dalla configurazione massima del modello.
Un server focalizzato su singola sequenza potrebbe iniziare così:
vllm serve MODEL_ID \
--max-model-len 65536 \
--max-num-seqs 1 \
--gpu-memory-utilization 0.90 \
--kv-cache-dtype fp8 \
--enable-prefix-caching
Non ogni GPU da 16 GB, modello, metodo di quantizzazione o backend di attenzione supporta quella combinazione esatta. Trattarla come una forma di configurazione: vincolare lunghezza e concorrenza, riservare margine, selezionare un tipo di cache supportato e validare il report di inizializzazione.
KV Cache FP8 in vLLM
La documentazione corrente sulla KV-cache quantizzata vLLM supporta formati cache FP8 su percorsi CUDA e ROCm compatibili. FP8 riduce approssimativamente a metà lo storage grezzo della cache rispetto a BF16 o FP16 e può quindi aumentare la capacità di token o la concorrenza.
Lo scaling conta. La documentazione distingue scale predefinite, calcolo warm-up e calibrazione su dataset, e raccomanda la calibrazione basata su dataset per la massima accuratezza; impostare semplicemente FP8 con scale 1.0 è comodo ma non automaticamente la scelta di qualità più affidabile.
Il Prefix Caching è un’ottimizzazione di riuso
Il prefix caching automatico permette a una nuova richiesta di riutilizzare blocchi KV per un prefisso cacheato identico. È eccellente per query ripetute sullo stesso documento lungo, prompt di sistema condivisi e conversazioni multi-round perché evita di ricalcolare il matching prefill.
Non rende una richiesta lunga unica più piccola, e non accelera la generazione di nuovi token. La documentazione sul prefix-caching vLLM limita esplicitamente il beneficio al lavoro di prefill a prefisso condiviso.
L’Utilizzo della Memoria GPU Non è Memoria Gratuita
Aumentare --gpu-memory-utilization dà a vLLM un obiettivo di riserva più grande, ma non crea VRAM. Spingerlo troppo vicino a 1.0 può lasciare spazio insufficiente per il display, un altro processo, picchi di attivazione variabili o allocazioni non-PyTorch.
Iniziare intorno a 0.88 a 0.92 su una GPU da 16 GB dedicata, ispezionare il profilo e aumentare solo se il workload rimane stabile. Se l’inizializzazione riesce ma prompt reali falliscono, ridurre token batched, concorrenza di sequenza, cattura grafo CUDA o il contesto massimo prima di assumere che l’allocazione sia rotta.
Ollama: Controlli Più Semplici, Diagnosi Meno Granulare
Ollama fornisce deliberatamente una superficie operativa più piccola. La sua attuale documentazione sulla lunghezza del contesto predefinisce le GPU sotto 24 GiB a contesto 4K, raccomanda almeno 64K per workload di agenti e coding, e avverte che contesti maggiori consumano più memoria.
Impostare il default globale del server e confermare il modello caricato così:
OLLAMA_CONTEXT_LENGTH=65536 ollama serve
ollama ps
Si può anche impostare num_ctx per richiesta o modello. ollama ps è importante perché le colonne PROCESSOR e CONTEXT rivelano se il modello è rimasto interamente sulla GPU e se il contesto richiesto è stato effettivamente allocato. Sii consapevole che il comportamento di scheduling dietro quei numeri è cambiato tra versioni di Ollama; il mio confronto dell’allocazione memoria Ollama v0.12.1 mostra il nuovo scheduler spingendo alcuni modelli più verso la CPU su una scheda da 16 GB, quindi fissare la versione misurata.
KV Cache Quantizzata in Ollama
Ollama espone OLLAMA_KV_CACHE_TYPE con scelte f16, q8_0 e q4_0 nella sua FAQ corrente. La KV quantizzata richiede Flash Attention, che Ollama usa automaticamente su backend supportati o può essere richiesto con OLLAMA_FLASH_ATTENTION=1.
Un servizio a lungo contesto da 16 GB può quindi essere avviato come:
OLLAMA_CONTEXT_LENGTH=65536 \
OLLAMA_FLASH_ATTENTION=1 \
OLLAMA_KV_CACHE_TYPE=q8_0 \
OLLAMA_NUM_PARALLEL=1 \
ollama serve
Q8_0 è l’alternativa raccomandata da Ollama a F16. La FAQ avverte che Q4_0 può produrre una perdita di qualità più marcata, specialmente a contesto più alto, quindi dovrebbe essere un fallback misurato piuttosto che un preset automatico per 16 GB.
La Parallelità di Ollama Moltiplica il Budget del Contesto
Ollama documenta una regola particolarmente chiara: la memoria richiesta scala con OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH. Quattro richieste parallele a un’impostazione 32K possono implicare un’allocazione aggregata di contesto 128K per quel modello.
Per un agente personale su 16 GB, mantenere OLLAMA_NUM_PARALLEL=1 finché una lunga sessione non è stabile. Mettere in coda una seconda richiesta è solitamente preferibile a spingere il primo modello parzialmente sulla CPU e rendere entrambe le richieste lente. I meccanismi di code, 503 e scarico modello dietro quella scelta sono documentati in come Ollama gestisce le richieste parallele.
CPU Offload: Una Valida Scappatoia con un Prezzo
Spostare alcuni layer del modello o stato KV nella RAM di sistema può trasformare un fallimento di allocazione in un processo funzionante. Colloca anche la banda PCIe e la latenza della memoria host nel percorso di decodifica, dove ogni token generato può pagare il costo. La lane e le evidenze di generazione su quando il PCIe incide davvero sono in Prestazioni LLM e Lane PCIe.
L’offload può essere sensato per lavoro batch occasionale, ma è raramente il default migliore per un agente di coding interattivo. Confrontare prima una quantizzazione pesi più piccola, KV Q8, concorrenza ridotta e un limite realistico di contesto; usare l’offload quando la capacità conta più della latenza.
Guardare la scogliera piuttosto che la media. Un server può decodificare rapidamente a 8K, poi rallentare severamente dopo che parte del working set si riversa, quindi fare benchmark a 32K, 64K e il massimo previsto invece di riportare solo un tasso di token a contesto vuoto.
Cache KV a Finestra Scorrevole e Adattiva
L’attenzione a finestra scorrevole cambia il budget tratteneando solo una finestra recente per layer selezionati. I modelli ibridi possono combinare quei layer con attenzione globale occasionale o stato ricorrente, rendendo un calcolo flat a contesto completo sostanzialmente sovrastimare o mal collocare la memoria.
L’ottimizzazione è parte dell’architettura del modello, non un’interruzione generica che può essere applicata senza conseguenze. Un engine deve capire correttamente il pattern dei layer, le regole di evizione, le posizioni e eventuali token globali.
Cosa Prova a Migliorare la KV Adattiva
I fork sperimentali spingono oltre scegliendo la precisione o il layout della cache per layer e profondità di contesto. L’obiettivo è attraente: preservare precisione più alta dove conta, comprimere layer meno sensibili e cambiare la miscela prima che la pressione VRAM causi un riversamento duro — la stessa scoperta sulla sensibilità delle chiavi rispetto ai valori descritta sopra è esattamente il tipo di segnale che un allocazione adattiva vorrebbe sfruttare automaticamente invece di lasciarlo al tuning manuale di --cache-type-k/--cache-type-v.
Un progetto downstream di agosto 2026, llama.cpp-adaptive-turboquant, riporta un selettore automatico per diverse modalità a layer adattivo e pubblica test a profondità lunga su RTX 5080 16 GB. Quei numeri sono risultati riportati dall’autore da un fork specializzato, non evidenza che il llama.cpp upstream si comporti nello stesso modo.
Perché è Ancora Sperimentale
Il fork combina tipi di cache custom, kernel CUDA, percorsi specifici del modello e vincoli di toolchain. È molto più codice da fidare rispetto al cambiare lo storage cache upstream da F16 a Q8_0.
Usare un tale fork solo quando l’upstream non può soddisfare un requisito reale e si può riproducire qualità, stabilità e velocità sul proprio modello. Registrare commit e versione CUDA, perché un risultato allegato solo a un nome di progetto non è riproducibile.
Un Test Equo della Cache Adattiva
Confrontare il fork contro una baseline upstream Q8_0 con lo stesso GGUF, prompt, sampler, profondità contesto e lunghezza output. Misurare VRAM all’avvio, VRAM prefill di picco, velocità elaborazione prompt, velocità di decodifica e un task di qualità che richieda effettivamente evidenze dalla parte più antica del contesto.
Non accettare una allocazione riuscita come risultato completo. Una cache può contenere 128K e comunque perdere fatti precoci, corrompere l’output tardi nella sequenza o decodificare troppo lentamente per essere utile.
Una Procedura di Tuning da 16 GB Esempi: Una Variabile alla Volta
La rotta più veloce a una configurazione stabile è cambiare una dimensione di memoria alla volta. Alterare casualmente tipo di cache, batch size, offload layer, parallelismo e contesto insieme produce un comando funzionante senza spiegazione.
Passo 1: Stabilire il Piatto dei Pesi
Caricare il modello a contesto 8K, una sequenza e l’offload GPU previsto. Registrare la VRAM del processo dopo il warm-up e verificare che nessun layer si sia spostato inaspettatamente sulla CPU.
Se pesi e runtime consumano già più di circa 14.5 a 15 GiB, il lungo contesto non ha margine sano. Scegliere una quantizzazione pesi o modello più piccolo prima di fare tuning della cache.
Passo 2: Misurare KV F16 o BF16 come Baseline Qualità
Eseguire il contesto più piccolo che supporta il test e mantenere la cache ad alta precisione predefinita. Salvare output da task di retrieval, editing codice, selezione tool e istruzioni lunghe.
Questa baseline dice se errori successivi derivano dalla quantizzazione della cache. Senza di essa, un problema di chat-template o un modello debole possono essere facilmente attribuiti a KV Q4.
Passo 3: Passare a Q8 o FP8
Abilitare Flash Attention dove richiesto, selezionare Q8_0 in llama.cpp o Ollama, o una modalità FP8 supportata in vLLM. Ripetere gli stessi prompt alla stessa profondità di token e confermare che il log mostri il tipo di cache previsto.
Per molti deployment da 16 GB, questo è il punto di arresto utile. Raddoppia approssimativamente la capacità grezza KV senza rendere la compressione della cache la quantizzazione più aggressiva dello stack.
Passo 4: Alzare il Contesto a Stadi
Testare 32K, 64K, 96K e 128K invece di saltare direttamente al massimo pubblicizzato. A ogni stadio, registrare token al secondo di elaborazione prompt, token al secondo di decodifica, VRAM di picco e se le evidenze vicino all’inizio possono ancora essere recuperate.
La decodifica a lungo contesto rallenta spesso anche dopo che la memoria entra perché l’attenzione legge più stato cacheato. Capacità e prestazioni sono assi separati.
Passo 5: Ottimizzare la Memoria Transiente
Se il fallimento avviene durante il prefill piuttosto che l’inizializzazione, ridurre micro-batch o token batched massimo. Se il fallimento avviene solo con richieste simultanee, ridurre la concorrenza di sequenza o gli slot paralleli.
Solo dopo che quei controlli sono compresi si dovrebbe provare cache mista Q8/Q4, cache Q4 completa, CPU offload o un fork adattivo. Mantenere l’esecuzione Q8 upstream come baseline di confronto. Se si aggiunge in seguito speculative decoding o MTP, ricordare che i suoi buffer di draft sono un’altra riga nell’equazione del budget, non velocità gratuita — la guida al speculative decoding copre le meccaniche e il loro costo VRAM, e il mio benchmark Qwen 3.6 27B e 35B MTP vs Standard mostra esattamente quanta costa il contesto aggiuntivo dello stato di una testa MTP su una scheda da 16 GB.
Cosa Registrare in un Benchmark a Lungo Contesto
Un singolo valore tokens/s nasconde esattamente il problema che questo articolo sta cercando di risolvere. Il test a lungo contesto dovrebbe preservare abbastanza dettagli per un altro operatore da riprodurre il limite di memoria.
| Campo | Perché conta |
|---|---|
| GPU e VRAM utilizzabile | Uso display e altri processi cambiano il budget |
| Versione o commit engine | Comportamento cache e flag evolvono rapidamente |
| Versione Driver, CUDA, ROCm o Vulkan | Determina comportamento backend e kernel |
| Esatto modello e quantizzazione pesi | Definisce residenza pesi e architettura |
| Tipi di cache K e V | Definisce dimensione cache persistente e rischio qualità |
| Capacità contesto e profondità prompt | Allocazione non è la stessa cosa di profondità effettiva |
| Sequenze parallele | Moltiplica o condivide la domanda di cache |
| Batch e micro-batch | Influenza picchi prefill e velocità |
| Velocità elaborazione prompt | Espone usabilità di prefill lunghi |
| Velocità decodifica a ogni profondità | Espone rallentamento della banda cache |
| VRAM di picco e offload CPU | Distingue adattamento da riversamento |
| Risultato qualità a lungo contesto | Rileva fallimenti di compressione o posizione |
Usare campionamento nvidia-smi o tooling vendor equivalente durante sia prefill che decodifica. Il report di allocazione dell’engine è necessario, ma la memoria dispositivo di picco durante un prompt reale è il numero che decide la stabilità.
Errori Comuni KV Cache su GPU da 16 GB
Trattare il Supporto 128K come Promessa Hardware
Il campo contesto in una configurazione del modello è un limite architetturale. Dice nulla sulla memoria rimasta dopo il caricamento di una particolare quantizzazione su un particolare engine.
Calcolare la cache e verificare il runtime. Contesto dimensionato marketing senza budget VRAM è semplicemente un OOM ritardato fino al primo prompt serio.
Quantizzare i Pesi ma Dimenticare la KV
Un GGUF a 4 bit riduce i pesi del modello, non una KV cache F16. A lungo contesto, la cache può cancellare l’intero risparmio e alla fine superare l’impronta dei pesi.
Riportare entrambe le quantizzazioni. Modello Q4_K_M, KV Q8_0 è significativo; modello a 4 bit è incompleto.
Assumere che la Paged Attention Comprimi i Token
La paginazione migliora il comportamento di allocazione e condivisione. Non cambia la precisione del tensore o rimuove lo stato KV richiesto da una sequenza unica.
Usare l’allocazione paginata per servire workload variabili efficientemente. Usare precisione cache, architettura modello, limiti contesto e limiti di concorrenza per controllare la capacità.
Assumere che il Prefix Caching Aiuti Ogni Prompt Lungo
Il prefix caching risparmia il calcolo di prefill ripetuto quando le richieste condividono un prefisso esatto. Un dump di repository una tantum da 100K non riceve alcuno sconto magico sulla memoria semplicemente perché il prefix caching è abilitato.
È un’ottimizzazione del workload, non un sostituto dell’equazione del budget. Misurare hit rate e pressione della cache trattenuta nel serving multi-utente.
Usare KV Q4 Senza un Test di Qualità
La cache a bit bassi può fallire sottilmente. Il modello scrive ancora testo fluente, ma l’attenzione su evidenze distanti, nomi esatti, argomenti tool o dipendenze di codice può degradare — e come la ricerca sulla divergenza dei token sopra mostra, persino l’impostazione “sicura” Q8_0 non è garantita a riprodurre l’output esatto F16 sotto decodifica deterministica, solo a preservare accuratezza aggregata.
Testare il task target alla profondità target. I benchmark di chat brevi sono quasi inutili per validare una cache a lungo contesto.
Lasciare la Parallelità su Auto
Un engine può scegliere una concorrenza sensata per il throughput ma impossibile per il tuo target a lungo contesto. Su 16 GB, una sequenza profonda e diverse sequenze corte sono workload fondamentali diversi.
Impostare il limite esplicitamente, poi alzarlo con traffico misurato. Altrimenti una seconda richiesta può trasformare una configurazione stabile a 64K in una sorpresa di allocazione o latenza.
Profili Raccomandati da 16 GB
Questi profili sono posizioni di partenza, non preset universali. Un modello con geometria KV insolita — in particolare un design MLA o ibrido a finestra scorrevole — può essere molto più economico o più costoso dell’esempio GQA convenzionale.
Agente di Coding Interattivo
Usare una sequenza, contesto 48K a 64K, cache Q8, Flash Attention e residenza pesi completa sulla GPU se possibile. Questo profilo privilegia latenza prevedibile e buona precisione cache rispetto a un massimo impressionante ma raramente utile.
Abilitare il riuso prefisso quando l’engine lo supporta perché i turni di coding spesso condividono un grande prefisso di repository o conversazione. Comunque compattare l’output tool e i trascritti vecchi; l’ingegneria della cache non rende i token irrilevanti preziosi.
Analisi di Documenti Lunghi
Usare un modello più piccolo con capacità 64K a 128K, cache Q8 o FP8 calibrata e caching a prefisso ripetuto quando più domande puntano allo stesso documento. Misurare il tempo al primo token perché il prefill può dominare anche quando la decodifica rimane accettabile.
Se verrà posta solo una domanda, retrieval o sommario a chunk può essere più veloce e più affidabile che forzare l’intero corpus attraverso una scheda da 16 GB. Il contesto lungo è uno strumento, non un sostituto per l’architettura dell’informazione.
Piccolo Server Multi-Utente
Limitare il contesto per richiesta e le sequenze attive totali invece di pubblicizzare il massimo del modello a ogni client. L’allocazione paginata di vLLM è utile qui, mentre Ollama e llama.cpp richiedono anche attenzione esplicita ai token live aggregati.
Preferire la code rispetto al riversamento incontrollato. Una politica di ammissione più lenta è meno dannosa di ogni richiesta che improvvisamente attraversa PCIe durante la decodifica.
Raccomandazione Finale per Contesto Lungo su 16 GB
Per contesto lungo su 16 GB, KV Q8 e una sequenza attiva sono la baseline giusta. Espongono il limite reale senza far fallire insieme qualità cache a bit bassi, allocazione parallela e latenza offload.
Calcolare dalla geometria di attenzione, sottrarre pesi e overhead runtime, e poi confermare il risultato nei log dell’engine e nelle misurazioni di memoria di picco. Se 128K non entra ancora, un modello più piccolo è spesso l’ottimizzazione più pulita; se entra ma striscia, ridurre il contesto è spesso quella onesta.
Paged attention, prefix caching, finestre scorrevoli e precisione adattiva risolvono tutti problemi utili ma diversi. La configurazione vincente è quella che rimane sulla GPU, recupera correttamente le evidenze antiche e sostiene una velocità di decodifica accettabile alla profondità di contesto che si usa davvero.
Riferimenti
- vLLM: conservazione memoria GPU
- vLLM: KV cache quantizzata (FP8)
- vLLM: prefix caching automatico
- Ollama: documentazione lunghezza contesto
- Ollama FAQ:
OLLAMA_KV_CACHE_TYPEe Flash Attention - llama.cpp-adaptive-turboquant — fork sperimentale a cache adattiva a layer (risultati riportati dall’autore)
- Raschka, S. “Multi-Head Latent Attention (MLA).” LLM Architecture Gallery. https://sebastianraschka.com/llm-architecture-gallery/mla/
- “Decoding Multi-Head Latent Attention: The KV Cache Memory Bottleneck, Solved.” Vizuara. https://vizuara.substack.com/p/decoding-multi-head-latent-attention
- “Quantize What Counts: Bit Allocation Insights Informed by Spectral Gaps in Keys and Values.” arXiv:2502.15075. https://ar5iv.labs.arxiv.org/html/2502.15075
- v-code01. “kvdivergence — does KV cache quantization change the generated text under greedy decoding?” GitHub. https://github.com/v-code01/kvdivergence
- Confronto delle prestazioni LLM su Ollama con GPU 16GB VRAM
- Qwen 3.6 27B e 35B MTP vs Standard su GPU 16GB