Rust per inference edge di modelli leggeri: quando la performance supera la produttività di Python
Ho fatto la prima porting di un servizio di embedding da Python a Rust il 12 aprile 2026, sulla mia sandbox di sperimentazione, con un obiettivo misurabile: capire quanto margine di performance c'è davvero fra i due linguaggi per inference di modelli leggeri (100-500M parametri), e dove vale la pena investire le 5-10x ore di sviluppo che Rust richiede rispetto a Python. L'ambiente di test è una coppia di container su Hetzner CPX31 (4 vCPU AMD EPYC, 8 GB RAM DDR5, 160 GB NVMe, Debian 12) con lo stesso modello Nomic Embed Text v1.5 (768 dim) caricato in entrambi i runtime. Il carico di test è stato un batch sintetico di 50.000 stringhe corte (media 80 token) e 5.000 stringhe lunghe (media 800 token), con throughput e memoria misurati al secondo. L'implementazione Python usava text-embeddings-inference di Hugging Face, la de facto reference per inference embedding production-grade. L'implementazione Rust usava Candle 0.8 (framework ML Hugging Face in Rust, bindings a safetensors e CUDA) compilata release con -O3 e lto=fat. I risultati, numeri alla mano: Rust è 2,3x più veloce sul throughput end-to-end, consuma il 60% della memoria, ha un binary deployabile di 32 MB contro l'immagine Docker Python di 2,1 GB. Ma la produttività di sviluppo è stata brutalmente a favore di Python: 2 giorni per setup Python equivalente vs 9 giorni per Rust (e Rust era già il mio linguaggio numero quattro in competenza). Questo articolo è il confronto strutturato - quando i numeri di Rust giustificano il costo di sviluppo, e quando no.
Quando il costo di sviluppo Rust vale il gain in performance su inference AI?
La risposta breve è che Rust vale la pena in tre condizioni precise, tutte e tre contemporaneamente: latenza hard real-time sotto i 5-10 ms per chiamata, memoria strettamente bounded per vincolo hardware (edge device, IoT, container con 128-256 MB di RAM), volume di deployment alto dove ogni millisecondo e ogni byte contano moltiplicato per milioni. Sotto una di queste condizioni, Python è la scelta ingegneristicamente pragmatica - la sua produttività di sviluppo domina qualunque guadagno di performance, e i framework Python per inference (PyTorch, ONNX Runtime, text-embeddings-inference) sono abbastanza ottimizzati per gran parte dei casi d'uso reali.
La tabella che segue è il confronto che uso in consulenza quando un cliente chiede "è il caso di usare Rust?". Include Python (riferimento), Rust con Candle, Rust con ort (wrapper ONNX Runtime in Rust), Rust con Burn. Le colonne sono le dimensioni che davvero contano per inference edge.
| Framework | Throughput (stringhe/s, corte) | Memoria residente | Binary deployabile | Setup complexity | Ecosistema modelli |
|---|---|---|---|---|---|
| Python + text-embeddings-inference | 3.800 | 1.850 MB | 2,1 GB (Docker image) | Bassa (docker pull + run) | Eccellente (Hugging Face) |
| Rust + Candle | 8.750 | 1.100 MB | 32 MB (static binary) | Alta (cargo + build dependencies) | Buona (molti modelli ma non tutti) |
| Rust + ort | 7.400 | 1.200 MB | 45 MB + libonnxruntime 60 MB | Media (ONNX model + bindings) | Eccellente (tutto ciò che esporti in ONNX) |
| Rust + Burn | 5.200 | 1.400 MB | 40 MB | Alta (ecosistema più giovane) | Limitata (modelli in crescita) |
Il 2,3x di throughput di Candle sopra Python è il dato più strong; il 60% di memoria è il dato più rilevante per edge deployment. Il 32 MB vs 2,1 GB di binario è la differenza che conta quando deployi su raspberry pi, su IoT, su un container ultra-piccolo, o quando la dimensione dell'artefatto è parte del pipeline CI/CD (un container da 32 MB si pull-a in 2 secondi, uno da 2,1 GB in 40-60).
Se vuoi vedere come progetto pipeline AI multi-stack dove ogni linguaggio va dove è strutturalmente competitivo, nel mio hub sull'integrazione AI per aziende trovo articoli su Go per gateway, Python per orchestrazione, Node.js per streaming, Rust per edge - tutti con filo comune di fit for purpose invece di religione di linguaggio.
I casi d'uso in cui Rust con Candle ha vinto nella mia sandbox
Ho identificato tre casi d'uso concreti dove ho sostituito Python con Rust nel mio laboratorio e la sostituzione ha prodotto guadagni misurabili che giustificano il costo.
Caso uno: embedding service per RAG ad alto volume. Il servizio che ospita Nomic Embed Text v1.5 per il chatbot RAG riceve burst di 500-2.000 embedding al secondo durante picchi di uso. Python con text-embeddings-inference regge, ma il P95 latency sale a 45 ms sotto carico e la memoria residente oscilla fra 1,6 e 2,0 GB. La versione Rust con Candle ha P95 di 18 ms e residente stabile a 1,1 GB. Il margin in latenza è critico quando l'embedding è parte di un flow sincrono (RAG retrieval) dove 27 ms in meno per step si accumulano visibilmente.
Caso due: classificatore di intent a bassissima latenza. Un piccolo modello di classificazione (150M parametri, 8 classi) che routing le richieste agli endpoint giusti in un'API gateway. Il requisito di latenza era P99 sotto i 5 ms. Python con ONNX Runtime arrivava a P99 di 8 ms best-case - insufficiente. Rust con Candle ha P99 di 2,1 ms, comodamente sotto la soglia, con margine per crescere sotto carico futuro.
Caso tre: preprocessing di PDF su stream. Un pipeline che estrae testo da PDF e applica embedding in un'unica passata per ingestione massiva. Python con PyPDF2 + sentence-transformers processava 12 PDF al secondo in parallelo su 8 core. Rust con pdf-extract + Candle processa 48 al secondo sullo stesso hardware. Il fattore 4x si spiega più con l'overhead del parser PDF Python che con l'inference, ma il risultato end-to-end è ciò che conta.
In tutti e tre i casi, il pattern di valutazione è stato: Python prima, misurare, identificare il collo di bottiglia, valutare Rust solo dove il bottleneck non è risolvibile con ottimizzazione Python. Mai Rust di default. Questa disciplina è l'applicazione del principio fit for purpose che ho descritto nell'articolo su Go come inference gateway LLM vs PHP e Node: strumento giusto per il problema giusto, mai religione.
I tre framework Rust per ML: Candle vs Burn vs ort
I tre framework Rust per ML che ho valutato sono Candle, Burn, e ort. Hanno design philosophy diverse e trade-off distinti.
Candle è di Hugging Face. Design minimalista, API PyTorch-like, supporto safetensors nativo, supporto CUDA via cudarc. Copre la maggior parte dei modelli della libreria Hugging Face. Il suo sweet spot è load-and-run di modelli pre-addestrati: prendi i pesi, li carichi in Candle, fai inference. Training è supportato ma è lo scope secondario.
Burn è un framework Rust native più giovane, sviluppato da Tracel AI. Design più ambizioso: vuole essere un full training + inference framework, con backend multipli (ndarray per CPU, wgpu per GPU multi-vendor, candle come backend). Più potente ma più complesso da imparare. Ecosistema di modelli pre-addestrati più limitato perché giovane.
ort è il binding Rust per ONNX Runtime - il framework Microsoft open source. È non-Rust-native (wrappa una libreria C++) ma offre la massima compatibilità: qualunque modello esportato in ONNX (da PyTorch, TensorFlow, scikit-learn) gira con ort. È la scelta pragmatica quando vuoi Rust in produzione ma la squadra di training è Python-based.
La mia scelta nei tre casi d'uso sopra è stata Candle, e il motivo è specifico: volevo un runtime puramente Rust senza dipendenze C++ dinamiche per semplicità di deploy (container scratch + static binary). ort richiede libonnxruntime.so a runtime e complica il packaging. Per un team che parte da zero con Rust ML, Candle è la curva di apprendimento più facile e copre gran parte dei casi reali.
Il costo di sviluppo reale: confessioni sincere dopo 9 giorni di porting
Voglio essere brutalmente onesto su quanto costa portare un servizio da Python a Rust, anche per chi (come me) ha Rust come linguaggio secondario e non sconosciuto. I miei 9 giorni si sono distribuiti così: giorno 1 - setup del progetto Cargo, dipendenze, familiarizzazione con l'API Candle. Giorno 2 - primo caricamento del modello safetensors e prima inference funzionante. Giorno 3-4 - wiring dell'HTTP server (Axum) con tokio async runtime, gestione concorrenza, error handling idiomatico Rust. Giorno 5-6 - ottimizzazione performance: batching, parallelismo CPU, profiling con flamegraph per trovare hot paths. Giorno 7 - scrittura test e fixture. Giorno 8 - packaging Docker con multi-stage build per binario statico e immagine scratch. Giorno 9 - deploy, smoke test in produzione sandbox, debugging dei primi incidenti.
In Python, lo stesso risultato funzionale avrebbe richiesto 2 giorni. Il 5x overhead di sviluppo è reale. I benefici che giustificano quel costo sono nei casi d'uso che ho citato, dove il gain operativo nel tempo ammortizza il costo iniziale. Se il servizio serve 200 richieste al giorno, i 9 giorni Rust non ammortizzano mai: ogni giorno di uso produce guadagno marginale di latenza di decine di ms * 200 chiamate = secondi totali - irrilevante. Se il servizio serve 10 milioni di richieste al giorno, i 9 giorni Rust ammortizzano in 3-4 settimane: il guadagno cumulativo è ore-macchina e soddisfazione utente a scala significativa.
Il pattern "Python davanti, Rust dietro" per ibridare i vantaggi
Un pattern che ho testato con successo è bilinguismo disciplinato: Python per le parti dove la produttività di sviluppo domina (training, sperimentazione, data pipeline ad hoc, scripting one-off), Rust per le parti dove la latenza e la memoria dominano (servizi inference production, gateway ad alta concorrenza, preprocessing stream). I due mondi comunicano via HTTP interno o via IPC, e ciascuno usa il linguaggio dove brilla.
Il pattern architetturale è simile a quello che ho descritto per Symfony backend e Python gateway LLM, applicato a un asse diverso: lì separavo dominio (PHP) e AI orchestration (Python), qui separo Python experimentation da Rust inference. Lo schema:
[Data pipeline Python] → modello fine-tuned → salvataggio safetensors
↓
[Servizio Rust Candle]
↓
API consumer HTTPQuesta separazione permette al team di training di continuare a lavorare in Python (quello che sanno, con i tool che hanno, con la velocità che produce) e al servizio di produzione di servire in Rust. La boundary è il file safetensors del modello: lo salva Python, lo carica Rust. Niente condivisione di codice runtime, niente conflitto di ecosistema, niente FFI complicata.
I casi in cui Rust per AI oggi NON è pronto
Nel 2026, nonostante i progressi, ci sono aree dove Rust AI è ancora immaturo e conviene restare Python. Training: PyTorch in Python resta il framework de facto per training - Burn e tch-rs esistono ma hanno ecosistema e documentazione decisamente inferiori. Research: la community ML è Python-based, i paper ML vengono rilasciati con implementazioni Python, le librerie di data augmentation e evaluation sono Python. Modelli molto grandi: oltre i 10B parametri, gli ottimizzatori CUDA ad-hoc di PyTorch sono difficili da eguagliare in Rust. Modelli multimodali cutting-edge: vision+language+audio combinati sono tipicamente rilasciati prima in Python con implementazioni complete, poi (mesi dopo) in altri runtime.
Se il tuo team è pure research, Rust nel 2026 non è la scelta giusta - resta Python. Se il tuo team è production edge serving con modelli leggeri pre-addestrati altrove, Rust comincia a essere competitivo.
Il confronto onesto binario a binario: Candle 0.8 vs text-embeddings-inference
Per chi vuole i numeri specifici, ecco il benchmark Nomic Embed Text v1.5 sul mio CPX31 con stringhe miste (75% corte, 25% lunghe):
| Metrica | Python TEI 1.5 | Rust Candle 0.8 | Delta |
|---|---|---|---|
| Throughput medio | 3.800 emb/s | 8.750 emb/s | +130% |
| Latenza P50 | 4,2 ms | 1,8 ms | -57% |
| Latenza P95 | 12,8 ms | 4,1 ms | -68% |
| Latenza P99 | 38 ms | 9,2 ms | -76% |
| Memoria residente (idle) | 680 MB | 420 MB | -38% |
| Memoria residente (carico) | 1.850 MB | 1.100 MB | -40% |
| Binary deploy | 2,1 GB (image) | 32 MB | -98% |
| Cold start | 8,4 s | 1,1 s | -87% |
| Build time CI | 40 s (image pull) | 4 m (compile) | +500% |
Il build time CI +500% è il prezzo da pagare: Rust compila, Python no. In un pipeline CI che fa test+deploy diverse volte al giorno, 4 minuti di compile accumulati diventano significativi. Incremental builds (cargo caching) mitigano ma non eliminano il problema.
Quando l'investimento in Rust è strategico oltre i numeri
Tre contesti dove conviene investire in Rust ML anche oltre il break-even puro di performance. Primo: edge deployment con hardware vincolato - se stai deployando inference su IoT device, gateway embedded, Raspberry Pi, la memoria e il footprint sono vincoli fisici. Rust è spesso l'unica scelta fattibile. Secondo: stack già Rust: se la tua azienda ha già un backend pesante Rust (CDN edge, gaming, fintech real-time), aggiungere inference in Python rompe l'omogeneità operativa e introduce una supply chain nuova. Meglio un'anomalia coerente in Rust. Terzo: supply chain audit simpler: il binario Rust statico ha dipendenze tracciate al millimetro in Cargo.lock, la scansione per CVE è chirurgica, la superficie di attacco è minima. Per contesti altamente regolamentati (defense, finance, healthcare) questa supply chain cleanliness vale da sola il costo di sviluppo, indipendentemente dal guadagno di performance.
La narrativa popolare dominante sull'AI realm è che "l'unico linguaggio che conta è Python perché tutti i modelli sono lì". Questa narrativa è corretta al 90% per il training e la sperimentazione, e profondamente sbagliata per production edge inference. Nel 2026, il pattern emergente che vedo nelle aziende con pipeline AI mature è polyglot: Python per training e sperimentazione, Rust per inference performance-critical, Go per gateway ad alta concorrenza (come descritto nell'articolo Go come inference gateway per LLM), JavaScript/TypeScript per l'edge browser. Ogni strumento al suo posto. La PMI italiana che internalizza questa lezione ha un vantaggio strutturale rispetto a quella che forza tutto in Python "perché è AI": la seconda sta pagando il 2-3x in infrastruttura e il 5x in latenza P99 senza alcun beneficio di ROI di business.
Se stai valutando un porting parziale o totale di servizi AI da Python a Rust - o se stai partendo e vuoi capire dove la scelta di linguaggio sarà strategica - il modulo di preventivo gratuito ti dà una prima lettura in 7 domande, 2 minuti. Ti dico se il tuo progetto rientra nelle cose che so fare bene e, se il caso richiede un profilo diverso, te lo dico e ti indico una direzione utile quando posso.