Introduction

Avec l'explosion des frameworks distribués et des APIs d'inférence à la demande, on pourrait croire qu'il suffit de brancher un orchestrateur existant sur un cluster GPU pour faire tourner un service LLM à l'échelle.

Mais très vite, certains obstacles techniques et économiques deviennent évidents.

Les limites des API-as-a-Service

Les APIs cloud (OpenAI, Mistral, etc.) imposent rapidement des limitations de débit -- quotas par minute, par heure -- qui deviennent contraignantes dès qu'on veut scaler proprement, surtout dans une logique de service local ou souverain.

Et même lorsque les limites sont acceptables, la dépendance à une ressource distante et non maîtrisée pose des problèmes de latence, de résilience et de coût.

Instancier un serveur GPU : une fausse bonne idée ?

On pourrait croire qu'en provisionnant dynamiquement des machines avec GPU, on récupère de la liberté. Mais là encore, on se heurte à des limitations concrètes :

Mon constat de terrain

Je n'ai pas encore testé Kubernetes, Ray ou d'autres orchestrateurs distribués, mais j'anticipe une complexité inutile dans mon cas d'usage. Ce que je cherche, c'est un système :

La majorité des solutions que j'ai évaluées sont surdimensionnées, trop verbeuses à déployer, peu souveraines (dépendances cloud difficiles à auditer), et surtout peu optimisées pour un modèle local, modulaire et frugal -- ce qui est précisément l'ADN de mAIstrow.

Un orchestrateur maison, écrit en Rust

C'est dans ce contexte que j'ai décidé de concevoir mon propre orchestrateur. Un système léger, faiblement couplé, asynchrone et résilient par design, pensé pour exploiter :

L'objectif : ne plus subir les limites imposées par l'infrastructure, mais bâtir un socle logiciel qui fonctionne ici, maintenant, et demain, peu importe les moyens.

Voici les choix que j'ai faits -- et les tests que je compte réaliser pour les valider.


1. Un systeme a trois corps : Hub, Engine, Client

L'architecture repose sur trois composants :

Un client qui disparaît ? Pas grave, la tâche continue. Un Engine qui tombe ? Pas grave, la tâche est relancée ailleurs. Un Hub qui redémarre ? Les tâches en cours sont restaurées.

Ce découplage est la clé de la résilience. Le reste est une question d'implémentation.


2. La tâche comme première classe

Tout tourne autour d'un concept central : la Task.

C'est une structure Rust générique. Peu importe si c'est une requête LLM, un calcul d'embedding ou une recherche hybride -- ça reste une Task.

Chaque tâche contient un payload, un context optionnel pour la reprise, et une status persistée à chaque mise à jour. Le Hub est le seul à écrire en base. L'Engine streame simplement des updates.


3. Le Hub : cerveau unique, mémoire centralisée

Le Hub reçoit toutes les requêtes. Il suit chaque tâche, ping chaque Engine, mesure les temps de latence, relance si besoin. Il gère les timeouts, les priorités, et peut décider de demander plus de puissance.

Pas besoin de Kafka. Pas besoin de Ray. Tout est streame, controle et relancable via un simple timer et une table SQL.


4. L'Engine : sans mémoire, mais pas sans honneur

Les Engine-services sont conçus pour mourir proprement. Ils ne détiennent aucun état durable. Ils ne font que recevoir une Task, l'exécuter, streamer les résultats, dire "done", et potentiellement s'arrêter s'ils ne servent plus à rien.

Ce design permet une scalabilité native. Une commande du Hub suffit à instancier un nouveau process GPU sur un serveur Scaleway, OVH ou même local.


5. Resilience sans surcharge

Ce système supporte la déconnexion du client, du moteur ou même du hub -- sans jamais casser la logique métier.

La base est mise à jour au fil de l'eau, chaque Engine envoie son état, et si plus rien ne se passe, le Hub relance automatiquement. Simple, lisible, testable.


Et maintenant ?

J'ai commencé à prototyper ce système en Rust, avec une stack simple (Tokio, SQLx, Actix ou Axum). L'objectif est de le rendre auto-déployable, observé et prédictif, via logs, seuils dynamiques et métriques.

Le design est intentionnellement minimaliste. Ce n'est pas un framework générique : c'est un outil forgé pour un besoin précis, celui d'orchestrer de l'inférence LLM sur des ressources hétérogènes, avec la résilience comme contrainte première.

La suite de cette série abordera l'architecture distribuée, les comparaisons avec Kafka, Pulsar et Ray, et les détails de l'implémentation asynchrone en Rust.