Orchestrare Modelli, Non Solo Container: Kubernetes nell'Era dell'IA
Ascolta l'articolo (sperimentale) · 17:06

La maggior parte del mio lavoro vive sul livello applicativo. È lì che i prodotti si vincono: i flussi, le API, le parti che gli utenti possono indicare. Molti prodotti poggiano direttamente su un unico fornitore, un modello di OpenAI o di Anthropic dietro una sola chiave, e sono serviti benissimo così. Ma quando un prodotto cresce oltre un modello, o comincia a toccare il proprio hardware, arriva a poggiare su uno strato di cui i suoi utenti non hanno mai sentito parlare: quello che decide, nell'istante dopo che premono invio, quale modello risponde loro, su quale pezzo di hardware, e cosa succede se quell'hardware è saturo o sparisce. Nessuno ringrazia quello strato quando funziona. Ha esattamente un requisito, ed è non negoziabile: non può tradire le persone che non sanno nemmeno di starlo usando.
E non riguarda più solo i prodotti che consegniamo: riguarda anche come li costruiamo. Gli strumenti di codice sulla mia macchina fanno le stesse mosse di cui parla questo articolo: Claude Code affida i sottocompiti leggeri a un modello piccolo e veloce e tiene il ragionamento difficile su uno grande, Cursor instrada tra i propri modelli e le API di frontiera a seconda dell'azione, e il racconto di Anthropic sul loro sistema di ricerca multi-agente descrive esattamente questa forma, un agente guida Opus che orchestra sottoagenti Sonnet più economici. L'instradamento modello-per-compito sta diventando in silenzio il modo in cui il software si fa, non solo il modo in cui si serve.
Nel frattempo, ovunque ci sono team che firmano con fornitori di API di frontiera (OpenAI, Anthropic, Google, xAI, MiniMax) mentre ospitano anche famiglie open-weights come Llama, Qwen, DeepSeek o Mistral sulle GPU che sono riusciti a procurarsi, e qualcuno deve orchestrare tutti quei modelli con l'hardware disponibile. "Quanto conosci Kubernetes" è una domanda del 2019. La domanda del 2026 è: dato un pool fisso di GPU, un insieme di modelli open-weights che vuoi ospitare e un insieme di modelli di frontiera che puoi solo affittare tramite API, riesci a far sembrare tutto questo un unico endpoint noioso? Ho costruito una proof of concept funzionante che risponde alla domanda: un router di modelli, un insieme di deployment consapevoli delle GPU e una demo che gira su un portatile. Questo post è il ragionamento che c'è dietro.
L'economia grida: utilizzo
Un po' di contesto sul perché questa competenza specifica conti all'improvviso. Le Friday Charts di Blockworks hanno fatto i conti questa settimana: hyperscaler e neocloud avranno speso circa 2.000 miliardi di dollari di capex entro fine anno, un singolo armadio di GPU nei nuovi pod Vera Rubin di Nvidia è stimato 9 milioni di dollari, e la newsletter si apre con Jensen Huang che dichiara che "il data center è la nuova unità di calcolo". Lo stesso numero nota che un gigawatt di capacità di data center produce ormai quasi 500.000 miliardi di token, contro un valore prossimo allo zero nel 2023.
Probabilmente non sei un hyperscaler, ma i numeri fanno male anche a livello di strada: una singola scheda di fascia alta può costare ben oltre 40.000€ prima ancora che esista un server dove metterla, e decidere come suddividere quella scheda (intera, a metà, affettata in settimi) è un esercizio di pianificazione a sé, motivo per cui i produttori di GPU pubblicano guide di dimensionamento e matrici di configurazione per accompagnare i compratori tra le opzioni.
Per essere chiari, non tutti i team devono preoccuparsene, e non è una scelta binaria tra una API e il tuo cluster. È una scala, e ogni gradino verso il basso ti dà più controllo su ciò che sta sotto l'endpoint:
- Tutto API. Un modello di OpenAI o Anthropic dietro una chiave, o un deployment di Azure OpenAI con il suo catalogo fisso di modelli e le quote di token al minuto. Le tue leve sono quote, caching e la scelta del tier di modello giusto per ogni compito.
- Inferenza a noleggio. Gli Hugging Face Inference Endpoints, RunPod e i loro pari, molti dei quali ho usato. Scegli il modello e la scheda, qualcun altro gestisce le macchine, e il contatore addebita per ora-GPU che tu sia occupato o fermo.
- GPU cloud gestite da te. VM con GPU fatturate a ore su GCP, AWS o Azure (una volta che il fornitore ti concede la quota, perché la scarsità è reale), o node pool di GPU collegati a un cluster Kubernetes gestito come GKE, EKS o AKS, spesso a prezzi spot se il tuo carico tollera l'eviction. Il contatore somiglia a quello del gradino sopra; la differenza è che l'orchestrazione sotto l'endpoint ora è tua.
- Metallo che possiedi. Schede comprate e montate in rack on-premise o in colocation: la scheda da 40.000€ di prima, ammortizzata invece che misurata a contatore. Ripaga con un utilizzo sostenuto o con vincoli duri di residenza dei dati, e Kubernetes gira anche su bare metal, quindi gli stessi manifest ti seguono fin lì.
Dal secondo gradino in giù il contatore non si ferma mai, e la leva più grande che un team normale controlla per intero diventa la frazione inutilizzata dell'ora-GPU che già paga. L'orchestrazione non è un vezzo di piattaforma; è la differenza tra una flotta che si guadagna il proprio costo e una stufa molto costosa. Kubernetes non è l'unica strada: Slurm è ancora il padrone del mondo HPC, Ray porta il proprio scheduler, e ogni cloud ti venderà volentieri un endpoint gestito che nasconde il problema dietro un margine. Ma se il tuo prodotto gira già su Kubernetes, la mossa pragmatica è lasciare che la stessa piattaforma faccia bin-packing anche dei modelli: carichi eterogenei su macchine eterogenee, in modo dichiarativo, accanto ai carichi che già esegue.
Tre forze, un design
Ogni setup multi-modello che ho visto è tirato in direzioni opposte dalle stesse tre forze:
- I modelli sono eterogenei. Alcuni girano sulle tue GPU (Qwen, Llama, e il resto del mondo open-weights), altri esistono solo dietro una API (i tier di frontiera, chiunque sia il fornitore con cui firmi). Differiscono per finestra di contesto, supporto ai tool, costo, latenza e implicazioni di residenza dei dati.
- L'hardware è eterogeneo e finito. Una flotta reale è quello che sei riuscito a ottenere: qualche A100, un pool di L4, H100 il prossimo trimestre se gli dei degli ordini sorridono. Ogni modello ha uno o due piazzamenti economicamente sensati, e il resto è spreco.
- Il codice di prodotto non vuole sapere nulla di tutto questo. Vuole un endpoint compatibile OpenAI e un nome di modello, e deve continuare a funzionare quando si cambia fornitore, si firma un contratto o una scheda si brucia.
La PoC divide le responsabilità lungo queste linee. Kubernetes possiede il piazzamento: quale pod atterra su quale GPU, espresso nel linguaggio che già parla, quello di resource request, node selector e taint. Un piccolo router possiede la selezione: quale backend serve questa richiesta, con failover. Un registro possiede la policy: cosa esiste, cosa sa fare, quanto costa. In pratica una ConfigMap, ricaricata a caldo, così che "abbiamo firmato con un nuovo fornitore" sia un kubectl apply, non un deploy di prodotto.
Il piazzamento è dove stanno i soldi
La parte della PoC che difenderei con più forza è il suo GPU playbook, perché è qui che la conoscenza di Kubernetes e quella delle GPU devono incontrarsi:
| Carico | Hardware | Tecnica |
|---|---|---|
| Qwen3-32B | 2x A100-80GB | Parallelismo tensoriale, TP=2, NVLink su un solo nodo |
| Qwen3-8B | 1x L4-24GB | Quantizzazione AWQ 4-bit per stare nella scheda economica |
| Embeddings | Un settimo di una A100 | Slice MIG, 1g.10gb, isolata in hardware |
| Esperimenti dev | GPU condivise | Time-slicing, quattro carichi per scheda |
| Modelli di frontiera | GPU di nessuno | API esterna dietro lo stesso endpoint |
Ogni riga è un piccolo argomento. Il modello da 32B non sta in una scheda da 80GB con margine per respirare, perché i soli pesi sono ~64GB in bf16. Viene quindi shardato su due, e il manifest fissa l'esatto prodotto GPU via nodeSelector, perché "due GPU da qualche parte nel cluster" e "due A100 collegate via NVLink sullo stesso nodo" sono macchine radicalmente diverse. Il modello da 8B va nella direzione opposta: quantizzare finché non sta nella scheda che puoi davvero comprare, e poi scalare in orizzontale. La memoria liberata dalla quantizzazione non è solo ingombro, è KV cache, e la KV cache è concorrenza: la quantizzazione spesso ti compra throughput, non solo pesi più piccoli.
La riga degli embeddings è la mia preferita, perché è lì che i soldi muoiono in silenzio nella maggior parte dei cluster. Un modello di embeddings ha bisogno di circa 2GB, e la stessa matematica vale per reranker, classificatori di guardrail e modelli di trascrizione della classe di Whisper, i piccoli servizi sempre attivi che ogni prodotto di IA accumula. Dai a uno qualsiasi di loro un'intera A100 e ~97% di una scheda molto costosa resterà inattivo per sempre. MIG, Multi-Instance GPU, taglia il silicio in sette slice isolate; il Deployment richiede nvidia.com/mig-1g.10gb: 1 come qualunque altra risorsa, e una scheda ne serve sette, ciascuno isolato in hardware dai vicini. Il time-slicing fa qualcosa di simile per i cluster di dev, senza alcun isolamento: un processo ingordo può mandare in OOM gli altri, ed è esattamente il motivo per cui quella configurazione nella PoC è marcata solo-dev.
Niente di tutto questo è esotico. È Kubernetes ordinario, fatto di resource request, label, probe e disruption budget, puntato su una risorsa poco familiare. È questo il vero significato di "sfruttare Kubernetes nell'era dell'IA": la piattaforma non è cambiata, è cambiato il carico di lavoro.
Anche il motore di serving è una scelta
Tutto ciò che gira nel cluster è servito da vLLM, e questa è una preferenza testata, non un default. La proprietà che lo vende è controintuitiva: migliora sotto pressione. Il continuous batching fa sì che più richieste concorrenti lanci a un'istanza, più token per ora-GPU produce; il traffico basso è il caso patologico, non l'ora di punta. Lungo la strada ho provato anche Ollama, e il confronto ha insegnato la stessa lezione della tabella di piazzamento: gli strumenti codificano assunzioni sul loro carico di lavoro. Ollama è eccellente per ciò per cui è stato costruito (una persona, una macchina), ma sotto carico concorrente nei miei test era chiaramente più lento, e passare rapidamente da un modello all'altro gli dava problemi reali, perché ogni cambio scarica un modello e ne carica un altro. Un'istanza di vLLM non ha mai quel problema perché non si cambia mai d'abito: un modello per server per tutta la sua vita, e il router sopra decide quale server merita ogni richiesta.
Il routing è una policy, non un if
Il router parla /v1/chat/completions e risolve i nomi in due modalità deliberatamente diverse. Un nome di modello esatto (qwen3-32b) segue una catena di failover esplicita, scritta dall'operatore: prima il vLLM nel cluster, una API di frontiera come troppopieno. Nessuna furbizia. Un alias (chat-default, chat-long-context) è invece un insieme di requisiti (finestra di contesto minima, supporto ai tool) più una strategia: cheapest-healthy ordina tutti i backend qualificati per costo combinato; prefer-cluster tiene il traffico sull'hardware che possiedi a meno che nulla nel cluster si qualifichi, il che funziona anche da interruttore di residenza dei dati.
Gli alias sono la metà che cambia il modo di lavorare di un team. Il codice di prodotto chiede un risultato, non un vendor, quindi "l'8B ormai regge questo traffico" o "instrada il contesto lungo verso il fornitore che lo prezza meglio" diventano modifiche al registro che escono in pochi secondi. Un circuit breaker per backend salta i backend malati, ogni risposta porta un header x-router-backend così sai sempre chi ha risposto, e lo streaming fa failover solo prima del primo byte: quando i token scorrono, rieseguire la generazione alle spalle dell'utente raddoppierebbe il costo e incollerebbe insieme due risposte diverse.
Una cosa che il router si rifiuta di fare: la coda. vLLM mantiene già la coda onesta, e nasconderla dietro una seconda distruggerebbe il miglior segnale di autoscaling del sistema. Il che mi porta alla trappola.
La trappola dell'utilizzo GPU
Se porti a casa un solo dettaglio operativo da questo post, che sia questo: l'utilizzo GPU è un segnale di autoscaling fuorviante per il serving di LLM. Il continuous batching tiene la scheda occupata sia che serva due richieste comodamente sia che anneghi sotto duecento: entrambe leggono ~100%. Scala sull'utilizzo e scalerai sul rumore.
Il numero che traccia ciò che gli utenti percepiscono è la profondità della coda: il num_requests_waiting di vLLM, richieste ammesse ma non ancora pianificate nel batch. La PoC scala il tier da lavoro da 8B esattamente su quella metrica via KEDA: su in fretta, perché le code crescono su se stesse; giù piano e un pod alla volta, perché ogni nuova replica paga minuti di caricamento del modello e il flapping costa caro. E quando l'intero pool di L4 si esaurisce? Quello non è più un problema di autoscaling. La catena di failover trabocca verso le API esterne, il tier elastico che affitti proprio perché non lo possiedi.
E nemmeno il livello applicativo può far finta che tutto questo non esista. Un collo di bottiglia di GPU prima o poi affiora nel prodotto, e le risposte oneste vivono lì: backpressure, timeout, una modalità degradata. Un modello più piccolo con un'etichetta onesta batte uno spinner che muore a sessanta secondi.
Cosa dimostra la PoC, e cosa non ancora
Il tutto gira senza una sola GPU: un overlay local sostituisce ogni Deployment vLLM con un mock che parla la stessa API ed esporta le stesse metriche, così routing, failover e segnali di scaling si dimostrano su un cluster kind da laptop. Uccidi il deployment del 32B a metà demo e guarda la stessa richiesta tornare servita dal tier "esterno". La logica di routing è pura e coperta da test unitari; i manifest si renderizzano via kustomize in CI.
Resta una PoC, e preferisco dirlo io piuttosto che lasciartelo scoprire: niente autenticazione, niente rate limiting, niente budget per tenant, e le chiavi esterne sono semplici Secret. Quella è la prima iterazione di produzione, e nulla di ciò cambia l'architettura. I concetti di piattaforma si trasferiscono quasi immutati tra distribuzioni e cloud; ciò che decide il successo è se l'economia del carico di lavoro è stata capita prima di scrivere lo YAML.
Il livello applicativo resta il posto dove passo la maggior parte delle mie giornate, e resta il posto dove i prodotti si vincono. Ma sotto i prodotti di IA che crescono oltre un modello c'è ormai uno strato che fa il lavoro di infrastruttura più difficile del decennio: prendere un vincolo dell'era dei 2.000 miliardi di capex, uno zoo di modelli e un mucchio fisso di hardware, e farli comportare come un endpoint affidabile, sostituibile e noioso, per persone che non sapranno mai che esiste. Deve funzionare. Non può tradirle. La PoC è il mio modo di mostrare che non deve farlo.
Le domande a cui questo articolo risponde
Servono GPU proprie per un prodotto di IA o basta una chiave API?
Una chiave basta per molti prodotti, e la scelta non è binaria. È una scala di quattro gradini: tutto API dietro un unico fornitore, inferenza a noleggio su servizi come gli Hugging Face Inference Endpoints o RunPod, GPU cloud gestite da te, e metallo che possiedi e monti in rack. Ogni gradino verso il basso dà più controllo su ciò che sta sotto l'endpoint, e dal secondo in poi paghi per ora-GPU che tu sia occupato o fermo.
Kubernetes non è troppo solo per servire qualche modello?
Non è l'unica strada, e le alternative sono reali: Slurm è ancora il padrone del mondo HPC, Ray porta il proprio scheduler, e ogni cloud ti vende un endpoint gestito che nasconde il problema dietro un margine. L'argomento pragmatico a favore di Kubernetes è più stretto di quanto suggerisca l'entusiasmo: se il tuo prodotto ci gira già sopra, lascia che la stessa piattaforma faccia bin-packing dei modelli accanto ai carichi che già esegue. Quello che la PoC usa è Kubernetes ordinario, cioè resource request, label, probe e disruption budget, puntato su una risorsa poco familiare.
Perché usare vLLM nel cluster invece di Ollama?
vLLM migliora sotto pressione, ed è questa la proprietà che lo vende. Il continuous batching fa sì che più richieste concorrenti producano più token per ora-GPU, quindi il traffico basso è il caso patologico e non l'ora di punta. Ollama è eccellente per una persona e una macchina, ma sotto carico concorrente nei miei test era chiaramente più lento, e passare da un modello all'altro gli dava problemi reali, perché ogni cambio scarica un modello e ne carica un altro. Un server vLLM tiene un solo modello per tutta la sua vita, e il router sopra decide a quale server va ogni richiesta.
Cosa succede quando tutte le GPU sono già occupate?
Smette di essere un problema di autoscaling e diventa di failover: la catena trabocca verso le API esterne, il tier elastico che affitti proprio perché non lo possiedi. Sotto quel punto la PoC scala il tier da 8B sul num_requests_waiting di vLLM via KEDA, su in fretta perché le code crescono su se stesse e giù piano perché ogni nuova replica paga minuti di caricamento del modello. Il livello applicativo deve comunque rispondere del resto con backpressure, timeout e una modalità degradata, perché un modello più piccolo con un'etichetta onesta batte uno spinner che muore a sessanta secondi.
Da leggere dopo
AIQuando l'IA deve filmare la propria revisione
Quando viene aperta una pull request, una mia o una che devo revisionare, ora chiedo all'agente di eseguirla sulla mia macchina, di provare a romperla e di filmarsi mentre lo fa. Un paragrafo può affermare che un test è stato fatto. Una registrazione deve mostrarlo, e decidere cosa significa resta compito mio.
AIJev risponde in probabilità. La domanda è ancora tua.
Jev di TypeSafe non scriverà una frase. Gli consegni uno stato e delle domande tipizzate, e lui ti restituisce una scelta, un punteggio o una probabilità sì/no, a quattro millesimi di centesimo per una decisione su mille token. La novità è la forma, che rimette l'autorità nel tuo codice, e la trappola è che una probabilità somiglia a una rivalsa e non lo è. Ho fatto partire il progetto pilota che vorrei prima di credere a qualsiasi numero, sui miei stessi saggi con il modello aperto che ha fatto le veci di Jev, e la risposta è stata no.