Skip to content

Self-hosting LLM en prod — l'art de tenir un cluster GPU

TL;DR Self-host un LLM en prod = 3 couches : (1) modèle (Mistral, Llama 4, Qwen 3, DeepSeek), (2) inference engine (vLLM, TGI), (3) plateforme (Kubernetes + KServe ou Ray Serve, GPU operator, monitoring, secrets, mTLS). Les use cases qui justifient le coût : souveraineté (banque, défense, santé HDS), volume très élevé (>1M req/mois), latence strict (voice), data ultra-sensible (brevets, M&A). Stack canonique FR 2026 : Scaleway Kapsule ou OVH MKS + vLLM + KServe + Prometheus/Grafana/Loki + Vault + Cilium mTLS. Coût plancher : ~ 6k€/mois pour 2× H100 HA. ROI vs API providers : break-even à ~ 250K req/mois pour Mistral Small équivalent.


🧠 Mental model

┌─────────────────────────────────────────────────────────────────────┐
│                         Self-hosting stack                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │  Application layer (Next.js, FastAPI, agents)               │   │
│   └────────────────────────┬────────────────────────────────────┘   │
│                            │                                        │
│   ┌────────────────────────▼────────────────────────────────────┐   │
│   │  Inference gateway                                          │   │
│   │  - auth (OIDC/JWT) - rate limit - audit log - GuardRails    │   │
│   └────────────────────────┬────────────────────────────────────┘   │
│                            │                                        │
│   ┌────────────────────────▼────────────────────────────────────┐   │
│   │  Inference engine (vLLM / TGI / SGLang)                     │   │
│   │  - PagedAttention - LoRA serving - prefix cache             │   │
│   └────────────────────────┬────────────────────────────────────┘   │
│                            │                                        │
│   ┌────────────────────────▼────────────────────────────────────┐   │
│   │  Kubernetes (KServe / Ray Serve)                            │   │
│   │  - HPA - PDB - node selectors GPU - taints/tolerations      │   │
│   └────────────────────────┬────────────────────────────────────┘   │
│                            │                                        │
│   ┌────────────────────────▼────────────────────────────────────┐   │
│   │  Infra : GPU operator + cluster + storage + network         │   │
│   │  - NVIDIA GPU operator - CSI - Cilium - Vault - cert-manager│   │
│   └────────────────────────┬────────────────────────────────────┘   │
│                            │                                        │
│   ┌────────────────────────▼────────────────────────────────────┐   │
│   │  Observabilité : Prometheus + Grafana + Loki + Tempo + OTel │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

  HDS / RGPD overlay :
  - chiffrement at-rest (LUKS, CSI encrypted)
  - chiffrement in-transit (mTLS Cilium, TLS Ingress)
  - audit log (Loki + WORM S3 bucket)
  - data residency contractuelle (Scaleway Paris, OVH Strasbourg)
  - DPO + registre traitements + DPIA

Analogie : self-host LLM = ouvrir un laboratoire pharmaceutique vs commander en pharmacie. Plus cher au démarrage, contrôle total, contraintes réglementaires lourdes, mais c'est la seule option pour fabriquer des produits soumis à secret industriel ou contraintes nationales.


🧮 L'économie de l'inférence — le modèle mental que tout le monde rate

Avant de provisionner un seul GPU, un staff engineer doit savoir pourquoi un cluster GPU coûte ce qu'il coûte. La quasi-totalité des erreurs de dimensionnement (et de devis) viennent d'une incompréhension de la mécanique d'inférence d'un transformer décodeur. Le coût n'est pas « par requête » — il est gouverné par trois variables couplées : mémoire, batching, et la dichotomie prefill / decode.

Prefill vs decode — deux régimes radicalement différents

Une génération LLM a deux phases qui n'ont rien à voir côté hardware :

PhaseCe qui se passeGoulotParallélisable ?
PrefillOn encode tout le prompt d'entrée en une passeCompute-bound (les GPU tensor cores tournent à fond)Oui — tous les tokens du prompt en parallèle
DecodeOn génère 1 token, puis le suivant, puis le suivant…Memory-bandwidth-bound (on relit tout le KV cache à chaque token)Non — strictement séquentiel par requête

