DIEGO CECATO / NOTE DAL CAMPO

TensorRT 11.1: la performance non si decide kernel per kernel

diego cecato//
TensorRT 11.1, Global Performance Tuner e build routes: la performance non si decide kernel per kernel

C’è un modo piuttosto efficace per peggiorare un sistema mentre si è convinti di averlo ottimizzato: prendere il componente più veloce, misurarlo da solo e dichiarare concluso il lavoro. Con l’inferenza AI questa scorciatoia è particolarmente seducente, perché tra kernel, precisione numerica e benchmark GPU è facilissimo trovare un numero da mostrare. Peccato che un modello non venga eseguito come una collezione di screenshot di benchmark. Viene compilato, trasformato, caricato, alimentato con dati reali e infine misurato come un insieme.

Il Global Performance Tuner introdotto con TensorRT 11.1 è interessante proprio perché sposta la domanda: non quale singola ottimizzazione sia più brillante, ma quale combinazione di decisioni produca l’engine migliore per quello specifico modello, su quella specifica macchina, rispettando un vincolo di accuratezza. La novità non è una manopola magica. È aver trasformato parte della strategia di compilazione in uno spazio di soluzioni esplorabile e misurabile.

Il debito tecnico delle euristiche che funzionano

Un compilatore per reti neurali deve scegliere come trasformare il grafo, quali operatori fondere, dove collocare i dati, quali implementazioni usare e quando cambiare precisione. Ogni scelta influenza le altre. Una fusione che elimina un passaggio può aumentare la pressione sulla memoria; un kernel eccellente isolatamente può imporre un layout che penalizza gli operatori successivi. È il motivo per cui l’ottimizzazione locale e la prestazione complessiva non sono sinonimi.

Per anni una parte di queste scelte è stata incorporata in euristiche predefinite, derivate dall’esperienza su modelli rappresentativi. È un approccio sensato finché i casi osservati assomigliano a quelli che arriveranno. Poi cambia un’architettura, compare una nuova GPU, mutano le forme dei tensori, e qualcuno aggiunge un’altra eccezione. Quella che doveva essere conoscenza riutilizzabile diventa progressivamente una rete di condizioni difficili da governare. NVIDIA descrive questo problema come heuristic sprawl nella prima parte dell’analisi tecnica.

Il punto architetturale è più generale di TensorRT: quando una piattaforma prova a servire tutti i casi con una sola strategia implicita, finisce per incorporare una quantità crescente di conoscenza contestuale nel codice condiviso. Non sempre serve aggiungere un altro if. A volte serve rendere esplicite le scelte, delimitarne la validità e misurarle separatamente.

Una build route non è un motore diverso per definizione

Il Global Performance Tuner espone oltre 200 knob, cioè opzioni che controllano aspetti della pipeline di compilazione. Una build route è una combinazione concreta di valori. Può indicare, per esempio, una strategia di fusione e un percorso di generazione del codice differenti da quelli predefiniti. Le API C++ IBuilderConfig::setBuildRoute(), getBuildRoute() e getAllBuildRoutes() consentono di applicare una route, leggerla e scoprire lo spazio delle opzioni disponibili.

Attenzione però a un dettaglio che nella pratica cambia parecchio: la route descrive le scelte richieste al builder, non costituisce una fotografia completa dell’engine finale. TensorRT continua a selezionare tattiche e può riconciliare alcuni valori con i vincoli della rete. Inoltre, due compilazioni non sono necessariamente identiche bit per bit. Per riprodurre davvero un risultato occorre conservare l’engine serializzato, insieme alla configurazione e all’ambiente che lo hanno prodotto. La documentazione ufficiale del tuning esplicita proprio questa distinzione.

Sembra una sottigliezza, finché non devi spiegare perché la route che ieri era la migliore oggi non lo è più. Se cambia il modello, la shape, il driver, la versione CUDA, la GPU o TensorRT stesso, la ricerca va rivalutata. Una configurazione ottima non è un brevetto di velocità trasferibile a qualunque deployment.

