Diseño de sistemas multimodelos: cuando un solo modelo no es suficiente

Elija el patrón más simple que funcione.

Índice

Los sistemas de un solo modelo son simples. Los sistemas de múltiples modelos son potentes. El desafío no consiste en elegir los modelos, sino en diseñar la arquitectura que los orqueste.

Un sistema de múltiples modelos no se trata simplemente de tener más modelos. Se trata de tener el modelo adecuado para la tarea adecuada en el momento adecuado.

Patrones de diseño de sistemas LLM de múltiples modelos

Patrones de arquitectura

Cinco patrones cubren la mayoría de los casos de uso:

Patrón Complejidad Cuándo usarlo Compromiso
Modelo único Más baja Prototipado, tareas simples Capacidades limitadas
Secuencial Baja Flujos de trabajo de varios pasos Mayor latencia
Paralelo Media Tareas independientes Mayor costo
Jerárquico Alta Razonamiento complejo Orquestación compleja
Conjunto (Ensemble) Más alta Decisiones críticas Costo más alto

Elija el más simple que funcione. La complejidad es real y se acumula.

Arquitectura secuencial

Procese las tareas a través de una cadena de modelos, cada uno especializado en un paso.

Patrón 1: Canalización (Pipeline)

Patrón de canalización: la salida de cada modelo alimenta al siguiente:

class ModelPipeline:
    def __init__(self):
        self.models = [
            {"model": "qwen2.5-1.5b", "task": "classify"},
            {"model": "qwen2.5-7b", "task": "extract"},
            {"model": "qwen2.5-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 latencia se acumula. Tres modelos en secuencia significan tres veces la latencia. Úselo solo cuando cada paso necesite realmente un modelo diferente.

Patrón 2: Enrutador (Router)

Patrón de enrutador: clasifique la tarea y enrute hacia el especialista:

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

    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)

El clasificador es el eslabón débil. Si clasifica incorrectamente, enruta hacia el modelo equivocado y se pierde calidad. Use un clasificador que sea lo suficientemente bueno; incluso uno pequeño funciona si las categorías están claras.

Arquitectura paralela

Procese tareas independientes simultáneamente.

Patrón 1: Distribución (Fan-Out)

Distribución: ejecute el mismo prompt a través de múltiples modelos:

import asyncio

class ModelFanOut:
    def __init__(self):
        self.models = [
            "qwen2.5-7b",
            "qwen2.5-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)

Útil para comparación, pruebas A/B o cuando desea seleccionar la mejor salida. Es costoso, pero la ganancia en calidad vale la pena para decisiones críticas.

Patrón 2: Votación

Votación: combine las salidas mediante consenso:

class ModelVoting:
    def __init__(self):
        self.models = [
            "qwen2.5-7b",
            "qwen2.5-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]

La votación por mayoría funciona para la clasificación. Para tareas de generación, es más difícil; necesita similitud semántica, no coincidencias exactas.

Arquitectura jerárquica

Use modelos en diferentes niveles de abstracción.

Patrón 1: Planificador-Ejecutor

Planificador-Ejecutor: un modelo potente planifica y los modelos más pequeños ejecutan:

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

    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"], "qwen2.5-7b")
            result = self.call_model(executor, step["prompt"])
            results.append(result)
        return self.call_model(self.planner, f"Synthesize: {results}")

El planificador realiza el trabajo pesado. Los ejecutores manejan tareas específicas. Este patrón funciona bien cuando el paso de planificación es costoso, pero los pasos de ejecución son económicos.

Patrón 2: Supervisor-Trabajador

Supervisor-Trabajador: un supervisor delega y revisa:

class SupervisorWorker:
    def __init__(self):
        self.supervisor = "qwen2.5-32b"
        self.workers = ["qwen2.5-7b", "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}")

El supervisor es el cuello de botella. Planifica, delega y revisa. Asegúrese de que sea lo suficientemente rápido, o todo el sistema se ralentizará.

Arquitectura de conjunto (Ensemble)

Combine múltiples modelos para decisiones críticas.

Patrón 1: Conjunto ponderado

Conjunto ponderado: evalúe la salida de cada modelo y seleccione la más alta:

class WeightedEnsemble:
    def __init__(self):
        self.models = {
            "qwen2.5-32b": 0.5,
            "claude-sonnet-4": 0.3,
            "qwen2.5-7b": 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)

Los pesos reflejan su confianza en cada modelo. Ajústelos según el rendimiento real, no según los benchmarks.

Patrón 2: Conjunto por consenso

Conjunto por consenso: requiera acuerdo y escale si no lo hay:

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

    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("qwen2.5-32b", prompt)

El umbral controla qué tan estricto es el consenso. 0.7 significa un acuerdo de dos tercios. Redúcelo para decisiones más rápidas o aumentarlo para mayor confianza.

Cuándo tienen sentido los sistemas de múltiples modelos

Los sistemas de múltiples modelos tienen sentido cuando tiene cargas de trabajo mixtas, necesita alta calidad para decisiones críticas o está optimizando el costo o la latencia.

No tienen sentido cuando todas las tareas tienen una complejidad similar, está prototipando o la simplicidad es más importante que la optimización.

La regla general: comience con un solo modelo. Agregue más cuando se enfrente a una restricción real: costo, latencia o calidad. No diseñe complejidad antes de necesitarla.

Compromisos (Trade-offs)

Patrón Costo Latencia Calidad Complejidad
Modelo único Más bajo Más baja Variable Más baja
Secuencial Medio Alta Alta Media
Paralelo Alto Baja Alta Media
Jerárquico Alto Alta Más alta Alta
Conjunto (Ensemble) Más alto Media Más alta Más alta

Cada patrón intercambia algo. Elija el que se ajuste a sus restricciones.

Relacionado

Suscribirse

Recibe nuevas publicaciones sobre sistemas, infraestructura e ingeniería de IA.