Conséquence directe sur ton devis : un use case « long prompt, courte réponse » (RAG, classification, résumé) est dominé par le prefill → tu es compute-bound → tu peux empiler beaucoup de requêtes par GPU. Un use case « court prompt, longue réponse » (génération de code, rédaction) est dominé par le decode → tu es bandwidth-bound → ton throughput s'effondre et le coût par token explose. Deux use cases au même volume de requêtes peuvent avoir un coût GPU 5× différent. Un junior devise « au nombre de requêtes » ; un senior devise sur le profil (input_tokens, output_tokens, p95 concurrence).

Le KV cache — pourquoi la mémoire, pas le compute, est ton mur

À chaque token généré, le modèle doit relire les clés/valeurs (KV) de tous les tokens précédents. On les met en cache en VRAM pour ne pas recalculer. Taille du KV cache :

kv_bytes ≈ 2 (K+V) × n_layers × n_kv_heads × head_dim × seq_len × batch × dtype_bytes

Pour un Mistral Large (~123B, ~88 layers) en bf16, un seul contexte de 32k tokens consomme déjà plusieurs dizaines de Go de KV cache — souvent plus que les poids ne laissent de VRAM libre. C'est ce qui plafonne ta concurrence réelle, pas la puissance de calcul. D'où l'alerte vllm_gpu_cache_usage_perc > 0.97 dans les manifests : quand le KV cache sature, vLLM preempt des requêtes (les recalcule plus tard) et ta latence p99 part en vrille. PagedAttention (le cœur de vLLM) attaque exactement ce problème : il pagine le KV cache comme un OS pagine la RAM, éliminant la fragmentation et doublant souvent le batch effectif.

Continuous batching — pourquoi vLLM écrase un serveur naïf

Un serveur naïf fait du static batching : il attend N requêtes, les traite ensemble, attend que la plus longue finisse avant de libérer le batch. Résultat : les requêtes courtes attendent les longues, GPU sous-utilisé. Continuous batching (alias in-flight batching) injecte une nouvelle requête dès qu'une place se libère dans le batch, token par token. Gain typique : 5–20× de throughput à latence égale. C'est la raison pour laquelle on déploie vLLM/TGI/SGLang et pas un model.generate() dans un Flask.

Les leviers que tu actionnes (et leurs tradeoffs)

LevierGagneCoûte
Quantization (FP8, AWQ, GPTQ INT4)2–4× moins de VRAM → modèle plus gros sur moins de GPU, ou plus de batchLégère perte qualité (souvent < 1% sur FP8/AWQ ; à mesurer, pas à supposer)
Tensor parallelism (--tensor-parallel-size)Fait tenir un modèle trop gros pour 1 GPUTrafic all-reduce inter-GPU à chaque layer → NVLink obligatoire, mortel en inter-AZ (cf. Pitfall #10)
Prefix caching (--enable-prefix-caching)Réutilise le KV cache d'un system prompt partagé → prefill quasi gratuitRAM/VRAM pour le cache ; gain nul si chaque prompt est unique
Speculative decoding (draft model)2–3× sur le decode (le mur bandwidth)Complexité ops + VRAM du draft model ; gain dépend du taux d'acceptation
Chunked prefillLisse la latence quand prefill et decode se disputent le GPUTuning fin du chunk size

Comment un staff engineer raisonne : il ne demande pas « combien de GPU ? » mais « quel est le profil de charge, et quel point de la courbe throughput/latence je vise ? ». Throughput max et latence min sont antagonistes : maximiser le batch (throughput, coût/token bas) gonfle la latence p99 ; viser une p95 stricte (voice, < 500ms) force un batch petit et fait exploser le coût/token. Le devis tombe de cette courbe, jamais d'un ratio magique requêtes/GPU.


🛠️ Code minimal — manifest KServe

yaml
# kserve-mistral.yml
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: mistral-large-prod
  namespace: ai-prod
  annotations:
    serving.kserve.io/deploymentMode: RawDeployment
spec:
  predictor:
    minReplicas: 2
    maxReplicas: 6
    scaleTarget: 70
    scaleMetric: gpu
    containers:
      - name: kserve-container
        image: rg.fr-par.scw.cloud/prod/vllm-mistral:1.2.0
        args:
          - --model=/models/mistral-large-2
          - --tensor-parallel-size=4
          - --dtype=bfloat16
          - --enable-prefix-caching
        resources:
          limits:
            nvidia.com/gpu: "4"
        ports:
          - containerPort: 8000
            protocol: TCP
            name: http
        readinessProbe:
          httpGet: {path: /health, port: 8000}
          initialDelaySeconds: 180
          periodSeconds: 15

🎬 Cas d'usage concrets

1. CHU avec assistant médecin — Mistral Large HDS-certified

CHU régional (3 sites, 2 800 médecins). Cas d'usage : assistant rédaction CR opératoires, recherche dans corpus interne (protocoles, articles), aide au codage CIM-11. Contrainte : HDS strict (hébergement données santé). Stack : OVH Healthcare HDS-certifié + Kubernetes + vLLM + Mistral Large 2 self-host. 8× H100 répartis 2 AZ Strasbourg. GuardRails internes (anonymisation à l'ingestion, blocage si PII non hashée). Audit log 5 ans WORM. Coût : 18k€/mois + 4k€/mois ops = 22k€/mois pour 2 800 médecins = 7.85€/médecin/mois. Comparaison Microsoft Copilot M365 health = impossible (pas HDS). ROI : assistant fait gagner 25 min/médecin/jour → 11.5h/an × 80€/h = 920€/an/médecin économisé.

