KV Cache su GPU da 16 GB: far convivere davvero i contesti lunghi

Perché il contesto da 128K va in crash su 16 GB

Indice

Un modello può pubblicizzare una finestra di contesto da 128K e comunque fallire a 40K token su una GPU da 16 GB. Il limite architetturale non ha mai garantito che i pesi, la cache KV, i buffer di calcolo e il compositore del desktop potessero stare sulla tua scheda contemporaneamente.

La cache KV è solitamente il punto in cui i piani a lungo contesto incontrano quel limite fisico. Cresce con ogni token e sequenza attivi, quindi una configurazione che sembra comoda all’avvio può rallentare drasticamente, riversarsi nella memoria di sistema o fallire durante un prefill di grandi dimensioni.

Budget di memoria della cache KV su una GPU da 16 GB

Questa guida trasforma il problema in un budget di VRAM. Copre la formula della cache, tabelle riproducibili delle dimensioni 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 su Prestazioni LLM.

La risposta breve per una GPU da 16 GB

Iniziare con una sola sequenza, un contesto massimo realistico, Flash Attention e una cache KV a 8 bit. Misurare quella configurazione prima di tentare cache a 4 bit, offload 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 di prefill e bandwidth della cache
128K Modello GQA piccolo, KV Q8 o Q4 testato, una sequenza La cache da sola può consumare la maggior parte della VRAM
Due sessioni 64K concorrenti Trattare come un budget di cache di circa 128K La capacità parallela è scambiata per throughput gratuito

La mia opinione è semplice: una configurazione stabile a 64K è di solito più utile di una configurazione nominale a 128K che gira al limite di un errore out-of-memory. La capacità del contesto non è un trofeo; è una decisione su latenza, qualità e concorrenza.

Cosa memorizza la cache KV

Durante la generazione autoregressiva, ogni layer di attenzione produce tensori chiave e valore per ogni token elaborato. Il runtime conserva quei tensori in modo che il token successivo possa prestare attenzione ai token precedenti senza ricalcolare l’intero prefisso.

La cache salva un’enorme quantità di calcolo, ma consuma memoria in proporzione al numero di token ritenuti. Per un transformer 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 di query, la grouped-query attention usa meno teste KV, e l’attenzione latente multi-testa o le architetture ricorrenti ibride necessitano di calcoli diversi.

Perché il numero di parametri non è sufficiente

Due modelli da 8B possono avere costi di cache KV molto diversi. Uno potrebbe usare 32 layer e otto teste KV, mentre un altro potrebbe usare meno teste KV, layer KV condivisi, attenzione a finestra scorrevole o stati latenti compressi.

Il numero di parametri predice principalmente la memoria dei pesi. La geometria KV deriva dall’architettura di attenzione, quindi leggere i metadati del modello invece di indovinare da 8B, 27B o dalla dimensione del file GGUF. L’illustrazione più chiara è di quanto il design dell’attenzione si sia spostato oltre la semplice attenzione multi-testa (MHA):

  • Multi-Query Attention (MQA) condivide una singola testa K/V tra tutte le teste di query — risparmio di cache massimo, ma è il compromesso più aggressivo in termini di qualità e raramente è usato da solo nei modelli frontier attuali.
  • Grouped-Query Attention (GQA) raggruppa le teste di 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 assume.
  • Multi-Head Latent Attention (MLA), introdotta in DeepSeek-V2 e portata in DeepSeek-V3 e Kimi K2, adotta un approccio completamente diverso: invece di condividere K/V tra le teste, proietta chiavi e valori in un vettore latente a rango compresso e ricostruisce i K/V a risoluzione piena on demand al momento dell’attenzione. DeepSeek ha riportato una riduzione della cache KV di circa il 93% rispetto a un modello denso MHA di dimensioni equivalenti, mantenendo una qualità competitiva con — a volte superiore a — la GQA a parità di budget di memoria.

