Instradamento del modello: smetti di usare un unico modello per tutto

Il modello giusto per il compito giusto.

Indice

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.

Diagramma delle strategie di routing dei modelli LLM

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:

  1. Basata sulle capacità — instradare in base a cosa il modello può fare
  2. Consapevole dei costi — instradare in base a quanto si è disposti a spendere
  3. Consapevole della latenza — instradare in base a quanto velocemente serve la risposta
  4. 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

Iscriviti

Ricevi nuovi articoli su sistemi, infrastruttura e ingegneria AI.