Una strategia di ottimizzazione che non registra contesto, vincoli e risultati non è una strategia. È un’ipotesi che si spera di ricordare quando qualcosa cambia.

La velocità viene dopo il filtro di correttezza

Il passaggio più serio del tuner non è scegliere il valore minimo in una colonna di tempi. È definire quali candidati abbiano il diritto di partecipare al confronto. Una route può fallire durante la build, andare in errore in esecuzione oppure produrre risultati numerici che superano la tolleranza ammessa. Quei candidati non devono vincere solo perché sono rapidi.

Quando si abilita la validazione con input e output di riferimento, trtexec confronta i risultati delle route con una metrica e una soglia di accuratezza configurate. Solo tra le soluzioni ammissibili cerca l’engine più veloce. La validazione non è automatica in qualunque esecuzione: richiede dati di riferimento, parametri come --loadRefOutputs e --accuracyThreshold e una metrica appropriata. La documentazione contempla, fra le altre, differenza assoluta, errore quadratico e distanza coseno. Senza questo controllo non si dovrebbe parlare di garanzia di accuratezza del tuning.

E anche quando il controllo è attivo, il risultato è valido rispetto ai campioni, alla metrica e alla soglia che hai fornito. Non dimostra che l’engine sia corretto su tutti gli input possibili. Un dataset troppo comodo può rendere accettabile una route che fallisce sui casi limite. In un sistema serio i dati di test non sono un allegato burocratico: definiscono una parte del contratto funzionale.

È una distinzione che vale ben oltre le GPU. Anche quando un compilatore promette di diventare molto più veloce, la domanda non finisce al tempo di esecuzione: bisogna verificare che il significato dell’output sia rimasto quello atteso.

Oltre 200 opzioni: il problema diventa il costo della ricerca

Rendere configurabile una pipeline non significa che convenga provare ogni combinazione. Con sei opzioni binarie si hanno già 64 route teoriche; con dodici se ne hanno 4.096. Non è una stima dei costi reali di TensorRT, ma un esempio elementare di crescita combinatoria. Ogni route utile deve essere costruita, eseguita, misurata e, quando previsto, validata. La fase di tuning consuma tempo macchina e può produrre artefatti pesanti prima che esista un engine da distribuire.

Fast, mixed e full non sono tre etichette decorative

La ricerca fast parte dalla configurazione base e varia un knob alla volta: è un buon modo per esplorare senza pagare subito il prodotto cartesiano. mixed usa una prima esplorazione rapida e poi prova combinazioni delle opzioni che hanno mostrato miglioramenti. full percorre tutte le combinazioni della selezione indicata ed è ragionevole soltanto quando lo spazio è piccolo e controllato. Sono strategie con costi e capacità esplorative diverse, non tre livelli di qualità da selezionare in ordine crescente.

Una prova con --dryRun permette di vedere le route concrete prima di costruire gli engine nei casi supportati; la documentazione del blog NVIDIA precisa che non è disponibile per la modalità mixed, perché la sua seconda fase dipende dalle misure della prima. Il file --tuningCacheFile conserva route, tempi GPU e risultato del controllo di accuratezza per ogni iterazione. Con --continue è possibile riprendere una ricerca interrotta, senza ricominciare l’esperimento da capo.

Questa è forse la parte meno appariscente ma più utile per chi deve mettere il risultato in produzione: il tuning diventa un processo incrementale, osservabile e riprendibile. Il costo della ricerca va confrontato con il beneficio atteso durante la vita dell’engine. Spendere ore di build per risparmiare pochi microsecondi su un job eseguito raramente può essere una pessima operazione; lo stesso investimento può avere senso su un servizio che serve richieste continuamente. Non c’è una soglia universale: servono volumi, profilo del carico e orizzonte di deployment.

Il benchmark NVIDIA è un indizio, non il tuo SLA

