Routing modeli: przestań używać jednego modelu do wszystkiego
Odpowiedni model dla odpowiedniego zadania.
Uruchamianie modelu o 70 miliardach parametrów w celu podsumowania 200-znakowego e-maila jest marnotrawstwem. Zastosowanie modelu o 3 miliardach parametrów do recenzji kodu produkcyjnego jest bezmyślną ryzykownością. Większość systemów funkcjonuje gdzieś w tym spektrum – i tutaj z pomocą przychodzi routing modeli.
Dopasowuje on złożoność zadania do możliwości modelu. Kompromisy są realne, ale oszczędności również.

Problem routingu
Zazwyczaj zaczynamy od jednego modelu i trzymamy się go. Działa to dobrze, dopóki nie zauważymy kosztów, opóźnień lub obu jednocześnie. Alternatywą jest zbudowanie routera – czegoś, co decyduje, który model obsłuży dane żądanie.
W praktyce sprawdzają się cztery strategie:
- Oparta na możliwościach – routing w zależności od tego, co model potrafi
- Z uwzględnieniem kosztów – routing w zależności od tego, ile jesteś skłonny wydać
- Z uwzględnieniem opóźnień (latencji) – routing w zależności od tego, jak szybko potrzebujesz wyniku
- Hybrydowa – połączenie powyższych
Każda z nich optymalizuje coś innego. Wybór jednej to zwykle decyzja o tym, co boli najbardziej.
Routing oparty na możliwościach
Najprostsze podejście. Sklasyfikuj zadanie, prześlij je do modelu, który je obsługuje.
| Zadanie | Rozmiar modelu | Przykłady |
|---|---|---|
| Klasyfikacja, tagowanie | 1-3B | Qwen3-1.7B, Gemma-2-2B |
| Podsumowanie, ekstrakcja | 3-7B | Qwen3-8B, Llama-3.1-8B |
| Generowanie kodu | 7-14B | Qwen2.5-Coder-7B, DeepSeek-Coder-V2 |
| Złożone rozumowanie | 14-32B | Qwen3-32B, Llama-3.1-70B |
| Twórcze pisanie, analiza | 32B+ | Qwen2.5-72B, Claude, GPT-4 |
Jeśli zadanie nie wymaga większego modelu, nie używaj go. Model o 1.5 miliarda parametrów świetnie radzi sobie z klasyfikacją sentymentu. Po prostu nie napisze spójnej eseju.
Implementacja jest prosta:
ROUTING_RULES = {
"classify": {"model": "qwen3-1.7B", "max_tokens": 100},
"summarize": {"model": "qwen3-8B", "max_tokens": 500},
"code_review": {"model": "qwen2.5-coder-7b", "max_tokens": 2000},
"reason": {"model": "qwen3-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"])
Haczykiem jest sama klasyfikacja. Jeśli źle określisz typ zadania, wyślesz je do niewłaściwego modelu. Widziałem systemy, które sklasyfikowały recenzję kodu jako „podsumowanie” i cicho straciły na jakości.
Routing z uwzględnieniem kosztów
Tu błyszczy się wnioskowanie lokalne. Modele lokalne są w praktyce darmowe po amorystyzacji sprzętu. RTX 5080 zwrotuje się w około sześciu miesięcy przy umiarkowanym korzystaniu z API.
| Model | Wejście ($/M tokenów) | Wyjście ($/M tokenów) | Koszt lokalny/godzinę |
|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | — |
| Claude Sonnet 4 | $3.00 | $15.00 | — |
| Qwen2.5-72B (API) | $0.50 | $2.00 | — |
| Qwen3-32B (lokalnie) | $0.00 | $0.00 | ~$0.10 |
| Qwen3-8B (lokalnie) | $0.00 | $0.00 | ~$0.05 |
Jeśli przetwarzasz tysiące żądań na sesję, nawet $0.05 za prąd bije $15 za milion tokenów.
Routing oparty na budżecie cofa się w miarę wydawania:
class CostAwareRouter:
def __init__(self, budget_per_session: float = 0.10):
self.budget = budget_per_session
self.spent = 0.0
self.models = {
"cheap": {"model": "qwen3-8B", "cost": 0.0},
"medium": {"model": "qwen3-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"]
Jakość spada wraz z cofaniem się. Zaczynasz od Claude’a, przechodzisz do Qwen3-32B, a następnie do Qwen3-8B. Pod koniec długiej sesji wynik jest wyraźnie gorszy. Czy to ma znaczenie, zależy od tego, co budujesz.
Routing z uwzględnieniem opóźnień (latencji)
Interaktywne narzędzia potrzebują szybkich pierwszych tokenów. Zadania wsadowe mogą czekać. Różnica to zwykle czynnik pięciokrotny w rozmiarze modelu.
| Przypadek użycia | Pierwszy token | Całość | Maks. rozmiar modelu |
|---|---|---|---|
| Czat w czasie rzeczywistym | < 200ms | < 2s | < 7B |
| Narzędzia interaktywne | < 500ms | < 5s | < 14B |
| Przetwarzanie wsadowe | < 1s | < 30s | Dowolny |
| Badania/analiza | < 2s | < 60s | Dowolny |
Gdy strumieniujesz tokeny do użytkownika, to opóźnienie pierwszego tokenu jest tym, co odczuwa. Model o 32 miliardach parametrów, który potrzebuje pół sekundy na start, wydaje się ociężały w porównaniu do modelu o 1.5 miliarda parametrów, który odpala natychmiastowo.
class LatencyAwareRouter:
def __init__(self):
self.model_latencies = {
"qwen3-1.7b": {"first_token": 0.05, "complete": 0.5},
"qwen3-8B": {"first_token": 0.15, "complete": 2.0},
"qwen3-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 "qwen3-1.7b"
Te liczby opóźnień są przybliżone – zależą od sprzętu, kwantyzacji i rozmiaru wsadu. Zmierz je na własnym setupie.
Strategie awaryjne (fallback)
Modele się psują. API nakłada limity. Występują timeouty. Wzorcem, który działa, jest łańcuch awaryjny, uporządkowany od najlepszego do najbardziej niezawodnego:
class FallbackRouter:
def __init__(self):
self.fallback_chain = [
{"model": "claude-sonnet-4", "timeout": 30},
{"model": "qwen2.5-72b", "timeout": 60},
{"model": "qwen3-32b", "timeout": 120},
{"model": "qwen3-8b", "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")
Ostatni model w łańcuchu powinien być lokalny. Jest wolniejszy, ale nie zawiedzie z powodu problemu z siecią lub klucza API.
Kiedy routing pomaga
Routing ma sens, gdy Twoje obciążenie jest mieszane. Jeśli w tym samym systemie wykonujesz klasyfikację, podsumowanie i rozumowanie, router oszczędza pieniądze i czas.
Nie ma sensu, gdy wszystko, co robisz, ma tę samą złożoność. Po prostu użyj modelu, który dobrze radzi sobie z tym zadaniem. Router dodaje złożoność, której nie potrzebujesz.
Wczesne prototypowanie to kolejny powód, by go ominąć. Zabezpiecz zadanie z jednym modelem, a następnie dodaj routing, gdy koszty lub opóźnienia faktycznie staną się problemem.
Kompromisy
Każda strategia routingu optymalizuje coś i ofiarowuje coś innego:
- Pojedynczy model – najprostszy, najdrogszy, stała jakość
- Oparty na możliwościach – lepszy koszt, wyższa jakość per zadanie, umiarkowana złożoność
- Z uwzględnieniem kosztów – najtańszy, zmienna jakość, umiarkowana złożoność
- Z uwzględnieniem opóźnień – najszybszy, może poświęcać jakość, umiarkowana złożoność
- Hybrydowa – najlepsze z całego zestawu, najbardziej złożona do implementacji
Systemy produkcyjne zwykle zbiegają się do hybrydowej. Zacznij od routingu opartego na możliwościach, dodaj świadomość kosztów, gdy przyjdzie rachunek, dodaj świadomość opóźnień, gdy użytkownicy skąplą się na powolność.
Powiązane
- Cost Optimization for LLM Systems — budżetowanie tokenów, caching, modele awaryjne
- LLM Guardrails in Practice — walidacja wejścia, filtrowanie wyjścia, bezpieczeństwo
- Multi-Model System Design — architektura dla wielu modeli
- LLM Architecture — filar projektowania systemów: routing, koszty, zabezpieczenia i orkiestracja