DIEGO CECATO / NOTE DAL CAMPO

Doomformer: una rete neurale in DOOM, verificata valore per valore

diego cecato//
Doomformer: una rete neurale in DOOM, verificata valore per valore. Copertina editoriale Diego Cecato.

C’è una differenza enorme tra far comparire una rete neurale dentro un videogioco e trasformare quel videogioco in una macchina che esegue davvero i calcoli della rete. Nel primo caso basta una buona scenografia. Nel secondo devi definire come rappresentare i numeri, come trasferirli da un’operazione all’altra e, soprattutto, come dimostrare che il risultato non è soltanto una bella animazione. Doomformer è interessante proprio perché prova a risolvere questo secondo problema.

Il progetto open source compila reti neurali a valori interi in mappe compatibili con ZDoom. Non si limita a disegnare neuroni sullo schermo: usa le meccaniche del motore, comprese le cosiddette voodoo dolls, per far avanzare quantità, accumulare pesi e produrre output. La parte meno spettacolare, ma più importante, è che il risultato viene letto e confrontato con un’implementazione di riferimento in NumPy. Un esperimento del genere non promette una nuova piattaforma AI. Costringe piuttosto a ragionare su che cosa significhi davvero eseguire e verificare un programma.

Da grafo di calcolo a geometria di gioco

In una rete neurale tradizionale siamo abituati a descrivere un insieme di operazioni matematiche: pesi, somme, soglie, attivazioni e, in alcuni modelli, attenzione. Il runtime si occupa di eseguirle su CPU o GPU. Doomformer sposta il problema su un terreno volutamente ostile: una mappa di gioco, dove il linguaggio disponibile è fatto di linee, settori, oggetti e trigger.

L’autore documenta una corrispondenza concreta. Un valore intero può essere rappresentato da un certo numero di voodoo dolls; l’attraversamento di linee contribuisce all’accumulo di un peso; un livello di luce o l’altezza di un settore conserva un risultato; sequenze di porte e scale implementano confronti e attivazioni. La mappa non è quindi la rappresentazione grafica di una rete che gira altrove. È una delle rappresentazioni eseguibili della rete stessa.

Il passaggio chiave è la compilazione. Un programma traduce una struttura matematica in una disposizione di oggetti del motore. Da lì in avanti le proprietà del gioco diventano vincoli dell’ambiente di esecuzione: il tempo avanza a tic, certi movimenti non accettano comandi sovrapposti e la geometria può introdurre comportamenti inattesi. Non è diverso, nel principio, dal dover rispettare istruzioni, memoria e convenzioni di una macchina reale. È molto diverso, ovviamente, nella convenienza.

Il dettaglio che conta: il confronto con NumPy

Un risultato visivamente convincente non dimostra che i calcoli siano corretti. Se una mappa mostra il numero sette dopo che il giocatore ha disegnato un sette, non sappiamo ancora se la rete ha funzionato o se un altro pezzo del programma ha semplicemente scelto il risultato atteso. Doomformer affronta il problema leggendo i valori che il motore espone e confrontandoli con una rete intera di riferimento eseguita in NumPy.

Nel README, l’autore chiama EXACT l’uguaglianza numerica a tolleranza zero fra i valori osservati e quelli della reference, sugli input effettivamente eseguiti. È una forma pratica di translation validation: invece di fidarsi ciecamente del compilatore, si controlla che il programma tradotto si comporti come quello originale nei casi osservati. Non equivale a una dimostrazione formale per tutti gli input possibili, ma è molto più informativo del solo output finale.

L’esempio MNIST è utile perché rende evidente la distinzione. La documentazione riporta 1.355 voodoo dolls in una mappa e 10.000 casi di test con valori interni corrispondenti alla reference. Il modello classifica correttamente 9.716 cifre su 10.000, secondo le misure pubblicate dall’autore. Significa che l’esecuzione può essere esatta rispetto al modello e che il modello, comunque, può sbagliare una cifra. Sono due proprietà diverse e confonderle produce valutazioni tecniche sbagliate.

La correttezza di un’esecuzione e la qualità della decisione prodotta non sono la stessa metrica. Una macchina può eseguire perfettamente un modello che prende una decisione sbagliata.

Il test deve provare a rompere la propria dimostrazione

La parte che rende l’esperimento più serio di un semplice esercizio di programmazione creativa è l’attenzione alle verifiche negative. Il repository descrive 20.000 reti casuali sottoposte a fuzzing senza discrepanze rilevate nei casi eseguiti. Include inoltre test di mutazione: cambiando una linea o un peso nella mappa, il controllo dovrebbe accorgersi della differenza. La documentazione distingue i mutanti intercettati da quelli per cui non è stata dimostrata l’innocuità.

Questo dettaglio è importante. Un test che passa può essere corretto, oppure può non osservare la parte sbagliata del sistema. Se altero un’operazione e il test continua a passare, devo chiedermi se quella mutazione sia davvero irrilevante o se il mio sistema di osservazione abbia un punto cieco. Doomformer documenta anche limiti espliciti: non tutti i confronti intermedi delle mappe più grandi vengono esposti direttamente, e alcuni vengono verificati soltanto attraverso i risultati a valle.

