Tous les articles

Orchestrer des Modèles, Pas Seulement des Conteneurs : Kubernetes à l'Ère de l'IA

Écouter l'article (expérimental) · 17:37

00:00 / 17:37

Un nœud routeur distribuant des requêtes vers un rack de serveurs de modèles aux GPU de tailles différentes, avec des lignes pointillées s'échappant du cluster vers des API de modèles externes

La plus grande partie de mon travail vit sur la couche applicative. C'est là que les produits se gagnent : les parcours, les API, les parties que les utilisateurs peuvent pointer du doigt. Beaucoup de produits reposent directement sur un seul fournisseur, un modèle OpenAI ou Anthropic derrière une seule clé, et s'en portent parfaitement bien. Mais dès qu'un produit dépasse un seul modèle, ou commence à toucher son propre matériel, il en vient à reposer sur une couche dont ses utilisateurs n'ont jamais entendu parler : celle qui décide, dans l'instant qui suit leur appui sur entrée, quel modèle leur répond, sur quel morceau de matériel, et ce qui se passe si ce matériel est saturé ou disparaît. Personne ne remercie cette couche quand elle fonctionne. Elle n'a qu'une seule exigence, et elle est non négociable : elle ne peut pas lâcher les gens qui ne savent même pas qu'ils l'utilisent.

Et il ne s'agit plus seulement des produits que nous livrons, mais aussi de la façon dont nous les construisons. Les outils de code sur ma machine font les mêmes gestes que ceux dont parle cet article : Claude Code confie les sous-tâches légères à un petit modèle rapide et garde le raisonnement dur sur un grand, Cursor route entre ses propres modèles et des API frontière selon l'action, et le récit d'Anthropic sur leur système de recherche multi-agents décrit exactement cette forme : un agent principal Opus orchestrant des sous-agents Sonnet moins chers. Le routage modèle-par-tâche devient discrètement la façon dont le logiciel se fabrique, pas seulement la façon dont il se sert.

Pendant ce temps, partout, des équipes signent avec des fournisseurs d'API frontière (OpenAI, Anthropic, Google, xAI, MiniMax) tout en hébergeant aussi des familles open-weights comme Llama, Qwen, DeepSeek ou Mistral sur les GPU qu'elles ont réussi à obtenir, et quelqu'un doit orchestrer tous ces modèles avec le matériel disponible. « Connaissez-vous bien Kubernetes » est une question de 2019. La question de 2026, c'est : étant donné un pool fixe de GPU, un ensemble de modèles open-weights que vous voulez héberger et un ensemble de modèles frontière que vous ne pouvez que louer via une API, pouvez-vous faire ressembler tout cela à un seul endpoint ennuyeux ? J'ai construit une preuve de concept fonctionnelle qui répond à la question : un routeur de modèles, un ensemble de déploiements conscients des GPU et une démo qui tourne sur un portable. Ce billet en est le raisonnement.

L'économie crie : utilisation

Un peu de contexte sur la raison pour laquelle cette compétence précise compte soudainement. Les Friday Charts de Blockworks ont fait les comptes cette semaine : les hyperscalers et les neoclouds auront dépensé environ 2 000 milliards de dollars de capex d'ici la fin de l'année, une seule armoire de GPU des nouveaux pods Vera Rubin de Nvidia est estimée à 9 millions de dollars, et la newsletter s'ouvre sur Jensen Huang déclarant que « le data center est la nouvelle unité de calcul ». La même édition note qu'un gigawatt de capacité de data center produit désormais près de 500 000 milliards de tokens, contre à peu près zéro en 2023.

Vous n'êtes probablement pas un hyperscaler, mais les chiffres restent douloureux au niveau de la rue : une seule carte haut de gamme peut coûter bien plus de 40 000 € avant même d'avoir un serveur où la mettre, et décider comment découper cette carte (entière, en deux, en septièmes) est un exercice de planification à part entière, raison pour laquelle les fabricants de GPU publient des guides de dimensionnement et des matrices de configuration pour accompagner les acheteurs dans les options.

