Douze constructions

Inférence locale sur un GPU en passthrough avec un endpoint compatible OpenAI

Pilote, runtime et modèle sur un G-L40S, servis derrière TLS avec une clé API, et un test de charge qui rapporte de vrais tokens par seconde plutôt qu'un slide de vendeur.

Ce que cela construit

Une instance GPU servant un modèle open-weights sur une API HTTP qui parle le schéma compatible OpenAI, donc chaque bibliothèque client que vous possédez déjà fonctionne avec une simple URL de base modifiée. Derrière nginx, avec TLS, une clé API et une unité systemd qui survit aux redémarrages.

Écrit sur un G-L40S : quarante-huit gigaoctets de VRAM sur une seule carte, transmis à l'instance via PCIe plutôt que partagés dans le temps entre locataires. Cette distinction fait la différence entre une latence prévisible et le fait d'attendre derrière le travail d'entraînement de quelqu'un d'autre. La carte est à vous jusqu'à annulation.

Avant de commencer

  • Un G-L40S avec Debian 13. Le G-ADA à vingt gigaoctets convient pour les modèles plus petits ; l'arithmétique à l'étape quatre vous indique lesquels.
  • Deux téraoctets de NVMe sur ce plan, ce qui est le bon endroit pour les poids. Télécharger quarante gigaoctets deux fois parce que le cache était sur le système de fichiers racine est une façon ennuyeuse de passer une heure.
  • Un nom d'hôte pointant vers l'instance, llm.example.com ci-dessous.

1. Pilote

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

Après son retour :

nvidia-smi

Vous voulez la carte listée, la version du pilote imprimée et l'utilisation mémoire proche de zéro. Une carte manquante à ce stade est presque toujours un initramfs obsolète ; update-initramfs -u et redémarrez une fois de plus avant d'ouvrir un ticket.

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

Le mode persistance maintient le pilote chargé entre les processus, ce qui supprime plusieurs secondes d'initialisation à chaque redémarrage.

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'installation tire un ensemble de wheels liés à CUDA volumineux et prend un certain temps. En attendant, décidez d'un modèle.

3. Choisir ce qui convient

Les poids sont le plancher, pas le budget. Un modèle en précision seize bits nécessite environ deux gigaoctets de VRAM par milliard de paramètres, plus le cache clé-valeur pour chaque requête concurrente, plus environ un gigaoctet de frais généraux d'exécution.

CartePoids en 16 bitsTaille réaliste du modèleReste pour le cache
L40S, 48 Go2 Go par milliardJusqu'à environ 20 milliards6 à 8 Go
L40S, 48 Go, 8 bits1 Go par milliardJusqu'à environ 34 milliards10 Go
RTX 4000 Ada, 20 Go2 Go par milliardJusqu'à environ 7 milliards5 Go
Deux × L40S, 96 Go2 Go par milliardJusqu'à environ 40 milliards12 Go

La colonne cache détermine combien de personnes peuvent utiliser le point de terminaison en même temps. Remplissez la carte à ras bord avec des poids et vous avez construit un jouet très rapide pour un seul utilisateur.

4. Servir le modèle

Pointez le cache vers le NVMe et démarrez le serveur. Remplacez l'identifiant du référentiel du modèle open-weights que vous avez choisi ; le runtime le télécharge au premier démarrage.

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

La liaison à loopback est délibérée. La vérification de la clé API dans le runtime est un secret partagé unique sans limite de débit derrière, donc nginx fait l'exposition et le runtime ne touche jamais l'interface publique.

Le délai de démarrage généreux existe parce que le premier lancement télécharge des dizaines de gigaoctets puis compile les kernels. Les démarrages suivants prennent moins d'une minute.

5. Proxy et 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 est la ligne qui fait que les réponses en streaming streament. Laissez-la activée et les jetons arrivent par lots polis chaque fois que nginx sent qu'un tampon est plein, ce qui ressemble exactement à un modèle lent et ne l'est pas.

Vérifiez

Commencez par la carte :

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

La mémoire utilisée devrait être autour de quatre-vingt-dix pour cent du total, car c'est ce que --gpu-memory-utilization a demandé. Ensuite le point de terminaison :

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

Un modèle, nommé local. Maintenant une complétion réelle, et chronométrez-la :

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}'

Enfin, mesurez-la sous charge plutôt qu'une requête à la fois, ce qui est le seul chiffre qui vaille la peine d'être cité :

/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

Lisez trois nombres dans la sortie : le débit de jetons de sortie par seconde en agrégat, le temps médian jusqu'au premier jeton et le quatre-vingt-dix-neuvième percentile de bout en bout. Sur un seul L40S avec un modèle dans la plage de dix à vingt milliards, un débit de sortie agrégé dans les centaines élevées de jetons par seconde et un temps jusqu'au premier jeton bien en dessous d'une seconde est la forme attendue. Un temps jusqu'au premier jeton qui grimpe avec la concurrence signifie que votre cache clé-valeur est trop petit, donc baissez --max-model-len ou --max-num-seqs et relancez.

Ensuite

Surveillez la température et l'étranglement de l'horloge pendant la première semaine avec nvidia-smi dmon, car une carte qui s'étrangle thermiquement sous charge soutenue produit exactement le genre de lenteur intermittente attribuée au réseau. Côté facturation, rappelez-vous qu'une instance GPU coûte le même prix que la carte soit occupée ou inactive, donc battez plutôt que de la laisser chaude. Les prix sont sur la page GPU.

Prêt quand vous l'êtes

Choisissez une ville. Choisissez une taille. Payez en crypto.

Aucun formulaire sur votre identité, pas d'attente d'approbation humaine, pas d'appel téléphonique pour vérifier quoi que ce soit. La facture est réglée et les identifiants arrivent dans votre boîte mail.