Skip to content

KV cache su disco: esattezza, ripristino e persistenza

Nel turno 2, il client invia l’intera conversazione ("Ciao" + "Ciao!" + "Come stai?"). Il server dovrebbe fare il prefill di tutti questi token da zero, anche se i primi due messaggi sono identici al turno 1.

Senza cache KV su disco, il server sprecherebbe tempo e potenza di calcolo. Con la cache, riconosce che il prefisso "Ciao" + "Ciao!" è già stato elaborato in precedenza, carica lo stato salvato e fa solo il prefill dei nuovi token ("Come stai?").

Il prompt ricevuto dal client viene renderizzato (cioè convertito nel formato che DeepSeek V4 si aspetta, con i tag speciali per i tool, ecc.) e poi trasformato in una stringa di testo (decodifica dei token). Di questa stringa viene calcolato l’SHA1.

Il nome del file cache è <sha1>.kv.

Perché SHA1 della stringa renderizzata e non dei token IDs?
Perché due prompt diversi possono tokenizzare in modo diverso a causa di spazi, maiuscole, ecc. La stringa renderizzata è più stabile e confrontabile. Inoltre, l’utente può ispezionare il contenuto del file .kv con hexdump e leggere il prompt originale.

Un file .kv è composto da:

  • Header fisso (48 byte) con magic KVC, versione, quant dei routed expert, motivo del salvataggio, flag, conteggi.
  • Testo renderizzato (UTF-8), preceduto dalla sua lunghezza (4 byte).
  • Payload DS4 (lo stato della sessione: token IDs, logits, tensori KV raw e compressi).
  • Mappa tool-id → DSML (opzionale, se il flag extension bit 0 è impostato).

L’header contiene campi importanti:

OffsetCampoSignificato
0-2magic KVCIdentifica il file come cache KV
4routed expert quant bits2 o 4; usato per evitare di mischiare quant diversi
8token countNumero di token nel checkpoint salvato
12hit countQuante volte questo checkpoint è stato riutilizzato (statistica)
16context sizeDimensione del contesto al momento del salvataggio
24creation timeUnix timestamp
32last-used timeUnix timestamp
40payload byte countDimensione del payload DS4

Il payload inizia con un header di 13 interi (magic DSV4, versione, dimensioni) e poi contiene:

  • Token IDs del checkpoint (sequenza di u32)
  • Logits del token successivo (array di float32 di lunghezza vocab_size)
  • Tensori KV raw (per la finestra scorrevole recente)
  • Tensori KV compressi (per la cache compressa 4:1)
  • Tensori dell’indexer (per il meccanismo di compressione)

Importante: I logits vengono salvati insieme al checkpoint. Quando si ripristina la cache, il server ha già pronta la distribuzione di probabilità per il token successivo, senza dover eseguire un passo aggiuntivo di inferenza.

Nel campo save reason (offset 5 dell’header) vengono codificate quattro situazioni:

ValoreNomeQuando avviene
1coldDopo il primo prefill di un prompt lungo (di solito dopo la prima richiesta)
2continuedQuando la generazione o il prefill raggiunge una frontiera assoluta (es. ogni 10k token)
3evictPrima che una nuova sessione sostituisca quella corrente in memoria
4shutdownQuando il server termina pulitamente

I salvataggi cold sono i più importanti: definiscono un checkpoint di base su cui possono innestarsi i successivi. I salvataggi continued creano checkpoint intermedi durante conversazioni molto lunghe, così se il server si interrompe si può ripartire da uno stato recente.

Per evitare problemi di confini di tokenizzazione (BPE), i checkpoint non vengono salvati a qualsiasi posizione, ma allineati a multipli di --kv-cache-boundary-align-tokens (default 2048) e troncati di --kv-cache-boundary-trim-tokens (default 32) token dalla fine.

Esempio: se la conversazione ha 10500 token, viene salvato un checkpoint a 10240 token (il multiplo di 2048 inferiore) meno 32 token di trim = 10208 token. In questo modo, se un client aggiunge qualche parola, la nuova stringa renderizzata avrà buona probabilità di avere come prefisso esatto il checkpoint salvato.

Ecco i parametri più utili da passare a ds4-server:

Terminal window
./ds4-server \
--kv-disk-dir /tmp/ds4-kv \
--kv-disk-space-mb 8192 \
--kv-cache-min-tokens 512 \
--kv-cache-cold-max-tokens 30000 \
--kv-cache-continued-interval-tokens 10000 \
--kv-cache-boundary-trim-tokens 32 \
--kv-cache-boundary-align-tokens 2048