NVIDIA riporta, su dieci workload valutati internamente, miglioramenti end-to-end compresi tra 1,14x e 1,63x rispetto alla route predefinita. È un risultato dichiarato dal produttore su quei workload, non una misura indipendente su qualunque GPU o rete neurale. Non significa che ogni modello guadagnerà almeno il 14%, né che la route vincente sia sempre la stessa. Il grafico dimostra soprattutto che lo spazio di ottimizzazione cambia da workload a workload.

Per valutarne l’utilità in un progetto reale serve una baseline comparabile: stesso modello, stesse shape rappresentative, stessa macchina, precisione e configurazione di test dichiarate. Bisogna distinguere il tempo speso a compilare dalla latenza dell’inferenza e verificare che il benchmark misuri ciò che l’applicazione percepisce. La guida Torch-TensorRT insiste su warm-up, sincronizzazione GPU e confronto corretto tra baseline; senza questi accorgimenti si rischia di premiare il rumore sperimentale.

È lo stesso errore concettuale che emerge quando si scambia la quantità di GPU per lavoro effettivamente utile. Un numero impressionante non basta se non descrive il risultato che interessa al sistema.

Cosa conserverei insieme all’engine

Se dovessi trasformare questa funzionalità in una procedura di rilascio, non partirei dalla route vincente. Partirei da un dossier minimo dell’esperimento: identificativo e hash del modello, distribuzione delle shape, GPU e stack software, espressione delle route esplorate, strategia di ricerca, dataset di validazione, metrica e soglia, baseline, tempi rilevati, cache del tuning ed engine serializzato. Sono gli elementi che permettono a un altro tecnico di capire cosa è stato ottimizzato e quali ipotesi reggevano la decisione.

Il secondo passaggio sarebbe automatizzare una verifica di regressione quando cambia una di quelle condizioni. Non perché ogni aggiornamento debba sempre ricompilare tutto, ma perché il sistema deve sapere quando il risultato precedente ha smesso di essere affidabile. Il Global Performance Tuner rende disponibili i meccanismi per cercare e misurare; la politica di rilascio, la qualità dei dati di riferimento e il costo operativo restano responsabilità di chi costruisce il servizio.

La vera ottimizzazione è sapere cosa non puoi sacrificare

TensorRT 11.1 non elimina il lavoro dell’ingegnere e non rende obsoleta la conoscenza dei compilatori. Sposta una parte delle decisioni da una strategia implicita, accumulata in euristiche, a una ricerca esplicita con risultati verificabili. È un passo interessante perché ammette un fatto spesso trascurato: la prestazione non è una proprietà isolata del codice, ma il risultato di interazioni fra software, hardware, dati e vincoli.

La build più veloce in assoluto potrebbe essere inutilizzabile. Quella più veloce sul banco di prova potrebbe essere mediocre in produzione. E quella migliore oggi potrebbe diventare una regressione al prossimo aggiornamento. Prima di aggiungere l’ennesima ottimizzazione, quindi, conviene fare una domanda meno spettacolare e molto più utile: quale risultato stiamo davvero cercando di preservare mentre acceleriamo il sistema?

Domande frequenti

Che cosa cambia tra una build route e un engine TensorRT?

La build route specifica una combinazione di opzioni del compilatore. L’engine è l’artefatto compilato effettivo, che incorpora anche decisioni del builder. Per riprodurre un deployment va conservato l’engine insieme a modello, versioni e configurazione.

Il Global Performance Tuner controlla sempre l'accuratezza?

No. Il controllo accuracy-aware richiede input e output di riferimento, una metrica e una soglia esplicitamente configurati. La validazione copre i casi forniti, non ogni possibile input.

Quando scegliere fast, mixed o full?

Fast è adatto a una prima scansione a costo contenuto; mixed esplora combinazioni delle opzioni promettenti emerse nella prima fase; full prova tutte le combinazioni ed è sensato solo per uno spazio ristretto. Il costo delle build fa parte della decisione.

Una build route ottimizzata è trasferibile su altre GPU?

Non automaticamente. Modello, shape, GPU, driver, CUDA e versione di TensorRT influenzano il risultato. Una route efficace in un ambiente va misurata e validata nuovamente quando cambia il deployment.