Soyons clairs : toutes les équipes n'ont pas à s'en soucier, et ce n'est pas un choix binaire entre une API et votre propre cluster. C'est une échelle, et chaque barreau vers le bas vous donne plus de contrôle sur ce qui vit sous l'endpoint :

  • Tout API. Un modèle OpenAI ou Anthropic derrière une clé, ou un déploiement Azure OpenAI avec son catalogue de modèles figé et ses quotas de tokens par minute. Vos leviers sont les quotas, le caching et le bon tier de modèle par tâche.
  • Inférence louée. Les Hugging Face Inference Endpoints, RunPod et leurs pairs, dont j'ai utilisé plusieurs. Vous choisissez le modèle et la carte, quelqu'un d'autre fait tourner les machines, et le compteur facture à l'heure-GPU que vous soyez occupé ou à l'arrêt.
  • GPU cloud que vous opérez. Des VM GPU facturées à l'heure sur GCP, AWS ou Azure (une fois que le fournisseur vous accorde du quota, car la pénurie est réelle), ou des node pools GPU rattachés à un cluster Kubernetes managé comme GKE, EKS ou AKS, souvent à prix spot si votre charge tolère l'éviction. Le compteur ressemble à celui du barreau au-dessus ; la différence est que l'orchestration sous l'endpoint est désormais à vous.
  • Métal que vous possédez. Des cartes achetées et montées en rack sur site ou en colocation : la carte à 40 000 € de tout à l'heure, amortie au lieu de facturée au compteur. Cela paie avec une utilisation soutenue ou des contraintes dures de résidence des données, et Kubernetes tourne aussi sur du bare metal, donc les mêmes manifestes vous y suivent.

À partir du deuxième barreau, le compteur ne s'arrête jamais, et le plus grand levier qu'une équipe normale contrôle entièrement devient la fraction inutilisée de l'heure-GPU qu'elle paie déjà. L'orchestration n'est pas un raffinement de plateforme ; c'est la différence entre une flotte qui mérite son coût et un chauffage très cher. Kubernetes n'est pas le seul chemin : Slurm règne toujours sur le monde HPC, Ray apporte son propre scheduler, et chaque cloud vous vendra volontiers un endpoint managé qui cache le problème derrière une marge. Mais si votre produit tourne déjà sur Kubernetes, le geste pragmatique est de laisser la même plateforme faire aussi le bin-packing des modèles : des charges hétérogènes sur des machines hétérogènes, déclarativement, à côté des charges qu'elle exécute déjà.

Trois forces, un design

Chaque configuration multi-modèles que j'ai vue est tiraillée par les trois mêmes forces :

  • Les modèles sont hétérogènes. Certains tournent sur vos GPU (Qwen, Llama, et le reste du monde open-weights), d'autres n'existent que derrière une API (les tiers frontière, quel que soit le fournisseur avec qui vous signez). Ils diffèrent en fenêtre de contexte, support d'outils, coût, latence et implications de résidence des données.
  • Le matériel est hétérogène et fini. Une vraie flotte, c'est ce que vous avez pu obtenir : quelques A100, un pool de L4, des H100 le trimestre prochain si les dieux des commandes sourient. Chaque modèle a un ou deux placements économiquement sensés, et le reste est du gaspillage.
  • Le code produit ne veut rien savoir de tout ça. Il veut un endpoint compatible OpenAI et un nom de modèle, et il doit continuer à fonctionner quand on change de fournisseur, qu'un contrat est signé ou qu'une carte grille.

