Dodici build

Inferenza locale su una GPU passthrough con un endpoint compatibile OpenAI

Driver, runtime e modello su una G-L40S, serviti dietro TLS con una chiave API, e un test di carico che riporta token reali al secondo invece di una diapositiva del venditore.

Cosa si realizza

Una istanza GPU che serve un modello open-weights tramite un'API HTTP compatibile con lo schema OpenAI, così ogni libreria client che già possiedi funziona cambiando solo l'URL di base. Dietro nginx, con TLS, una chiave API e un'unità systemd che sopravvive ai riavvii.

Scritto su una G-L40S: quarantotto gigabyte di VRAM su una singola scheda, passata all'istanza via PCIe anziché essere suddivisa nel tempo tra più tenant. Questa differenza è ciò che separa una latenza prevedibile dall'attesa dietro il job di addestramento di qualcun altro. La scheda è tua finché non la cancelli.

Prima di iniziare

  • Una G-L40S con Debian 13. La G-ADA da venti gigabyte va bene per modelli più piccoli; l'aritmetica al punto quattro ti dice quali.
  • Due terabyte di NVMe su questo piano, che è il posto giusto per i pesi. Scaricare quaranta gigabyte due volte perché la cache era sul filesystem di root è un modo fastidioso di spendere un'ora.
  • Un hostname che punta all'istanza, llm.example.com sotto.

1. Driver

sed -i "s/ main$/ main contrib non-free-firmware non-free/" /etc/apt/sources.list.d/debian.sources
apt update
apt install -y nvidia-driver firmware-misc-nonfree build-essential
reboot

Dopo che torna:

nvidia-smi

Vuoi la scheda elencata, il driver stampato e l'uso di memoria vicino a zero. Una scheda mancante a questo punto è quasi sempre un initramfs obsoleto; update-initramfs -u e riavvia un'altra volta prima di aprire un ticket.

nvidia-smi -q -d PERFORMANCE | grep -A3 "Clocks Event Reasons"
nvidia-smi -pm 1

La modalità di persistenza tiene il driver caricato tra i processi, il che rimuove alcuni secondi di inizializzazione a ogni riavvio.

2. Runtime

apt install -y python3-venv python3-pip
install -d -o root -g root /srv/models /opt/vllm
python3 -m venv /opt/vllm/venv
/opt/vllm/venv/bin/pip install --upgrade pip
/opt/vllm/venv/bin/pip install vllm

L'installazione scarica un ampio set di wheel CUDA e richiede tempo. Nel frattempo, decidi un modello.

3. Scegliere cosa entra

I pesi sono il minimo, non il budget. Un modello a precisione a 16 bit richiede circa due gigabyte di VRAM per miliardo di parametri, più la cache key-value per ogni richiesta concorrente, più un gigabyte o due di overhead runtime.

SchedaPesi a 16 bitDimensione realistica del modelloRimasto per la cache
L40S, 48 GB2 GB per miliardoFino a circa 20 miliardi6 a 8 GB
L40S, 48 GB, 8 bit1 GB per miliardoFino a circa 34 miliardi10 GB
RTX 4000 Ada, 20 GB2 GB per miliardoFino a circa 7 miliardi5 GB
Due × L40S, 96 GB2 GB per miliardoFino a circa 40 miliardi12 GB

La colonna della cache è ciò che determina quante persone possono usare l'endpoint contemporaneamente. Riempi la scheda fino all'orlo di pesi e avrai costruito un giocattolo per utente singolo molto veloce.

4. Servire il modello

Punta la cache sull'NVMe e avvia il server. Sostituisci l'identificatore del repository del modello open-weights che hai scelto; il runtime lo scarica al primo avvio.

cat > /etc/systemd/system/vllm.service <<EOF
[Unit]
Description=vLLM inference server
After=network-online.target

[Service]
Environment=HF_HOME=/srv/models
Environment=VLLM_API_KEY=<a long random string>
ExecStart=/opt/vllm/venv/bin/vllm serve <org>/<model> \\
  --host 127.0.0.1 --port 8000 \\
  --served-model-name local \\
  --max-model-len 16384 \\
  --gpu-memory-utilization 0.92 \\
  --max-num-seqs 32
Restart=on-failure
RestartSec=10
TimeoutStartSec=1800

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable --now vllm
journalctl -fu vllm

Il bind su loopback è voluto. Il controllo della chiave API nel runtime è un singolo segreto condiviso senza limitazione di frequenza dietro, quindi nginx gestisce l'esposizione e il runtime non tocca mai l'interfaccia pubblica.

Il timeout di avvio generoso esiste perché il primo avvio scarica decine di gigabyte e poi compila i kernel. Gli avvii successivi richiedono meno di un minuto.

5. Proxy e TLS

server {
  listen 443 ssl;
  listen [::]:443 ssl;
  http2 on;
  server_name llm.example.com;

  ssl_certificate     /etc/letsencrypt/live/llm.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/llm.example.com/privkey.pem;

  location /v1/ {
    proxy_pass http://127.0.0.1:8000;
    proxy_buffering off;
    proxy_read_timeout 600s;
    proxy_set_header Host $host;
  }
}

proxy_buffering off è la riga che fa sì che le risposte in streaming scorrano. Lasciala attiva e i token arrivano in batch ordinati quando nginx ritiene che un buffer sia pieno, il che sembra esattamente un modello lento e non lo è.

Verifica

Inizia con la scheda:

nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu --format=csv

La memoria usata dovrebbe essere circa il novanta percento del totale, perché è ciò che --gpu-memory-utilization ha richiesto. Poi l'endpoint:

curl -s https://llm.example.com/v1/models -H "Authorization: Bearer <the key>" | head -20

Un modello, chiamato local. Ora una completazione vera, e cronometrala:

time curl -s https://llm.example.com/v1/chat/completions \
  -H "Authorization: Bearer <the key>" \
  -H "Content-Type: application/json" \
  -d '{"model":"local","messages":[{"role":"user","content":"List three prime numbers."}],"max_tokens":64}'

Infine, misuralo sotto carico anziché una richiesta alla volta, che è l'unico numero che vale la pena citare:

/opt/vllm/venv/bin/vllm bench serve \
  --backend openai-chat \
  --base-url https://llm.example.com \
  --endpoint /v1/chat/completions \
  --model local --num-prompts 200 --request-rate 8

Leggi tre numeri dall'output: token di output al secondo in aggregato, mediana del tempo al primo token e il 99° percentile end-to-end. Su una singola L40S con un modello nella fascia da dieci a venti miliardi, una velocità di output aggregata nell'ordine delle centinaia di token al secondo e un tempo al primo token ben sotto il secondo sono la forma attesa. Un tempo al primo token che cresce con la concorrenza significa che la tua cache key-value è troppo piccola, quindi riduci --max-model-len o --max-num-seqs ed esegui di nuovo.

Dopo

Controlla temperatura e throttling della frequenza per la prima settimana con nvidia-smi dmon, poiché una scheda che si termal-throttola sotto carico sostenuto produce esattamente il tipo di lentezza intermittente che viene attribuita alla rete. Per quanto riguarda la fatturazione, ricorda che un'istanza GPU costa lo stesso sia che la scheda sia occupata che inattiva, quindi processa il lavoro in batch piuttosto che lasciarla calda. I prezzi sono su la pagina GPU.

Pronto quando lo sei

Scegli una città. Scegli una dimensione. Paga in criptovaluta.

Nessun modulo su chi sei, nessuna attesa per l'approvazione di una persona, nessuna chiamata per verificare nulla. La fattura viene saldata e le credenziali arrivano nella tua casella di posta.