La conseguenza pratica è che un “modello GQA 27B” e un “modello MLA 27B” possono avere impronte di cache KV 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 in stile DeltaNet o layer a finestra scorrevole — verificare prima la sezione architettura della scheda del modello.

Con Ollama, ollama show MODEL --verbose espone i metadati del modello incluso il numero di layer, teste di attenzione, teste KV e lunghezza del contesto, quando il formato lo fornisce. Con llama.cpp, l’output del model-loader stampato all’avvio di solito include metadati GGUF equivalenti e l’allocazione cache reale del runtime.

Limite del modello, contesto allocato e contesto usato

Questi sono tre numeri distinti. Il limite del modello è il massimo supportato dal suo training e dalla codifica posizionale, il contesto allocato è ciò che il runtime riserva o consente, e il contesto usato è i token attualmente ritenuti per una sequenza.

Alzare un flag del motore non può estendere in sicurezza un modello oltre il suo schema posizionale supportato. Lo scaling RoPE può estendere alcune architetture, ma è un esperimento sulla qualità del modello, non un’ottimizzazione della memoria KV.

Tabella Dimensioni Cache KV: 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 della moltiplicazione per la dimensione di storage di ogni elemento.

La tabella usa GiB binari e le dimensioni fisiche dei blocchi 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 Approx. byte 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
Q8_0 K e Q4_0 V Misto 1,63 GiB 3,25 GiB 6,50 GiB

Ora raddoppiare il numero di layer a 64 lasciando 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é una singola raccomandazione di contesto non può coprire ogni modello.

L’Equazione Reale per 16 GB

Un budget pratico è più ampio della formula KV:

VRAM utile = VRAM totale - riserva desktop e driver

Budget KV = VRAM utile
          - pesi del modello residenti su GPU
          - buffer di grafo e attivazione
          - workspace del runtime
          - stato del decodifica speculativa
          - margine di sicurezza

Su una scheda da 16 GB collegata a un display, non pianificare come se tutti i 16 GiB fossero disponibili. Riservare almeno alcuni MiB per il desktop e il driver, poi lasciare un altro margine per i buffer dipendenti dal carico; 1,0 a 1,5 GiB di spazio di manovra totale è un’ipotesi di partenza ragionevole, ma i tuoi log sono l’autorità.

Supponiamo che un modello GGUF occupi 10,8 GiB sulla GPU e l’overhead del runtime picchi vicino a 1,2 GiB. Dopo un margine di sicurezza di 1 GiB, restano circa 3 GiB per la KV, quindi il modello rappresentativo sta approssimativamente a 45K token con Q8_0 o 87K con Q4_0, prima degli overhead specifici del motore.

Questo non rende automaticamente Q4_0 la scelta giusta. Se l’accuratezza a lungo contesto cala sul tuo workload, un modello più piccolo o più aggressivamente quantizzato con una cache Q8_0 potrebbe essere migliore di pesi più grandi accoppiati a una cache fragile. Gli ancoraggi misurati per proprio questo calcolo si trovano nelle tabelle di benchmark llama.cpp per 16 GB VRAM, dove la VRAM per modello è registrata a 19K, 32K e 64K di contesto. Per una survey più ampia di quali dimensioni di modello e livelli di quantizzazione si comportano bene con Ollama sulla stessa classe di scheda, vedere Confronto prestazioni LLM su Ollama con GPU 16GB VRAM.

Calcolare il Budget di Cache KV 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 mezza per valore. Il report di avvio del runtime rimane più accurato perché conosce i layout della 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 ritengono solo una finestra recente, i layer KV condivisi riducono la duplicazione, i layer ricorrenti possono portare stati di dimensioni fisse, e l’attenzione latente multi-testa memorizza una rappresentazione compressa invece di tensori K/V per testa — il caso MLA sopra essendo l’esempio più drammatico.

I moderni engine gestiscono sempre più esplicitamente questi layout misti. Usare la formula per spiegare i termini dominanti, poi confermare l’allocazione riportata dall’esatto build del motore e dal backend che si intende distribuire.