È un modo di ragionare applicabile a molto software meno pittoresco. Un parser, una pipeline ETL, un compilatore, un’integrazione fra servizi: tutti possono restituire un risultato apparentemente plausibile senza rispettare gli invarianti richiesti. È il motivo per cui un test eseguibile vale più della dichiarazione di un agente che dice di aver trovato o corretto un bug. L’affidabilità nasce da osservazioni riproducibili, non dalla sicurezza con cui viene raccontato un risultato.

Ventitremila volte più lento: il numero che rimette tutto in prospettiva

Nel README l’autore dichiara che una piccola rete MLP eseguita nel motore è circa 23.000 volte più lenta della reference NumPy. In altre misure, che includono anche l’intero percorso di invocazione, compare un fattore intorno a 65.000. Non sono benchmark indipendenti e non misurano esattamente la stessa cosa: vanno letti come numeri pubblicati dal progetto, con perimetri differenti, non come una classifica generale fra motori di gioco e librerie numeriche.

La lentezza non è un difetto da nascondere per salvare la notizia. È una conseguenza del tipo di astrazione scelto. Per rappresentare un’operazione elementare, il sistema deve muovere oggetti, attraversare trigger, attendere eventi e trasferire stati. Ogni livello aggiunge costo. Il punto non è cercare un’ottimizzazione miracolosa che trasformi DOOM in un acceleratore AI: è osservare quanto il modello di esecuzione influisca sulla prestazione.

Chi progetta sistemi incontra lo stesso problema quando aggiunge code, serializzazioni, chiamate remote o conversioni di formato senza misurarne il costo. Cambia la scala, non il principio. Anche il percorso reale dei dati può contare più della velocità teorica del componente che dovrebbe elaborarli.

Che cosa funziona e che cosa resta una dimostrazione

Il repository include esempi di classificazione di cifre, piccoli modelli di generazione, una rete che seleziona azioni per un marine e persino esperimenti di apprendimento all’interno della mappa. Ma l’autore stesso delimita le proprie affermazioni: i modelli sono interi, l’attenzione è una variante intera, non viene usata LayerNorm e le dimostrazioni più grandi richiedono ViZDoom e un’orchestrazione esterna.

In alcuni scenari Python prepara gli input, fa avanzare il motore e legge i risultati; quando la rete viene spezzata in più mappe, copia anche i valori fra un livello e l’altro. In altri esempi, come il museo giocabile, il calcolo resta nella mappa senza Python durante l’esecuzione. Non è una contraddizione, ma è una differenza architetturale che va dichiarata quando si dice che la rete gira dentro DOOM.

Anche il marine che prende decisioni non è un agente autonomo capace di affrontare qualunque partita. La documentazione segnala che una decisione può richiedere decine di secondi di tempo di gioco e che il personaggio, con i nemici attivi, non riesce a competere. Il valore dell’esperimento sta nella possibilità di tracciare il circuito che porta alla scelta, non nelle prestazioni del giocatore virtuale.

La lezione per chi costruisce software normale

È facile liquidare Doomformer come un gioco per programmatori. Sarebbe altrettanto facile presentarlo come la prova che l’intelligenza artificiale può girare ovunque. Entrambe le letture perdono il punto. Il progetto dimostra che un modello computazionale può essere tradotto in un ambiente imprevisto se ne conosci abbastanza bene le regole, e che la traduzione va verificata contro un riferimento esplicito.

Ci sono almeno tre domande da portarsi dietro quando si progetta un sistema: quali proprietà deve preservare una trasformazione; quali stati intermedi posso osservare per verificarle; e quali costi introduce il runtime scelto? Se non sappiamo rispondere, la demo che funziona una volta non è ancora una prova affidabile. Questo vale anche quando il codice viene generato da un modello AI: il generatore non sostituisce il contratto, i test e la comprensione dell’ambiente.

La cosa più riuscita di Doomformer, alla fine, non è aver messo una rete neurale dentro un gioco del passato. È aver trasformato una provocazione tecnica in un insieme di traduzioni, misure, limiti e verifiche che altri possono ispezionare. Il risultato non è una nuova strada per accelerare l’inferenza. È un buon promemoria di quanto lavoro serva per poter dire, con un minimo di rigore, che un programma fa davvero ciò che sosteniamo.

Domande frequenti

Doomformer esegue davvero la rete neurale dentro DOOM?

Sì, nelle mappe compatibili con ZDoom le meccaniche di gioco eseguono somme, confronti e attivazioni di modelli interi. Python può preparare gli input e leggere i risultati; in alcune demo copia anche valori tra mappe, mentre nel museo giocabile il calcolo resta nel livello.

Che cosa significa EXACT nei test di Doomformer?

Indica che i valori numerici esposti dalla mappa coincidono con quelli della rete intera di riferimento in NumPy, con tolleranza zero per gli input eseguiti. Non dimostra che il modello classifichi sempre correttamente o che ogni possibile input sia verificato.

Quanto è lento Doomformer rispetto a NumPy?

Il README del progetto riporta per una piccola MLP una lentezza di circa 23.000 volte rispetto a NumPy nel solo motore. Altre misure end-to-end citano circa 65.000 volte. Sono benchmark dichiarati dall’autore e con perimetri differenti, non misure indipendenti.

Si può usare Doomformer per un'applicazione AI reale?

È soprattutto un esperimento di compilazione e verifica, non un runtime competitivo. Usa modelli interi, richiede ZDoom/ViZDoom e ha costi di esecuzione molto elevati. È utile per studiare rappresentazioni, invarianti, test differenziali e limiti delle astrazioni.