Instradamento del modello: 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. Eseguire un modello da 3 miliardi di parametri per revisionare il codice in produzione è imprudente. La maggior parte dei sistemi si colloca da qualche punto intermedio: ed è qui che entra in gioco il routing dei modelli.
Questo approccio allinea la complessità del compito alle capacità del modello. I compromessi sono reali, ma così anche i risparmi.

Il problema del routing
Di solito, le persone iniziano con un singolo modello e ci si attengono. 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 cosa il modello può fare
- Consapevole dei costi — instradare in base a quanto si è disposti a spendere
- Consapevole della latenza — instradare in base a quanto velocemente serve la risposta
- Ibrida — combinarle
Ogni strategia ottimizza qualcosa di diverso. Sceglierne una è solitamente una decisione su cosa fa più “male” in quel momento.
Routing basato sulle capacità
L’approccio più semplice. Classifica il compito e invialo al modello che lo gestisce.
| Compito | Dimensione del modello | Esempi |
|---|---|---|
| Classificazione, tagging | 1-3B | Qwen3-1.7B, Gemma-2-2B |
| Riassunto, estrazione | 3-7B | Qwen3-8B, Llama-3.1-8B |
| Generazione di codice | 7-14B | Qwen2.5-Coder-7B, DeepSeek-Coder-V2 |
| Ragionamento complesso | 14-32B | Qwen3-32B, Llama-3.1-70B |
| Scrittura creativa, analisi | 32B+ | Qwen2.5-72B, Claude, GPT-4 |
Se il compito non richiede il modello più grande, non usarlo. Un modello da 1.5B gestisce bene la classificazione del sentiment. Semplicemente, non scriverà un saggio coerente.
L’implementazione è diretta:
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"])
Il punto critico è la classificazione stessa. Se si sbaglia il tipo di compito, si instrada verso il modello sbagliato. Ho visto sistemi classificare la revisione del codice come “riassunto” e perdere qualità in silenzio.
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 si ripaga in circa sei mesi con un uso moderato dell’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 | — |
| Qwen3-32B (locale) | $0.00 | $0.00 | ~$0.10 |
| Qwen3-8B (locale) | $0.00 | $0.00 | ~$0.05 |
Se si stanno elaborando migliaia di richieste per sessione, anche $0.05 in elettricità battono $15/M di token.
Il routing basato sul budget fa fallback 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": "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"]
La qualità peggiora man mano che si fa fallback. Si inizia con Claude, si passa a Qwen3-32B, poi a Qwen3-8B. 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 hanno bisogno di token iniziali veloci. I job batch possono aspettare. La differenza è solitamente un fattore cinque nella dimensione del modello.
| Caso d’uso | Primo token | Completamento | Dimensione massima del modello |
|---|---|---|---|
| Chat in tempo reale | < 200ms | < 2s | < 7B |
| Strumenti interattivi | < 500ms | < 5s | < 14B |
| Elaborazione batch | < 1s | < 30s | Qualsiasi |
| Ricerca/analisi | < 2s | < 60s | Qualsiasi |
Quando si streamano 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 risponde istantaneamente.
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"
I numeri di 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 applicano limiti di frequenza. 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": "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")
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 sta facendo classificazione, riassunto e ragionamento nello stesso sistema, un router fa risparmiare denaro e latenza.
Non ha senso quando tutto ciò che si fa ha la stessa complessità. Usate semplicemente il modello che è bravo in quel compito. Il router aggiunge complessità di cui non c’è bisogno.
La prototipazione iniziale è un altro motivo per saltarlo. Fate funzionare il compito con un modello, poi aggiungete il routing quando costo o latenza diventano effettivamente un problema.
Compromessi
Ogni strategia di routing ottimizza qualcosa e sacrifica qualcos’altro:
- Modello singolo — più semplice, più costoso, qualità costante
- Basato sulle capacità — costo migliore, qualità più alta per compito, complessità moderata
- Consapevole dei costi — più economico, qualità variabile, complessità moderata
- Consapevole della latenza — più veloce, può sacrificare la qualità, complessità moderata
- Ibrido — il meglio di tutto, più complesso da implementare
I sistemi in 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 i sistemi LLM — budgeting dei token, caching, modelli di fallback
- Guardrail LLM nella pratica — validazione degli input, filtraggio degli output, sicurezza
- Design di sistemi multi-modello — architettura per più modelli
- Architettura LLM — pilastro del design del sistema: routing, costi, guardrail e orchestrazione