Conception de systèmes multi-modèles : quand un seul modèle ne suffit plus

« Choisissez le motif le plus simple qui fonctionne. »

Sommaire

Les systèmes à modèle unique sont simples. Les systèmes multi-modèles sont puissants. Le défi ne réside pas dans le choix des modèles, mais dans la conception de l’architecture qui les orchestre.

Un système multi-modèles ne consiste pas à avoir plus de modèles. Il s’agit d’avoir le bon modèle pour la bonne tâche au bon moment.

Multi-model LLM system design patterns

Modèles d’architecture

Cinq modèles couvrent la plupart des cas d’utilisation :

Modèle Complexité Quand l’utiliser Compromis
Modèle unique La plus faible Prototypage, tâches simples Capacités limitées
Séquentiel Faible Flux de travail en plusieurs étapes Latence plus élevée
Parallèle Moyenne Tâches indépendantes Coût plus élevé
Hiérarchique Élevée Raisonnement complexe Orchestration complexe
Ensemble La plus élevée Décisions critiques Coût le plus élevé

Choisissez le plus simple qui fonctionne. La complexité est réelle, et elle s’accumule.

Architecture séquentielle

Traitez les tâches via une chaîne de modèles, chacun spécialisé dans une étape.

Modèle 1 : Pipeline (Chaîne de traitement)

Modèle pipeline — la sortie de chaque modèle alimente le suivant :

class ModelPipeline:
    def __init__(self):
        self.models = [
            {"model": "qwen3-1.7b", "task": "classify"},
            {"model": "qwen3-8b", "task": "extract"},
            {"model": "qwen3-32b", "task": "reason"},
        ]

    def process(self, input: str) -> str:
        current = input
        for model_config in self.models:
            current = self.call_model(
                model_config["model"],
                self.create_prompt(model_config["task"], current)
            )
        return current

La latence s’additionne. Trois modèles en séquence signifient trois fois la latence. N’utilisez ceci que si chaque étape nécessite réellement un modèle différent.

Modèle 2 : Routage (Router)

Modèle de routage — classifiez la tâche, redirigez vers le spécialiste :

class ModelRouter:
    def __init__(self):
        self.classifier = "qwen3-1.7b"
        self.specialists = {
            "code": "qwen2.5-coder-7b",
            "math": "qwen3-32b",
            "creative": "claude-sonnet-4",
            "general": "qwen3-8b",
        }

    def route(self, prompt: str) -> str:
        task_type = self.classify(prompt)
        model = self.specialists.get(task_type, self.specialists["general"])
        return self.call_model(model, prompt)

Le classificateur est le maillon faible. S’il mal classe, vous routez vers le mauvais modèle et perdez en qualité. Utilisez un classificateur suffisamment performant — même un petit modèle fonctionne si les catégories sont claires.

Architecture parallèle

Traitez les tâches indépendantes simultanément.

Modèle 1 : Fan-Out (Éventail)

Fan-out — exécutez le même prompt via plusieurs modèles :

import asyncio

class ModelFanOut:
    def __init__(self):
        self.models = [
            "qwen3-8b",
            "qwen3-32b",
            "claude-sonnet-4",
        ]

    async def process(self, prompt: str) -> list[str]:
        tasks = [self.call_model(model, prompt) for model in self.models]
        return await asyncio.gather(*tasks)

Utile pour la comparaison, les tests A/B, ou lorsque vous souhaitez choisir la meilleure sortie. C’est coûteux, mais le gain de qualité en vaut la peine pour les décisions critiques.

Modèle 2 : Vote

Vote — combinez les sorties par consensus :

class ModelVoting:
    def __init__(self):
        self.models = [
            "qwen3-8b",
            "qwen3-32b",
            "claude-sonnet-4",
        ]

    def vote(self, prompt: str) -> str:
        responses = [self.call_model(model, prompt) for model in self.models]
        from collections import Counter
        votes = Counter(responses)
        return votes.most_common(1)[0][0]

Le vote à la majorité fonctionne pour la classification. Pour les tâches de génération, c’est plus difficile — vous avez besoin de similarité sémantique, pas de correspondances exactes.

Architecture hiérarchique

Utilisez des modèles à différents niveaux d’abstraction.

Modèle 1 : Planificateur-Exécuteur

Planificateur-exécuteur — un modèle fort planifie, des modèles plus petits exécutent :

