Instradamento dei modelli: smetti di usare un unico modello per tutto
Il modello giusto per il compito giusto.
Eseguire un modello con 70 miliardi di parametri per riassumere un’email di 200 parole è uno spreco. Utilizzare un modello da 3 miliardi di parametri per revisionare il codice di produzione è imprudente. La maggior parte dei sistemi si trova da qualche punto intermedio ed è qui che entra in gioco il routing dei modelli.
Il routing allinea la complessità del task alla capacità del modello. I compromessi sono reali, ma anche i risparmi lo sono.

Il problema del routing
Di solito, le persone iniziano con un modello e lo usano per tutto. Funziona finché non si nota il costo, la latenza, o entrambi. L’alternativa è costruire un router: qualcosa che decide quale modello gestisce quale richiesta.
In pratica, funzionano quattro strategie:
- Basata sulle capacità — instradare in base a ciò che il modello può fare
- Consapevole dei costi — instradare in base a quanto si è disposti a spendere
- Consapevole della latenza — instradare in base alla velocità necessaria
- Ibrida — combinarle
Ognuna ottimizza qualcosa di diverso. Sceglierne una è solitamente una decisione su quale aspetto rappresenta il problema maggiore.
Routing basato sulle capacità
L’approccio più semplice. Classificare il task e inviarlo al modello che lo gestisce.
| Task | Dimensione modello | Esempi |
|---|---|---|
| Classificazione, tagging | 1-3B | Qwen2.5-1.5B, Gemma-2-2B |
| Riassunto, estrazione | 3-7B | Qwen2.5-7B, Llama-3.1-8B |
| Generazione di codice | 7-14B | Qwen2.5-Coder-7B, DeepSeek-Coder-V2 |
| Ragionamento complesso | 14-32B | Qwen2.5-32B, Llama-3.1-70B |
| Scrittura creativa, analisi | 32B+ | Qwen2.5-72B, Claude, GPT-4 |
Se il task non necessita del modello più grande, non usarlo. Un modello da 1.5B gestisce bene la classificazione del sentiment. Non scriverà, però, un saggio coerente.
L’implementazione è diretta:
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"])
Il problema è la classificazione stessa. Se si identifica male il tipo di task, si invia al modello sbagliato. Ho visto sistemi classificare la revisione del codice come “riassunto” e perdere qualità in modo silenzioso.
Routing consapevole dei costi
L’inferenza locale brilla in questo contesto. I modelli locali sono praticamente gratuiti dopo l’ammortamento dell’hardware. Una RTX 5080 ripaga il suo costo in circa sei mesi con un utilizzo moderato delle API.
| Modello | Input ($/M token) | Output ($/M token) | Costo locale/ora |
|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | — |
| Claude Sonnet 4 | $3.00 | $15.00 | — |
| Qwen2.5-72B (API) | $0.50 | $2.00 | — |
| Qwen2.5-32B (locale) | $0.00 | $0.00 | ~$0.10 |
| Qwen2.5-7B (locale) | $0.00 | $0.00 | ~$0.05 |
Se si elaborano migliaia di richieste per sessione, anche $0.05 di elettricità battono i $15/M token.
Il routing basato sul budget passa a modelli meno costosi man mano che si spende:
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"]
La qualità peggiora man mano che si passa a modelli alternativi. Si inizia con Claude, si passa a Qwen-32B, poi a Qwen-7B. Alla fine di una sessione lunga, l’output è visibilmente peggiore. Se ciò sia un problema dipende da cosa si sta costruendo.
Routing consapevole della latenza
Gli strumenti interattivi necessitano di primi token veloci. I job batch possono aspettare. La differenza è solitamente un fattore cinque nella dimensione del modello.
| Caso d’uso | Primo token | Completamento | Dimensione modello max |
|---|---|---|---|
| Chat in tempo reale | < 200ms | < 2s | < 7B |
| Strumenti interattivi | < 500ms | < 5s | < 14B |
| Elaborazione batch | < 1s | < 30s | Qualsiasi |
| Ricerca/analisi | < 2s | < 60s | Qualsiasi |
Quando si streamma token a un utente, è la latenza del primo token ciò che l’utente percepisce. Un modello da 32B che impiega mezzo secondo per iniziare sembra lento rispetto a un modello da 1.5B che parte istantaneamente.
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"
I numeri sulla latenza sono approssimativi: dipendono dall’hardware, dalla quantizzazione e dalla dimensione del batch. Misurate sul vostro setup.
Strategie di fallback
I modelli falliscono. Le API hanno limiti di rate. I timeout accadono. Il pattern che funziona è una catena di fallback, ordinata dal migliore al più affidabile:
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")
L’ultimo modello nella catena dovrebbe essere locale. È più lento, ma non fallirà a causa di un problema di rete o di una chiave API.
Quando il routing aiuta
Il routing ha senso quando il carico di lavoro è misto. Se si eseguono classificazione, riassunto e ragionamento nello stesso sistema, un router fa risparmiare denaro e riduce la latenza.
Non ha senso quando tutto ciò che si fa ha la stessa complessità. Usate semplicemente il modello bravo per quel task. Il router aggiunge complessità non necessaria.
La prototipazione iniziale è un altro motivo per evitare il routing. Fate funzionare il task con un modello, poi aggiungete il routing quando il costo o la latenza diventano effettivamente un problema.
Compromessi
Ogni strategia di routing ottimizza qualcosa e sacrifica qualcos’altro:
- Modello singolo — il più semplice, il più costoso, qualità costante
- Basato sulle capacità — migliore costo, qualità più alta per task, complessità moderata
- Consapevole dei costi — il più economico, qualità variabile, complessità moderata
- Consapevole della latenza — il più veloce, può sacrificare la qualità, complessità moderata
- Ibrido — il meglio di tutto, il più complesso da implementare
I sistemi di produzione solitamente convergono verso l’ibrido. Iniziate con il routing basato sulle capacità, aggiungete la consapevolezza dei costi quando arriva il conto, aggiungete la consapevolezza della latenza quando gli utenti si lamentano della lentezza.
Correlati
- Ottimizzazione dei costi per sistemi LLM — budgeting dei token, caching, modelli di fallback
- Guardrails LLM in pratica — validazione dell’input, filtraggio dell’output, sicurezza
- Design di sistemi multi-modello — architettura per più modelli
- Architettura LLM — pilastro del design del sistema: routing, costi, guardrails e orchestrazione