Apogée Consult
Retour au blog
Jules Ginhac
Jules GinhacCo-Founder & ingénieur IA

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-Instruct en 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 8000

Le 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étriqueValeur
Time to First Token (p50)380ms
Time to First Token (p95)820ms
Time to First Token (p99)1 650ms
Throughput total tokens/s1 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 metrics

La 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 ?

Disponible pour de nouveaux projets

Un projet à concrétiser ?
Parlons-en, sans engagement.

Un échange de 30 minutes pour cadrer votre besoin, qualifier la faisabilité et vous proposer une trajectoire claire.

1// kick-off : réponse sous 24h
2const project = await apogee.scope({
3 type: 'web | mobile | IA',
4 timeline: '6 à 16 semaines',
5 approach: 'sur-mesure'
6})
7// → cadrage offert
Llama 3.3 self-hosted A100 vLLM batching | Apogée Consult