Lab 05-02 · Offload ibrido CPU+GPU¶
Obiettivo¶
Prendere un modello più grande della VRAM disponibile, trovare sperimentalmente il valore di -ngl che dà la
generazione più veloce, e poi recuperare VRAM con la KV cache quantizzata e (per un MoE) con gli esperti su CPU.
Confronterai infine la tua scelta con quella automatica di llama fit-params.
Prerequisiti¶
- Un binario con backend GPU (
llama cli --list-devicesdeve elencare almeno un dispositivo: CUDA, Vulkan, Metal, HIP…). - Almeno 16 GB di RAM di sistema e 6-8 GB di VRAM (con più VRAM scegli un modello più grande: l'esercizio ha senso solo se il modello non entra tutto).
- Lab 05-01 completato (sai leggere l'uscita di
llama bench).
Materiale¶
| Cosa | Fonte | Dimensione | Note |
|---|---|---|---|
| Modello denso | ggml-org/gemma-3-12b-it-GGUF → gemma-3-12b-it-Q4_K_M.gguf (licenza Gemma) |
6,96 GB | 48 layer; non entra in 8 GB con contesto e buffer |
| Modello MoE (variante) | ggml-org/Qwen3-30B-A3B-GGUF → Qwen3-30B-A3B-Q4_K_M.gguf (Apache 2.0) |
17,7 GB | 30 miliardi di parametri, 3 attivi per token: perfetto per -ncmoe |
| Script | scan-ngl.sh in questa cartella |
esegue llama bench su una lista di -ngl e stampa una tabella |
llama download -hf ggml-org/gemma-3-12b-it-GGUF:Q4_K_M --no-mmproj
G=$(ls ~/.cache/huggingface/hub/models--ggml-org--gemma-3-12b-it-GGUF/snapshots/*/gemma-3-12b-it-Q4_K_M.gguf)
Procedura¶
-
Quanti layer ha il modello e quanto pesa un layer? Avvia con log a livello info e cerca
n_layer:Con 48 layer e 6,96 GB, ogni layer pesa in media ~140 MB (l'embedding e l'output pesano di più). Annota anche la riga
load_tensors: offloaded N/49 layers to GPU: con-ngl auto(default) è la funzione di fit a scegliere N. -
Che cosa ha deciso il fit? Prima di misurare, chiedi la stima:
Stampa una riga tipo
-c 8192 -ngl 37: annotala, è il valore da battere. -
Scansione di
-ngl. Lo script prende il modello, la lista di valori e il contesto; disattiva il fit (--fit offnon esiste inllama bench, che usa-nglesattamente come indicato) e usa-fa on:Output atteso (valori indicativi per una GPU da 8 GB; i tuoi saranno diversi):
ngl test t/s ± dev 0 pp512 45.10 0.80 0 tg128 4.90 0.05 16 pp512 120.30 1.10 16 tg128 7.80 0.10 … 40 pp512 390.00 3.00 40 tg128 19.50 0.30 44 pp512 (errore: out of memory)Il valore di
-nglpiù alto che non va in out-of-memory è il tuo candidato. Se i test grandi vanno in OOM, la tabella si interrompe: rilancia con una lista più fitta intorno all'ultimo valore riuscito (es."38,39,40,41,42"). -
Recupera VRAM con la KV cache quantizzata. Ripeti i valori intorno al limite con
-ctk q8_0 -ctv q8_0:Con 8192 token di contesto la cache f16 di Gemma 3 12B occupa circa 1,5 GB; in q8_0 ne libera metà, spesso abbastanza per 2-4 layer in più. Se arrivi a
-ngl 48(tutto in VRAM) vedrai il "salto" di tg descritto nel modulo. -
Confronto con la scelta automatica. Avvia il server con i parametri migliori trovati e con quelli del fit e confronta
predicted_per_secondnel campotimingsdi una richiesta (modulo 04):llama serve -m "$G" -ngl 44 -c 8192 -fa on -ctk q8_0 -ctv q8_0 --port 18104 curl -s localhost:18104/v1/chat/completions -H 'content-type: application/json' \ -d '{"messages":[{"role":"user","content":"Descrivi Roma in 200 parole."}],"max_tokens":200}' | python3 -c "import json,sys; print(json.load(sys.stdin)['timings'])"Alla chiusura del server (Ctrl+C) leggi
llama_memory_breakdown_print: per il dispositivo GPU veditotal,free,model,context,compute. La colonnafreedice quanto margine hai lasciato. -
Variante MoE. Con Qwen3-30B-A3B (17,7 GB) nessun valore di
-nglentra in 8 GB con tutti i tensori. Prova invece tutti i layer in VRAM ma gli esperti su CPU:Q=$(ls ~/.cache/huggingface/hub/models--ggml-org--Qwen3-30B-A3B-GGUF/snapshots/*/Qwen3-30B-A3B-Q4_K_M.gguf) llama fit-params -m "$Q" -c 8192 # (1)! llama bench -m "$Q" -ngl 99 -ncmoe 48,40,32,24 -p 512 -n 128 -r 2 --progress- Stampa
-ngl 48 -ot blk\.NN\.ffn_(up|down|gate)_(ch|)exps=CPU,…: è la forma "esplicita" di-ncmoe.
-ncmoe Ntiene su CPU gli esperti dei primi N layer: più basso è N, più esperti stanno in VRAM e più veloce è la generazione, finché non finisce la memoria. Cerca l'N minimo che non va in OOM. - Stampa
Verifica del risultato¶
- Hai una tabella t/s vs
-nglcon almeno 6 punti e sai indicare il valore massimo che entra in VRAM. - Con la KV cache q8_0 sei riuscito ad aumentare
-ngldi almeno un layer (o hai capito perché no). - Il valore di
-nglche hai trovato è ≥ di quello proposto dallama fit-paramscon margine di 1024 MiB (se è molto più alto, il fit sta lasciando margine: prova--fit-target 512). - Per il MoE: hai trovato l'
-ncmoeminimo e la tg corrispondente.
Domande e varianti¶
- Perché tra
-ngl 44e-ngl 48la tg può raddoppiare mentre tra 0 e 16 cresce solo del 60 %? - Che succede a pp e tg se lasci
-ngl 48ma disattivi l'offload della KV cache con-nkvo? In quali casi ha senso? - Ripeti la scansione con
-c 32768: quanto scende il massimo-nglammissibile? Calcola prima la dimensione della KV cache a mano (formula nel modulo). - Con due GPU:
-ts 3,1contro il default proporzionale alla memoria; e-sm tensor -fa on -ctk f16 -ctv f16(solo CUDA, modello denso). - Senza GPU puoi comunque fare i passi 1, 2 e leggere
llama_memory_breakdown_print: l'uscita difit-paramssarà-c <contesto del modello> -ngl -1.
Consegna¶
La tabella del passo 3 (o il CSV risultati.csv prodotto dallo script), la riga di llama fit-params, il timings
del passo 5 e una relazione di 10 righe: valore di -ngl scelto, con quali opzioni di KV cache, quanto margine di VRAM
hai lasciato e perché.