PI-GROUPE
IAKubernetesInfrastructureArchitectureCloudMLOps

Infrastructure d'IA sur site ou hybride : bâtir un socle Kubernetes qui tient la charge GPU

Rapatrier l'inférence et l'entraînement derrière son pare-feu ne se résume pas à acheter des cartes graphiques. Ordonnancement, partage de GPU, stockage, réseau, souveraineté : voici l'architecture qui fonctionne et les pièges qui coûtent cher.

La question ne se pose plus en termes de mode. Trois contraintes poussent aujourd'hui les directions techniques à ramener une partie de leurs charges d'intelligence artificielle sur leur propre infrastructure : la confidentialité des données, le coût récurrent de l'inférence facturée au jeton, et la latence des appels sortants.

Reste que déployer un modèle derrière son pare-feu diffère profondément d'un appel d'API. Les charges d'IA malmènent les hypothèses sur lesquelles reposent la plupart des clusters Kubernetes : elles réclament des ressources indivisibles, des volumes de données considérables, et supportent mal la préemption.

Ce qui change par rapport à une charge applicative classique

Un conteneur applicatif se découpe finement. Vous lui allouez 250 millicœurs et 512 Mio, le planificateur le place où il trouve de la place, et vous surcommittez sans grand risque.

Un GPU ne se découpe pas ainsi. Le nvidia.com/gpu reste une ressource entière, non surcommittable par défaut. Trois conséquences immédiates.

La granularité d'allocation devient grossière. Un pod qui réclame un GPU l'accapare, même s'il n'en exploite que 12 %. Sur une carte à 25 000 euros, le gâchis se chiffre vite.

Le temps de démarrage explose. Une image contenant CUDA, PyTorch et les poids d'un modèle de 30 Gio ne se télécharge pas en deux secondes. Sans stratégie de pré-chargement, vous attendez plusieurs minutes avant le premier jeton.

L'ordonnancement devient un problème de lots. Un entraînement distribué sur huit GPU ne démarre que si les huit sont disponibles simultanément. Le planificateur par défaut, qui place les pods un par un, provoque des interblocages : quatre pods réservent quatre GPU et attendent indéfiniment les quatre autres.

Le socle : opérateur GPU et runtime de conteneur

Tout commence par l'exposition correcte du matériel. L'opérateur GPU de NVIDIA installe et maintient la chaîne complète — pilote, runtime conteneur, greffon de périphérique, exportateur de métriques — sans intervention manuelle sur les nœuds.

yaml
# valeurs de l'opérateur GPU, via Helm et gérées par ArgoCD
driver:
  enabled: true
  version: "580.65.06"
  rdma:
    enabled: true          # indispensable pour l'entraînement multi-nœuds
toolkit:
  enabled: true
devicePlugin:
  config:
    name: config-gpu
    default: partage-temporel
migManager:
  enabled: true            # découpage matériel sur A100, H100, H200
dcgmExporter:
  enabled: true
  serviceMonitor:
    enabled: true
nodeStatusExporter:
  enabled: true

Le point structurant se trouve dans devicePlugin.config. C'est lui qui détermine comment vous partagez une carte entre plusieurs charges.

Trois stratégies de partage, trois usages

MécanismeIsolationCas d'usageLimite
MIG — découpage matérielForte : mémoire et calcul cloisonnésInférence mutualisée, multi-locatairesRéservé aux A100, H100, H200 ; profils figés
Partage temporelAucune : les contextes s'entrelacentDéveloppement, notebooks, prototypageUn voisin bruyant dégrade tout le monde
MPSMoyenne : processus concurrentsPetites inférences homogènesPas d'isolation mémoire stricte

En production multi-locataires, MIG constitue le seul choix défendable. Un profil 1g.10gb garantit à chaque locataire sa mémoire et ses unités de calcul. Sur une H100 de 80 Gio, vous obtenez sept instances indépendantes — et sept charges d'inférence qui ne se marchent pas dessus.

yaml
# Découpage MIG déclaré par étiquette de nœud, appliqué par le mig-manager
apiVersion: v1
kind: ConfigMap
metadata:
  name: config-mig-personnalisee
  namespace: gpu-operator
data:
  config.yaml: |
    version: v1
    mig-configs:
      inference-dense:
        - devices: [0, 1, 2, 3]
          mig-enabled: true
          mig-devices:
            "1g.10gb": 7
      entrainement:
        - devices: [4, 5, 6, 7]
          mig-enabled: false

