Projektowanie systemów wielomodelowych: Kiedy jeden model to za mało

Wybierz najprostszy wzorzec, który spełnia zadanie.

Page content

Systemy oparte na pojedynczym modelu są proste. Systemy wielomodelowe są potężne. Wyzwanie nie polega na wyborze modeli – ale na zaprojektowaniu architektury, która je orkiestruje.

System wielomodelowy nie dotyczy posiadania większej liczby modeli. Chodzi o to, aby mieć odpowiedni model do odpowiedniego zadania w odpowiednim czasie.

Wzorce projektowania systemów wielomodelowych LLM

Wzorce architektoniczne

Pięć wzorców pokrywa większość zastosowań:

Wzorzec Złożoność Kiedy stosować Kompromis
Pojedynczy model Najniższa Prototypowanie, proste zadania Ograniczone możliwości
Sekwencyjny Niska Prace wieloetapowe Wyższa latencja
Równoległy Średnia Niezależne zadania Wyższy koszt
Hierarchiczny Wysoka Złożone rozumowanie Złożona orkiestracja
Zbiorowy (Ensemble) Najwyższa Krytyczne decyzje Najwyższy koszt

Wybierz najprostsze rozwiązanie, które działa. Złożoność jest realna i narasta kaskadowo.

Architektura sekwencyjna

Przetwarzaj zadania za pomocą łańcucha modeli, gdzie każdy specjalizuje się w konkretnym etapie.

Wzorzec 1: Linia produkcyjna (Pipeline)

Wzorzec linii produkcyjnej – wyjście jednego modelu jest wejściem dla następnego:

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

Latencja się kumuluje. Trzy modele w sekwencji oznaczają trzykrotnie większą latencję. Stosuj ten wzorzec tylko wtedy, gdy każdy etap faktycznie wymaga innego modelu.

Wzorzec 2: Router

Wzorzec routera – sklasyfikuj zadanie, a następnie skieruj je do specjalisty:

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)

Klasyfikator jest elementem najsłabszym. Jeśli sklasyfikuje zadanie błędnie, zostanie ono skierowane do niewłaściwego modelu, co pogorszy jakość wyniku. Używaj klasyfikatora, który jest wystarczająco dobry – nawet mały model zadziała, o ile kategorie są jasno zdefiniowane.

Architektura równoległa

Przetwarzaj niezależne zadania jednocześnie.

Wzorzec 1: Rozrzut (Fan-Out)

Rozrzut – uruchom ten sam prompt w wielu modelach:

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)

Przydatne do porównań, testów A/B lub kiedy chcesz wybrać najlepszy wynik. Jest to kosztowne, ale zysk na jakości jest warty ceny w przypadku krytycznych decyzji.

Wzorzec 2: Głosowanie (Voting)

Głosowanie – łączenie wyników poprzez konsensus:

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]

Głosowanie większościowe działa w przypadku klasyfikacji. W zadaniach generatywnych jest trudniejsze – potrzebujesz podobieństwa semantycznego, a nie dokładnych zbieżności.

Architektura hierarchiczna

Używaj modeli na różnych poziomach abstrakcji.

Wzorzec 1: Planista-Executor

Planista-executor – potężny model planuje, a mniejsze modele wykonują zadania:

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}")

Planista wykonuje główną pracę. Executorzy zajmują się konkretnymi zadaniami. Ten wzorzec sprawdza się dobrze, gdy etap planowania jest kosztowny, ale etapy wykonania są tanie.

Wzorzec 2: Przewodnik-Pracownik

Przewodnik-pracownik – przewodnik deleguje zadania i przegląda wyniki:

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}")

Przewodnik jest wąskim gardłem. Planuje, deleguje i przegląda. Upewnij się, że jest wystarczająco szybki, w przeciwnym razie cały system spowolni.

Architektura zbiorowa (Ensemble)

Łącz wiele modeli dla krytycznych decyzji.

Wzorzec 1: Zbiorowość ważona

Zbiorowość ważona – oceń wynik każdego modelu i wybierz najwyższy:

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)

Wagi odzwierciedlają Twój poziom zaufania do każdego modelu. Dostosuj je na podstawie rzeczywistych wyników, a nie benchmarków.

Wzorzec 2: Zbiorowość konsensusowa

Zbiorowość konsensusowa – wymagaj zgodności, a w razie jej braku eskaluj proces:

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)

Próg kontroluje, jak rygorystyczny musi być konsensus. Wartość 0,7 oznacza zgodność dwóch trzecich. Obniż go dla szybszych decyzji, podnieś dla wyższego poziomu pewności.

Kiedy systemy wielomodelowe mają sens

Systemy wielomodelowe mają sens, gdy masz mieszane obciążenia, potrzebujesz wysokiej jakości dla krytycznych decyzji lub optymalizujesz koszty lub latencję.

Nie mają sensu, gdy wszystkie zadania mają podobną złożoność, znajdujesz się w fazie prototypowania lub prostota jest ważniejsza niż optymalizacja.

Zasada kciukowa: zacznij od jednego modelu. Dodawaj kolejne, gdy natrafisz na realne ograniczenie – koszt, latencję lub jakość. Nie projektuj złożoności, zanim jej nie potrzebujesz.

Kompromisy

Wzorzec Koszt Latencja Jakość Złożoność
Pojedynczy model Najniższy Najniższa Zmienna Najniższa
Sekwencyjny Średni Wysoka Wysoka Średnia
Równoległy Wysoki Niska Wysoka Średnia
Hierarchiczny Wysoki Wysoka Najwyższa Wysoka
Zbiorowy (Ensemble) Najwyższy Średnia Najwyższa Najwyższa

Każdy wzorzec wiąże się z pewnymi kompromisami. Wybierz ten, który najlepiej odpowiada Twoim ograniczeniom.

Powiązane

Subskrybuj

Otrzymuj nowe wpisy o systemach, infrastrukturze i inżynierii AI.