2. Banque privée souveraine — Mistral + GuardRails internes

Banque privée 1 200 collaborateurs. Cas d'usage : assistant analyste (note de synthèse marché, comptes-rendus rdv client, due-diligence M&A). Contrainte : aucune donnée client en clair hors du périmètre banque, conformité ACPR. Stack : Scaleway Dedibox dédié + Kubernetes Rancher + vLLM + Mistral Large 2 + Cilium mTLS + Vault HSM. Multi-tenant LoRA (3 départements). Coût : 14k€/mois infra + 6k€/mois équipe ops = 20k€/mois. ROI : équivalent 28 ETP analystes-juniors.

3. Défense / collectivités — SCC, OVH GPU, Outscale SecNumCloud

Ministère des Armées / DGA — assistant pour analystes (synthèse documents OSINT, traduction multi-langues). Contrainte : SecNumCloud (ANSSI), pas de cloud US. Stack : Outscale SecNumCloud + Llama 4 self-host (license commerciale entreprise) + TGI + audit ANSSI. Air-gap partiel possible. Coût opaque mais ~ 400-800k€/an pour 5K utilisateurs (objectif souveraineté domine ROI strict).

4. Industrie sensible — AOC alimentation, brevets

Groupe agroalimentaire FR (AOC, marques mondiales). Cas d'usage : assistant R&D pour recherche dans 80 000 brevets internes + littérature scientifique. Contrainte : brevets = ne JAMAIS sortir. Stack : Kubernetes on-prem (datacenter usine), GPU H100 racks privés, vLLM, Mistral Small FT sur jargon AOC. Coût matériel CAPEX 280k€ + 50k€/an maintenance vs OPEX 320k€/an chez Scaleway. Choix on-prem retenu (déjà des admins systèmes en interne).


🛠️ Exemple end-to-end — KServe + Mistral Large + GuardRails sur Kubernetes Scaleway HDS

Contexte client : startup MedTech FR, B2B SaaS pour cabinets de radiologie. L'app analyse CR de radiologie et propose un re-formulage + check qualité. Doit être HDS-certifié.

Étape 1 — Sélection cluster HDS

Scaleway Kapsule Dedicated (Paris, HDS-certifié 2024). 3 control plane managed + 4 GPU nodes (2× A100 + 2× H100). Storage CSI chiffré (encrypted block volumes). Réseau privé VPC isolé, mTLS Cilium activé.

Étape 2 — Architecture détaillée

                    ┌─────────────────────────────────────────┐
                    │  Cabinets radio (clients)               │
                    │  Auth OIDC fédéré (cabinet → MedTech)   │
                    └────────────────────┬────────────────────┘
                                         │ HTTPS + mTLS client
                    ┌────────────────────▼────────────────────┐
                    │  Ingress (Traefik + cert-manager LE)    │
                    │  - rate limit par cabinet (200 req/min) │
                    │  - WAF anti-injection prompt            │
                    └────────────────────┬────────────────────┘

                    ┌────────────────────▼────────────────────┐
                    │  API gateway (FastAPI)                  │
                    │  - JWT validation                       │
                    │  - de-identification PII (Presidio)     │
                    │  - audit log → Loki (WORM 5 ans)        │
                    │  - GuardRails (NeMo Guardrails)         │
                    └────────────────────┬────────────────────┘
                                         │ gRPC interne mTLS
                    ┌────────────────────▼────────────────────┐
                    │  KServe — InferenceService              │
                    │  - vLLM container                       │
                    │  - Mistral Large 2 + LoRA radiologie    │
                    │  - 4× H100 (TP=4)                       │
                    │  - HPA sur GPU util                     │
                    └────────────────────┬────────────────────┘

                    ┌────────────────────▼────────────────────┐
                    │  Storage : CSI encrypted (LUKS)         │
                    │  Modèles versionnés sur S3 Scaleway     │
                    │  Backup quotidien chiffré               │
                    └─────────────────────────────────────────┘

  Observabilité parallèle :
  Prometheus → Grafana (dashboards)
  Loki → audit log + erreurs (WORM)
  Tempo → tracing (latence par cabinet, par étape)
  AlertManager → PagerDuty (on-call)

