by Bruno Tessaro, posted on February 11, 2025
Per chi, come me, ha studiato la teoria delle reti neurali, l'attuale sviluppo è stato un’opportunità straordinaria di vedere concretizzarsi ciò che fino a pochi anni fa sembrava pura fantasia con tecnologie che
fino a poco tempo fa erano impensabili.
Ricordo che fin dai tempi dell’università mi divertivo a implementare in codice piccole reti neurali. Utilizzavo architetture come la backpropagation o il modello
Hopfield, per affrontare problemi interessanti come il riconoscimento delle lettere scritte a mano a scopo didattico e di apprendimento. Quei primi esperimenti mi hanno fatto capire l’enorme potenziale di questi
strumenti, ma anche le difficoltà e le limitazioni che all'epoca si presentavano.
Più recentemente, nel 2020, ho approfondito il mio interesse per i modelli generativi emergenti, un campo affascinante che ha dato vita a molte applicazioni avanzate dell’intelligenza artificiale. Ho partecipato
ad un corso online di Didaktik Academy, che mi ha permesso di approfondire la teoria e i fondamenti matematici alla base del machine learning. Provenivo da una solida esperienza di programmazione in .NET, ma
mi sono subito reso conto che il settore dell’intelligenza artificiale era dominato dal linguaggio Python.
Per colmare questa lacuna, ho dedicato del tempo allo studio di Python e del framework
TensorFlow, strumenti essenziali per lavorare con l’intelligenza artificiale. Tuttavia, mi sono presto accorto della difficoltà del percorso e ho notato
che spesso anche chi lavora nel settore utilizza librerie e framework già collaudati, senza entrare troppo nei dettagli teorici che li sottendono.
In seguito, mi sono concentrato maggiormente sulle AI generative,
approfittando della disponibilità di numerosi modelli open source online. Questi modelli mi hanno permesso di sperimentare con strumenti avanzati, senza la necessità di costruire tutto da zero.
Fooocus
Nell’autunno del 2023, mi ha colpito un post che annunciava il rilascio di una versione stabile di Fooocus, un’interfaccia grafica basata su Gradio
per l'uso locale di modelli generativi di immagini della famiglia Stable Diffusion. A differenza dei tradizionali servizi online, questa soluzione permetteva di lavorare offline direttamente su computer client,
offrendo una maggiore autonomia e controllo sul processo di generazione e veniva fornita già pronta all'uso, senza dover creare l'ambiente Python. Affascinato da questa nuova possibilità, ho deciso di esplorare
la creazione di contenuti.
La generazione di immagini si è rivelata un campo particolarmente stimolante, ma anche complesso. La quantità di informazioni da gestire per ottenere risultati di qualità era sorprendentemente
ampia. Ho quindi cercato di comprendere al meglio le tecniche necessarie per ottenere immagini soddisfacenti.
Durante le festività natalizie, conobbi la community di CivitAI,
un vero e proprio punto di riferimento per chi desidera apprendere da esperti e appassionati del settore. Dopo alcune prime sperimentazioni, ho iniziato a capire come scegliere i modelli più adatti, selezionando
i checkpoint giusti e perfezionando la scrittura dei prompt. Questo processo mi ha permesso di apprendere le tecniche creative più avanzate e l’utilizzo di LoRA ed Embedded a supporto del modello di base, che
hanno notevolmente ampliato le potenzialità di personalizzazione e resa delle immagini.
Con il tempo, ho sviluppato un approccio che privilegiava la semplicità e l’efficienza. Ho optato per modelli "merged",
addestrati con LoRA e VAE integrati, che mi hanno consentito di ottenere risultati migliori in tempi più brevi, senza sacrificare la qualità. Allo stesso tempo, ho definito alcune linee guida personali per la
creazione di prompt efficaci. Ho scelto di utilizzare modelli non censurati, lasciando che fosse il prompt stesso a filtrare i contenuti in base alle specifiche esigenze di ciascun caso.
Nel 2024, il rilascio
di un nuovo modello chiamato FLUX, più potente ma con la necessità di requisiti hardware più elevati per l’esecuzione locale, mi ha spinto a riflettere sulle opzioni disponibili. Dopo aver valutato attentamente,
ho deciso di continuare a lavorare con i modelli SDXL già collaudati, consolidando le competenze acquisite fino a quel momento. Questa scelta mi ha permesso di ottimizzare i risultati e di spostare la mia attenzione
anche su altri campi.
Tra i modelli che ho scelto per la generazione personale di immagini, ne voglio evidenziare tre che riescono a combinare in modo efficace semplicità architetturale e alta qualità.
Se l'obiettivo è creare immagini fotorealistiche, che ritraggano soggetti umani, ambienti o concetti astratti, un'ottima scelta è senza dubbio Juggernaut XL XI. Questo modello è particolarmente adatto per ottenere immagini con una qualità visiva molto alta e un grande realismo. Può essere utilizzato per una vasta gamma di applicazioni, dalla fotografia digitale alla creazione di scene complesse. Puoi trovarlo qui.
Quando l'immagine finale deve avere una qualità fotorealistica, ma con un focus specifico sulla rappresentazione di persone, emozioni, pose, abbigliamento e altri dettagli, i modelli della famiglia Pony Diffusion sono la scelta ideale. Sono principalmente pensati per generare disegni, ma la versione Pony Realism v2.2 è un'ottima opzione per ottenere immagini fotorealistiche con una resa sorprendente. In particolare, la corretta interpretazione dei dettagli è uno degli aspetti in cui questo modello eccelle. Puoi trovarlo qui.
Infine, se l'immagine che desideri creare è un disegno, e in particolare se hai bisogno di una rappresentazione artistica di alta qualità, il modello ONE FOR ALL Pony Fantasy 2.0 è la soluzione migliore. Questo modello è progettato per generare facilmente immagini artistiche, mantenendo al contempo una qualità visiva elevata e una forte componente creativa. Puoi trovarlo qui.
Riepilogando e completando quanto fin qui visto, nell'ambito delle intelligenze artificiali generative per immagini, un modello come Stable Diffusion è un tipo di intelligenza artificiale open source progettato
per creare immagini a partire da descrizioni testuali che può essere utilizzato anche per l’elaborazione e la modifica di immagini esistenti.
La sua forza risiede nella capacità di "diffondere" informazioni
attraverso un processo che riduce in modo controllato e iterativo il rumore, grazie a numerosi passaggi computazionali.
La versione 1.5 di Stable Diffusion era una versione relativamente “standard”. Con l’arrivo di Stable Diffusion 2.0, sono state introdotte innovazioni significative, come la possibilità di generare immagini a una
risoluzione maggiore e un miglioramento delle capacità di composizione e di coerenza stilistica. Stable Diffusion XL (SDXL) è un ulteriore passo avanti, che porta la creazione di immagini di qualità superiore
e una gestione di richieste più complesse.
Una delle principali innovazioni di SDXL è la capacità di gestire risoluzioni più alte senza compromettere la qualità visiva o la coerenza dell’immagine. Inoltre,
il modello è stato perfezionato nel completamento delle immagini, nella gestione degli oggetti stilizzati e nella precisione dei dettagli legati alle luci, alle ombre e ai colori. Un altro miglioramento importante
riguarda la capacità del modello di comprendere meglio la semantica del testo. SDXL è in grado di capire con maggiore precisione l’intenzione dietro il prompt, permettendo di generare immagini che rispecchiano
con maggiore fedeltà anche le richieste più articolate.
Per utilizzare correttamente questi modelli è importante conoscere alcuni concetti chiave.
I Trained Checkpoints sono modelli addestrati su enormi quantità di dati, capaci di generare immagini in base a ciò che hanno appreso. Questi modelli possono essere utilizzati "così come sono", senza subire
modifiche durante l’utilizzo. Tuttavia, un Trained Checkpoint è generalmente ottimizzato per uno specifico tipo di output.
I Merged
Checkpoints sono il risultato della fusione di più checkpoint. Questo processo consente di ottenere un modello che combina le caratteristiche di più modelli preesistenti, aumentando la versatilità del
modello stesso.
Il Low-Rank Adaptation (LoRA) è una tecnica che permette di adattare un modello preesistente a nuovi compiti aggiungendo un numero molto ridotto di parametri. In questo modo, è possibile
personalizzare il modello senza doverlo addestrare completamente da zero. Questo approccio è utile quando si desidera aggiungere nuove capacità al modello senza perdere quelle già acquisite.
Gli embedding sono rappresentazioni numeriche di concetti, parole o oggetti. Un embedding è una forma di codifica che consente al modello di interpretare un’idea o un concetto visivo. Per esempio, può descrivere un determinato
stile visivo o una composizione da applicare all’immagine generata.
Il Variational Autoencoder (VAE) è un tipo di modello utilizzato per ridurre la complessità dei dati, trasformandoli in una rappresentazione latente più semplice da manipolare e generare. Nel caso
di Stable Diffusion, il VAE aiuta a comprimere le informazioni, consentendo una generazione di immagini più fluida e meno costosa in termini di risorse computazionali.
Il prompt è il testo fornito al modello per guidarlo nella generazione dell’immagine. È una descrizione di ciò che si desidera ottenere come output. Il negative prompt, invece, è uno strumento
che consente di specificare ciò che si vuole escludere dal risultato finale.
Il seed è un numero casuale che viene utilizzato come punto di partenza per la generazione dell’immagine. Variando il seed,
è possibile ottenere immagini diverse anche mantenendo invariati gli altri parametri.
La Guidance
Scale (o Classifier-Free Guidance) è un parametro che controlla quanto il modello si deve attenere al prompt fornito. Un valore più alto di Guidance Scale farà sì che il modello si concentri maggiormente
sulle istruzioni, mentre un valore più basso permetterà una maggiore libertà creativa e meno aderenza alla descrizione testuale.
Il
Classifier-Free Guidance (CFG) è un meccanismo che cerca di far aderire la generazione alle richieste del prompt. Viene regolato tramite la Guidance Scale e determina quanto il modello deve concentrarsi
nel generare un’immagine che rispecchi il testo fornito.
Il clip si riferisce alla funzione di "embedding" testuale che aiuta ad allineare il prompt testuale con l’immagine generata. In sostanza,
il clip guida il modello verso una comprensione più precisa del testo, migliorando la qualità del risultato finale.
Il sampler è l’algoritmo che guida il modello nel processo di generazione dell’immagine, esplorando lo spazio delle possibili immagini per trovare quella che meglio corrisponde al prompt. Esistono
diversi tipi di sampler come Euler, LMS o DDIM, ognuno con caratteristiche specifiche che influenzano la qualità dell’immagine finale e la velocità di generazione.
Lo scheduler è una funzione che
controlla come e quando viene applicato il rumore durante la generazione dell’immagine. In un modello di diffusione, il rumore viene progressivamente aggiunto e successivamente rimosso. Lo scheduler regola il
ritmo di questo processo. Modificare lo scheduler può migliorare la qualità dell’immagine o velocizzare il processo di generazione, a seconda delle necessità.
Nella primavera del 2024, il mio interesse si è spostato sulla generazione di testo, coincidendo con il lancio di Phi-3 di Microsoft, un modello small language che può essere integrato nelle applicazioni tramite
ONNX. Questo mi ha spinto ad approfondire la sua applicabilità all’interno dell’ecosistema .NET e a studiare il runtime ONNX, esplorando le possibilità offerte da questa tecnologia.
Ho avuto modo di approfondire
anche il concetto di quantizzazione, una tecnica utilizzata per ottimizzare i modelli riducendo le risorse computazionali necessarie durante l’inferenza, cercando al contempo di mantenere il più possibile inalterate
le prestazioni. Questo approccio è particolarmente utile per eseguire modelli di intelligenza artificiale su dispositivi con capacità hardware limitate, come smartphone o sistemi embedded.
Nello stesso periodo, ho avuto l’opportunità di partecipare a una conferenza di Federico Faggin sull’intelligenza artificiale, in cui illustrava la sua teoria della coscienza. Le sue riflessioni mi hanno portato a interrogarmi sulla vera natura delle AI generative e sulle frequenti attribuzioni errate che vengono loro assegnate. Osservando il modo in cui i media mainstream raccontano l’intelligenza artificiale e le aspettative che molte persone ripongono in queste tecnologie, appare evidente come tali fraintendimenti siano piuttosto diffusi. Il tema mi ha affascinato e ho deciso di approfondire il pensiero di Faggin leggendo alcuni suoi saggi, disponibili sul sito della sua fondazione.
Nonostante avessi già avuto esperienze precedenti con Python, non era mia intenzione sposarlo come linguaggio. Di conseguenza, ho scelto di non utilizzare framework come TensorFlow e PyTorch e, nel mese di luglio, ho iniziato a lavorare con TensorFlow.NET, un wrapper di TensorFlow pensato per ambienti .NET, che permette di sfruttare le potenzialità di machine learning senza abbandonare l’ecosistema Microsoft.
LM Studio
Così come nel campo della generazione di immagini esistono numerosi strumenti per padroneggiare i modelli disponibili, anche nel settore della generazione di testo si trovano diverse applicazioni pensate per facilitare
l’uso dei modelli linguistici. Proseguendo nella mia esplorazione dei language models utilizzabili offline, mi sono imbattuto in LM Studio, un’applicazione che
permette di eseguire modelli linguistici in locale, ampliando così le possibilità di sperimentazione senza dover dipendere da servizi cloud.
Attraverso questa piattaforma, ho avuto modo di testare e confrontare
diversi modelli open source, concentrandomi in particolare sulle tecniche di generazione di racconti e su altri utilizzi interessanti nel campo della scrittura assistita dall’intelligenza artificiale.
In questo ambito, le tecniche di formattazione dei prompt giocano un ruolo fondamentale per ottenere il risultato desiderato. L'obiettivo è costruire un contesto linguistico adeguato prima di formulare una domanda o impartire un comando, affinché il modello possa interpretarlo nel modo più preciso possibile.
Tra le strategie che ho avuto modo di studiare e testare, le più significative sono:
Zero-Shot – Questa tecnica prevede di chiedere al modello una risposta senza fornirgli alcun esempio di riferimento. È come chiedere a qualcuno di risolvere un problema senza dargli alcuna spiegazione o contesto
preliminare. Il modello deve quindi fare affidamento esclusivamente sulle conoscenze apprese durante l’addestramento.
Few-Shot – A differenza dello Zero-Shot, in questo caso si forniscono al modello alcuni esempi per aiutarlo a comprendere il tipo di risposta attesa. È come mostrare un paio di soluzioni a un problema
prima di chiedere di replicarne il procedimento, aumentando così la probabilità di ottenere una risposta coerente con le aspettative.
Chain-of-Thought (CoT) – Questa tecnica spinge il modello a esplicitare il ragionamento passo dopo passo prima di arrivare alla risposta finale. Il concetto è simile al modo in cui si risolve un problema
matematico annotando tutti i passaggi intermedi per arrivare alla soluzione. Questo approccio migliora la capacità del modello di affrontare problemi complessi e riduce la probabilità di errori logici.
Self-Consistency – Qui il modello non fornisce una sola risposta, ma genera più soluzioni possibili a una stessa domanda e poi seleziona quella più coerente o più ricorrente tra le alternative generate.
Il principio è simile a quello di chiedere la stessa cosa a più persone e affidarsi alla risposta più frequente, aumentando così l'affidabilità del risultato.
Meta-Prompting – Questa tecnica consiste nel far sì che il modello generi autonomamente i propri prompt prima di rispondere a una domanda. È come insegnare a qualcuno a formulare le domande giuste prima
di affrontare un problema, affinché possa riflettere sul modo migliore di elaborare la risposta. Questo metodo permette di ottenere risultati più strutturati e pertinenti, migliorando la qualità delle risposte
fornite dal modello.
Tra i numerosi modelli linguistici disponibili online, molti sono progettati per scopi specifici, mentre altri sono più generici e versatili. Tra quelli che ho avuto modo di testare, ne voglio evidenziare uno in particolare, che si è rivelato estremamente efficace in tutti gli ambiti in cui l’ho utilizzato, garantendo sempre un'elevata qualità dei risultati.
Si tratta di Llama 3.1 Lexi Uncensored V2 8B, un modello basato su Llama 3.1 di Meta. Oltre a offrire ottime prestazioni in termini di generazione di testo, si distingue per la sua flessibilità e affidabilità.
Un aspetto particolarmente interessante è la sua capacità di funzionare in modo efficiente anche in versione quantizzata a 5-bit, rendendolo adatto all’integrazione in ambienti embedded all'interno di applicazioni.
Il modello è disponibile su Hugging Face.
Con le nuove competenze acquisite, il passo successivo è stato provare ad integrare direttamente i modelli di intelligenza artificiale all’interno di applicazioni personalizzate.
L’obiettivo principale era rimanere
nell’ecosistema .NET, e il primo approccio da sviluppatore è stato l’utilizzo del runtime
ONNX, sviluppato da Microsoft in collaborazione con Meta. Ho seguito i tutorial ufficiali disponibili in rete.
Questa soluzione si è rivelata particolarmente interessante, in quanto perfettamente integrabile nel codice e relativamente semplice da implementare. Per testarne l’efficacia, ho sperimentato il runtime con diversi
modelli.
Chatbot testuali basati sul modello Phi-3.
Generazione di immagini con Stable Diffusion XL, utilizzando il
modello Dreamshaper v8 LCM.
Generazione di immagini con
Juggernaut XL v11, un modello particolarmente esigente in termini di risorse computazionali, che ha richiesto lunghi tempi
di elaborazione.
Modelli di visione, come Phi-3 Vision, che hanno dimostrato una notevole capacità di descrivere
in forma testuale il contenuto delle immagini fornite.
L’integrazione di ONNX offre alcuni benefici significativi.
Un’interfaccia unificata per la gestione sia di modelli testuali che di visione.
Prestazioni elevate per i modelli convertiti, che risultano ottimizzati per l’inferenza su dispositivi locali.
Nonostante i risultati promettenti, ho individuato alcune criticità nell’utilizzo di ONNX.
Le librerie necessarie al runtime risultano estremamente pesanti, superando facilmente 1 GB, anche a causa delle librerie CUDA di nVidia.
Per utilizzare un modello con ONNX, deve essere convertito nel suo
formato compatibile attraverso una toolchain basata su Python. Il processo non è sempre intuitivo, e i modelli preconvertiti disponibili già in formato ONNX sono meno numerosi e vari rispetto a quelli nei formati
più diffusi, come .safetensors e .gguf.
La difficoltà nel reperire modelli onnx della qualità desiderata mi ha spinto a esplorare nuove architetture, anche a costo di rinunciare a un'integrazione profonda con l’ecosistema .NET.
Un approccio alternativo
era l’utilizzo di modelli locali non integrati direttamente nell’applicazione, ma esposti come servizi API REST. Questo metodo sfrutta server applicativi in grado di gestire modelli in formati standard e li
rende accessibili tramite API.
Tra le varie soluzioni testate, Ollama si è rivelato un ottimo prodotto: semplice da utilizzare, funzionale e ben progettato. Tuttavia, anche in questo caso ho riscontrato una limitazione: la disponibilità di modelli compatibili con il suo formato non è universale. Sebbene l’offerta sia ampia, non tutti i modelli di interesse sono stati convertiti e resi disponibili.
Ho poi dedicato del tempo a Llamafile, un progetto sviluppato da Mozilla basato sul motore di LM Studio (llama.cpp). Utilizza il formato GGUF, uno standard ampiamente
supportato su Hugging Face, e offre alcuni vantaggi interessanti come le dimensioni contenute della runtime che occupa circa 400MB, meno della metà di ONNX, pur includendo le librerie per GPU CUDA e Vulkan.
Integra runtime e modello in un unico file .EXE, semplificando la distribuzione ed esponendolo come API REST compatibile con il protocollo OpenAI.
Tuttavia, l’uso di Llamafile su Windows presenta un grosso limite: il sistema operativo non consente l’esecuzione di file superiori a 4GB, mentre i modelli più avanzati superano facilmente gli 8GB. Questa
restrizione ne riduce drasticamente l’usabilità su questa piattaforma.
KoboldCpp
Un’altra soluzione che ha attirato la mia attenzione è stata KoboldCpp. Anch’esso basato su llama.cpp, si distingue per diverse caratteristiche:
Permette di interagire con i modelli tramite un’interfaccia utente web intuitiva.
Occupa solo 60MB in un unico file e supporta sia l’esecuzione su CPU che su GPU.
Compatibile con modelli Safetensors
e GGUF, ampiamente utilizzati su Hugging Face.
KoboldCpp si è rivelato particolarmente efficace ed è molto apprezzato anche nel mondo del gaming.
A questo punto, approfondire il motore alla base di questi strumenti – llama.cpp – era diventato inevitabile, nonostante fosse scritto in C++. Tuttavia, durante le ricerche, ho fatto una scoperta inaspettata:
LlamaSharp, un wrapper per .NET che integra il motore llama.cpp in modo nativo.
Grazie a LlamaSharp, ho finalmente potuto tornare al mio
obiettivo iniziale: sviluppare un ambiente embedded per le applicazioni .NET, unendo la potenza dei modelli standard di intelligenza artificiale con la flessibilità del mio ecosistema di sviluppo preferito.
Il mondo dello sviluppo software offre sempre nuove opportunità per integrare funzionalità innovative nei progetti. Per completare e arricchire questo ambiente di base, ci sono tre aree di interesse che ho voluto affrontare.
La prima è il riconoscimento vocale, una tecnologia fondamentale per abilitare l'input tramite voce anziché testo. Ho testato i modelli Whisper di OpenAI che, grazie alla libreria Whisper.net, si integrano facilmente nelle applicazioni .NET. I risultati sono stati eccellenti sia in inglese che in italiano: con un impegno di 1 GB, il sistema è in grado di trascrivere il parlato in tempo reale con una buona precisione.
La seconda area riguarda la sintesi vocale, che rappresenta la controparte del riconoscimento vocale. In ambiente .NET, le opzioni disponibili sono piuttosto limitate e al di fuori delle librerie Speech di Microsoft, utilizzabili solo su Windows, non ci sono molte alternative. Dopo diverse ricerche, ho trovato Piper TTS, un sistema stand-alone che utilizza modelli ONNX da circa 80 MB e garantisce un'ottima qualità di sintesi vocale, anche in italiano.
Infine, un aspetto cruciale nel prompt engineering è la possibilità di organizzare e strutturare una base di prompt indicizzati in base al contesto, in modo da richiamarli e combinarli quando necessario per istruire al meglio i modelli LLM. Per mantenere un’architettura embedded, senza la necessità di dipendere da server esterni come MongoDB, una soluzione efficace è l’integrazione di un database NoSQL direttamente nell’applicazione, utilizzando la libreria LiteDB.
I mattoni fondamentali sono stati individuati e assemblati. L’ecosistema ha iniziato a prendere forma in modo organico e coerente, ponendo solide basi per lo sviluppo futuro.
Come in ogni progetto, una volta
conclusa la fase esplorativa, arriva il momento di trarre le somme e trasformare le conoscenze acquisite in soluzioni concrete e funzionali per applicazioni reali.
Ora possiamo dare inizio a questa nuova fase del progetto…