Parametri aggiuntivi:

  • --tool-memory-max-ids N (default 100000) – controlla la dimensione massima della mappa di replay DSML per i tool call.
  • --kv-cache-reject-different-quant – di default i checkpoint sono riutilizzabili tra quant diversi (es. q2 e q4). Con questo flag, li accetta solo se il quant corrisponde esattamente.

Come accennato nel capitolo 2, ds4 conserva una mappa che lega l’ID univoco di una tool call al blocco DSML esatto che il modello ha generato. Questa mappa viene salvata dentro il file .kv (nella sezione opzionale KTM). Quando un checkpoint viene ripristinato, anche la mappa viene ricaricata in memoria.

In questo modo, anche dopo un riavvio del server, se un client rimanda indietro lo stesso ID di tool call, il server può rigenerare il DSML originale, mantenendo la coerenza della cache KV.

Formato della sezione tool-id map:

magic "KTM" (3 byte)
version 1 (1 byte)
entry count (4 byte)
per ogni entry:
- tool id byte length (4 byte)
- sampled DSML byte length (4 byte)
- tool id (bytes)
- sampled DSML (bytes)
  1. Avvi il server con --kv-disk-dir /tmp/ds4-kv.
  2. Un client (es. Codex CLI) invia una richiesta con un prompt di 5000 token.
  3. Il server esegue il prefill (tempo: qualche secondo) e poi risponde.
  4. Terminata la risposta, il server fa un cold save: crea un file /tmp/ds4-kv/<sha1>.kv con lo stato dopo i primi 5000 token (allineati e trimmati).
  5. Il client invia una seconda richiesta con la conversazione estesa (5000 token vecchi + 200 nuovi).
  6. Il server calcola lo SHA1 del prompt renderizzato. I primi 5000 token corrispondono esattamente al checkpoint salvato? Quasi sempre sì (grazie al trim e all’allineamento). Quindi carica il file .kv e ripristina lo stato.
  7. Ora deve solo fare il prefill dei 200 token nuovi e poi generare la risposta. Il tempo di prefill è ridotto a frazioni di secondo.
  8. Durante la generazione, se si superano i 10000 token totali, fa un continued save (un secondo checkpoint più avanti).
  9. Se il server viene riavviato, il file .kv esiste ancora. La prossima volta che un client invia lo stesso prompt (ad esempio un agente che ricostruisce la conversazione), il server lo troverà e ripartirà istantaneamente.
  • Cache solo per il server, non per il CLI: Il CLI ds4 non usa la cache su disco (ma può salvare le sessioni con /save nel caso dell’agente nativo, che è un meccanismo diverso).
  • Una sola sessione attiva in memoria: Il server tiene una sola conversazione attiva alla volta. Quando arriva una richiesta con un prompt diverso, la sessione corrente viene salvata su disco (evict) e sostituita. Questo limita il parallelismo ma semplifica la gestione della memoria.
  • I checkpoint cold sono troncati: Come detto, vengono sempre tagliati di --kv-cache-boundary-trim-tokens token per evitare problemi di confine. Questo significa che il checkpoint non include l’ultimo pezzo della conversazione. Va bene così perché quel pezzo verrà ricalcolato come nuovo prefill.
  • Compatibilità tra versioni di ds4: Il formato .kv non è ancora stabile. Aggiornamenti di ds4 potrebbero rendere i vecchi file incompatibili. Se noti comportamenti strani, cancella la directory della cache.

Puoi usare hexdump o un editor esadecimale per guardare dentro un file .kv:

Terminal window
hexdump -C /tmp/ds4-kv/*.kv | head -80

Vedrai l’header, poi il testo renderizzato (leggibile), poi i dati binari del payload DS4. La sezione tool-id map è opzionale.

Per capire perché un checkpoint non viene riutilizzato, avvia il server con --trace /tmp/trace.txt. Nel trace troverai righe come:

KV cache: miss for sha1=abc123 (reason: prefix mismatch at byte 1234)
KV cache: hit for sha1=def456 (loaded 10208 tokens)

La cache KV su disco è una delle funzionalità più utili di ds4-server per chi usa agenti di coding. Trasforma richieste che richiederebbero decine di secondi di prefill in operazioni quasi istantanee, specialmente quando la conversazione è lunga.

Consiglio pratico: Abilitala sempre con una directory dedicata (es. --kv-disk-dir /tmp/ds4-kv). Imposta --kv-disk-space-mb in base allo spazio che vuoi dedicare (8 GB sono un buon inizio). Se lavori con molti progetti diversi, la cache crescerà, ma l’eviction automatica dei file meno recenti la terrà sotto controllo.

💡 La cache persiste anche tra riavvii del server
A differenza della cache in memoria, i file .kv restano su disco. Se riavvii ds4-server con la stessa directory, le conversazioni già elaborate ripartiranno velocemente. Utile per ambienti di sviluppo dove il server viene riavviato spesso.