모델 라우팅: 모든 작업에 하나의 모델을 쓰지 마세요

적합한 작업에 가장 적합한 모델.

Page content

200단어 분량의 이메일을 요약하기 위해 700억 파라미터 모델을 실행하는 것은 낭비입니다. 프로덕션 코드를 검토하기 위해 30억 파라미터 모델을 사용하는 것은 무모합니다. 대부분의 시스템은 이 두 극단 사이 어딘가에 존재하며, 바로 여기서 모델 라우팅(model routing)이 그 역할을 합니다.

모델 라우팅은 작업의 복잡도를 모델의 처리 능력에 맞게 매칭시킵니다. 이때 발생하는 트레이드오프(tradeoffs)는 현실적이지만, 그로 인해 얻는 절약 효과 또한 분명합니다.

LLM model routing strategies diagram

라우팅 문제의 본질

일반적으로 사람들은 하나의 모델을 선택하고 그대로 사용합니다. 이 방식은 비용이나 지연 시간(latency), 혹은 둘 다 문제가 발생하기 전까지는 잘 작동합니다. 대안으로는 라우터(router)를 구축하는 것이 있습니다. 라우터는 어떤 모델이 어떤 요청을 처리할지 결정하는 역할을 합니다.

실무에서 효과적인 네 가지 전략이 있습니다:

  1. 성능 기반(Capability-based) — 모델이 수행할 수 있는 작업에 따라 라우팅
  2. 비용 인지(Cost-aware) — 사용하고자 하는 비용에 따라 라우팅
  3. 지연 시간 인지(Latency-aware) — 필요한 응답 속도에 따라 라우팅
  4. 하이브리드(Hybrid) — 위 요소들을 결합

각 전략은 서로 다른 요소를 최적화합니다. 특정 전략을 선택하는 것은 대체로 어떤 요소가 가장 큰 부담이 되는지에 대한 결정입니다.

성능 기반 라우팅

가장 단순한 접근법입니다. 작업을 분류하고, 해당 작업을 처리할 수 있는 모델로 전송합니다.

작업 모델 크기 예시
분류, 태깅 1-3B Qwen2.5-1.5B, Gemma-2-2B
요약, 정보 추출 3-7B Qwen2.5-7B, Llama-3.1-8B
코드 생성 7-14B Qwen2.5-Coder-7B, DeepSeek-Coder-V2
복잡한 추론 14-32B Qwen2.5-32B, Llama-3.1-70B
창의적 글쓰기, 분석 32B+ Qwen2.5-72B, Claude, GPT-4

작업이 더 큰 모델을 필요로 하지 않는다면 사용하지 마세요. 1.5B 모델은 감정 분류(sentiment classification)를 잘 처리합니다. 다만, 일관성 있는 에세이를 작성하는 데는 한계가 있을 뿐입니다.

구현은 직관적입니다:

ROUTING_RULES = {
    "classify": {"model": "qwen2.5-1.5b", "max_tokens": 100},
    "summarize": {"model": "qwen2.5-7b", "max_tokens": 500},
    "code_review": {"model": "qwen2.5-coder-7b", "max_tokens": 2000},
    "reason": {"model": "qwen2.5-32b", "max_tokens": 4000},
    "creative": {"model": "claude-sonnet-4", "max_tokens": 8000},
}

def route_request(task_type: str) -> dict:
    return ROUTING_RULES.get(task_type, ROUTING_RULES["reason"])

여기서 주의할 점은 분류(classification) 자체입니다. 작업 유형을 잘못 분류하면 잘못된 모델로 라우팅됩니다. 코드 리뷰를 ‘요약’으로 잘못 분류하여 품질 저하를 묵인하는 시스템을 본 적이 있습니다.

비용 인지 라우팅

이 부분에서는 로컬 추론(local inference)의 장점이 빛을 발합니다. 하드웨어 원가 회수(amortization) 이후 로컬 모델은 사실상 무료입니다. 중간 수준의 API 사용량을 가정할 때, RTX 5080은 약 6개월 만에 그 비용을 상쇄합니다.

모델 입력 ($/M tokens) 출력 ($/M tokens) 로컬 비용/시간
GPT-4o $2.50 $10.00
Claude Sonnet 4 $3.00 $15.00
Qwen2.5-72B (API) $0.50 $2.00
Qwen2.5-32B (local) $0.00 $0.00 ~$0.10
Qwen2.5-7B (local) $0.00 $0.00 ~$0.05

세션당 수천 개의 요청을 처리한다면, 전기 요금 $0.05라도 $15/M tokens보다 낫습니다.

예산 기반 라우팅은 지출이 증가함에 따라 대체 모델을 사용합니다:

class CostAwareRouter:
    def __init__(self, budget_per_session: float = 0.10):
        self.budget = budget_per_session
        self.spent = 0.0
        self.models = {
            "cheap": {"model": "qwen2.5-7b", "cost": 0.0},
            "medium": {"model": "qwen2.5-32b", "cost": 0.0},
            "expensive": {"model": "claude-sonnet-4", "cost": 0.000015},
        }

    def route(self, task: str) -> str:
        ratio = self.spent / self.budget
        if ratio < 0.5:
            return self.models["expensive"]["model"]
        elif ratio < 0.8:
            return self.models["medium"]["model"]
        return self.models["cheap"]["model"]

