Planification des tâches pour les clusters d'IA : SLURM, Kubernetes, Ray et savoir quand s'en passer.

Un planificateur détermine quelle tâche s'exécute sur quels GPU, quand et à quel coût. Sans lui, un cluster partagé se transforme en une situation où les utilisateurs s'envoient des messages sur Slack pour savoir si quelqu'un utilise le nœud 3. Avec un planificateur inadapté, vous passerez plus de temps à rédiger des fichiers YAML qu'à modéliser des données.

Cet article compare les planificateurs réellement utilisés — SLURM, Kubernetes (avec Kueue ou Volcano), Ray et KubeRay, ainsi que les solutions commerciales Run:ai et Determined — et reconnaît que la meilleure solution peut être « rien, juste SSH ». Le lecteur a lu K01 , K02 et K03 et cherche maintenant à déterminer quel système installer sur un cluster de 1 à 32 nœuds.

Que fait réellement un planificateur ?

Trois tâches, plus ou moins par ordre de difficulté : placer le travail sur les ressources libres, mettre en file d’attente le travail qui ne peut pas encore être effectué et coordonner les tâches multi-nœuds — l’entraînement distribué nécessite que tous les N rangs démarrent en même temps (planification des gangs), car un PyTorch dist.barrier() bloque indéfiniment si un rang ne se présente jamais (voir K06).

Tout cluster dépassant le cadre d'une seule équipe nécessite également des quotas multi-locataires avec emprunt, une décroissance équitable entre les équipes, la préemption et une comptabilisation GPU appropriée (afin que la tâche voie CUDA_VISIBLE_DEVICES (Configuration correcte). Chaque planificateur ci-dessous effectue les trois premières opérations. Leur différence réside dans la deuxième liste et le type de tâches qu'ils supposent exécuter.

La grande distinction : traitements par lots contre services de longue durée

Il s'agit là du cadre le plus important. Les tâches par lots s'entraînent pendant 8 heures puis s'arrêtent, ou exécutent un balayage des hyperparamètres de 200 tâches courtes, ou traitent un ensemble de données pendant la nuit — début, fin, résultat, rien ne répond au protocole HTTP. Les services à longue durée de vie sont un point de terminaison vLLM assurant l'inférence 24h/24 et 7j/7, une base de données de mémoire de scène, une pile de surveillance — censés rester opérationnels en permanence et redémarrer en cas de panne.

SLURM a été conçu pour le traitement par lots. Kubernetes a été conçu pour les services de longue durée. Tout le reste consiste à étendre l'un des deux pour assurer l'autre moitié du travail.

Charge de travail SLURM Kubernetes
Course d'entraînement de 8 heures Originaire Il faut Kueue ou Volcan
balayage des hyperparamètres de 200 tâches Originaire Gênant
Point final d'inférence vLLM 24h/24 et 7j/7 Gênant Originaire
Mixte : formation + inférence + outils Douloureux Native (avec Kueue)
Formation distribuée multi-nœuds Autochtone (gang) Nécessite le plugin gang
Services de redémarrage automatique ayant planté Non Originaire

Si votre cluster n'effectue qu'une seule de ces tâches, le choix est simple. S'il effectue les deux (ce qui est de plus en plus courant), la question est de savoir pour quel planificateur vous êtes prêt à accepter le surcoût opérationnel.

SLURM — le choix par défaut en HPC qui reste la référence pour l'entraînement pur

SLURM est utilisé par plus de 65 % des supercalculateurs du TOP500 et par la plupart des programmes d'entraînement de pointe publiés. Pour les charges de travail du type « soumettre une tâche d'entraînement, attendre les résultats, obtenir le point de contrôle », il s'avère être la solution idéale depuis vingt ans.

Le modèle mental est simple : un partition est un pool de nœuds nommé (gpu-5090, cpu-only, bigmem); à JOB est un script shell avec #SBATCH directives, soumises avec sbatch; GRES (Ressources génériques) suit les GPU — --gres=gpu:rtxpro6000:2 demande deux cartes spécifiques ; planification des gangs est intégré.

Un minimum slurm.conf pour un cluster à 4 nœuds avec 8 cartes RTX Pro 6000 Blackwell par nœud :