Le PoC répartit les responsabilités selon ces lignes. Kubernetes possède le placement : quel pod atterrit sur quel GPU, exprimé dans le langage qu'il parle déjà (resource requests, node selectors, taints). Un petit routeur possède la sélection : quel backend sert cette requête, avec failover. Un registre possède la politique : ce qui existe, ce que ça sait faire, ce que ça coûte. Le tout dans une ConfigMap, rechargée à chaud, pour que « on a signé avec un nouveau fournisseur » soit un kubectl apply, pas un déploiement produit.

Le placement, c'est là qu'est l'argent

La partie du PoC que je défendrais le plus fort, c'est son GPU playbook, parce que c'est là que la connaissance de Kubernetes et celle des GPU doivent se rencontrer :

Le GPU playbook du PoC : chaque charge, le matériel sur lequel elle atterrit et la technique qui la fait tenir.
Charge Matériel Technique
Qwen3-32B 2x A100-80GB Parallélisme tensoriel, TP=2, NVLink sur un seul nœud
Qwen3-8B 1x L4-24GB Quantification AWQ 4 bits pour tenir sur la carte bon marché
Embeddings Un septième d'une A100 Tranche MIG, 1g.10gb, isolée matériellement
Expériences dev GPU partagés Time-slicing, quatre charges par carte
Modèles frontière Les GPU de personne API externe derrière le même endpoint

Chaque ligne est un petit argument. Le modèle 32B ne tient pas sur une carte de 80GB avec de la marge (les poids seuls font ~64GB en bf16), donc il est shardé sur deux, et le manifeste épingle le produit GPU exact via nodeSelector, parce que « deux GPU quelque part dans le cluster » et « deux A100 reliées par NVLink sur le même nœud » sont des machines radicalement différentes. Le modèle 8B prend le chemin inverse : quantifier jusqu'à tenir sur la carte que vous pouvez réellement acheter, puis scaler horizontalement. La mémoire libérée par la quantification n'est pas qu'une question d'encombrement, c'est du KV cache, et le KV cache, c'est de la concurrence : la quantification vous achète souvent du débit, pas seulement des poids plus petits.

La ligne des embeddings est ma préférée, parce que c'est là que l'argent meurt en silence dans la plupart des clusters. Un modèle d'embeddings a besoin d'environ 2GB, et le même calcul vaut pour les rerankers, les classifieurs de garde-fous et les modèles de transcription de la classe de Whisper, ces petits services toujours actifs que tout produit d'IA accumule. Donnez à n'importe lequel d'entre eux une A100 entière et ~97% d'une carte très chère restera inactive pour toujours. MIG, Multi-Instance GPU, découpe le silicium en sept tranches isolées ; le Deployment demande nvidia.com/mig-1g.10gb: 1 comme n'importe quelle autre ressource, et une carte en sert sept, chacun isolé matériellement de ses voisins. Le time-slicing fait quelque chose de similaire pour les clusters de dev, sans aucune isolation : un processus gourmand peut mettre les autres en OOM, et c'est la raison exacte pour laquelle cette configuration du PoC est marquée dev-only.

Rien de tout cela n'est exotique. C'est du Kubernetes ordinaire (resource requests, labels, probes, disruption budgets) pointé vers une ressource inhabituelle. C'est le vrai sens de « tirer parti de Kubernetes à l'ère de l'IA » : la plateforme n'a pas changé, la charge de travail, si.

Le moteur de serving est aussi un choix

Tout ce qui tourne dans le cluster est servi par vLLM, et c'est une préférence testée, pas un défaut. La propriété qui le vend est contre-intuitive : il devient meilleur sous pression. Le continuous batching fait que plus vous lancez de requêtes concurrentes sur une instance, plus elle produit de tokens par heure-GPU ; le trafic faible est le cas pathologique, pas l'heure de pointe. J'ai aussi essayé Ollama en chemin, et la comparaison a enseigné la même leçon que la table de placement : les outils encodent des hypothèses sur leur charge de travail. Ollama est excellent pour ce pour quoi il a été construit (une personne, une machine), mais sous charge concurrente il était nettement plus lent dans mes tests, et passer rapidement d'un modèle à l'autre lui posait de vrais problèmes, puisque chaque bascule décharge un modèle et en charge un autre. Une instance vLLM n'a jamais ce problème parce qu'elle ne se change jamais : un modèle par serveur pour toute sa vie, et le routeur au-dessus décide quel serveur chaque requête mérite.