대체 모델로 전환할수록 품질이 저하됩니다. Claude로 시작하여 Qwen-32B, 그리고 Qwen-7B로 이동합니다. 긴 세션이 끝날 즈음에는 출력 품질이 현저히 나빠집니다. 이것이 문제인지 여부는 구축 중인 서비스에 달려 있습니다.

지연 시간 인지 라우팅

인터랙티브 도구에는 빠른 첫 토큰(first token) 응답이 필요합니다. 배치 작업(batch jobs)은 기다릴 수 있습니다. 이 차이는 일반적으로 모델 크기의 5배 차이만큼 영향을 미칩니다.

사용 사례 첫 토큰 응답 완료 시간 최대 모델 크기
실시간 채팅 < 200ms < 2s < 7B
인터랙티브 도구 < 500ms < 5s < 14B
배치 처리 < 1s < 30s Any
연구/분석 < 2s < 60s Any

사용자에게 토큰을 스트리밍할 때, 사용자가 체감하는 것은 바로 첫 토큰 지연 시간입니다. 시작하는 데 0.5초가 걸리는 32B 모델은 즉시 반응하는 1.5B 모델에 비해 느리게 느껴집니다.

class LatencyAwareRouter:
    def __init__(self):
        self.model_latencies = {
            "qwen2.5-1.5b": {"first_token": 0.05, "complete": 0.5},
            "qwen2.5-7b": {"first_token": 0.15, "complete": 2.0},
            "qwen2.5-32b": {"first_token": 0.5, "complete": 10.0},
            "claude-sonnet-4": {"first_token": 0.3, "complete": 5.0},
        }

    def route(self, target_latency: float) -> str:
        for model, latencies in sorted(
            self.model_latencies.items(),
            key=lambda x: x[1]["complete"]
        ):
            if latencies["complete"] <= target_latency:
                return model
        return "qwen2.5-1.5b"

지연 시간 수치는 대략적입니다. 이는 하드웨어, 양자화(quantization), 배치 크기(batch size)에 따라 달라집니다. 반드시 자체 환경에서 측정해야 합니다.

폴백(Fallback) 전략

모델은 실패합니다. API는 속도 제한(rate-limit)을 적용합니다. 타임아웃은 발생하며, 이는 어쩔 수 없는 일입니다. 여기서 효과적인 패턴은 폴백 체인(fallback chain)입니다. 가장 이상적인 모델부터 가장 신뢰할 수 있는 모델까지 순차적으로 배치합니다:

class FallbackRouter:
    def __init__(self):
        self.fallback_chain = [
            {"model": "claude-sonnet-4", "timeout": 30},
            {"model": "qwen2.5-72b", "timeout": 60},
            {"model": "qwen2.5-32b", "timeout": 120},
            {"model": "qwen2.5-7b", "timeout": 300},
        ]

    def route_with_fallback(self, prompt: str) -> str:
        for config in self.fallback_chain:
            try:
                return self.call_model(
                    config["model"], prompt,
                    timeout=config["timeout"]
                )
            except (TimeoutError, APIError) as e:
                log.warning(f"Model {config['model']} failed: {e}")
                continue
        raise RuntimeError("All fallback models failed")

체인에 있는 마지막 모델은 로컬 모델이어야 합니다. 속도는 느리지만, 네트워크 문제나 API 키 오류로 인해 실패하지 않습니다.

라우팅이 도움이 되는 경우

워크로드가 혼합되어 있을 때 라우팅은 의미가 있습니다. 같은 시스템 내에서 분류, 요약, 추론을 모두 수행한다면 라우터는 비용과 지연 시간을 절약해 줍니다.

하지만 모든 작업의 복잡도가 동일하다면 라우팅은 의미가 없습니다. 해당 작업에 적합한 모델을 그냥 사용하세요. 라우터는 불필요한 복잡성을 추가할 뿐입니다.

초기 프로토타이핑 역시 라우팅을 생략해야 하는 또 다른 이유입니다. 먼저 하나의 모델로 작업이 제대로 작동하는지 확인한 후, 비용이나 지연 시간이 실제 문제가 되었을 때 라우팅을 추가해야 합니다.

트레이드오프

모든 라우팅 전략은 어떤 요소를 최적화하는 동시에 다른 무엇을 희석(sacrifice)합니다:

  • 단일 모델 — 가장 단순함, 가장 비쌈, 일관된 품질
  • 성능 기반 — 더 나은 비용 효율, 작업별 더 높은 품질, 중간 정도의 복잡도
  • 비용 인지 — 가장 저렴함, 품질 변동 존재, 중간 정도의 복잡도
  • 지연 시간 인지 — 가장 빠름, 품질 희생 가능성, 중간 정도의 복잡도
  • 하이브리드 — 모든 장점을 갖춤, 구현이 가장 복잡함

프로덕션 시스템은 보통 하이브리드 방식으로 수렴합니다. 성능 기반 라우팅으로 시작하고, 비용 지출이 발생하면 비용 인지를 추가하며, 사용자가 느림을 호소할 때 지연 시간 인지를 추가합니다.

관련 자료

구독하기

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