ClusterName=kentino-lab
SlurmctldHost=head01

SchedulerType=sched/backfill
SelectType=select/cons_tres
SelectTypeParameters=CR_Core_Memory
GresTypes=gpu

# GPU accounting — without these two lines, fair-share runs on CPU-seconds and is meaningless
AccountingStorageType=accounting_storage/slurmdbd
AccountingStorageTRES=gres/gpu
PriorityType=priority/multifactor
PriorityDecayHalfLife=7-0
PriorityWeightFairshare=10000
PriorityWeightAge=1000

# cgroups isolate the GPUs a job is allowed to see
ProctrackType=proctrack/cgroup

NodeName=node[01-04] CPUs=128 RealMemory=1024000 Sockets=2 \
    CoresPerSocket=64 ThreadsPerCore=1 Gres=gpu:rtxpro6000:8 State=UNKNOWN

PartitionName=train Nodes=node[01-03] Default=YES MaxTime=48:00:00 State=UP
PartitionName=interactive Nodes=node04 MaxTime=04:00:00 \
    PriorityTier=10 State=UP

L'appariement gres.conf sur chaque nœud :

AutoDetect=nvml
NodeName=node[01-04] Name=gpu Type=rtxpro6000 File=/dev/nvidia[0-7]

AutoDetect=nvml Il interroge directement la bibliothèque de gestion NVIDIA, récupère automatiquement les tranches MIG et vous évite de configurer manuellement les chemins d'accès aux fichiers de périphériques.

Un utilisateur soumet :

#!/bin/bash
#SBATCH --job-name=qwen-finetune
#SBATCH --partition=train
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=8
#SBATCH --gres=gpu:rtxpro6000:8
#SBATCH --time=12:00:00
#SBATCH --output=logs/%j.out

srun python -m torch.distributed.run \
    --nnodes=2 --nproc-per-node=8 \
    --rdzv-backend=c10d \
    --rdzv-endpoint=$(scontrol show hostnames | head -1):29500 \
    train.py --config configs/qwen72b.yaml

PyTorch Elastic et SLURM se combinent parfaitement : SLURM gère l'allocation, srun lance un processus par tâche, torch.distributed.run De plus, le backend de rendez-vous gère l'attribution des rangs. En cas de suppression d'un nœud, --max-restarts on torchrun plus --requeue on #SBATCH Il reprend à partir du dernier point de contrôle. Tout le monde l'utilise de cette façon.

Ce que vous obtenez gratuitement : planification des groupes, dégradation équitable des ressources, remplissage des ressources, comptabilisation (chaque seconde d’utilisation du GPU est enregistrée). sacctSLURM offre une sémantique simple : pas de CRD, pas de contrôleur, pas de graphe d'objets YAML. En revanche, il ne prend pas en charge les services de longue durée avec redémarrage automatique, l'ingress HTTP, les mises à jour progressives, les conteneurs sidecar ni la gestion d'état déclarative. SLURM est un planificateur de tâches par lots. L'utiliser comme Kubernetes serait contre-productif.

SLURM est la solution idéale lorsque : vous effectuez des entraînements, des inférences par lots ou des simulations ; les utilisateurs écrivent des scripts shell ; un SRE à temps partiel peut l'exploiter.

Là où SLURM atteint ses limites

Trois endroits. Services de type Web — pas de concept de « fonctionnement continu, redémarrage en cas de panne, exposition du port 8000 ». Vous pouvez intégrer vLLM dans une tâche SLURM de longue durée, mais la vérification de l'état, les mises à jour progressives et l'équilibrage de charge deviennent votre problème. Charges de travail hétérogènes — SLURM suppose un seul pool de ressources ; un cluster exécutant l’entraînement + l’inférence + Jupyter + la surveillance + la préparation des données nécessite des contrôles plus précis. Isolation stricte des locataires multiples — Les cgroups isolent le CPU et la mémoire ; l’isolation du GPU repose sur CUDA_VISIBLE_DEVICES et le code utilisateur doit le respecter. Pas d'espaces de noms, pas de politique réseau par tâche, pas de gestion de conteneurs. Convient aux environnements de test ; inadapté à un service hébergé multi-clients.

