
Je suis Jules Ginhac, Co-Founder & ingénieur IA chez Apogée Consult à Lyon. Je conçois et déploie des architectures IA génératives (RAG, agents, LLMOps) pour des PME, startups et organisations publiques.
Llama 3.3 70B en self-hosted sur 2 A100 : setup, latences, et ce qu'on a découvert sur le batching
- llm-comparison
Retour d'expérience sur le déploiement de Llama 3.3 70B sur deux A100 80 Go avec vLLM. Latences mesurées, surprises sur le batching, et arbitrages d'architecture.
Llama 3.3 70B en self-hosted sur 2 A100 : setup, latences, et ce qu'on a découvert sur le batching
Trois semaines de setup, deux semaines de charge progressive, une semaine d'audit. Voilà le condensé de notre déploiement de Llama 3.3 70B en production sur deux A100 80 Go.
L'objectif n'était pas de prouver que le self-hosting est supérieur à une API cloud. C'était de répondre à une question client précise : peut-on atteindre des SLA de latence inférieurs à 2 secondes pour du texte court, avec des données confidentielles qui ne peuvent pas sortir du réseau ?
Setup matériel et logiciel
- 2 x NVIDIA A100 SXM4 80 Go (NVLink)
- Driver CUDA 12.4, Python 3.11
- vLLM v0.6.x avec tensor parallelism sur 2 GPUs
- Modèle :
meta-llama/Llama-3.3-70B-Instructen bfloat16 (140 Go VRAM total)
# Lancement du serveur vLLM
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.3-70B-Instruct \
--tensor-parallel-size 2 \
--max-model-len 8192 \
--gpu-memory-utilization 0.92 \
--max-num-seqs 32 \
--dtype bfloat16 \
--host 0.0.0.0 \
--port 8000Le max-num-seqs 32 est le paramètre critique pour le batching, on y revient.
Latences mesurées en conditions réelles
Mesures sur 5 000 requêtes réelles (prompts 200-800 tokens, outputs 50-300 tokens) :
| Métrique | Valeur |
|---|---|
| Time to First Token (p50) | 380ms |
| Time to First Token (p95) | 820ms |
| Time to First Token (p99) | 1 650ms |
| Throughput total tokens/s | 1 200 tok/s |
| Latence end-to-end p50 (output 100 tok) | 710ms |
| Latence end-to-end p95 (output 100 tok) | 1 480ms |
Le p50 respecte notre SLA de 2 secondes confortablement. Le p99 non. En dessous de 10 requêtes simultanées, tous les percentiles passent. Au-delà de 20 requêtes simultanées, le p95 dépasse 2 secondes.
Ce qu'on a découvert sur le batching
vLLM implémente le continuous batching (aussi appelé iteration-level scheduling) : les requêtes entrent dans le batch dès qu'une séquence se termine, sans attendre que toutes les séquences du batch soient terminées. C'est ce qui rend vLLM efficace à fort throughput.
La découverte : avec max-num-seqs 32, les requêtes à output long (>200 tokens) bloquent les slots. Quand une séquence génère 500 tokens, elle occupe un slot pendant toute sa durée, et les nouvelles requêtes entrant dans la file d'attente voient leur TTFT augmenter.
# Monitoring de la file d'attente vLLM (endpoint /metrics Prometheus)
import httpx
async def check_queue_depth() -> dict:
response = await httpx.AsyncClient().get("http://localhost:8000/metrics")
lines = response.text.split("\n")
metrics = {}
for line in lines:
if "vllm:num_requests_waiting" in line and not line.startswith("#"):
metrics["waiting"] = float(line.split()[-1])
if "vllm:num_requests_running" in line and not line.startswith("#"):
metrics["running"] = float(line.split()[-1])
return metricsLa solution : séparer les workloads selon l'output expected. Nous routons les requêtes à output long vers une file dédiée avec un batch size plus petit (max-num-seqs 8) pour préserver la latence des requêtes courtes sur la file principale.
Comparaison avec TGI
Nous avons aussi testé Text Generation Inference de HuggingFace avant de retenir vLLM.
Sur notre workload, TGI affichait un TTFT p50 de 420ms (contre 380ms pour vLLM) et un throughput de 950 tok/s (contre 1 200). La différence tient au flash attention 2 et à l'implémentation du paged attention dans vLLM, qui est plus optimisée sur notre profil de requêtes mixtes courtes/longues.
TGI reste pertinent pour des profils de requêtes très homogènes ou pour des cas nécessitant des quantizations spécifiques (GPTQ, AWQ) mieux supportées dans TGI.
Ce que ce setup ne résout pas
La haute disponibilité est manquante. Sur deux GPU sans redondance, une panne matérielle arrête le service. La solution correcte est un troisième noeud en standby avec failover automatique, ce que nous avons prévu pour la phase 2 mais pas encore déployé.
Le coût de revient réel (amortissement GPU sur 3 ans, électricité, maintenance système) dépasse souvent le coût API pour des volumes inférieurs à 50 millions de tokens par mois. Au-delà, le calcul s'inverse.
La vraie question pour la suite : à partir de quel volume et de quelle contrainte de confidentialité le self-hosting justifie-t-il réellement le coût total d'exploitation ?