llama.cpp: Controllo Diretto della Precisione di K e V

llama.cpp espone opzioni separate --cache-type-k e --cache-type-v nel suo parser di argomenti attuale. Questa è l’interfaccia di inferenza locale più utile quando si deve scambiare la precisione della cache con la capacità di contesto invece di accettare un preset globale. Se prima serve il setup di installazione e serving circostante, la guida a llama.cpp copre llama-cli, llama-server e i flag VRAM chiave.

Una configurazione conservativa a 64K per un singolo utente è 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 a metà la memoria KV rispetto a F16, e test indipendenti di perlessità su modelli da 20B in su (Qwen3.6-27B, Nemotron-30B) mostrano che il delta di qualità aggregato da F16 è entro il rumore di misura — una scommessa molto meno drammatica del 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 sta, testare chiavi Q8_0 con valori Q4_0 prima di quantizzare entrambi i lati a Q4_0. Questo ordine ha un riscontro nella ricerca, non solo folklore: studi controllati di allocazione di bit su checkpoint di Llama, Phi-4, Qwen3 e Mistral hanno trovato che i tensori chiave sono costantemente 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 (ad esempio chiavi a 4 bit con valori a 2 bit) recupera fino al 94–98% dell’accuratezza a piena precisione — mentre la suddivisione invertita (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 solida, non solo quella che suona più sicura.

Configurazione Memoria Rischio qualità Raccomandazione
F16 K e V Massima Minima Baseline quando sta
Q8_0 K e V Circa metà di F16 Basso ma non zero Punto di partenza 16 GB di default
Q8_0 K, Q4_0 V Tra Q8 e Q4 Moderato Secondo passo utile
Q4_0 K e V Circa un quarto di F16 Massimo Validare alla profondità target

Un caveat da interiorizzare: “basso rischio qualità” nei benchmark aggregati non significa zero rischio a livello di token. Un test controllato che ha tenuto costante Flash Attention e ha cambiato solo la precisione KV sotto decodifica greedy (deterministica) ha trovato che la cache Q8_0 ha cambiato il testo esatto generato sulla grande maggioranza dei prompt, e Q4_0 su essenzialmente tutti — una volta che un token cambia, il resto della continuazione può divergere. Perlessità e punteggi di task downstream possono sembrare sani in media mentre gli output individuali differiscono comunque dalla baseline F16. Se la tua applicazione richiede riproducibilità byte-per-byte (test di regressione, risposte in cache, agenti deterministici), trattare qualsiasi quantizzazione KV come un cambiamento comportamentale, non solo un’ottimizzazione di memoria, e validare contro il proprio set di prompt fissi.

La cache V quantizzata può richiedere Flash Attention o un percorso backend compatibile. Un server che silenziosamente ricade 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à del motore, non una garanzia che ogni slot parallelo riceva quel numero di token indipendentemente. La gestione della cache è evoluta in llama.cpp, incluso il comportamento di cache unificata, quindi testare l’esatto build invece di fare affidamento su 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 hanno bisogno di storage da qualche parte. Se due sessioni agent possono ciascuna raggiungere 48K, budgetizzare per circa 96K token live a meno che il workload non condivida prefissi o tolleri espulsione e ricalcolo.

La Batch Size non riduce la KV memorizzata

--batch-size e --ubatch-size influenzano l’elaborazione del prompt e la memoria temporanea. Abbassarli può salvare un grande prefill da un picco di memoria di attivazione, ma non cambia i byte persistenti richiesti per ogni token ritenuto.

Questa distinzione spiega un comune pattern di fallimento: il modello si avvia e una richiesta vuota funziona, ma un prompt da 60K fallisce durante l’ingestione. Ridurre la micro-batch per diagnosticare il picco transitorio; ridurre contesto, precisione della cache, parallelismo o residenza dei pesi per cambiare la capacità persistente.

vLLM: La capacità Paginata è Ancora Capacità

vLLM affronta il problema come un engine di serving. Profilizza la memoria disponibile, riserva un pool di cache KV e alloca la cache in blocchi in modo che le sequenze concorrenti non richiedano ciascuna una grande regione contigua. Se si sta decidendo se passare 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 di vLLM copre installazione e flag di serving generali oltre alle leve di capacità sotto.

PagedAttention riduce frammentazione e spreco intorno a lunghezze di sequenza variabili — l’allocazione paginata rimuove la frammentazione, non il costo di storage per token, quindi una richiesta unica da 128K ha ancora bisogno di abbastanza blocchi per il suo stato KV.

La guida ufficiale al risparmio di memoria di 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, entrambi gli setting dovrebbero essere intenzionali piuttosto che ereditati dalla configurazione massima di un modello.

Un server a sequenza singola focalizzato potrebbe iniziare qui:

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 dtype di cache supportato e validare il report di inizializzazione.

Cache KV FP8 in vLLM

La documentazione attuale sulla cache KV quantizzata di vLLM supporta formati di 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 di default, calcolo di warm-up e calibrazione sul dataset, e raccomanda la calibrazione basata su dataset per la più alta accuratezza; semplicemente impostare 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 riusare blocchi KV per un prefisso identico in cache. È eccellente per query ripetute sullo stesso lungo documento, prompt di sistema condivisi e conversazioni multi-round perché evita di ricalcolare il prefill corrispondente.

Non rende più piccola una richiesta lunga unica, e non accelera la generazione di nuovi token. La documentazione sul prefix caching di vLLM limita esplicitamente il beneficio al lavoro di prefill a prefisso condiviso.

L’utilizzo della memoria GPU non è memoria gratuita

Alzare --gpu-memory-utilization dà a vLLM un target 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 ha successo ma prompt reali falliscono, ridurre token in batch, concorrenza di sequenza, cattura del grafo CUDA o il contesto massimo prima di assumere che l’allocazione sia rotta.

Ollama: Controlli più facili, diagnosi meno granulare

Ollama fornisce deliberatamente una superficie operativa più piccola. La sua documentazione attuale sulla lunghezza del contesto mette di default le GPU sotto 24 GiB a contesto 4K, raccomanda almeno 64K per workload agent e coding, e avverte che un contesto più grande consuma 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 sue colonne PROCESSOR e CONTEXT rivelano se il modello è rimasto completamente sulla GPU e se il contesto richiesto è stato effettivamente allocato. Essere consapevoli che il comportamento di scheduling dietro quei numeri è cambiato tra versioni di Ollama; il mio confronto dell’allocazione memoria in Ollama v0.12.1 mostra il nuovo scheduler che spinge alcuni modelli più verso la CPU su una scheda da 16 GB, quindi bloccare la versione che si è misurata.

Cache KV Quantizzata in Ollama

Ollama espone OLLAMA_KV_CACHE_TYPE con scelte f16, q8_0 e q4_0 nel suo FAQ attuale. La cache KV quantizzata richiede Flash Attention, che Ollama usa automaticamente su backend supportati o può essere richiesta 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. Il FAQ avverte che Q4_0 può produrre una perdita di qualità più evidente, specialmente a contesto più alto, quindi dovrebbe essere un fallback misurato piuttosto che un preset automatico da 16 GB.

Il Parallelismo di Ollama moltiplica il budget di contesto

Ollama documenta una regola particolarmente chiara: la memoria richiesta scala con OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH. Quattro richieste parallele a un setting 32K possono implicare un’allocazione di contesto aggregata da 128K per quel modello.

Per un agent personale su 16 GB, mantenere OLLAMA_NUM_PARALLEL=1 finché una sessione lunga non è stabile. Mettere in coda una seconda richiesta è di solito preferibile a spingere il primo modello parzialmente sulla CPU e rendere entrambe le richieste lente. Le meccaniche di code, 503 e scaricamento modello dietro quella scelta sono documentate in come Ollama gestisce le richieste parallele.

Offload CPU: Un’uscita di sicurezza valida 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. Pone anche la bandwidth PCIe e la latenza della memoria host nel percorso di decodifica, dove ogni token generato può pagare il costo. La corsia e le evidenze di generazione su quando PCIe morde davvero sono in Prestazioni LLM e Lane PCIe.

L’offload può essere sensato per lavoro batch occasionale, ma raramente è il default migliore per un agent coding interattivo. Prima confrontare una quantizzazione dei pesi più piccola, KV Q8, concorrenza ridotta e un tetto di contesto realistico; usare l’offload quando la capacità conta più della latenza.

Fare attenzione al cliff piuttosto che alla media. Un server può decodificare velocemente 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 a Finestra Scorrevole e KV Adattive

L’attenzione a finestra scorrevole cambia il budget ritenendo solo una finestra recente per layer selezionati. I modelli ibridi possono combinare quei layer con attenzione globale occasionale o stato ricorrente, rendendo un calcolo piatto a contesto pieno un sostanziale sovrastimare o dislocare la memoria.

L’ottimizzazione è parte dell’architettura del modello, non un interruttore generico che può essere applicato senza conseguenze. Un motore deve capire correttamente il pattern dei layer, le regole di espulsione, le posizioni e eventuali token globali.

Cosa cerca di migliorare la KV adattiva

I fork sperimentali vanno oltre scegliendo la precisione o il layout della cache per layer e profondità di contesto. L’obiettivo è attraente: preservare alta precisione dove conta, comprimere layer meno sensibili e cambiare la miscela prima che la pressione VRAM causi un riversamento duro — lo stesso riscontro di sensibilità-chiave-sopra-sensibilità-valore descritto sopra è esattamente il tipo di segnale che un allocatore adattivo vorrebbe sfruttare automaticamente invece di lasciarlo a tuning manuale --cache-type-k/--cache-type-v.

Un progetto downstream di agosto 2026, llama.cpp-adaptive-turboquant, riporta un selettore automatico per diversi modi layer-adattivi e pubblica test a profondità lunga su una RTX 5080 16 GB. Quei numeri sono risultati riportati dall’autore da un fork specializzato, non evidenza che llama.cpp upstream si comporti allo stesso modo.

Perché è ancora sperimentale

Il fork combina tipi di cache custom, kernel CUDA, percorsi specifici del modello e vincoli della toolchain. È molto più codice da fidarsi rispetto a cambiare lo storage cache upstream da F16 a Q8_0.

Usare un fork del genere solo quando upstream non può soddisfare un requisito reale e si può riprodurre qualità, stabilità e velocità sul proprio modello. Registrare il commit e la versione CUDA, perché un risultato attaccato 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à di contesto e lunghezza output. Misurare VRAM all’avvio, VRAM picco di prefill, velocità di elaborazione prompt, velocità di decodifica e un task di qualità che richiede effettivamente evidenze dalla parte più vecchia del contesto.

Non accettare un’allocazione riuscita come risultato completo. Una cache può stare a 128K e ancora perdere fatti iniziali, corrompere l’output in fondo alla sequenza o decodificare troppo lentamente per essere utile.

Una procedura di tuning 16 GB lavorata: una variabile alla volta

Il percorso 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.

flowchart TD A["Passo 1: carica a contesto 8K, una sequenza, registra VRAM warm-up"] --> B{"Pesi + runtime sotto circa 14,5 GiB?"} B -- "no" --> C["Scegli una quant o modello più piccolo, ripeti Passo 1"] C --> A B -- "yes" --> D["Passo 2: baseline qualità KV F16/BF16, salva output task"] D --> E["Passo 3: passa a KV Q8_0 / FP8 con Flash Attention"] E --> F["Passo 4: scala contesto su - 32K, 64K, 96K, 128K"] F --> G{"Fallimento durante prefill?"} G -- "yes" --> H["Passo 5: riduci batch / ubatch size"] H --> F G -- "no" --> I{"Fallimento solo con richieste concorrenti?"} I -- "yes" --> J["Passo 5: riduci slot paralleli / max-num-seqs"] J --> F I -- "no" --> K["Solo ora: misto Q8/Q4, Q4 completo, offload, fork adattivo"]

Passo 1: Stabilire il pavimento 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 i pesi e il runtime consumano già più di circa 14,5 a 15 GiB, il contesto lungo non ha un margine sano. Scegliere una quantizzazione dei pesi più piccola o un modello prima di tuning la cache.

Passo 2: Misurare KV F16 o BF16 come baseline di qualità

Eseguire il contesto più piccolo che supporta il test e mantenere la cache ad alta precisione di default. Salvare output da task di retrieval, editing codice, selezione tool e istruzioni lunghe.

Questa baseline dice se gli errori successivi vengono dalla quantizzazione della cache. Senza di essa, un problema di template di chat o un modello debole può essere facilmente attribuito alla KV Q4.

Passo 3: Passare a Q8 o FP8

Abilitare Flash Attention dove richiesto, selezionare Q8_0 in llama.cpp o Ollama, o un modo FP8 supportato in vLLM. Ripetere gli stessi prompt alle stesse profondità di token e confermare che il log mostri il tipo di cache previsto.

Per molte distribuzioni 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 nello 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 picco e se le evidenze vicino all’inizio possono ancora essere recuperate.

La decodifica a lungo contesto spesso rallenta anche dopo che la memoria sta perché l’attenzione legge più stato in cache. Capacità e prestazioni sono assi separati.

Passo 5: Tuning la memoria transitoria

Se il fallimento avviene durante il prefill piuttosto che all’inizializzazione, ridurre micro-batch o token massimi in batch. Se il fallimento avviene solo con richieste simultanee, ridurre la concorrenza di sequenza o gli slot paralleli.

Solo dopo che quei controlli sono compresi provare cache mista Q8/Q4, cache Q4 completa, offload CPU o un fork adattivo. Mantenere l’esecuzione upstream Q8 come baseline di confronto. Se si aggiunge successivamente decodifica speculativa o MTP, ricordare che i suoi buffer draft sono un’altra riga nell’equazione del budget, non velocità gratuita — la guida alla decodifica speculativa copre le meccaniche e il loro costo in VRAM, e il mio benchmark Qwen 3.6 27B e 35B MTP vs Standard mostra esattamente quanto contesto lo stato extra di una testa MTP può costare su una scheda da 16 GB.

Cosa registrare in un benchmark a lungo contesto

Un singolo valore di tokens/s nasconde proprio il problema che questo articolo cerca di risolvere. Il test a lungo contesto dovrebbe preservare abbastanza dettagli per un altro operatore da riprodurre il confine di memoria.

Campo Perché conta
GPU e VRAM utile Uso display e altri processi cambiano il budget
Versione o commit del motore Comportamento cache e flag evolvono rapidamente
Versione driver, CUDA, ROCm o Vulkan Determina comportamento backend e kernel
Modello esatto e quant dei 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 di profondità reale
Sequenze parallele Moltiplica o condivide la domanda di cache
Batch e micro-batch Influenza picchi di prefill e velocità
Velocità elaborazione prompt Espone usabilità di prefill lunghi
Velocità decodifica a ogni profondità Espone rallentamento da bandwidth cache
VRAM picco e offload CPU Distingue stare da riversamento
Risultato qualità a lungo contesto Rileva fallimenti di compressione o posizione

Usare campionamento nvidia-smi o tooling vendor equivalente durante entrambi prefill e decodifica. Il report di allocazione del motore è necessario, ma la memoria dispositivo picco durante un prompt reale è il numero che decide la stabilità.

Errori comuni della cache KV su GPU da 16 GB

Trattare il supporto 128K come una promessa hardware

Il campo contesto in una configurazione del modello è un tetto architetturale. Non dice nulla sulla memoria rimasta dopo il caricamento di una particolare quantizzazione su un particolare motore.

Calcolare la cache e verificare il runtime. Contesto di dimensioni marketing senza un budget VRAM è meramente 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 cache KV 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 l’attenzione paginata comprima i token

La paginazione migliora il comportamento di allocazione e condivisione. Non cambia la precisione dei tensori né rimuove lo stato KV richiesto da una sequenza unica.

Usare l’allocazione paginata per servire workload variabili efficientemente. Usare precisione della cache, architettura del modello, tetti di contesto e limiti di concorrenza per controllare la capacità.

Assumere che il prefix caching aiuti ogni prompt lungo

Il prefix caching salva il calcolo ripetuto di prefill quando le richieste condividono un prefisso esatto. Un dump di repository da 100K una tantum non riceve uno sconto magico di memoria solo perché il prefix caching è abilitato.

È un’ottimizzazione del workload, non un sostituto per l’equazione del budget. Misurare tasso di hit e pressione cache ritenuta 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 codice può degradare — e come la ricerca sulla divergenza di token sopra mostra, anche il setting “sicuro” Q8_0 non è garantito a riprodurre l’esatto output F16 sotto decodifica deterministica, solo a preservare l’accuratezza in aggregato.

Testare il task target alla profondità target. I benchmark di chat brevi sono quasi inutili per validare una cache a lungo contesto.

Lasciare il parallelismo su Auto

Un motore 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 fondamentalmente 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 16 GB raccomandati

Questi profili sono posizioni di partenza, non preset universali. Un modello con geometria KV insolita — un design MLA o ibrido a finestra scorrevole in particolare — può essere molto più economico o più costoso dell’esempio GQA convenzionale.

Agent Coding Interattivo

Usare una sequenza, contesto da 48K a 64K, cache Q8, Flash Attention e piena residenza dei pesi sulla GPU se possibile. Questo profilo favorisce latenza prevedibile e buona precisione della cache su un massimo impressionante ma raramente utile.

Abilitare il riuso del prefisso quando il motore lo supporta perché i turni di coding spesso condividono un grande prefisso di repository o conversazione. Compattare comunque l’output dei tool e i trascritti vecchi; l’ingegneria della cache non rende i token irrilevanti preziosi.

Analisi di Documenti Lonti

Usare un modello più piccolo con capacità da 64K a 128K, cache Q8 o FP8 calibrato, e caching del prefisso ripetuto quando più domande targettano lo stesso documento. Misurare il tempo al primo token perché il prefill può dominare anche quando la decodifica rimane accettabile.

Se verrà fatta solo una domanda, il retrieval o la summarization a blocchi possono essere più veloci e affidabili che forzare l’intero corpus attraverso una scheda da 16 GB. Il contesto lungo è uno strumento, non un sostituto per l’architettura dell’informazione.

Server Multi-Utente Piccolo

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 coda al riversamento incontrollato. Una politica di ammissione più lenta è meno dannosa che ogni richiesta che improvvisamente attraversa PCIe durante la decodifica.

Raccomandazione finale per contesto lungo su 16 GB

Per il contesto lungo su 16 GB, KV Q8 e una sequenza attiva sono la baseline giusta. Espongono il limite reale senza far fallire contemporaneamente la qualità della cache a bit bassi, l’allocazione parallela e la latenza di offload.

Calcolare dalla geometria di attenzione, sottrarre pesi e overhead del runtime, e poi confermare il risultato nei log del motore e nelle misure di memoria picco. Se 128K non sta ancora, un modello più piccolo è spesso l’ottimizzazione più pulita; se sta ma striscia, ridurre il contesto è spesso quella onesta.

Attenzione paginata, prefix caching, finestre scorrevoli e precisione adattiva risolvono tutti problemi utili ma diversi. Il setup vincente è quello che rimane sulla GPU, recupera correttamente le vecchie evidenze e sostiene una velocità di decodifica accettabile alla profondità di contesto che si usa effettivamente.

Riferimenti

Iscriviti

Ricevi nuovi articoli su sistemi, infrastruttura e ingegneria AI.