Routing modeli: Przestań używać jednego modelu do wszystkiego
Odpowiedni model do odpowiedniego zadania.
Uruchamianie modelu o 70 miliardach parametrów w celu podsumowania 200-zdaniowego e-maila jest marnotrawstwem. Zastosowanie modelu o 3 miliardach parametrów do recenzji kodu produkcyjnego jest ryzykowne. Większość systemów znajduje się gdzieś w połowie tej skali – i właśnie tam przydaje się routing modeli.
Routing dopasowuje 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 go używamy. Działa to dobrze, dopóki nie zauważy się kosztów, opóźnień lub obu na raz. Alternatywą jest budowa routera – mechanizmu, który decyduje, który model obsłuży dane żądanie.
W praktyce działają cztery strategie:
- Oparte na możliwościach – routing w zależności od tego, co model potrafi zrobić
- Oparte na kosztach – routing w zależności od tego, ile jesteśmy w stanie wydać
- Oparte na opóźnieniach – routing w zależności od tego, jak szybko potrzebujemy wyniku
- Hybrydowe – połączenie powyższych
Każda z nich optymalizuje inny aspekt. Wybór jednej to zwykle decyzja o tym, co boli najbardziej.
Routing oparty na możliwościach
Najprostsze podejście. Zklasifikuj zadanie, wyślij je do modelu, który je obsłuży.
| Zadanie | Rozmiar modelu | Przykłady |
|---|---|---|
| Klasyfikacja, tagowanie | 1-3B | Qwen2.5-1.5B, Gemma-2-2B |
| Podsumowanie, ekstrakcja | 3-7B | Qwen2.5-7B, Llama-3.1-8B |
| Generowanie kodu | 7-14B | Qwen2.5-Coder-7B, DeepSeek-Coder-V2 |
| Złożone rozumowanie | 14-32B | Qwen2.5-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.5B parametrów poradzi sobie dobrze z klasyfikacją sentymentu. Po prostu nie napisze spójnej eseju.
Implementacja jest prosta:
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"])
Haczykiem jest sama klasyfikacja. Jeśli źle określisz typ zadania, wyślesz je do niewłaściwego modelu. Widziałem systemy, które klasyfikowały recenzję kodu jako „podsumowanie” i cicho traciły na jakości.
Routing oparty na kosztach
Lokalna inferencja świetnie się tu sprawdza. Modele lokalne są praktycznie darmowe po odliczeniu amortyzacji sprzętu. Karta RTX 5080 zwraca się w około sześć miesięcy przy umiarkowanym korzystaniu z API.
| Model | Wejście ($/M tokenów) | Wyjście ($/M tokenów) | Koszt lokalny/godz. |
|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | — |
| Claude Sonnet 4 | $3.00 | $15.00 | — |
| Qwen2.5-72B (API) | $0.50 | $2.00 | — |
| Qwen2.5-32B (lokalnie) | $0.00 | $0.00 | ~$0.10 |
| Qwen2.5-7B (lokalnie) | $0.00 | $0.00 | ~$0.05 |
Jeśli przetwarzasz tysiące żądań na sesję, nawet $0.05 za prąd jest lepsze niż $15 za milion tokenów.
Routing oparty na budżecie stosuje mechanizm cofania się w miarę wydawania pieniędzy:
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"]
Jakość spada wraz z przechodzeniem do tańszych opcji. Zaczynasz od Claude’a, przechodzisz do Qwen-32B, a następnie do Qwen-7B. Pod koniec długiej sesji wynik jest zauważalnie gorszy. Czy to ma znaczenie, zależy od tego, co budujesz.
Routing oparty na opóźnieniach
Narzędzia interaktywne 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łkowity | 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 |
Kiedy strumieniujesz tokeny do użytkownika, to opóźnienie pierwszego tokena jest tym, co odczuwa. Model o 32B parametrów, który potrzebuje pół sekury na start, wydaje się ociężały w porównaniu do modelu o 1.5B, który odpala natychmiastowo.
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"
Liczby dotyczące opóźnień są przybliżone – zależą od sprzętu, kwantyzacji i rozmiaru wsadu. Pomiary wykonuj na własnym setupie.
Strategie awaryjne
Modele zawalają się. API ogranicza limity. Występują przekroczenia limitów czasu. Działającym wzorcem 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": "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")
Ostatni model w łańcuchu powinien być lokalny. Jest wolniejszy, ale nie zawali się z powodu problemu z siecią lub klucza API.
Kiedy routing pomaga
Routing ma sens, gdy obciążenie jest mieszane. Jeśli w tym samym systemie robisz klasyfikację, podsumowanie i rozumowanie, router oszczędza pieniądze i zmniejsza opóźnienia.
Nie ma sensu, gdy wszystko, co robisz, ma tę samą złożoność. Użyj po prostu modelu, który dobrze radzi sobie z tym zadaniem. Router dodaje złożoność, której nie potrzebujesz.
Wczesne prototypowanie to kolejny powód, aby go pominąć. Najpierw spraw, żeby zadanie zadziałało z jednym modelem, a routing dodaj dopiero wtedy, gdy koszty lub opóźnienia staną się realnym problemem.
Kompromisy
Każda strategia routingu optymalizuje coś innego i ofiara jakąś inną cechą:
- Jeden model — najprostszy, najdrogszy, stała jakość
- Oparty na możliwościach — lepszy koszt, wyższa jakość dla danego zadania, umiarkowana złożoność
- Oparty na kosztach — najtańszy, zmienna jakość, umiarkowana złożoność
- Oparty na opóźnieniach — najszybszy, może obniżać jakość, umiarkowana złożoność
- Hybrydowy — połączenie najlepszych cech, najtrudniejszy w implementacji
Systemy produkcyjne zazwyczaj zbiegają się do rozwiązania hybrydowego. Zacznij od routingu opartego na możliwościach, dodaj świadomość kosztów, gdy przyjdzie faktura, a świadomość opóźnień, gdy użytkownicy będą narzekać na wolność.
Powiązane
- Optymalizacja kosztów dla systemów LLM — budżetowanie tokenów, buforowanie, modele awaryjne
- Ochrona LLM w praktyce — walidacja wejścia, filtrowanie wyjścia, bezpieczeństwo
- Projektowanie systemów wielomodelowych — architektura dla wielu modeli
- Architektura LLM — filar projektowania systemów: routing, koszty, ochrona i orkiestracja