class PlannerExecutor:
    def __init__(self):
        self.planner = "qwen3-32b"
        self.executors = {
            "code": "qwen2.5-coder-7b",
            "search": "qwen3-8b",
            "math": "qwen3-8b",
        }

    def process(self, task: str) -> str:
        plan = self.call_model(self.planner, f"Plan: {task}")
        results = []
        for step in self.parse_plan(plan):
            executor = self.executors.get(step["type"], "qwen3-8b")
            result = self.call_model(executor, step["prompt"])
            results.append(result)
        return self.call_model(self.planner, f"Synthesize: {results}")

Le planificateur fait le travail le plus lourd. Les exécuteurs gèrent les tâches spécifiques. Ce modèle fonctionne bien lorsque l’étape de planification est coûteuse mais que les étapes d’exécution sont peu coûteuses.

Modèle 2 : Superviseur-Travailleur

Superviseur-travailleur — un superviseur délègue et révise :

class SupervisorWorker:
    def __init__(self):
        self.supervisor = "qwen3-32b"
        self.workers = ["qwen3-8b", "qwen2.5-coder-7b"]

    def process(self, task: str) -> str:
        assignments = self.call_model(self.supervisor, f"Assign: {task}")
        results = []
        for assignment in self.parse_assignments(assignments):
            result = self.call_model(
                assignment["worker"], assignment["task"]
            )
            results.append(result)
        return self.call_model(self.supervisor, f"Review: {results}")

Le superviseur est le goulot d’étranglement. Il planifie, délègue et révise. Assurez-vous qu’il est suffisamment rapide, sinon tout le système ralentit.

Architecture d’ensemble

Combinez plusieurs modèles pour les décisions critiques.

Modèle 1 : Ensemble pondéré

Ensemble pondéré — notez la sortie de chaque modèle, choisissez la plus élevée :

class WeightedEnsemble:
    def __init__(self):
        self.models = {
            "qwen3-32b": 0.5,
            "claude-sonnet-4": 0.3,
            "qwen3-8b": 0.2,
        }

    def decide(self, prompt: str) -> str:
        responses = {
            model: self.call_model(model, prompt)
            for model in self.models
        }
        scores = {}
        for model, response in responses.items():
            score = self.evaluate(response) * self.models[model]
            scores[response] = scores.get(response, 0) + score
        return max(scores, key=scores.get)

Les poids reflètent votre confiance dans chaque modèle. Ajustez-les en fonction des performances réelles, pas des benchmarks.

Modèle 2 : Ensemble par consensus

Ensemble par consensus — exigez un accord, escaladez s’il n’y en a pas :

class ConsensusEnsemble:
    def __init__(self, threshold: float = 0.7):
        self.threshold = threshold
        self.models = [
            "qwen3-32b",
            "claude-sonnet-4",
            "qwen3-8b",
        ]

    def decide(self, prompt: str) -> str:
        responses = [
            self.call_model(model, prompt)
            for model in self.models
        ]
        from collections import Counter
        votes = Counter(responses)
        max_votes = max(votes.values())

        if max_votes / len(self.models) >= self.threshold:
            return votes.most_common(1)[0][0]

        return self.call_model("qwen3-32b", prompt)

Le seuil contrôle la rigueur du consensus. 0.7 signifie un accord des deux tiers. Baissez-le pour des décisions plus rapides, augmentez-le pour plus de confiance.

Quand les systèmes multi-modèles ont du sens

Les systèmes multi-modèles ont du sens lorsque vous avez des charges de travail mixtes, avez besoin d’une haute qualité pour les décisions critiques, ou optimisez pour le coût ou la latence.

Ils n’ont pas de sens lorsque toutes les tâches ont une complexité similaire, que vous êtes en phase de prototypage, ou que la simplicité prime sur l’optimisation.

La règle générale : commencez avec un seul modèle. Ajoutez-en d’autres lorsque vous atteignez une contrainte réelle — coût, latence ou qualité. N’architectez pas la complexité avant d’en avoir besoin.

Compromis

Modèle Coût Latence Qualité Complexité
Modèle unique Le plus bas La plus basse Variable La plus faible
Séquentiel Moyen Élevée Élevée Moyenne
Parallèle Élevé Faible Élevée Moyenne
Hiérarchique Élevé Élevée La plus élevée Élevée
Ensemble Le plus élevé Moyenne La plus élevée La plus élevée

Chaque modèle implique un compromis. Choisissez celui qui correspond à vos contraintes.

Liés

S'abonner

Recevez de nouveaux articles sur les systèmes, l'infrastructure et l'ingénierie IA.