Face à l'un de ces problèmes, la solution la plus judicieuse consiste généralement à ajouter une seconde couche (Kubernetes pour les services) plutôt que de surcharger SLURM. Les offres managées SUNK et 2026 de CoreWeave exécutent SLURM sur Kubernetes, permettant ainsi au même cluster de gérer les deux. À l'échelle de Kentino, deux petits clusters distincts s'avèrent souvent plus simples.

Kubernetes pour l'IA : la voie native du cloud

Kubernetes a été conçu pour assurer le fonctionnement des services web sans état. Pour les clusters d'IA, tout le reste est ajouté ultérieurement. La pile technologique de 2026 comprend : NVIDIA GPU Operator (pilotes, CUDA, NCCL, DCGM, plugin de périphérique, configuration MIG), Kueue (file d'attente, quotas, admission), Volcano (ordonnanceur prenant en charge le traitement par lots), Kubeflow Trainer (CRD PyTorchJob, TFJob, MPIJob), KubeRay (Ray sur K8s) et Karpenter ou Cluster Autoscaler pour le provisionnement des nœuds.

Pourquoi une configuration Kubernetes naïve est inadaptée à l'IA : le planificateur kube-scheduler par défaut fonctionne selon le principe du premier entré, premier sorti (FIFO), sans gestion des groupes de tâches. Il peut démarrer 7 des 8 pods d'une tâche d'entraînement distribuée et laisser le huitième en attente, tandis que les 7 pods en cours d'exécution monopolisent les GPU. Ce n'est pas un problème théorique : c'est l'erreur la plus fréquente commise par les équipes qui tentent d'exécuter un entraînement sur un cluster conçu pour l'inférence. Les solutions sont Kueue (admission au niveau de la tâche) et Volcano (placement groupé des pods). La meilleure pratique actuelle consiste à utiliser les deux : Kueue en amont et Volcano en aval.

Volcan

Volcano est un planificateur de tâches par lots CNCF qui s'installe en parallèle ou à la place de kube-scheduler. Il ajoute une véritable planification groupée avec minAvailable sémantique (admission de zéro ou N, jamais partielle), priorités et préemption des files d'attente, stratégies de partage équitable / binpack / prenant en compte la topologie et prise en charge de premier ordre pour PyTorchJob, TFJob, MPIJob, RayJob et SparkApplication.

Une file d'attente minimale et une tâche d'entraînement PyTorch planifiée en équipe :

apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
  name: training
spec:
  weight: 4
  capability:
    nvidia.com/gpu: 32
---
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: qwen-finetune
spec:
  schedulerName: volcano
  minAvailable: 16          # gang: all 16 ranks or zero
  queue: training
  policies:
    - event: PodEvicted
      action: RestartJob
  tasks:
    - replicas: 16
      name: worker
      template:
        spec:
          containers:
            - name: pytorch
              image: kentino/pytorch:2.5-cuda13
              resources:
                limits:
                  nvidia.com/gpu: 1
              command: ["torchrun", "--nnodes=16", "--nproc-per-node=1", "train.py"]

Lorsque Volcano n'est pas nécessaire : un cluster d'inférence pur exécutant des déploiements vLLM indépendants. Chaque pod possède ses propres GPU, aucune coordination entre les pods n'est requise, et le planificateur kube par défaut convient parfaitement. Volcano prend tout son sens dès que vous intégrez l'entraînement distribué au cluster.

Kueue et emprunts par quotas

Kueue gère une couche que Volcano ne gère pas : qui est autorisé à consommer quelle part du cluster, et que se passe-t-il lorsqu’une équipe est inactive ?

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: rtxpro6000
spec:
  nodeLabels:
    nvidia.com/gpu.product: "RTX-PRO-6000-Blackwell"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: team-research
spec:
  cohort: "shared-gpu"
  resourceGroups:
    - coveredResources: ["nvidia.com/gpu"]
      flavors:
        - name: rtxpro6000
          resources:
            - name: "nvidia.com/gpu"
              nominalQuota: 16
              borrowingLimit: 8