Le routage est une politique, pas un if

Le routeur parle /v1/chat/completions et résout les noms selon deux modes délibérément différents. Un nom de modèle exact (qwen3-32b) suit une chaîne de failover explicite, écrite par l'opérateur : d'abord le vLLM du cluster, puis une API frontière en débordement. Aucune astuce. Un alias (chat-default, chat-long-context) est au contraire un ensemble d'exigences (fenêtre de contexte minimale, support d'outils) plus une stratégie : cheapest-healthy trie tous les backends qualifiés par coût combiné ; prefer-cluster garde le trafic sur le matériel que vous possédez sauf si rien dans le cluster ne se qualifie, ce qui sert aussi de bouton de résidence des données.

Les alias sont la moitié qui change la façon de travailler d'une équipe. Le code produit demande un résultat, pas un fournisseur, donc « le 8B encaisse très bien ce trafic maintenant » ou « router le contexte long vers le fournisseur qui le tarife le mieux » deviennent des modifications du registre livrées en quelques secondes. Un circuit breaker par backend saute les backends malades, chaque réponse porte un en-tête x-router-backend pour que vous sachiez toujours qui a répondu, et le streaming ne bascule qu'avant le premier octet : une fois les tokens en train de couler, relancer la génération dans le dos de l'utilisateur doublerait le coût et recollerait deux réponses différentes.

Une chose que le routeur refuse de faire : la file d'attente. vLLM tient déjà la file honnête, et la cacher derrière une seconde détruirait le meilleur signal d'autoscaling du système. Ce qui m'amène au piège.

Le piège de l'utilisation GPU

Si vous ne retenez qu'un détail opérationnel de ce billet, retenez celui-ci : l'utilisation GPU est un signal d'autoscaling trompeur pour le serving de LLM. Le continuous batching garde la carte occupée, qu'elle serve deux requêtes confortablement ou qu'elle se noie sous deux cents : les deux affichent ~100%. Scalez sur l'utilisation et vous scalerez sur du bruit.

Le nombre qui suit ce que ressentent les utilisateurs, c'est la profondeur de file : le num_requests_waiting de vLLM, les requêtes admises mais pas encore planifiées dans le batch. Le PoC scale le tier de travail 8B exactement sur cette métrique via KEDA : vite vers le haut, parce que les files se composent ; lentement et pod par pod vers le bas, parce que chaque nouvelle réplique paie des minutes de chargement de modèle et que le flapping coûte cher. Et quand tout le pool de L4 est épuisé ? Ce n'est plus un problème d'autoscaling. La chaîne de failover déborde vers les API externes, le tier élastique que vous louez précisément parce que vous ne le possédez pas.

Et la couche applicative n'a pas le droit de faire comme si rien de tout cela n'existait : un goulot d'étranglement GPU finit par remonter dans le produit. Les réponses honnêtes vivent là : backpressure, timeouts, un mode dégradé. Un modèle plus petit avec une étiquette honnête vaut mieux qu'un spinner qui meurt à soixante secondes.

Ce que le PoC prouve, et ce qu'il ne prouve pas

Le tout tourne sans un seul GPU : un overlay local remplace chaque Deployment vLLM par un mock qui parle la même API et exporte les mêmes métriques, si bien que le routage, le failover et les signaux de scaling se démontrent sur un cluster kind de portable. Tuez le deployment du 32B en pleine démo et regardez la même requête revenir, servie par le tier « externe ». La logique de routage est pure et couverte de tests unitaires ; les manifestes se rendent via kustomize en CI.

