다중 모델 시스템 설계: 단일 모델로는 부족한 경우
작동하는 가장 단순한 패턴을 선택하라.
단일 모델 시스템은 단순합니다. 다중 모델 시스템은 강력합니다. 여기서 핵심 과제는 모델을 선택하는 것이 아니라, 이러한 모델들을 조율하는 아키텍처를 설계하는 것입니다.
다중 모델 시스템은 단순히 모델을 많이 사용하는 것에 관한 것이 아닙니다. 올바른 시점에 올바른 작업에 적절한 모델을 사용하는 것에 관한 것입니다.

아키텍처 패턴
다음 다섯 가지 패턴이 대부분의 사용 사례를 커버합니다:
| 패턴 | 복잡도 | 사용 시기 | 트레이드오프 |
|---|---|---|---|
| 단일 모델 | 최저 | 프로토타입, 단순 작업 | 제한된 성능 |
| 순차적 | 낮음 | 다단계 워크플로우 | 높은 지연 시간 |
| 병렬 | 중간 | 독립적인 작업 | 높은 비용 |
| 계층적 | 높음 | 복잡한 추론 | 복잡한 오케스트레이션 |
| 앙상블 | 최고 | 중요한 결정 | 가장 높은 비용 |
작동하는 가장 간단한 것을 선택하십시오. 복잡성은 실제 존재하며, 이는 누적됩니다.
순차적 아키텍처
각각의 단계에 특화된 모델을 통해 작업 체인을 처리합니다.
패턴 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
지연 시간(Latency)은 누적됩니다. 세 개의 모델을 순차적으로 사용하면 지연 시간도 세 배가 됩니다. 각 단계가 실제로 다른 모델이 필요할 때만 이 방식을 사용하십시오.
패턴 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: 가중 앙상블(Weighted Ensemble)
가중 앙상블 — 각 모델의 출력에 점수를 매겨 가장 높은 것을 선택합니다:
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: 합의 앙상블(Consensus Ensemble)
합의 앙상블 — 합의를 필요로 하며, 합의가 없으면 에스컬레이션합니다:
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은 삼분 의 두(2/3)의 합의를 의미합니다. 더 빠른 결정을 위해 낮추거나, 더 높은 신뢰도를 위해 높일 수 있습니다.
다중 모델 시스템이 적합한 경우
혼합 워크로드가 있거나, 중요한 결정에 높은 품질이 필요하거나, 비용 또는 지연 시간을 최적화할 때 다중 모델 시스템이 적합합니다.
모든 작업의 복잡도가 유사하거나, 프로토타입을 작성 중이거나, 최적화보다 단순함이 더 중요한 경우에는 적합하지 않습니다.
일반적인 규칙은 다음과 같습니다: 하나의 모델로 시작하십시오. 비용, 지연 시간, 또는 품질과 같은 실제 제약에 부딪혔을 때 더 많은 모델을 추가하십시오. 필요하기 전에 복잡성을 아키텍처에 포함하지 마십시오.
트레이드오프
| 패턴 | 비용 | 지연 시간 | 품질 | 복잡도 |
|---|---|---|---|---|
| 단일 모델 | 최저 | 최저 | 변동 | 최저 |
| 순차적 | 중간 | 높음 | 높음 | 중간 |
| 병렬 | 높음 | 낮음 | 높음 | 중간 |
| 계층적 | 높음 | 높음 | 최고 | 높음 |
| 앙상블 | 최고 | 중간 | 최고 | 최고 |
모든 패턴은 서로 다른 것을 트레이드오프합니다. 귀하의 제약 조건에 부합하는 것을 선택하십시오.
관련 자료
- Model Routing Strategies — 성능 기반, 비용 인지, 지연 시간 인지 라우팅
- Cost Optimization for LLM Systems — 토큰 예산 관리, 폴백 모델, 캐싱
- LLM Guardrails in Practice — 입력 검증, 출력 필터링, 안전성
- LLM Architecture — 시스템 설계의 핵심 기둥: 라우팅, 비용, 가드레일, 그리고 오케스트레이션