Chaque équipe du groupe bénéficie de 16 GPU garantis et peut en emprunter jusqu'à 8 à l'autre. Les tâches prioritaires utilisent la capacité empruntée avant la limite. La feuille de route 2026 de Kueue met l'accent sur MultiKueue (distribution multi-clusters) et la préemption coopérative : une tâche de sauvegarde peut recevoir l'instruction « sauvegarder l'état et céder la place dans 60 secondes » au lieu d'être interrompue brutalement.

Partage de MIG et de GPU

Le plugin NVIDIA alloue par défaut l'intégralité du GPU. Ce comportement est approprié pour l'entraînement et les inférences complexes ; en revanche, il est inefficace pour les inférences plus simples, les notebooks ou le développement. Trois modes de partage sont disponibles : MIG (isolation de la mémoire matérielle et des pannes, inférence multi-locataire en production), MPS (coopératif, sans isolation) et le partage temporel (sans isolation, réservé au développement).

La technologie MIG est disponible sur les chipsets H100/H200, A100 et B200, mais pas sur les GPU de la gamme Kentino. Les RTX 5090, RTX 4090, RTX Pro 6000 Blackwell, L40 et L4 ne prennent pas en charge MIG. Si votre conception nécessite des partitions GPU isolées matériellement, vous serez limité aux composants SXM/datacenter non fabriqués par Kentino. Le partage de temps (time-slicing) alloue N GPU virtuels par GPU physique, et le planificateur ignore la sursouscription ; utilisez-le uniquement en développement.

Points forts de Kubernetes : services d’inférence de longue durée (déploiements vLLM dimensionnés par HPA en fonction de la profondeur de la file d’attente, mises à jour progressives, contrôles d’intégrité – SLURM n’offre rien de comparable), charges de travail hétérogènes sur un seul cluster, écosystème (Prometheus, Grafana, Argo). Coût : une pile Kubernetes + GPU Operator + Kueue + Volcano + Kubeflow opérationnelle comprend au minimum 20 composants. Estimation réaliste : la charge opérationnelle de Kubernetes est 5 à 10 fois supérieure à celle de SLURM pour l’entraînement par lots.

Kubernetes est le bon choix lorsque : le cluster exécute des services d'inférence en parallèle de l'entraînement, vous avez une équipe plateforme, tout est déclaratif, ou vous avez besoin d'une portabilité multi-cluster.

Ray et KubeRay — Calcul distribué natif Python

Ray est un tout autre outil. Il ne s'agit pas vraiment d'un ordonnanceur de cluster au sens de SLURM/K8s, mais d'un environnement d'exécution Python distribué qui intègre un ordonnanceur, un système de stockage d'objets et un autoscaler. Vous écrivez du Python avec @ray.remote Les décorateurs et Ray déterminent où exécuter chaque tâche.

Points forts de Ray : optimisation des hyperparamètres (Ray Tune – des centaines d’essais en parallèle, avec élimination précoce des mauvais ; le cas d’usage pour lequel Ray a été conçu et qui reste la référence), apprentissage par renforcement (RLlib – environnements et apprenants considérés comme des acteurs, ce qui s’intègre parfaitement à Ray mais moins bien aux tâches par lots SLURM), entraînement distribué (Ray Train, encapsulant PyTorch DDP/FSDP/DeepSpeed), déploiement de modèles (Ray Serve, natif Python, pipelines multi-modèles personnalisés) et prétraitement des données (Ray Data). Ce que Ray n’est pas : un ordonnanceur de tâches par lots mutualisé pour un cluster partagé. Ray suppose qu’une seule application est propriétaire du cluster.

KubeRay est l'opérateur qui exécute Ray sur Kubernetes. Trois CRD : RayCluster (longévité), RayJob (one-shot), RayService (Ray Serve, mises à jour continues). Un RayCluster minimal :

apiVersion: ray.io/v1
kind: RayCluster
metadata:
  name: rl-cluster
