멀티 모델 시스템 설계: 하나의 모델이 부족할 때

가장 단순하게 동작하는 패턴을 선택하세요.

Page content

단일 모델 시스템은 간단합니다. 다중 모델 시스템은 강력합니다. 도전과제는 모델을 선택하는 것이 아니라, 이들을 조율하는 아키텍처를 설계하는 것입니다.

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

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

아키텍처 패턴

대부분의 사용 사례를 다루는 다섯 가지 패턴이 있습니다:

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

작동을 보장하는 가장 간단한 패턴을 선택하세요. 복잡성은 실제로 존재하며, 누적됩니다.

순차적 아키텍처

각 단계에 특화된 모델을 통해 작업 체인 형태로 처리합니다.

패턴 1: 파이프라인

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

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

지연 시간은 누적됩니다. 순차적으로 세 개의 모델을 사용하면 지연 시간도 세 배가 됩니다. 각 단계가 실제로 서로 다른 모델을 필요로 할 때만 이 패턴을 사용하세요.

패턴 2: 라우터

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

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)

분류기는 약점입니다. 분류를 잘못하면 잘못된 모델로 라우팅되어 품질이 저하됩니다. 충분히 우수한 분류기를 사용하세요 — 범주가 명확하다면 작은 모델도 작동할 수 있습니다.

병렬 아키텍처

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

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

팬아웃 — 동일한 프롬프트를 여러 모델에 통과시킵니다:

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)

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

패턴 2: 투표(Voting)

투표 — 컨센서스(합의)를 통해 출력을 결합합니다:

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]

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

계층적 아키텍처

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

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

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

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

플래너가 주요 작업을 수행합니다. 실행자들은 구체적인 작업을 처리합니다. 계획 단계는 비용이 많이 들지만 실행 단계는 저렴할 때 이 패턴이 잘 작동합니다.

패턴 2: 슈퍼바이저-워커(Supervisor-Worker)

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

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

슈퍼바이저가 병목 현상의 원인이 됩니다. 계획을 세우고, 위임하며, 검토합니다. 충분히 빠른지 확인하세요. 그렇지 않으면 시스템 전체가 느려집니다.

앙상블 아키텍처

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

패턴 1: 가중치 앙상블

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

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)

가중치는 각 모델에 대한 여러분의 확신을 반영합니다. 벤치마크가 아닌 실제 성능 기반에서 이를 조정하세요.

패턴 2: 컨센서스 앙상블

컨센서스 앙상블 — 합의가 필요하며, 없다면 에스컬레이션(상위 처리)합니다:

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)

임계값(threshold)은 컨센서스의 엄격도를 조절합니다. 0.7은 3분의 2 합의를 의미합니다. 더 빠른 의사결정을 원하면 낮추고, 더 높은 확신을 원하면 높여주세요.

다중 모델 시스템이 의미 있는 경우

다중 모델 시스템은 혼합된 워크로드를 가지고 있거나, 중요한 의사결정에 대한 높은 품질이 필요하거나, 비용이나 지연 시간 최적화를 추구할 때 의미があります.

모든 작업의 복잡도가 유사하거나, 프로토타이핑 단계이거나, 최적화보다 단순함이 더 중요할 때는 의미가 없습니다.

경험상 규칙: 하나의 모델로 시작하세요. 비용, 지연 시간, 또는 품질과 같은 실제 제약에 부딪혔을 때 더 많은 모델을 추가하세요. 필요하기 전에 복잡성을 설계하지 마세요.

트레이드오프

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

모든 패턴은 무언가를 희생합니다. 여러분의 제약 조건에 맞는 패턴을 선택하세요.

관련 문서

구독하기

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