Étape 3 — Manifests clés

yaml
# namespace.yml
apiVersion: v1
kind: Namespace
metadata:
  name: ai-prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
---
# networkpolicy.yml — zero-trust
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-default
  namespace: ai-prod
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: gateway-to-vllm
  namespace: ai-prod
spec:
  endpointSelector:
    matchLabels:
      app: vllm-mistral
  ingress:
    - fromEndpoints:
        - matchLabels: {app: api-gateway}
      toPorts:
        - ports: [{port: "8000", protocol: TCP}]
          rules:
            http: [{method: POST, path: "/v1/chat/completions"}]
yaml
# guardrails.yml — config NeMo Guardrails
models:
  - type: main
    engine: openai
    model: vllm/mistral-large
    parameters:
      base_url: http://vllm-mistral.ai-prod.svc.cluster.local:8000/v1

rails:
  input:
    flows:
      - check pii leak
      - check prompt injection
  output:
    flows:
      - check phi leakage
      - check factual hallucination
yaml
# vault-secret.yml — clés HSM Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: vllm-secrets
  namespace: ai-prod
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: vllm-secrets
  data:
    - secretKey: mistral_license
      remoteRef: {key: kv/mistral/license}

Étape 4 — Monitoring & alerting

yaml
# prometheus-rules.yml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: vllm-alerts
spec:
  groups:
    - name: vllm
      rules:
        - alert: VllmHighLatency
          expr: histogram_quantile(0.95, vllm_e2e_request_latency_seconds_bucket) > 3
          for: 5m
          annotations: {summary: "vLLM p95 latency > 3s"}
        - alert: VllmGpuOom
          expr: vllm_gpu_cache_usage_perc > 0.97
          for: 2m
          annotations: {summary: "vLLM KV cache near saturation"}
        - alert: VllmPodCrashLoop
          expr: kube_pod_container_status_restarts_total{namespace="ai-prod"} > 3
          for: 10m

Dashboards Grafana clés :

  • Latence par cabinet (p50, p95, p99) avec filtre cabinet_id
  • GPU util (nvidia_smi_utilization_gpu_ratio)
  • Throughput (requêtes/s, tokens/s)
  • Erreurs taux 4xx/5xx
  • Coût estimé ($GPU_hours × $rate)

Étape 5 — Conformité HDS

Exigence HDSImplémentation
Hébergeur agrééScaleway Kapsule Dedicated (certifié)
Chiffrement at-restCSI encrypted volumes + S3 SSE-C
Chiffrement in-transitmTLS Cilium + TLS Ingress
Auth forteOIDC + MFA obligatoire
Audit logLoki + WORM S3 bucket 5 ans
SauvegardeVelero quotidien chiffré, snapshot S3
DPOnommé dans contrat
DPIArédigé, mis à jour
Sous-traitantDPA Scaleway signé + flowdown
Localisation FRToute infra Paris

Étape 6 — Runbook ops

  • Déploiement : GitOps via ArgoCD, image vLLM pinned à un SHA, rollback < 2 min via argocd app rollback.
  • Re-training LoRA : pipeline Argo Workflows mensuel, déclenche merge + push image + bump version KServe.
  • Incident : alerting → PagerDuty → on-call (24/7 pour clients health). Runbook par alerte (HighLatency, OOM, PodCrash, GpuThermal).
  • DR : second cluster Scaleway Amsterdam en cold standby, snapshot S3 cross-region quotidien. RTO 2h, RPO 24h.