spec:
  rayVersion: '2.55.0'
  headGroupSpec:
    rayStartParams: { dashboard-host: '0.0.0.0' }
    template:
      spec:
        containers:
          - name: ray-head
            image: rayproject/ray:2.55.0-py311-cu125
            resources:
              limits: { cpu: 8, memory: 32Gi }
  workerGroupSpecs:
    - groupName: gpu-workers
      replicas: 4
      minReplicas: 0
      maxReplicas: 8
      rayStartParams: {}
      template:
        spec:
          containers:
            - name: ray-worker
              image: rayproject/ray:2.55.0-py311-cu125
              resources:
                limits:
                  cpu: 16
                  memory: 128Gi
                  nvidia.com/gpu: 4

KubeRay ajuste automatiquement la taille du groupe de nœuds de calcul entre minReplicas et maxReplicas En se basant sur la vue des tâches en attente de Ray, et en l'associant à Kueue (la planification groupée RayJob + Kueue est documentée et fonctionnelle), on obtient une architecture Ray mutualisée sur un cluster Kubernetes mutualisé — la configuration de production que la plupart des entreprises utilisant Ray déploient réellement.

Quand Ray en mode autonome (sans Kubernetes) convient : dans un petit laboratoire de recherche où une seule personne gère le cluster, où le dynamisme prime sur la mutualisation et où la charge de travail est axée sur l’apprentissage par renforcement ou la recherche d’hyperparamètres. La solution hybride privilégiée par la plupart des équipes : SLURM ou Kubernetes comme couche de base gérant les nœuds et la mutualisation ; Ray est lancé dans une tâche SLURM ou un espace de noms Kubernetes pour la durée de la charge de travail d’un utilisateur. N’installez pas Ray comme ordonnanceur principal.

Run:ai et Determined — le niveau commercial

Deux solutions payantes sont régulièrement évoquées et méritent d'être abordées. Elles ciblent toutes deux le même problème : l'utilisation de Kubernetes, Volcano, Kueue et GPU Operator implique une quantité importante de YAML, et certaines organisations préfèrent acheter plutôt que de développer elles-mêmes.

NVIDIA Run:ai (acquis en 2024, rebaptisé en 2025) est un planificateur GPU natif de K8s. Fractionnement GPU répartit un seul GPU entre les charges de travail au niveau de la mémoire — 0.5 GPU Requêtes, redimensionnement dynamique, optimisation du placement de ressources : Run:ai justifie son prix dans les environnements d'entreprise où plus de 10 équipes de ML se partagent les GPU. En dessous de ce niveau, Kueue + GPU Operator offre gratuitement des fonctionnalités similaires.

Determined.AI (HPE, 2021) est une plateforme de formation gérée qui intègre le suivi des expériences, la recherche d'hyperparamètres, la gestion des points de contrôle et la formation distribuée dans un seul produit. Elle est idéale pour les équipes de recherche qui souhaitent un tableau de bord performant sans avoir à intégrer elles-mêmes cinq outils.

Le problème du cahier interactif

Un problème récurrent dans presque tous les clusters GPU partagés : les chercheurs souhaitent accéder aux GPU via JupyterHub pour le développement, ce qui doit coexister avec des tâches d’entraînement de longue durée occupant des nœuds entiers. La solution simpliste – un GPU dédié par notebook – gaspille 80 % du cluster. La solution rigoureuse : rendre les chercheurs autonomes. sbatch Tout — les transfère sur des ordinateurs portables avec des maquettes.

Trois modèles viables : SLURM salloc + Jupyter sur l'allocation (salloc --gres=gpu:1 --time=4:00:00, démarrer un serveur Jupyter sur le nœud alloué, tunneliser ; limite de temps stricte, GPU correctement pris en compte — solution la plus propre pour un cluster SLURM), Kubernetes + JupyterHub + Kueue (les modules d'ordinateurs portables passent par une file d'attente à faible priorité, les tâches d'entraînement préemptent les ordinateurs portables inactifs), ou Exécuter:ai avec fractionnement GPU (Les ordinateurs portables bénéficient chacun de 0.25/0.5 GPU). Quel que soit votre choix, fixer une limite de temps pour les sessions interactives — Un ordinateur portable sans délai d'expiration correspond à un GPU définitivement retiré du cluster.

La réalité honnête des petites équipes