L'étiquette nvidia.com/mig.config=inference-dense bascule le nœud. Le mig-manager draine, reconfigure et réintègre — opération qui reste perturbante, à planifier hors des heures de charge.

Ordonnancement : abandonner le planificateur par défaut

Pour l'entraînement distribué, le planificateur natif ne convient pas. Il lui manque la notion d'ordonnancement par groupe : soit tous les pods d'un travail obtiennent leurs ressources, soit aucun ne démarre.

Deux options éprouvées : Volcano et Kueue. Kueue a l'avantage de s'intégrer nativement aux objets Job et aux files de ressources, sans introduire de type de pod parallèle.

yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: file-recherche
spec:
  namespaceSelector: {}
  cohort: gpu-partage
  resourceGroups:
    - coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
      flavors:
        - name: h100-sxm
          resources:
            - name: "nvidia.com/gpu"
              nominalQuota: 16
              borrowingLimit: 8      # emprunt aux autres files de la cohorte
            - name: "cpu"
              nominalQuota: 512
            - name: "memory"
              nominalQuota: 4Ti
  preemption:
    reclaimWithinCohort: Any
    withinClusterQueue: LowerPriority

Le mécanisme de cohorte résout le problème politique autant que technique : l'équipe recherche dispose de seize GPU garantis, peut en emprunter huit à la production quand celle-ci tourne au ralenti, et les rend dès que la production les réclame. Vous évitez ainsi la partition rigide qui laisse dormir des cartes hors de prix.

Le stockage, angle mort de la plupart des projets

Les architectures échouent rarement sur le calcul. Elles échouent sur l'accès aux données.

Un entraînement qui lit un jeu de données de 4 Tio à travers NFS sature le réseau et laisse les GPU inactifs. Le symptôme se lit directement dans les métriques : DCGM_FI_DEV_GPU_UTIL plafonne à 30 % pendant que la carte réseau est saturée.

Trois strates à distinguer :

Le stockage objet conserve les jeux de données bruts et les poids de modèle. MinIO ou Ceph RGW sur site. Accès via S3, versionnement, réplication.

Le cache local sur NVMe alimente l'entraînement. Un local-path-provisioner pointant sur des SSD NVMe, avec pré-chargement du jeu de données avant démarrage du travail. Le débit passe de 1 Gio/s en NFS à 12 Gio/s en local.

Le stockage partagé performant sert les cas où le jeu de données dépasse le disque local. CephFS avec Rook, ou un système parallèle type Lustre pour les charges les plus exigeantes.

yaml
# Pré-chargement du jeu de données avant l'entraînement,
# exécuté comme conteneur d'initialisation
initContainers:
  - name: hydrater-cache
    image: registry.interne/mc:RELEASE.2026-08-14
    command:
      - sh
      - -c
      - |
        set -euo pipefail
        mc alias set depot http://minio.stockage.svc:9000 "$CLE" "$SECRET"
        mc mirror --overwrite --retry \
          depot/jeux-donnees/vision-2026/ /cache/vision-2026/
        echo "Cache hydraté : $(du -sh /cache/vision-2026 | cut -f1)"
    volumeMounts:
      - name: cache-nvme
        mountPath: /cache

Réseau : RDMA n'est pas une option au-delà de quatre nœuds

Sur un entraînement distribué, la phase de synchronisation des gradients domine le temps de cycle. En Ethernet classique à 25 Gbit/s, cette phase consomme jusqu'à 60 % du temps total. Les GPU attendent le réseau.

La réponse tient en deux briques : RoCEv2 ou InfiniBand côté matériel, et l'opérateur réseau de NVIDIA côté Kubernetes, qui expose les interfaces RDMA aux pods via des périphériques secondaires.

yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: rdma-entrainement
  namespace: ia-production
spec:
  config: |
    {
      "cniVersion": "0.3.1",
      "type": "sriov",
      "name": "rdma-entrainement",
      "ipam": { "type": "whereabouts", "range": "10.56.0.0/16" }
    }

Le pod déclare ensuite l'annotation k8s.v1.cni.cncf.io/networks: rdma-entrainement et réclame la ressource rdma/hca_shared_devices_a. NCCL détecte automatiquement le transport et bascule dessus.

Mesurez avant d'investir. Un nccl-tests de type all_reduce_perf sur votre topologie réelle vous dira en dix minutes si le réseau constitue votre goulot. Sur huit nœuds équipés de H100, on attend au-delà de 300 Gio/s en bande passante agrégée. En dessous de 50, quelque chose ne va pas.

