다중 모델 시스템 설계: 하나의 모델로는 부족할 때

가장 간단한 패턴을 선택하세요.

Page content

단일 모델 시스템은 간단합니다. 다중 모델 시스템은 강력합니다. 여기서 핵심 과제는 모델을 선택하는 것이 아니라, 이러한 모델을 지휘할 아키텍처를 설계하는 것입니다.

다중 모델 시스템은 단순히 모델의 수를 늘리는 것이 아닙니다. 올바른 시간과 올바른 작업에 올바른 모델을 배치하는 것입니다.

다중 모델 LLM 시스템 설계 패턴

아키텍처 패턴

다음 다섯 가지 패턴이 대부분의 사용 사례를 커버합니다:

패턴 복잡도 사용 시기 트레이드오프
단일 모델 가장 낮음 프로토타이핑, 간단한 작업 제한된 기능
순차적 낮음 다단계 워크플로우 높은 지연 시간
병렬 중간 독립적인 작업 높은 비용
계층적 높음 복잡한 추론 복잡한 오케스트레이션
앙상블 가장 높음 중요한 의사결정 가장 높은 비용

작동하는 가장 단순한 방식을 선택하십시오. 복잡성은 실재하며, 이는 누적됩니다.

순차적 아키텍처

작업을 모델의 사슬을 통해 처리하며, 각 모델은 특정 단계에 특화되어 있습니다.

패턴 1: 파이프라인

파이프라인 패턴 — 각 모델의 출력이 다음 모델의 입력으로 전달됩니다:

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

지연 시간이 누적됩니다. 세 개의 모델을 순차적으로 사용한다는 것은 지연 시간이 세 배가 된다는 것을 의미합니다. 각 단계가 실제로 다른 모델이 필요할 때만 이 방식을 사용하십시오.

패턴 2: 라우터

라우터 패턴 — 작업을 분류하고 전문가 모델로 라우팅합니다:

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)

분류기가 약한 고리가 됩니다. 분류가 잘못되면 잘못된 모델로 라우팅되어 품질이 저하됩니다. 충분히 좋은 분류기를 사용하십시오. 범주가 명확하다면 작은 모델로도 작동합니다.

병렬 아키텍처

독립적인 작업을 동시에 처리합니다.

패턴 1: 팬아웃(Fan-Out)

팬아웃 — 동일한 프롬프트를 여러 모델에 동시에 실행합니다:

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)

비교, A/B 테스트, 또는 최상의 출력을 선택하고자 할 때 유용합니다. 비용은 높지만, 중요한 의사결정에서는 품질 향상이 그 비용을 상쇄합니다.

패턴 2: 투표(Voting)

투표 — 합의를 통해 출력을 결합합니다:

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]

다수결 투표는 분류 작업에 적합합니다. 생성 작업의 경우 더 어렵습니다. 정확한 일치보다는 의미적 유사성이 필요합니다.

계층적 아키텍처

서로 다른 추상화 수준에서 모델을 사용합니다.

패턴 1: 플래너-실행자(Planner-Executor)

플래너-실행자 — 강력한 모델이 계획을 세우고, 작은 모델들이 실행합니다:

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

플래너가 무거운 작업을 수행합니다. 실행자들은 특정 작업을 처리합니다. 이 패턴은 계획 단계가 비용이 많이 들지만 실행 단계는 비용이 적게 드는 경우에 잘 작동합니다.

패턴 2: 서번터-워커(Supervisor-Worker)

서번터-워커 — 슈퍼바이저가 위임하고 검토합니다:

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

슈퍼바이저가 병목 현상을 일으킵니다. 슈퍼바이저가 계획을 세우고, 위임하며, 검토합니다. 충분히 빠르도록 해야 하며, 그렇지 않으면 전체 시스템이 느려집니다.

앙상블 아키텍처

중요한 의사결정을 위해 여러 모델을 결합합니다.

패턴 1: 가중 앙상블(Weighted Ensemble)

가중 앙상블 — 각 모델의 출력에 점수를 매기고 가장 높은 것을 선택합니다:

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)

가중치는 각 모델에 대한 신뢰도를 반영합니다. 벤치마크가 아닌 실제 성능에 따라 이를 조정하십시오.

패턴 2: 합의 앙상블(Consensus Ensemble)

합의 앙상블 — 합의를 요구하며, 합의가 없으면 에스컬레이션합니다:

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)

임계값(threshold)은 합의의 엄격함을 제어합니다. 0.7은 3분의 2의 합의를 의미합니다. 더 빠른 의사결정을 위해 낮추거나, 더 높은 신뢰도를 위해 높일 수 있습니다.

다중 모델 시스템이 적절한 경우

혼합 워크로드가 있거나, 중요한 의사결정에 높은 품질이 필요하거나, 비용 또는 지연 시간을 최적화하려는 경우에 다중 모델 시스템이 적절합니다.

모든 작업의 복잡도가 유사하거나, 프로토타이핑 단계이거나, 최적화보다 단순함이 더 중요한 경우에는 적절하지 않습니다.

일반적인 원칙은 다음과 같습니다: 하나의 모델로 시작하십시오. 비용, 지연 시간, 또는 품질과 같은 실제 제약에 부딪혔을 때만 더 많은 모델을 추가하십시오. 필요하기 전에 복잡성을 아키텍처에 포함시키지 마십시오.

트레이드오프

패턴 비용 지연 시간 품질 복잡도
단일 모델 가장 낮음 가장 낮음 변동적 가장 낮음
순차적 중간 높음 높음 중간
병렬 높음 낮음 높음 중간
계층적 높음 높음 가장 높음 높음
앙상블 가장 높음 중간 가장 높음 가장 높음

각 패턴은 무언가를 희생합니다. 귀하의 제약 조건에 맞는 패턴을 선택하십시오.

관련 자료

구독하기

시스템, 인프라, AI 엔지니어링에 관한 새 글을 받아보세요.