Da Ubuntu nudo a LLM locale su una GPU NVIDIA da 16 GB
Da Ubuntu nudo a inferenza LLM locale su GPU NVIDIA da 16 GB: driver, CUDA, llama.cpp con offload, modelli che entrano e benchmark reali.
Ambiente: Ubuntu 24.04.5 LTS · kernel 7.0.0-34-generic · NVIDIA 580.178.04 · CUDA 13.2.78 · cmake 3.28.3 · gcc 13.3.0 · llama.cpp 0.5.0-dev (fcc8915) · RTX 3080 Ti Laptop 16 GB
Prerequisiti
- Ubuntu 24.04 LTS installato, con accesso
sudo. I comandi valgono anche su 22.04, ma i pacchetti sono verificati su 24.04. - Una GPU NVIDIA con 16 GB di VRAM. I numeri di questa guida vengono da una RTX 3080 Ti Laptop, ma l'architettura è la stessa per 4080, 4070 Ti Super, 4060 Ti 16 GB e 3090.
- Circa 25 GB liberi su disco: CUDA pesa qualche GB, i due modelli di prova altri 17 GB.
- Connessione a internet per driver, toolkit e modelli.
1. Prepara il sistema e la toolchain di build
llama.cpp si compila, quindi servono compilatore, cmake e git. Su una Ubuntu appena installata non ci sono.
sudo apt update
sudo apt install -y build-essential cmake git curl wget pkg-configSe ha funzionato, cmake e gcc rispondono con le versioni che userai per la build:
cmake --version | head -1
gcc --version | head -1Output atteso:
cmake version 3.28.3
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.02. Installa il driver NVIDIA e verifica la GPU
Prima del driver, nvidia-smi non esiste o non vede la scheda. Ubuntu impacchetta i driver nel repository graphics-drivers; il branch 580 è quello su cui sono state misurate le prestazioni di questa guida.
ubuntu-drivers devices
sudo ubuntu-drivers install nvidia:580
sudo rebootubuntu-drivers devices elenca i branch disponibili per la tua scheda e marca quello consigliato. Oggi potrebbe consigliare un branch più recente (qui suggerisce il 595-open): va bene anche quello, ma i numeri che leggerai più avanti sono del 580. Se vuoi la stessa combinazione, installa il pacchetto esatto:
sudo apt install -y nvidia-driver-580-open
sudo rebootDopo il riavvio, la verifica è una sola:
nvidia-smiDevi vedere la scheda, il driver e la memoria totale. Sulla macchina di prova:
Fri Sep 25 20:08:12 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.178.04 Driver Version: 580.178.04 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 3080 ... Off | 00000000:01:00.0 Off | N/A |
| N/A 53C P8 12W / 90W | 223MiB / 16384MiB | 0% Default |
+-----------------------------------------+------------------------+----------------------+Il numero che conta è 16384MiB: sono i tuoi 16 GB. Se nvidia-smi restituisce "command not found", il driver non è installato. Se restituisce un errore di comunicazione con il driver, spesso è il Secure Boot.
3. Installa CUDA Toolkit 13.2
Per compilare llama.cpp con il backend CUDA serve nvcc, che non arriva con il driver. NVIDIA distribuisce il toolkit via repository APT. Il pacchetto cuda-toolkit-13-2 è presente nel repo ubuntu2404 e la chiave cuda-keyring è aggiornata.
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install -y cuda-toolkit-13-2Il pacchetto è grosso, qualche GB. Alla fine aggiungi nvcc al PATH e verifica:
echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
nvcc --versionOutput atteso:
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2026 NVIDIA Corporation
Built on Thu_Mar_19_11:12:51_PM_PDT_2026
Cuda compilation tools, release 13.2, V13.2.78
Build cuda_13.2.r13.2/compiler.37668154_0Il driver 580.178.04 dichiara come runtime massimo CUDA 13.0, mentre il toolkit installato è 13.2. Non è un errore: la compatibilità tra minor version di CUDA permette di compilare ed eseguire con un driver un po' più vecchio del toolkit. Questa combinazione è stata testata e funziona.
4. Compila llama.cpp con il backend CUDA
Il binario che molte distribuzioni installano non ha CUDA dentro. Serve la build da sorgente con -DGGML_CUDA=ON.
cd ~
git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp
cmake -B build -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j$(nproc)Durante la configure devi vedere che cmake trova il toolkit e la tua architettura:
-- Found CUDAToolkit: /usr/local/cuda/targets/x86_64-linux/include (found version "13.2.78")
-- Using CMAKE_CUDA_ARCHITECTURES=86-real CMAKE_CUDA_ARCHITECTURES_NATIVE=86-real
-- FlashAttention K-V type combinations: f16-f16;q4_0-q4_0;q8_0-q8_0;bf16-bf16
-- Warning: NCCL not found, performance for multiple CUDA GPUs will be suboptimal
-- ggml version: 0.25.3
-- ggml commit: fcc8915Il warning su NCCL è normale con una sola GPU: NCCL serve solo per il multi-GPU. La build finisce con [100%] Built target llama-cli e [100%] Built target llama-server. A quel punto controlla che la GPU sia vista dal binario appena compilato:
./build/bin/llama-cli --list-devices
./build/bin/llama-cli --versionOutput atteso:
Available devices:
CUDA0: NVIDIA GeForce RTX 3080 Ti Laptop GPU (15982 MiB, 15566 MiB free)
version: 0.5.0-dev (build 1, commit fcc8915)
built with GNU 13.3.0 for Linux x86_64Se --list-devices mostra solo CPU, la build non ha preso CUDA: cancella build/ e riparti, controllando che nvcc sia nel PATH.
5. Scarica i modelli che entrano in 16 GB
Qui sta il vincolo vero. Non basta che i pesi entrino nella VRAM: ci deve stare anche la KV cache, che cresce con il contesto e con il numero di layer di attenzione. Un 8B denso in Q4 pesa meno di 5 GB e ti lascia spazio per contesti lunghi; un 20B MoE in MXFP4 ne pesa 12 e ti porta vicino al tetto; un 24B denso in Q4 sfora.
mkdir -p ~/models && cd ~/models
curl -L -o Qwen3-8B-Q4_K_M.gguf \
"https://huggingface.co/Qwen/Qwen3-8B-GGUF/resolve/main/Qwen3-8B-Q4_K_M.gguf"
curl -L -o gpt-oss-20b-MXFP4.gguf \
"https://huggingface.co/ggml-org/gpt-oss-20b-GGUF/resolve/main/gpt-oss-20b-MXFP4.gguf"Al termine ls -lh deve mostrare queste dimensioni:
-rw-rw-r-- 1 adriano adriano 4,7G set 25 20:59 Qwen3-8B-Q4_K_M.gguf
-rw-rw-r-- 1 adriano adriano 12G set 25 21:05 gpt-oss-20b-MXFP4.ggufIl primo è il modello di lavoro comodo: 4,68 GiB di pesi, contesto nativo 32.768 token. Il secondo è il caso interessante: 11,27 GiB di pesi, un MoE da 20B che sta in 16 GB e con meno layer di attenzione del denso, quindi consuma molta meno KV cache per token di contesto.
6. Avvia il server con l'offload sulla GPU
llama-server espone un'API compatibile con OpenAI e un'interfaccia web. -ngl 99 dice di caricare tutti i layer sulla GPU, cioè l'offload completo; -np 1 tiene un solo slot, perché con più slot paralleli sprechi memoria; -c è la dimensione del contesto.
cd ~/llama.cpp
./build/bin/llama-server \
-m ~/models/gpt-oss-20b-MXFP4.gguf \
-ngl 99 -c 65536 -np 1 \
--host 127.0.0.1 --port 8080In pochi secondi il log conferma il caricamento e la messa in ascolto:
0.00.036.645 I srv load_model: loading model 'gpt-oss-20b-MXFP4.gguf'
0.03.999.347 I srv load_model: initializing, n_slots = 1, n_ctx_slot = 65536, kv_unified = 'false'
0.04.002.488 I srv llama_server: model loaded
0.04.002.499 I srv llama_server: listening on http://127.0.0.1:8080Verifica da un altro terminale:
curl -s http://127.0.0.1:8080/healthDeve rispondere {"status":"ok"}. A questo punto parla col modello:
curl -s http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"Rispondi in una frase: quanti GB di VRAM ha una RTX 4080?"}],"max_tokens":1024}'La risposta è un JSON con la frase in content e le velocità in timings. I valori di questo test:
content: "La RTX 4080 è dotata di 16 GB di VRAM GDDR6X."
finish_reason: stop
predicted_per_second: 97.51
prompt_per_second: 437.23Se usi Qwen3-8B
Per il denso cambia solo il modello. Con -fa on -ctk q8_0 -ctv q8_0 comprimi la KV cache e recuperi memoria:
./build/bin/llama-server \
-m ~/models/Qwen3-8B-Q4_K_M.gguf \
-ngl 99 -np 1 -c 32768 -fa on -ctk q8_0 -ctv q8_0 \
--host 127.0.0.1 --port 80807. Misura con llama-bench e trova il tetto di memoria
llama-bench dà numeri riproducibili: pp512 è la velocità di lavorazione del prompt, tg128 quella di generazione token per token. La generazione è quella che senti mentre il testo scorre.
cd ~/llama.cpp
./build/bin/llama-bench -m ~/models/Qwen3-8B-Q4_K_M.gguf -ngl 99 -p 512 -n 128 -r 3
./build/bin/llama-bench -m ~/models/gpt-oss-20b-MXFP4.gguf -ngl 99 -p 512 -n 128 -r 3Output reale sulla RTX 3080 Ti Laptop:
| model | size | params | backend | ngl | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | --------------: | -------------------: |
| qwen3 8B Q4_K - Medium | 4.68 GiB | 8.19 B | CUDA | 99 | pp512 | 2081.05 ± 104.72 |
| qwen3 8B Q4_K - Medium | 4.68 GiB | 8.19 B | CUDA | 99 | tg128 | 47.98 ± 0.97 |
| model | size | params | backend | ngl | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | --------------: | -------------------: |
| gpt-oss 20B MXFP4 MoE | 11.27 GiB | 20.91 B | CUDA | 99 | pp512 | 2260.99 ± 15.80 |
| gpt-oss 20B MXFP4 MoE | 11.27 GiB | 20.91 B | CUDA | 99 | tg128 | 90.64 ± 1.42 |
build: fcc8915 (1)Il MoE da 20B genera più veloce del denso da 8B: attiva solo una frazione dei parametri per token. Lo stesso test con -ngl 0, cioè solo CPU, mostra perché l'offload non è opzionale:
| Modello | Backend | pp512 (t/s) | tg128 (t/s) |
|---|---|---|---|
| Qwen3-8B Q4_K_M | CUDA, -ngl 99 | 2081,05 | 47,98 |
| Qwen3-8B Q4_K_M | CPU, -ngl 0 | 478,65 | 6,12 |
| gpt-oss-20b MXFP4 | CUDA, -ngl 99 | 2260,99 | 90,64 |
| gpt-oss-20b MXFP4 | CPU, -ngl 0 | 498,86 | 19,12 |
Quanta VRAM resta per il contesto dipende dal modello. Queste sono le configurazioni testate, con nvidia-smi --query-gpu=memory.used --format=csv,noheader:
| Modello | Comando | VRAM usata | Esito |
|---|---|---|---|
| gpt-oss-20b | -ngl 99 -c 32768 | 12308 MiB | OK |
| gpt-oss-20b | -ngl 99 -np 1 -c 65536 | 13102 MiB | OK |
| gpt-oss-20b | -ngl 99 -np 1 -c 131072 -fa on -ctk q8_0 -ctv q8_0 | 13454 MiB | OK |
| Qwen3-8B | -ngl 99 -np 1 -c 32768 | 9640 MiB | OK |
| Qwen3-8B | -ngl 99 -np 1 -c 65536 -fa on -ctk q8_0 -ctv q8_0 | 10154 MiB | OK (contesto reale 40960) |
| Qwen3-8B | -ngl 99 -np 1 -c 65536 (KV f16) | 14280 MiB | OK (contesto reale 40960) |
Su Qwen3-8B la KV cache in f16 mangia 4 GB in più a 40960 token; con -ctk q8_0 -ctv q8_0 scendi sotto gli 11 GB e la velocità perde poco (46,05 contro 47,98 t/s). Su gpt-oss-20b il risparmio c'è ma è minore, perché i layer di attenzione sono pochi.
8. Ollama e vLLM: quando lasciare llama.cpp
llama.cpp è la scelta giusta per una singola macchina, un utente e il controllo totale dei flag. Due alternative coprono casi diversi.
Ollama incapsula llama.cpp e gestisce da solo download, offload e swap dei modelli. È già installato qui e si usa in un comando:
ollama run gpt-oss:20bPerdi il controllo fine su -ngl, -c e KV cache, ma se il tuo problema è "voglio chattare senza pensare ai flag", è la strada più corta.
vLLM è un altro mestiere: serve più richieste in parallelo con throughput alto, usa PagedAttention e batching continuo. Si installa con pip install vllm ed espone un server OpenAI-compatible sulla porta 8000. In cambio pretende uno stack Python, compila i suoi kernel CUDA e su una GPU da 16 GB con un modello vicino al tetto lascia meno margine di llama.cpp. Se ti serve un endpoint per un'applicazione multiutente, vale; per inferenza personale, no.
Verifica
Il setup è a posto quando tutti questi comandi danno il risultato atteso.
# 1. La GPU è visibile e il driver è caricato
nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader
# 2. nvcc c'è e ha la versione giusta
nvcc --version | tail -2
# 3. llama.cpp vede la GPU
~/llama.cpp/build/bin/llama-cli --list-devices
# 4. Il modello gira sulla GPU, non sulla CPU
~/llama.cpp/build/bin/llama-bench \
-m ~/models/gpt-oss-20b-MXFP4.gguf -ngl 99 -p 512 -n 128 -r 1
# 5. Il server risponde
curl -s http://127.0.0.1:8080/healthSe il punto 4 restituisce una tg128 intorno ai 90 t/s hai la GPU in uso. Se restituisce valori a una cifra, l'offload non è attivo: manca -ngl 99 o la build è senza CUDA.
Limiti noti
- Questa guida non copre il multi-GPU. Il warning su NCCL in fase di build è la spia che il supporto non è configurato.
- Mistral Small 24B in Q4_K_M pesa 14,33 GB: non è stato misurato perché con la KV cache non entra in 16 GB con un contesto utile. Per un denso di quella taglia serve il downclock o l'offload parziale di alcuni layer in RAM, che qui non è stato testato.
- Il contesto oltre 40960 su Qwen3 richiede YaRN, non testato.
- I numeri valgono per questa GPU. Una 4060 Ti 16 GB ha la stessa VRAM ma meno banda di memoria, quindi si aspetta una
tg128più bassa a parità di modello.