L'hybridation : quelle charge reste où

L'architecture purement locale se heurte à un mur : les pics. Vous ne dimensionnez pas un parc pour absorber le pic d'un trimestre. L'hybridation répond à cette élasticité.

Le partage se raisonne selon deux axes — la sensibilité des données et la variabilité de la charge.

ChargeEmplacementRaison
Inférence sur données confidentiellesSur siteAucune donnée ne sort du périmètre
Entraînement sur données internesSur siteVolume important, sortie coûteuse
Réglage fin ponctuel de grands modèlesCloud, à la demandeBesoin de 64 GPU pendant trois jours par trimestre
Recherche exploratoireCloud, instances interruptiblesTolère la préemption, coût divisé par trois
Inférence publiqueCloud, proche des utilisateursLatence géographique

Techniquement, deux clusters distincts fédérés par GitOps donnent de bien meilleurs résultats qu'un cluster étiré entre site et cloud. La latence entre plans de contrôle rend ce dernier fragile : une coupure de liaison et votre etcd perd son quorum.

text
dépôt-gitops/
├── socle/
│   ├── gpu-operator/
│   ├── kueue/
│   └── observabilite/
├── clusters/
│   ├── onprem-dc1/          # H100, données sensibles
│   │   └── kustomization.yaml
│   ├── onprem-dc2/          # A100, recette
│   └── cloud-burst/         # provisionné à la demande
└── charges/
    ├── inference-interne/   → onprem-dc1 uniquement
    ├── entrainement/        → onprem-dc1, onprem-dc2
    └── recherche/           → cloud-burst

Chaque cluster reste autonome, avec son propre etcd et son propre plan de contrôle. ArgoCD, déployé sur un cluster de gestion, réconcilie les trois. Une coupure réseau isole un cluster sans le casser.

Observabilité : trois signaux qui comptent vraiment

Les tableaux de bord GPU regorgent de métriques inutiles. Trois suffisent à détecter l'essentiel.

L'utilisation effective du calcul — DCGM_FI_PROF_SM_ACTIVE — et non GPU_UTIL, qui ne mesure que l'existence d'un noyau actif. Une carte peut afficher 100 % d'utilisation avec 4 % d'activité réelle des multiprocesseurs.

La mémoire allouée face à la mémoire disponible — DCGM_FI_DEV_FB_USED. Un dépassement mémoire tue le processus sans ménagement, et les journaux d'entraînement disparaissent avec lui.

Le temps d'attente d'ordonnancement — exposé par Kueue via kueue_pending_workloads. Si vos travaux attendent trois heures en file, votre problème n'est pas la puissance de calcul mais la politique d'allocation.

promql
# Cartes réservées mais réellement inexploitées — le gâchis en euros
(
  count by (node) (DCGM_FI_DEV_FB_USED > 0)
  -
  count by (node) (DCGM_FI_PROF_SM_ACTIVE > 0.15)
) > 0

Cette requête révèle souvent des surprises : des notebooks oubliés qui immobilisent une carte depuis douze jours.

Le coût réel, chiffré

Un serveur à huit H100 SXM coûte autour de 280 000 euros, amorti sur quatre ans, soit environ 5 800 euros par mois. Ajoutez l'électricité — 10 kW en charge, soit 1 100 euros par mois à 0,15 euro le kilowattheure —, le refroidissement et l'hébergement : vous approchez 8 000 euros mensuels pour huit GPU.

La même capacité chez un fournisseur cloud se facture entre 25 000 et 45 000 euros par mois en réservation continue. L'équation bascule vers le local dès que le taux d'occupation dépasse 35 à 40 %.

En dessous, le cloud reste plus économique. Et c'est précisément là que le partage MIG et une politique de files de ressources font la différence : ils transforment un parc occupé à 25 % en un parc occupé à 70 %.

Ce que je retiens du terrain

Commencez par mesurer votre taux d'occupation réel avant d'acheter quoi que ce soit. La plupart des organisations découvrent que leurs GPU existants dorment.

Traitez le stockage et le réseau comme des sujets de premier ordre, pas comme des détails d'intendance. Un cluster de calcul mal alimenté en données coûte autant qu'un cluster bien conçu, pour la moitié du résultat.

Séparez les clusters plutôt que de les étirer. La fédération par GitOps apporte la cohérence sans la fragilité.

Enfin, automatisez la reconfiguration MIG et les files de ressources dès le départ. Ce qui reste manuel finit toujours par diverger — et sur des ressources aussi coûteuses, la dérive se paie comptant.