🎯 Patterns courants

  • GitOps obligatoire : ArgoCD ou Flux. Pas de kubectl apply manuel en prod.
  • GPU operator NVIDIA : gère drivers, runtime container, monitoring DCGM. Ne JAMAIS installer les drivers à la main.
  • Pod anti-affinity : 1 réplica vLLM par node GPU, sinon ils se piétinent.
  • PodDisruptionBudget : minAvailable: 1 pour survivre aux drain.
  • Image registry privé : pas de pull depuis Docker Hub en prod (quota + sécurité).
  • Modèles sur volume persistant : éviter de re-downloader 50GB à chaque pod restart.
  • Secrets via Vault ou ESO : jamais en clair dans manifests.
  • Audit log immuable : Loki + S3 Object Lock pour conformité.
  • Test de charge avant rollout : k6 ou locust sur env staging avec vrai trafic répliqué.

🔄 Versions & écosystème 2026

  • Kubernetes 1.32+ : DRA (Dynamic Resource Allocation) stabilisé pour GPU.
  • KServe 0.14+ : multi-model serving, OCI model loading, support vLLM natif.
  • Ray Serve 2.40+ : alternative pythonique à KServe, excellent pour agents complexes.
  • KubeRay : opérateur Ray dans K8s, scale Ray clusters dynamiquement.
  • NVIDIA GPU Operator 24.9+ : support H200, B200.
  • DCGM Exporter : métriques GPU pour Prometheus.
  • Cilium 1.16+ : eBPF, mTLS, Gateway API, Hubble pour observabilité.
  • OpenTelemetry : standard tracing/metrics, intégration native vLLM 0.7+.
  • Hyperscalers FR souverains : Scaleway (Paris), OVH (Strasbourg, Roubaix), Outscale (Paris SecNumCloud), 3DS Outscale, NumSpot (Dassault).
  • GAIA-X : framework conformité européen, certains marchés publics l'exigent.

⚠️ Pitfalls

  1. Sous-estimer la complexité ops : K8s + GPU + monitoring = 1 ETP minimum dédié, idéalement 2.
  2. GPU thermal throttling : H100 mal ventilées passent de 700W à 400W silencieusement. Monitor nvidia_smi_temperature.
  3. Modèles non versionnés : "le modèle de prod" stocké dans un volume sans version git/dvc = perte de reproductibilité.
  4. HPA mal calibré : scale up GPU prend 5-10 min (pull image 50GB + chargement modèle). Pré-warmer ou over-provision.
  5. PodDisruptionBudget oublié : drain de node = downtime. Toujours définir PDB.
  6. Audit log non WORM : conformité KO en cas d'audit. Object Lock S3 ou Loki + immutable storage.
  7. Pas de chaos test : kill un node GPU le dimanche = chaos. Tester DR mensuellement.
  8. License OSS mal comprise : Llama 3 a clause "use AUP" (Apple, Anthropic, etc. exclus), Llama 4 idem. Vérifier compat license avec ton modèle business.
  9. RGPD oublié sur les logs : prompts utilisateurs = data perso. Pseudonymiser/anonymiser avant Loki, ou TTL court.
  10. Coût caché du networking inter-AZ : 4 GPU sur 2 AZ avec tensor-parallel = trafic inter-AZ massif. Co-localiser sur 1 AZ + DR sur autre.

💰 Pricing / ROI client

Stack production HA minimal (HDS-grade) :

ComposantCoût/mois
2× H100 (HA actif/actif, Scaleway)4 600€
Kapsule control plane dedicated300€
Block storage encrypted (1TB)80€
LB + bandwidth250€
Backup Velero + S3100€
Monitoring (Grafana Cloud ou self)400€
Vault hosted200€
Infra5 930€
Ops (1 ETP × 0.5 dédié)5 000€
Total mensuel~11 000€

Break-even vs API providers :

APIBreak-even self-host (req/mois)
Mistral Small API (3.2$/1M tok)~ 300K req
Claude Sonnet 4.6 (3$/1M in, 15$/1M out)~ 120K req
Claude Haiku 4.5 (1$/1M in, 5$/1M out)~ 350K req
GPT-4o-mini~ 800K req