La plupart des ressources en ligne concernant les planificateurs de tâches supposent un cluster de 256 GPU et une équipe dédiée. En réalité, un client Kentino dispose plutôt de 1 à 4 nœuds, de 4 à 32 GPU au total, et de 2 à 6 utilisateurs qui se connaissent et communiquent via Slack. Dans ce cas, un planificateur de tâches n'est pas nécessaire.

# user 1
ssh node01
tmux new -s my-training
CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py
# Ctrl-B D to detach

# user 2 — coordinates on Slack first
ssh node01
nvidia-smi                              # which GPUs are free?
tmux new -s other-training
CUDA_VISIBLE_DEVICES=4,5,6,7 python train.py

Voilà l'intégralité du système d'ordonnancement des tâches. SLURM devient rentable à partir de 16 GPU ou 8 utilisateurs, selon la première éventualité. En dessous de ce seuil, les coûts d'exploitation dépassent les avantages. Installez un ordonnanceur uniquement lorsque la coordination humaine devient insuffisante.

Quand chaque outil trouve sa place — le résumé

Scénario Recommandation
1 à 2 nœuds, 4 à 16 GPU, 2 à 6 utilisateurs communiquant entre eux SSH + tmux + nvidia-smi. Pas de planificateur.
Laboratoire de recherche, 4 à 32 nœuds, tâches de formation par lots SLURM. Ennuyeux, éprouvé, adapté.
Plateforme d'inférence au service du trafic client Kubernetes + opérateur GPU. Pas de planification par lots.
Cluster mixte : entraînement + inférence + outils Kubernetes + Kueue + Volcano + Kubeflow.
Python distribué lourd : renforcement par apprentissage, recherche d'hyperparamètres Ray (ou KubeRay) au-dessus de SLURM ou K8s.
Plus de 10 équipes d'apprentissage automatique se disputent les GPU et le budget pour l'outillage. Exécuter:ai sur Kubernetes.
Expériences d'entraînement gérées + suivi IA déterminée.
Plus de 200 GPU, multi-équipes, multi-charges de travail Fédéré : SLURM pour le traitement par lots, K8s pour les services.

Que faire ensuite ? — l’arbre de décision

Suivez ces étapes dans l'ordre. Le premier « oui » met fin à la conversation.

  1. Moins de 16 GPU et moins de 8 utilisateurs qui communiquent entre eux ? Planificateur : oui → non. Documentez les conventions dans un fichier Markdown. Reprenez-les en cas de rupture de coordination.
  2. Cluster exécutant des services d'inférence de longue durée ainsi que des services d'entraînement ? Oui → Kubernetes est la base. Ajoutez GPU Operator, puis Kueue, puis Volcano si l'entraînement distribué est prévu. Non → SLURM est la base.
  3. Disposez-vous d'une équipe plateforme ou d'un budget pour en créer une (0.5 à 1.0 ETP pendant six mois, 0.25 ETP en régime permanent) ? Non → restez sur SLURM quelle que soit la répartition de la charge de travail. Le surcoût lié à Kubernetes est bien réel.
  4. >10 équipes en compétition pour du temps GPU avec des quotas stricts ? Oui → évaluer Run:ai. Non → Kueue + cohortes vous permettent d'atteindre 80 % du résultat.
  5. Charge de travail importante basée sur le renforcement par apprentissage, la recherche d'hyperparamètres ou Python distribué ? Oui → Ray (KubeRay sur K8s, ou salloc + Ray sur SLURM). Non → omettre Ray.
  6. Matériel compatible MIG (cartes de centre de données) ? Non à la gamme Kentino → prévoir une allocation GPU complète ou un partage de temps uniquement en développement. Ne pas concevoir autour de MIG.

La plupart des conversations Kentino s'arrêtent à l'étape 1, 2 ou 3. Le reste n'est que décoration.

Articles associés : formation distribuée dans K02 , clusters d’inférence dans K03 , stockage en cluster dans K04 , gestion des pannes dans K06 , plafond de bande passante PCIe dans K07.


Cet article fait partie du Kentino Wiki, une série de référence sur l'intelligence artificielle, la robotique et les systèmes qui les connectent. Commentaires et corrections bienvenus à info@kentino.com.