Coûts réels GPU H100 en France (2026)
Les coûts GPU varient considérablement selon le mode d'acquisition. Voici une analyse réaliste basée sur les grilles tarifaires des fournisseurs français et européens actifs en 2026.
Location GPU (cloud souverain français)
| Fournisseur | GPU | Prix/heure (€ HT) | Prix/mois (€ HT) | Prix/an (€ HT) |
|---|---|---|---|---|
| OVHcloud (FR) | H100 PCIe 80GB | ~2.50–3.50 | ~1 800–2 520 | ~21 600–30 240 |
| Scaleway (FR) | H100 SXM5 80GB | ~3.50–4.50 | ~2 520–3 240 | ~30 240–38 880 |
| Outscale / Dassault | A100 80GB | ~1.80–2.50 | ~1 296–1 800 | ~15 552–21 600 |
| Clever Cloud (FR) | A100 40GB | ~1.20–1.80 | ~864–1 296 | ~10 368–15 552 |
Achat GPU (on-premise)
| Configuration | GPU | Prix serveur complet (€) | Durée amortissement | Coût/an amorti |
|---|---|---|---|---|
| 1× DGX H100 | 8× H100 SXM5 80GB | ~280 000–350 000 | 5 ans | 56 000–70 000 |
| Custom HGX H100 | 4× H100 SXM5 80GB | ~140 000–180 000 | 5 ans | 28 000–36 000 |
| Serveur A100 PCIe | 4× A100 80GB | ~80 000–110 000 | 5 ans | 16 000–22 000 |
| Serveur L40S | 4× L40S 48GB | ~50 000–70 000 | 4 ans | 12 500–17 500 |
À ces coûts d'achat matériel, il faut ajouter : espace datacenter (300-500 W/GPU, soit ~2000 W pour un serveur 4× GPU → ~2 100 €/an d'électricité à 0.12 €/kWh), maintenance et support constructeur (~10% du prix d'achat/an), et colocation ou refroidissement si on-premise (~500-1000 €/mois selon prestataire).
TCO comparé sur 3 ans
# tco_calculator.py — Calculateur TCO GPU sur 3 ans
def calculate_tco_3y(
num_gpus: int,
gpu_model: str = "H100",
mode: str = "cloud", # "cloud", "on-premise"
utilization_pct: float = 0.70 # Utilisation visée
):
"""Calcule le TCO sur 3 ans pour une infrastructure GPU LLM."""
costs = {
"H100": {
"cloud_per_gpu_year": 32000, # OVHcloud/Scaleway moy.
"onprem_server_cost": 165000, # 4× H100 SXM5 serveur
"onprem_gpus_per_server": 4,
},
"A100": {
"cloud_per_gpu_year": 18000,
"onprem_server_cost": 90000,
"onprem_gpus_per_server": 4,
}
}
c = costs[gpu_model]
if mode == "cloud":
compute_3y = num_gpus * c["cloud_per_gpu_year"] * 3
ops_3y = 0 # Inclus dans le cloud
total = compute_3y
else: # on-premise
servers_needed = (num_gpus + c["onprem_gpus_per_server"] - 1) // c["onprem_gpus_per_server"]
capex = servers_needed * c["onprem_server_cost"]
electricity_year = num_gpus * 300 * 8760 / 1000 * 0.12 # ~kWh × tarif
ops_year = capex * 0.12 # Maintenance 12%/an
datacenter_year = servers_needed * 8000 # Colo/refroidissement
opex_3y = (electricity_year + ops_year + datacenter_year) * 3
total = capex + opex_3y
cost_per_gpu_year = total / (num_gpus * 3)
return {
"total_3y_eur": round(total),
"cost_per_gpu_year": round(cost_per_gpu_year),
"cost_per_gpu_month": round(cost_per_gpu_year / 12)
}
# Exemples comparatifs
print("=== TCO 4× H100 sur 3 ans ===")
print("Cloud FR:", calculate_tco_3y(4, "H100", "cloud"))
print("On-premise:", calculate_tco_3y(4, "H100", "on-premise"))
# Cloud FR: {'total_3y_eur': 384000, 'cost_per_gpu_year': 32000, ...}
# On-premise: {'total_3y_eur': 312000, 'cost_per_gpu_year': 26000, ...}
# → Achat rentable dès l'année 3 à utilisation > 70%
Quantization : INT8/INT4 — gains réels et pertes de qualité
La quantization réduit la précision numérique des poids du modèle pour diminuer la consommation VRAM et accélérer l'inférence. C'est la technique d'optimisation au meilleur ratio impact/effort pour les équipes.
| Précision | VRAM ELODIE 32B | Débit (tok/s) 1× H100 | Perte qualité (MMLU) | Recommandation |
|---|---|---|---|---|
| BF16 (référence) | 64 GB | ~45 tok/s | 0% (baseline) | Dev/évaluation |
| INT8 (bitsandbytes) | 32 GB | ~60 tok/s (+33%) | -0.5 à -1.5% | Production standard |
| GPTQ INT4 (4-bit) | 18 GB | ~90 tok/s (+100%) | -1 à -3% | Production coûts serrés |
| AWQ INT4 (4-bit) | 18 GB | ~95 tok/s (+111%) | -0.5 à -2% | Production recommandé INT4 |
| GGUF Q5_K_M | 22 GB | ~40 tok/s (-11%) | -0.3 à -1% | CPU/hybride CPU-GPU |
# Quantization AWQ INT4 avec autoawq
pip install autoawq
python3 - <<'EOF'
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_path = "/models/elodie-32b-bf16"
quant_path = "/models/elodie-32b-awq-int4"
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM" # GEMM optimisé pour inférence
}
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoAWQForCausalLM.from_pretrained(
model_path,
low_cpu_mem_usage=True,
use_cache=False
)
# Calibration sur données représentatives
calib_data = [
"Résume ce document juridique : ...",
"Analyse ce rapport financier : ...",
# 128 exemples représentatifs de vos cas d'usage
]
model.quantize(tokenizer, quant_config=quant_config, calib_data=calib_data)
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)
print(f"Modèle quantisé sauvegardé dans {quant_path}")
EOF
# Démarrage vLLM avec modèle AWQ
python -m vllm.entrypoints.openai.api_server \
--model /models/elodie-32b-awq-int4 \
--quantization awq \
--dtype float16 \
--max-model-len 8192 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.85
Validation qualité obligatoire
Ne déployez jamais un modèle quantisé en production sans le valider sur vos cas d'usage réels. La perte sur MMLU (benchmark généraliste) peut masquer des dégradations plus sévères sur des domaines spécifiques (juridique, médical, technique). Utilisez un ensemble de test métier représentatif et mesurez la qualité avec au moins 3 évaluateurs humains sur 100 questions clés avant tout passage en production.
Batching dynamique : jusqu'à +3x throughput
Le continuous batching (batching dynamique) est la fonctionnalité qui a révolutionné l'efficacité des serveurs LLM. Au lieu de traiter une requête à la fois ou d'attendre un batch complet, vLLM insère dynamiquement de nouvelles requêtes dans les slots libérés par les requêtes terminées — multipliant le débit par 2 à 4×.
SANS continuous batching (approche naïve) :
Temps → 0 1 2 3 4 5 6 7 8 9 10
Slot1 : [===REQ1===] [===REQ3===]
Slot2 : [==REQ2==] [====REQ4====]
↑ vide ↑
Gaspillage
AVEC continuous batching (vLLM) :
Temps → 0 1 2 3 4 5 6 7 8 9 10
Slot1 : [===REQ1===][REQ5][====REQ7====]
Slot2 : [==REQ2==][==REQ6==][=REQ8=]
Slot3 : [REQ3][====REQ9====]
↑ Zéro gaspillage ↑
# Configuration vLLM optimisée pour le batching
# Les paramètres clés pour maximiser le débit
python -m vllm.entrypoints.openai.api_server \
--model /models/elodie-32b \
--max-num-seqs 256 \
# Nombre max de séquences simultanées dans le batch
--max-num-batched-tokens 32768 \
# Tokens max traités en un seul batch (prompt+génération)
--enable-chunked-prefill \
# Découpe les longs prefills pour éviter les blocages
--scheduler-delay-factor 0.05 \
# Délai d'attente (ms) pour agréger les requêtes
--enable-prefix-caching \
# Cache les prefixes communs (system prompts)
--max-paddings 256 \
# Padding max pour alignement batch
--block-size 16 \
# Taille des blocs KV cache (16 = bon compromis)
--swap-space 4 \
# GB de RAM CPU pour le KV swap (overflow VRAM)
Benchmark batching : impact mesurable
# benchmark_batching.py — Mesure de l'impact du batching
import asyncio
import httpx
import time
async def send_concurrent_requests(n: int, endpoint: str, api_key: str):
"""Envoie N requêtes simultanées et mesure le débit total."""
prompts = [f"Explique le concept numéro {i} en 100 mots." for i in range(n)]
start = time.perf_counter()
async with httpx.AsyncClient(timeout=120) as client:
tasks = [
client.post(
f"{endpoint}/v1/completions",
json={"model": "elodie-32b", "prompt": p, "max_tokens": 100},
headers={"Authorization": f"Bearer {api_key}"}
)
for p in prompts
]
responses = await asyncio.gather(*tasks)
elapsed = time.perf_counter() - start
total_tokens = sum(
r.json()["usage"]["total_tokens"]
for r in responses if r.status_code == 200
)
print(f"Concurrence: {n} requêtes")
print(f"Temps total: {elapsed:.1f}s")
print(f"Débit: {total_tokens/elapsed:.0f} tokens/s")
print(f"Tokens par requête: {total_tokens/n:.0f}")
# Test avec différents niveaux de concurrence
for concurrency in [1, 4, 16, 32, 64]:
asyncio.run(send_concurrent_requests(concurrency,
"http://vllm-service", "key"))
KV Cache et Prefix Caching
Le KV (Key-Value) cache stocke les représentations intermédiaires de l'attention calculées lors du prefill — évitant de les recalculer pour chaque token généré. Le Prefix Caching va plus loin : il réutilise ces représentations entre plusieurs requêtes partageant le même début de prompt (typiquement le system prompt).
# Mesure de l'impact du prefix caching
# Exemple : system prompt de 500 tokens partagé par toutes les requêtes
SYSTEM_PROMPT = """Tu es ELODIE, l'assistant IA souverain d'Intelligence Privée.
Tu analyses uniquement les documents internes de l'entreprise.
Tu réponds toujours en français, de manière professionnelle et précise.
Tu ne divulgues jamais d'informations confidentielles en dehors du contexte autorisé.
[... 400 tokens supplémentaires de contexte système ...]
"""
# Sans prefix caching : 500 tokens recalculés à chaque requête
# Coût : 500 tokens × latence attention = ~50ms overhead par requête
# Avec prefix caching (--enable-prefix-caching) :
# Premier appel : 500 tokens calculés et mis en cache
# Appels suivants : 0 tokens recalculés → TTFT divisé par 2-3
# Estimation du gain :
# 1000 requêtes/jour × 50ms économisés = 50 secondes GPU libérées
# = ~1.4% de capacité GPU récupérée gratuitement
Right-sizing : quand le 32B suffit à la place du 70B
Un modèle 70B ne délivre pas systématiquement de meilleures performances qu'un 32B sur tous les cas d'usage. Le right-sizing permet d'utiliser le modèle le plus petit qui satisfait les exigences de qualité — divisant les coûts par 2 à 3.
| Cas d'usage | Modèle minimum satisfaisant | Gain cost vs 70B |
|---|---|---|
| Résumé de document | ELODIE 32B / Mistral 32B | -60% VRAM, -55% coût |
| Q&A sur documents internes (RAG) | ELODIE 32B | -60% VRAM |
| Génération de code Python/SQL | Code Llama 34B / ELODIE 32B | -55% VRAM |
| Analyse juridique complexe | Llama 3.3 70B / KEVINA 32B | variable |
| Raisonnement multi-étapes | Llama 3.3 70B ou Qwen2.5 72B | N/A (70B nécessaire) |
| Traduction FR/EN | Mistral 7B / NLLB-3.3B | -90% VRAM |
| Classification/extraction | Mistral 7B fine-tuné | -90% VRAM |
# router_llm.py — Routage automatique vers le bon modèle
from enum import Enum
class ModelTier(Enum):
SMALL = "mistral-7b" # Tâches simples
MEDIUM = "elodie-32b" # Majorité des usages
LARGE = "llama-70b" # Raisonnement complexe
def route_request(request: dict) -> ModelTier:
"""
Sélectionne automatiquement le modèle selon la complexité estimée.
Économise 40-60% des coûts GPU en moyenne.
"""
messages = request.get("messages", [])
last_message = messages[-1].get("content", "") if messages else ""
# Tâches simples → modèle léger
simple_keywords = [
"résume", "traduis", "corrige", "reformule",
"extrait", "classe", "catégorise"
]
if any(kw in last_message.lower() for kw in simple_keywords):
if len(last_message) < 500:
return ModelTier.SMALL
# Raisonnement complexe → grand modèle
complex_keywords = [
"analyse juridique", "stratégie", "raisonne",
"compare et explique", "évalue les risques"
]
if any(kw in last_message.lower() for kw in complex_keywords):
return ModelTier.LARGE
# Par défaut : modèle medium (bon compromis)
return ModelTier.MEDIUM
Scheduling intelligent : batch la nuit, interactif le jour
# kubernetes-priority-classes.yaml
# Deux classes de priorité : interactive (haute) vs batch (basse)
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: llm-interactive
value: 1000
globalDefault: false
description: "Inférence interactive utilisateur — haute priorité"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: llm-batch
value: 100
globalDefault: false
description: "Jobs batch IA — basse priorité, préemptable"
---
# CronJob pour traitement batch nocturne (23h-7h)
apiVersion: batch/v1
kind: CronJob
metadata:
name: llm-batch-processor
namespace: llm-production
spec:
schedule: "0 23 * * *" # Démarrage à 23h00
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
priorityClassName: llm-batch
containers:
- name: batch-processor
image: registry.interne/llm-batch:latest
env:
- name: VLLM_ENDPOINT
value: http://vllm-service.llm-production
- name: BATCH_MODE
value: "true"
- name: MAX_TOKENS_PER_DOC
value: "4096"
resources:
requests:
cpu: "4"
memory: "8Gi" # Pas de GPU alloué — utilise l'API vLLM
Chargeback par département
-- Rapport mensuel de chargeback IA par département
-- Exécuter le 1er du mois pour le mois précédent
WITH monthly_usage AS (
SELECT
department,
model,
COUNT(*) as request_count,
SUM(prompt_tokens) as prompt_tokens_total,
SUM(completion_tokens) as completion_tokens_total,
SUM(prompt_tokens + completion_tokens) as total_tokens,
AVG(latency_ms) as avg_latency_ms,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms) as p95_latency_ms
FROM llm_usage_logs
WHERE
timestamp >= date_trunc('month', CURRENT_DATE - INTERVAL '1 month')
AND timestamp < date_trunc('month', CURRENT_DATE)
GROUP BY department, model
),
costs AS (
SELECT
department,
model,
request_count,
total_tokens,
avg_latency_ms,
p95_latency_ms,
ROUND(
CASE model
WHEN 'elodie-32b' THEN total_tokens * 0.000004 -- 0.004€/1k tokens
WHEN 'kevina-32b' THEN total_tokens * 0.000004
WHEN 'llama-70b' THEN total_tokens * 0.000012
ELSE total_tokens * 0.000005
END, 2
) AS cost_eur
FROM monthly_usage
)
SELECT
department,
SUM(request_count) as total_requests,
SUM(total_tokens) as total_tokens,
SUM(cost_eur) as total_cost_eur,
ROUND(SUM(cost_eur) / SUM(request_count), 4) as avg_cost_per_request,
ROUND(SUM(total_tokens) / SUM(request_count), 0) as avg_tokens_per_request
FROM costs
GROUP BY department
ORDER BY total_cost_eur DESC;
KPIs FinOps IA à reporter à la direction
| KPI | Formule | Cible | Fréquence |
|---|---|---|---|
| Coût par requête (€) | coût_total / nb_requêtes | < 0.01 € | Hebdomadaire |
| Utilisation GPU (%) | DCGM_FI_DEV_GPU_UTIL moyen | > 60% | Quotidien |
| Coût par utilisateur/mois (€) | coût_total / DAU / 30 | < 15 €/user | Mensuel |
| Tokens per GPU-hour | total_tokens / GPU-heures | > 150k tok/GPU-h | Hebdomadaire |
| Taux idle GPU (%) | temps GPU util.<10% / temps total | < 20% | Quotidien |
| ROI adoption (%) | (bénéfices_productivité - coûts) / coûts | > 150% | Trimestriel |
# Script de rapport FinOps hebdomadaire
#!/bin/bash
# Généré chaque lundi matin à 8h via CronJob
GPU_UTIL=$(kubectl exec -n monitoring deploy/prometheus -- \
promtool query instant \
'avg(avg_over_time(DCGM_FI_DEV_GPU_UTIL[7d]))' | \
grep -oP '(?<=value: )\d+\.?\d*')
COST_WEEK=$(psql -U llm_tracker -t -c \
"SELECT ROUND(SUM(cost_eur)::numeric, 2) FROM llm_usage_logs \
WHERE timestamp > NOW() - INTERVAL '7 days'")
REQUESTS_WEEK=$(psql -U llm_tracker -t -c \
"SELECT COUNT(*) FROM llm_usage_logs \
WHERE timestamp > NOW() - INTERVAL '7 days'")
echo "=== RAPPORT FINOPS IA — $(date +%Y-%m-%d) ==="
echo "Utilisation GPU moyenne (7j) : ${GPU_UTIL}%"
echo "Coût total semaine : ${COST_WEEK}€"
echo "Requêtes traitées : ${REQUESTS_WEEK}"
echo "Coût par requête : $(echo "scale=4; $COST_WEEK / $REQUESTS_WEEK" | bc)€"
Ce qu'il faut retenir
- La quantization AWQ INT4 est l'optimisation avec le meilleur ratio effort/gain : -50% VRAM, +100% débit, -1 à -2% qualité seulement.
- Le continuous batching vLLM (activé par défaut) est responsable de 60% des gains de débit — mais il faut bien le configurer (max-num-seqs, max-num-batched-tokens).
- Le routing multi-modèle (7B/32B/70B selon complexité) peut réduire les coûts de 40-60% avec un impact qualité transparent pour l'utilisateur.
- L'utilisation GPU < 60% est un signal de sur-dimensionnement — consolidez vos workloads ou réduisez le nombre de GPU loués.
- Sans chargeback par département, les équipes n'ont aucune incitation à optimiser leurs prompts. Facturez en interne pour responsabiliser.
Audit FinOps IA : trouvez vos économies cachées
Nos experts FinOps analysent votre infrastructure GPU, identifient les gisements d'optimisation et implementent les changements — quantization, batching, routing — avec garantie de résultat.
Demander mon audit FinOps IA →FAQ
Quelle quantization choisir : GPTQ ou AWQ ?
AWQ (Activation-aware Weight Quantization) est généralement supérieure à GPTQ pour les modèles 32B+ en 2026. AWQ préserve mieux la qualité car elle tient compte de l'activation lors de la quantization (pas seulement les poids). Elle génère des modèles légèrement plus rapides à l'inférence sur H100. Utilisez AWQ avec w_bit=4, q_group_size=128 pour la meilleure qualité en INT4. GPTQ reste valide si vous avez déjà un pipeline existant.
Le swap KV cache (CPU offloading) est-il viable en production ?
Le swap KV cache (paramètre --swap-space dans vLLM) permet de décharger le KV cache de la VRAM vers la RAM CPU quand la VRAM est pleine. C'est viable pour les requêtes à très long contexte (32k+ tokens) dont vous pouvez accepter une latence plus élevée. En revanche, pour les conversations interactives, le swap ajoute 100-500ms de latence supplémentaire par décharge — inacceptable pour les utilisateurs. Utilisez-le exclusivement pour les jobs batch.
Comment justifier le FinOps IA auprès du ComEx ?
Présentez trois métriques : (1) coût par utilisateur actif/mois comparé à la valeur produite (économie en heures-homme), (2) taux d'utilisation GPU vs baseline cloud (prouvez que vous faites mieux que la moyenne), (3) trajectoire ROI sur 24 mois avec les optimisations planifiées. Évitez les métriques purement techniques (tokens/s) — traduisez toujours en impact business mesurable.