Ça reste un PoC, et je préfère le dire plutôt que vous ne le découvriez : pas d'authentification, pas de rate limiting, pas de budgets par tenant, et les clés externes sont de simples Secrets. C'est la première itération de production, et rien de tout cela ne change l'architecture. Les concepts de plateforme se transfèrent presque inchangés entre distributions et clouds ; ce qui décide du succès, c'est d'avoir compris l'économie de la charge de travail avant d'écrire le YAML.

La couche applicative reste l'endroit où je passe la plupart de mes journées, et reste l'endroit où les produits se gagnent. Mais sous les produits d'IA qui dépassent un seul modèle, il y a désormais une couche qui fait le travail d'infrastructure le plus dur de la décennie : prendre une contrainte de l'ère des 2 000 milliards de capex, un zoo de modèles et un tas fixe de matériel, et les faire se comporter comme un endpoint fiable, interchangeable et ennuyeux, pour des gens qui ne sauront jamais qu'elle existe. Elle doit fonctionner. Elle ne peut pas les lâcher. Le PoC est ma façon de montrer qu'elle n'a pas à le faire.

Les questions auxquelles cet article répond

  • Faut-il ses propres GPU pour un produit d'IA, ou une clé d'API suffit-elle ?

    Une seule clé suffit pour beaucoup de produits, et le choix n'est pas binaire. C'est une échelle à quatre barreaux : tout API derrière un fournisseur unique, inférence louée sur des services comme les Hugging Face Inference Endpoints ou RunPod, GPU cloud que vous opérez vous-même, et métal que vous possédez et montez en rack. Chaque barreau vers le bas vous donne plus de contrôle sur ce qui vit sous l'endpoint, et à partir du deuxième vous payez à l'heure-GPU que vous soyez occupé ou à l'arrêt.

  • Kubernetes n'est-il pas disproportionné juste pour servir quelques modèles ?

    Ce n'est pas le seul chemin, et les alternatives sont réelles : Slurm règne toujours sur le monde HPC, Ray apporte son propre scheduler, et chaque cloud vous vendra un endpoint managé qui cache le problème derrière une marge. L'argument pragmatique en faveur de Kubernetes est plus étroit que le battage : si votre produit tourne déjà dessus, laissez la même plateforme faire le bin-packing des modèles à côté des charges qu'elle exécute déjà. Ce que le PoC utilise, c'est du Kubernetes ordinaire, à savoir resource requests, labels, probes et disruption budgets, pointé vers une ressource inhabituelle.

  • Pourquoi faire tourner vLLM dans le cluster plutôt qu'Ollama ?

    vLLM devient meilleur sous pression, et c'est la propriété qui le vend. Le continuous batching fait que plus de requêtes concurrentes produisent plus de tokens par heure-GPU, donc le trafic faible est le cas pathologique, pas l'heure de pointe. Ollama est excellent pour une personne et une machine, mais sous charge concurrente il était nettement plus lent dans mes tests, et passer d'un modèle à l'autre lui posait de vrais problèmes, puisque chaque bascule décharge un modèle et en charge un autre. Un serveur vLLM garde un seul modèle pour toute sa vie, et le routeur au-dessus décide vers quel serveur part une requête.

  • Que se passe-t-il quand tous les GPU sont déjà occupés ?

    Ce n'est plus un problème d'autoscaling mais de failover : la chaîne déborde vers les API externes, le tier élastique que vous louez précisément parce que vous ne le possédez pas. En dessous de ce point, le PoC scale le tier 8B sur le num_requests_waiting de vLLM via KEDA, vite vers le haut parce que les files se composent, lentement vers le bas parce que chaque nouvelle réplique paie des minutes de chargement de modèle. La couche applicative doit quand même répondre pour le reste avec backpressure, timeouts et un mode dégradé, parce qu'un modèle plus petit avec une étiquette honnête vaut mieux qu'un spinner qui meurt à soixante secondes.