⚠️ Le break-even n'est PAS le critère décisif pour un modèle frontier. Tu ne self-host pas Claude Opus 4.8 (5$/25$ /1M tok à 1M de contexte) — les poids ne sont pas open-weight. Pour les use cases ci-dessus, le self-host se compare à un modèle open-weight (Mistral, Llama 4, Qwen 3) face à une API d'inférence de ce même modèle, ou face à une API propriétaire de qualité équivalente. Si le use case exige réellement la qualité d'un frontier propriétaire (Opus, GPT-4o-pro), self-host est hors-jeu : c'est API only, et la décision se déplace de l'économie vers la conformité (cf. § Quand utiliser / éviter).

Tarification mission "build self-host LLM prod" :

  • Audit + design (10j) : 12 000€
  • Setup K8s + GPU + vLLM (15j) : 18 000€
  • Conformité HDS/RGPD (10j) : 12 000€
  • Monitoring + alerting + runbook (8j) : 9 600€
  • Doc + formation équipe (5j) : 6 000€
  • Total mission : ~ 60k€ + run rate maintenance 3-5k€/mois ou TJM ponctuel

🧪 Testing / Eval

  • Smoke test post-déploiement : 10 requêtes canoniques, comparer outputs vs référence.
  • Load test mensuel : k6 ou locust ramp 0→peak×2, mesurer p95 latence et taux erreur.
  • Chaos test trimestriel : kill node GPU, kill pod gateway, simulate AZ outage.
  • Pen test annuel : prompt injection, jailbreak, data exfiltration, escalation privilèges.
  • DR test trimestriel : restore from backup sur cluster vide, RTO < 2h vérifié.
  • Audit conformité : RGPD + HDS audit externe annuel.

🔁 Quand utiliser / éviter

Self-host quand :

  • Souveraineté / HDS / SecNumCloud requis.
  • Volume > 250K req/mois (break-even économique).
  • Data ultra-sensible (M&A, brevets, données patients).
  • Besoin LoRA spécifique non disponible en API.
  • Latence stricte < 500ms p95.
  • Tu as une équipe ops K8s + GPU.

Évite self-host quand :

  • Startup early-stage avec < 50K req/mois.
  • Pas d'équipe ops dédiée.
  • Modèle frontier nécessaire (Opus, GPT-4o-pro).
  • Budget total < 100k€/an (insuffisant pour faire correctement).
  • Use case évolue chaque mois (figer un FT model coûteux à itérer).

🏋️ Exercices

Demandants et progressifs. Le but n'est pas de copier un YAML — c'est de défendre un chiffre, casser puis réparer, et raisonner comme un architecte HDS sous audit.

Exercice 1 — Dimensionner depuis le profil de charge (pas le nombre de requêtes)

Objectif : produire un dimensionnement GPU défendable à partir d'un profil (input_tokens, output_tokens, concurrence p95), pas d'un ratio magique.

On te donne : Mistral Large self-host, 2 use cases sur le même cluster — (A) RAG support : 6k tokens in / 300 out, 40 req/s peak ; (B) génération de code : 800 in / 4k out, 8 req/s peak. Calcule pour chacun la phase dominante (prefill vs decode), estime grossièrement le KV cache nécessaire au batch p95, et déduis le nombre de H100 (80 Go) par use case. Conclus : faut-il un seul pool partagé ou deux pools séparés ?

Indice / Solution

(A) est prefill-dominé (compute-bound, KV cache modeste car output court) → bon candidat à un gros batch, throughput élevé par GPU. (B) est decode-dominé (4k tokens générés séquentiellement, KV cache qui gonfle pendant toute la génération, bandwidth-bound) → throughput par GPU bien plus faible, coût/token 3–5× supérieur. Les mélanger dans un même pool fait que les longues générations de (B) monopolisent les slots de batch et dégradent la p95 de (A). Réponse senior : deux InferenceService distincts (deux node pools, deux profils de batch/max-num-seqs), routés par modèle de charge — ou a minima priorité + quotas. Le livrable n'est pas un nombre exact mais une fourchette + l'hypothèse de concurrence explicitée, parce que la VRAM KV cache est ce qui plafonne, pas les TFLOPs.

Exercice 2 — Casser le HPA, puis le réparer

Objectif : reproduire un incident de scale-up trop lent, puis le rendre production-grade.

Déploie le manifest KServe de la doc tel quel, lance un k6 qui ramp de 0 à 3× la capacité en 90 s. Observe : la p99 explose, des 503 apparaissent pendant ~6 min. Explique la cause racine, puis corrige pour que le même test passe sous SLO.

Indice / Solution

Cause : un scale-up GPU = pull image (~50 Go) + chargement modèle en VRAM = 5–10 min (cf. Pitfall #4). Le HPA réagit trop tard ; le ramp de 90 s est terminé avant qu'un pod soit Ready. Corrections cumulables : (1) over-provisioning / minReplicas plus haut pour absorber le pic sans scale ; (2) image pré-pull via DaemonSet ou pinned sur les nodes + modèle sur PVC (pas de re-download) ; (3) pré-warm : pods chauds en standby ; (4) HPA sur une métrique prédictive (queue depth / vllm_num_requests_waiting) plutôt que GPU util réactive ; (5) PDB + surge pour ne pas perdre de capacité pendant un rollout. Le piège junior : monter maxReplicas — inutile si le temps de démarrage est le mur.

Exercice 3 — Prouver (ou réfuter) le break-even HDS

Objectif : défendre le chiffre « break-even ~250K req/mois » devant un CFO sceptique.

Reprends le tableau Pricing/ROI. Construis un modèle de coût total (infra HA + ops + conformité amortie) et trace le coût/req self-host vs API en fonction du volume, pour les deux profils de l'Exercice 1. Montre où le break-even se déplace, et quand le self-host ne se justifie jamais économiquement (mais reste obligatoire).

Indice / Solution

Le break-even n'est pas un point unique : il dépend du profil de charge (coût/token decode-dominé ≫ prefill-dominé) ET du fait que le coût ops + conformité est fixe (1–2 ETP, audit HDS annuel) donc dilué par le volume. À faible volume, le coût fixe écrase tout → API gagne. Le break-even « 250K » suppose un profil prefill-dominé ; pour un use case decode-dominé il remonte nettement. Conclusion à défendre : si la décision est purement économique et le volume < seuil, reste sur API. Le self-host se justifie économiquement seulement au-dessus du seuil propre à ton profil — et se justifie toujours, indépendamment du chiffre, quand la contrainte est HDS/SecNumCloud/souveraineté (l'API n'est alors même pas une option légale). Le livrable est un graphe + la phrase : « voici à partir de quel volume, pour notre profil, et voici pourquoi la conformité court-circuite l'argument économique ».

Exercice 4 — Chaos test inter-AZ (casse le tensor parallelism)

Objectif : démontrer empiriquement pourquoi tensor-parallel-size=4 réparti sur 2 AZ est un anti-pattern.

Déploie un modèle en TP=4 avec 2 GPU par AZ sur 2 AZ. Mesure le throughput tokens/s. Re-déploie les 4 GPU co-localisés (NVLink, 1 AZ). Compare. Puis simule une coupure réseau inter-AZ et observe le comportement.

Indice / Solution

TP fait un all-reduce à chaque layer : pour 88 layers, c'est des dizaines de milliers de synchronisations inter-GPU par requête. Sur NVLink (~900 Go/s) c'est négligeable ; sur lien inter-AZ (latence ms, bande passante 1000× inférieure) le throughput s'effondre (souvent 3–10×) et la latence devient erratique. La coupure réseau = le serveur d'inférence se bloque ou crash (un rank attend l'all-reduce d'un rank devenu injoignable). Règle d'or : TP reste intra-node (ou intra-AZ avec NVLink/NVSwitch) ; la résilience multi-AZ se fait par réplication de pods complets (data parallelism), pas par étalement du TP. L'AZ secondaire sert au DR (cold/warm standby), pas à partager un seul modèle.

Exercice 5 — Quantization sous audit qualité

Objectif : décider FP8 vs INT4 (AWQ) pour un CHU HDS, en défendant la perte de qualité par des chiffres, pas par une intuition.

Sers Mistral Large en bf16, FP8, puis AWQ INT4. Mesure pour chacun : VRAM, throughput, et qualité sur un eval set métier (aide au codage CIM-11, anonymisation PII). Recommande une config, et explique pourquoi en santé la décision n'est pas que technique.

Indice / Solution

FP8 : ~2× moins de VRAM, perte qualité souvent < 1%, quasi gratuit sur Hopper (H100). AWQ INT4 : ~4× moins de VRAM (tient sur 2 GPU au lieu de 4 → CAPEX divisé) mais perte mesurable, surtout sur les tâches sensibles (un faux négatif d'anonymisation PII = fuite de données de santé = incident RGPD/HDS). Le piège : choisir INT4 « parce que ça tient sur moins de GPU » sans eval. En santé, une dégradation sur l'anonymisation n'est pas un tradeoff coût/qualité ordinaire — c'est un risque de conformité non négociable. Réponse défendable : FP8 par défaut (gain VRAM réel, perte négligeable, mesurée), INT4 seulement si l'eval métier prouve l'absence de régression sur les tâches critiques, jamais « par défaut pour économiser ». Le livrable inclut le tableau eval + une décision tracée (DPIA-compatible).

Exercice 6 — Audit conformité : casse la chaîne WORM

Objectif : trouver et corriger une faille de conformité dans une pipeline de logs « qui semble conforme ».

On te donne une stack où les prompts utilisateurs partent dans Loki, rétention 5 ans, bucket S3. À première vue conforme HDS. Trouve les trois failles RGPD/HDS, et corrige.

Indice / Solution

Failles : (1) les prompts contiennent de la donnée de santé en clair → loguer un prompt brut = stocker de la PII non chiffrée hors du périmètre prévu (cf. Pitfall #9) ; il faut pseudonymiser/anonymiser avant Loki, ou ne loguer que des métadonnées. (2) « bucket S3 » ≠ WORM : sans Object Lock (mode compliance), un admin compromis peut altérer/supprimer les logs → l'audit tombe (cf. Pitfall #6). Il faut S3 Object Lock + politique d'immutabilité. (3) Rétention 5 ans appliquée aveuglément à la PII : le RGPD impose la minimisation — 5 ans WORM se justifie pour la traçabilité d'accès (qui a requêté quoi), pas pour le contenu des prompts, qui doit avoir un TTL court et un fondement légal. Réponse senior : séparer deux flux — audit d'accès (WORM 5 ans, métadonnées pseudonymisées) et contenu (TTL court, chiffré, finalité documentée au registre). La conformité n'est pas « tout garder longtemps » — c'est « garder le bon, le bon temps, au bon endroit ».


🎤 En entretien

Questions que ce sujet appelle quasi systématiquement à un niveau senior/staff, avec la réponse en une ligne.

  • « On a 500K req/mois, faut-il self-host ? » → « Ça dépend du profil, pas du volume : un use case decode-dominé coûte 3–5× plus cher/token qu'un prefill-dominé, donc le break-even bouge ; et si c'est HDS/souverain, la question économique est secondaire — l'API n'est pas une option légale. »
  • « Pourquoi vLLM plutôt qu'un model.generate() derrière FastAPI ? » → « Continuous batching + PagedAttention : 5–20× de throughput à latence égale, parce qu'un serveur naïf fait du static batching et gaspille le GPU en attendant la requête la plus longue. »
  • « Ton p99 explose en charge alors que le GPU util est à 60%, pourquoi ? » → « Tu es bandwidth-bound, pas compute-bound : le KV cache sature, vLLM preempt des requêtes, et l'util GPU trompeuse cache un mur mémoire — il faut regarder gpu_cache_usage_perc et le nombre de requêtes en attente, pas le % d'util. »
  • « Comment tu rends ça HA sur 2 AZ ? » → « Réplication de pods complets (data parallelism) entre AZ, jamais étalement du tensor parallelism inter-AZ — le TP fait un all-reduce par layer qui meurt hors NVLink ; l'AZ secondaire fait du DR, pas du partage de modèle. »
  • « FP8, AWQ INT4 : tu quantizes ou pas, et comment tu le défends ? » → « FP8 par défaut (gain VRAM réel, perte < 1% mesurée), INT4 uniquement si un eval métier prouve l'absence de régression sur les tâches critiques — en santé une perte sur l'anonymisation PII est un risque de conformité, pas un tradeoff coût. »

🔗 Liens

  • KServe docs (kserve.github.io)
  • Scaleway HDS — page certification
  • OVH HDS Healthcare
  • Outscale SecNumCloud
  • ANSSI — guides cloud souverain
  • HDS référentiel ASIP Santé
  • NeMo Guardrails (github.com/NVIDIA/NeMo-Guardrails)
  • Fichier voisin : 04-vllm-tgi-llamacpp.md (couche inference)
  • Fichier voisin : 02-lora-peft.md (modèles à servir)
  • Section voisine : 05-llm-ops/ (observabilité, sécurité globale)

Bibliothèque tech perso — Achref