Doomformer is een experimentele tool die integer neurale netwerken compileert naar ZDoom-kaarten en deze uitvoert in ViZDoom. In dit systeem fungeren Voodoo-poppen als de rekeneenheden voor wiskundige bewerkingen, terwijl sector-lichtniveaus en vlakhoogtes worden gebruikt om waarden op te slaan.
Kernpunten uit het artikel:
- Werking en Exactheid: Hoewel de uitvoering extreem traag is vergeleken met NumPy, zijn de resultaten integer-exact. Bij MNIST-tests behaalde het systeem een hoge accuratesse.
- Functionaliteiten: Het project ondersteunt diverse architecturen zoals MLP's, Transformers, Conv1d, Conv2d en spiking networks. Het maakt gebruik van quantisation-aware training vanuit PyTorch.
- Experimenten: De auteur heeft diverse scenario's gebouwd, waaronder een 'museum-kaart' waar netwerkverbindingen handmatig gemuteerd kunnen worden, een Transformer die namen genereert, en een systeem waarbij de AI de acties van de marine aanstuurt.
- Technische Architectuur: De functionele verdeling is strikt: Voodoo-poppen voeren de berekeningen uit, terwijl ACS-level scripts zorgen voor het adresseren van cellen en het beheer van de dataflow.
Het project is open source (MIT) en biedt een uitgebreide Python-API voor het compileren en verifiëren van netwerken binnen de DOOM-engine.
Doomformer: Compileer integer neurale netwerken naar DOOM-kaarten
Het proces is traag: ongeveer 23.000 keer trager dan NumPy voor een klein MLP (engine-tijd; 65.000 keer met een timer rond de volledige aanroep). Een handgeschreven cijfer kost 163 seconden aan real-time DOOM in één kaart bij het standaardschema, en 8 seconden bij --opt 3.
Installatie en uitvoering
Installatie
pip install git+https://github.com/mottopanikeiku/doomformer # Python 3.11+, Linux of macOS
Voorbeelden van commando's
doomformer hello: (2 s) Een klein MLP, waarbij elke waarde wordt gecontroleerd.
doomformer draw --digit 7: (15 s) De marine loopt in de vorm van een 7 en het level leest dit uit.
Het commando doomformer doctor controleert ViZDoom, Freedoom en het display, bouwt ACC indien deze afwezig is, en compileert een ACS-probe voordat succes wordt gemeld. De eerste build vereist git, cmake, een C-compiler en netwerktoegang tot github.com.
Opmerking voor Linux: Er wordt gebruikgemaakt van dynamische runtimes; glibc-static is niet vereist. Op Fedora: sudo dnf install git cmake gcc gcc-c++ make. Colab notebook: Vereist geen setup, maar je tekent op een browser-canvas en doomformer mnist --image voert dit in DOOM in, zonder marine.
Bewijs: Een cijfer lopen, DOOM leest het
Wanneer de marine de vuurknop (FIRE) ingedrukt houdt, laat hij een penseel van 3×3 vloercellen onder zich achter. READ zorgt ervoor dat het level de 784 pixels in het netwerk schrijft, waarna 1.355 voodoo-poppen in dezelfde kaart de berekening uitvoeren. Met --digit neemt Python jouw rol over: het bedient de toetsen van de marine en de USE-knop één keer, en versnelt daarna alleen de klok en leest de resultaten.
Procesverloop:
- De poppen berekenen 784 pixels → 64 hidden units → 45 paarsgewijze scorevergelijkingen → 10 winnaars-tellingen.
- Alleen het cijfer dat al negen vergelijkingen wint, laat een pop door. De lijn die deze pop oversteekt, start het level-script dat het cijfer op het display verlicht.
- Python leest de 784 pixels, de hidden layer, elke vergelijking en het antwoord terug en controleert alle 248 waarden.
Exactheid: EXACT betekent dat DOOM gelijk is aan het integer-netwerk. Dit zegt niets over het feit of het cijfer correct is herkend. Op de eerste 100 MNIST-testcijfers leest DOOM er 99 correct, en 966 van de 1.000 samples uit de volledige set van 10.000; elk resultaat is exact. Het integer-netwerk scoort 97,16% op alle 10.000 (het float-model scoort 97,58%).
De museumkaart: Een cijfer verlichten, één verbinding herbedraden
Het commando doomformer museum build schrijft museum.wad, een kaart die je zonder Python kunt spelen. Je stapt op zeven vloerpads om een cijfer te verlichten en drukt op READ. 136 voodoo-poppen berekenen een klein handgeschreven netwerk.
MUTATE herbedraadt één verbinding van dat netwerk binnen het actieve level: segment a naar cijfer 7 verandert van +1 naar -1 door de specials van de twee gewichtslijnen aan te passen. Hetzelfde cijfer 7 wordt dan gelezen als een 1, en RESTORE herstelt dit.
Verificatie en controle
Wat wordt gecontroleerd
Python leest sector-lichtniveaus en vloer- en plafondhoogtes uit het actieve spel en vergelijkt deze met een onafhankelijke NumPy-integerimplementatie van hetzelfde netwerk. De controle is een integer-gelijkheid over de uitgevoerde runs.
De readback is standaard beveiligd: vóór elke pagina "vergiftigt" Python zijn USER-slots en controleert deze, waarna de pagina vereist wordt deze te vervangen. Hiermee wordt voorkomen dat een pagina stilletjes het antwoord van een herhaalde input of waarden van een vorige pagina hergebruikt.
Fuzzing en stresstests
doomformer fuzz --nets 40 vergelijkt elke waarde van 40 willekeurige netwerken in DOOM.
fuzz --edge plaatst gewichten, biases en inputs op de grenzen van het bereik.
doomformer verify voert elke inputklasse van een klein MLP uit.
Extractie en Mutatie
doomformer verify --extract net.wad --net net.npz leest het netwerk terug uit een gecompileerde kaart zonder DOOM te starten en vergelijkt elk gewicht, bias en drempelwaarde met het bronnetwerk.
Een mutatie-harness verandert één lijn van een gecompileerde kaart. Van de 1.004 geteste locaties werden er 297 "gedood" (de fout werd gedetecteerd), waaronder alle gewicht-, teken- en doelklassen.
Functionele Verdeling: Wie doet wat?
- Voodoo-poppen: Voeren elke multiply-add, activatie en vergelijking uit.
- ACS-level scripts: Adresseren cellen, verplaatsen input-gates, spoelen de poppen terug, voelen de wereld en kopiëren resultaten naar buiten. Geen van de rekenkundige bewerkingen van het netwerk draait in ACS.
- df.Agent: De enige uitzondering; deze voert de argmax uit in ACS.
Specifieke scenario's:
- MNIST: Python schrijft de 784 pixel-poorten en bepaalt wanneer de poppen zijn gestopt; de poppen kiezen het cijfer.
- Demo: Python schrijft de token-ids en bepaalt wanneer de poppen zijn gestopt, samplet elke letter uit de logits die DOOM berekende en kopieert integers tussen games wanneer een model over meerdere kaarten is verdeeld.
- Draw: De level is het brein. Python versnelt alleen de klok en voert readout-scripts uit.
- Fly loop: Python versnelt de klok en voert readout-scripts uit; het level tekent de input-spikes en de output-spikes vuren de thrust-lijnen af.
- Machine: De poppen runnen de program counter en de registers.
Overzicht van Commando's
| Commando | Wat DOOM doet | Tijd | Moeilijkheidsgraad |
doomformer hello | Klein MLP, 5 willekeurige inputs | 2 s | I'm too young to die |
doomformer draw --digit 7 | Marine loopt een 7, level leest dit | 15 s | (Niet gespecificeerd) |
doomformer mnist | Leest 4 testcijfers of eigen afbeelding | 1.5 - 3 min | Hey, not too rough |
doomformer demo | Transformer schrijft een level-naam | 15 - 60 s | Hurt me plenty |
doomformer demo --nightmare | 2-laags model, 106 tile maps | 3.5 - 7 min | Nightmare! |
doomformer ask "[vraag]" | Antwoorden uit 189 DOOM-feiten | ~11 min | Ultra-Violence |
doomformer machine subleq all | SUBLEQ-machine draait drie programma's | ~5 min | (Niet gespecificeerd) |
doomformer learn digits | Perceptron leert 8×8 cijfers in één map | ~5 min | (Niet gespecificeerd) |
Uitgebreide Experimenten
"His brain is the level"
Een level-script detecteert de dichtstbijzijnde demon; 1.345 voodoo-poppen van de marine voeren zijn policy transformer uit en kiezen een actie. De 50 van de 50 beslissingen waren exact. Met --opt 3 duurde dit 22 seconden per beslissing in plaats van 124 seconden. In een live scenario (--live) is de marine echter dood na 5,5 seconden zonder kills, omdat de reken-tijd van de poppen te lang is vergeleken met de reactiesnelheid van de vijanden.
Overige experimenten
- Transformer-naamgeving: Een 2-laags model schreef "DEMON CANYON" over 1.690 tile-runs.
- Perceptron Learning: Een perceptron leert 8×8 cijfers binnen één game. De gewichten zijn plafondhoogtes; de poppen berekenen de scores, de argmax en de update.
- Fruitvlieg-circuit: 255 neuronen afgeleid van het FlyWire-connectoom sturen de bewegingen van de marine aan.
- Transformer in één map: 3.548 poppen schrijven "SHIL" zonder Python tussen de lagen.
Zelf een netwerk bouwen
Je kunt een netwerk compileren naar een kaart en de outputs gebruiken om deuren te openen, lichten te laten knipperen of de marine te duwen.
Voorbeeldcode:
import numpy as np
import doomformer as df
# 2 inputs -> [a AND b, a OR b] -> 2 outputs, allen integers
net = df.MLP(
weights=[np.array([[1, 1], [1, 1]]), np.array([[1, 0], [0, 1]])],
biases=[np.array([-1, 0]), np.array([0, 0])],
thresholds=[np.array([1]), np.array([1])],
signed_input=False,
)
with df.compile(net) as brain:
for x in ([0, 0], [1, 0], [1, 1]):
# berekend door voodoo-poppen; gooit df.MismatchError bij verschil met net.reference
print(x, "->", brain(np.array(x)).tolist())
brain.record([np.array([0, 1]), np.array([1, 1])], "dolls.gif") # film de poppen
df.MLP.from_torch quantiseert een PyTorch-model. De API ondersteunt ook spiking networks, transformers, Conv1d en Conv2d.
Schaalbaarheid en Prestaties
Meerdere games tegelijk
Een kaart is één game en gebruikt één core. df.run_many verdeelt inputs over beschikbare games. 1.000 MNIST-testcijfers in 14 games op een 16-core machine duurden 2 uur.
Tile Maps
Dit is een multi-map ontwerp waarbij verschillende kaarten verschillende lagen representeren. Python kopieert de waarden van de ene game naar de volgende. Voor MNIST betekende dit een reductie van 5.696 game-tics (één kaart) naar 216 game-tics (tiles).
Technische Details en Beperkingen
Snelheid
DOOM is traag. De engine-tijd van één pass tegenover de NumPy-referentie (14 tot 48 µs) is ongeveer 23.000 keer trager voor een 12→16→6 MLP.
Beperkingen van de Engine
- Objectlimiet: ViZDoom lijst maximaal 4.096 objecten; daardoor worden sommige poppen niet getekend bij zeer grote modellen.
- Coördinaten: Om fouten in de BSP-node te voorkomen, past elke kaart binnen 32.000 eenheden.
- Vlakbewegingen: Een plane-mover weigert een tweede verzoek zolang de eerste beweging bezig is.
Architecturale keuzes
- Attention: Gebruikt integer-attention (ReLU-stijl in de meeste modellen). Geen LayerNorm (sommige gebruiken integer RMSNorm).
- Quantisatie: Modellen worden offline getraind in PyTorch met quantisation-aware training.
Projectstructuur
| Pad | Inhoud |
doomformer/ | Toolkit: map compiler, DOOM runtime, exact checks, CLI |
demos/ | Onderzoekscode: brain-level, walker, fly circuit, learning arena |
training/ | Quantisation-aware training voor elk model, inclusief corpora |
models/ | Getrainde modellen met logs en evaluaties |
examples/ | Twaalf eendelige voorbeelden (meestal < 15 s) |
tests/ | 479 tests, waarvan 175 op headless DOOM |
docs/ | Documentatie over werking, demo's, engine facts en gidsen |
Licenties
- Code en documentatie: MIT.
- Data: Diverse licenties (Doom Wiki CC BY-SA 4.0, id Software GPL-2.0, MNIST CC BY-SA 3.0, FlyWire CC BY-NC 4.0).
- Graphics: Freedoom is BSD-gelicenseerd.
- Merknaam: DOOM is een handelsmerk van id Software / ZeniMax; dit project is niet aan hen gelieerd.
Doomformer: Compileer integer neurale netwerken naar DOOM-kaarten
Het proces is traag: ongeveer 23.000 keer trager dan NumPy voor een klein MLP (engine-tijd; 65.000 keer met een timer rond de volledige aanroep). Een handgeschreven cijfer kost 163 seconden aan real-time DOOM in één kaart bij het standaardschema, en 8 seconden bij --opt 3.
Installatie en uitvoering
Installatie
pip install git+https://github.com/mottopanikeiku/doomformer # Python 3.11+, Linux of macOS
Voorbeelden van commando's
doomformer hello: (2 s) Een klein MLP, waarbij elke waarde wordt gecontroleerd.
doomformer draw --digit 7: (15 s) De marine loopt in de vorm van een 7 en het level leest dit uit.
Het commando doomformer doctor controleert ViZDoom, Freedoom en het display, bouwt ACC indien deze afwezig is, en compileert een ACS-probe voordat succes wordt gemeld. De eerste build vereist git, cmake, een C-compiler en netwerktoegang tot github.com.
Opmerking voor Linux: Er wordt gebruikgemaakt van dynamische runtimes; glibc-static is niet vereist. Op Fedora: sudo dnf install git cmake gcc gcc-c++ make. Colab notebook: Vereist geen setup, maar je tekent op een browser-canvas en doomformer mnist --image voert dit in DOOM in, zonder marine.
Bewijs: Een cijfer lopen, DOOM leest het
Wanneer de marine de vuurknop (FIRE) ingedrukt houdt, laat hij een penseel van 3×3 vloercellen onder zich achter. READ zorgt ervoor dat het level de 784 pixels in het netwerk schrijft, waarna 1.355 voodoo-poppen in dezelfde kaart de berekening uitvoeren. Met --digit neemt Python jouw rol over: het bedient de toetsen van de marine en de USE-knop één keer, en versnelt daarna alleen de klok en leest de resultaten.
Procesverloop:
- De poppen berekenen 784 pixels → 64 hidden units → 45 paarsgewijze scorevergelijkingen → 10 winnaars-tellingen.
- Alleen het cijfer dat al negen vergelijkingen wint, laat een pop door. De lijn die deze pop oversteekt, start het level-script dat het cijfer op het display verlicht.
- Python leest de 784 pixels, de hidden layer, elke vergelijking en het antwoord terug en controleert alle 248 waarden.
Exactheid: EXACT betekent dat DOOM gelijk is aan het integer-netwerk. Dit zegt niets over het feit of het cijfer correct is herkend. Op de eerste 100 MNIST-testcijfers leest DOOM er 99 correct, en 966 van de 1.000 samples uit de volledige set van 10.000; elk resultaat is exact. Het integer-netwerk scoort 97,16% op alle 10.000 (het float-model scoort 97,58%).
De museumkaart: Een cijfer verlichten, één verbinding herbedraden
Het commando doomformer museum build schrijft museum.wad, een kaart die je zonder Python kunt spelen. Je stapt op zeven vloerpads om een cijfer te verlichten en drukt op READ. 136 voodoo-poppen berekenen een klein handgeschreven netwerk.
MUTATE herbedraadt één verbinding van dat netwerk binnen het actieve level: segment a naar cijfer 7 verandert van +1 naar -1 door de specials van de twee gewichtslijnen aan te passen. Hetzelfde cijfer 7 wordt dan gelezen als een 1, en RESTORE herstelt dit.
Verificatie en controle
Wat wordt gecontroleerd
Python leest sector-lichtniveaus en vloer- en plafondhoogtes uit het actieve spel en vergelijkt deze met een onafhankelijke NumPy-integerimplementatie van hetzelfde netwerk. De controle is een integer-gelijkheid over de uitgevoerde runs.
De readback is standaard beveiligd: vóór elke pagina "vergiftigt" Python zijn USER-slots en controleert deze, waarna de pagina vereist wordt deze te vervangen. Hiermee wordt voorkomen dat een pagina stilletjes het antwoord van een herhaalde input of waarden van een vorige pagina hergebruikt.
Fuzzing en stresstests
doomformer fuzz --nets 40 vergelijkt elke waarde van 40 willekeurige netwerken in DOOM.
fuzz --edge plaatst gewichten, biases en inputs op de grenzen van het bereik.
doomformer verify voert elke inputklasse van een klein MLP uit.
Extractie en Mutatie
doomformer verify --extract net.wad --net net.npz leest het netwerk terug uit een gecompileerde kaart zonder DOOM te starten en vergelijkt elk gewicht, bias en drempelwaarde met het bronnetwerk.
Een mutatie-harness verandert één lijn van een gecompileerde kaart. Van de 1.004 geteste locaties werden er 297 "gedood" (de fout werd gedetecteerd), waaronder alle gewicht-, teken- en doelklassen.
Functionele Verdeling: Wie doet wat?
- Voodoo-poppen: Voeren elke multiply-add, activatie en vergelijking uit.
- ACS-level scripts: Adresseren cellen, verplaatsen input-gates, spoelen de poppen terug, voelen de wereld en kopiëren resultaten naar buiten. Geen van de rekenkundige bewerkingen van het netwerk draait in ACS.
- df.Agent: De enige uitzondering; deze voert de argmax uit in ACS.
Specifieke scenario's:
- MNIST: Python schrijft de 784 pixel-poorten en bepaalt wanneer de poppen zijn gestopt; de poppen kiezen het cijfer.
- Demo: Python schrijft de token-ids en bepaalt wanneer de poppen zijn gestopt, samplet elke letter uit de logits die DOOM berekende en kopieert integers tussen games wanneer een model over meerdere kaarten is verdeeld.
- Draw: De level is het brein. Python versnelt alleen de klok en voert readout-scripts uit.
- Fly loop: Python versnelt de klok en voert readout-scripts uit; het level tekent de input-spikes en de output-spikes vuren de thrust-lijnen af.
- Machine: De poppen runnen de program counter en de registers.
Overzicht van Commando's
| Commando | Wat DOOM doet | Tijd | Moeilijkheidsgraad |
doomformer hello | Klein MLP, 5 willekeurige inputs | 2 s | I'm too young to die |
doomformer draw --digit 7 | Marine loopt een 7, level leest dit | 15 s | (Niet gespecificeerd) |
doomformer mnist | Leest 4 testcijfers of eigen afbeelding | 1.5 - 3 min | Hey, not too rough |
doomformer demo | Transformer schrijft een level-naam | 15 - 60 s | Hurt me plenty |
doomformer demo --nightmare | 2-laags model, 106 tile maps | 3.5 - 7 min | Nightmare! |
doomformer ask "[vraag]" | Antwoorden uit 189 DOOM-feiten | ~11 min | Ultra-Violence |
doomformer machine subleq all | SUBLEQ-machine draait drie programma's | ~5 min | (Niet gespecificeerd) |
doomformer learn digits | Perceptron leert 8×8 cijfers in één map | ~5 min | (Niet gespecificeerd) |
Uitgebreide Experimenten
"His brain is the level"
Een level-script detecteert de dichtstbijzijnde demon; 1.345 voodoo-poppen van de marine voeren zijn policy transformer uit en kiezen een actie. De 50 van de 50 beslissingen waren exact. Met --opt 3 duurde dit 22 seconden per beslissing in plaats van 124 seconden. In een live scenario (--live) is de marine echter dood na 5,5 seconden zonder kills, omdat de reken-tijd van de poppen te lang is vergeleken met de reactiesnelheid van de vijanden.
Overige experimenten
- Transformer-naamgeving: Een 2-laags model schreef "DEMON CANYON" over 1.690 tile-runs.
- Perceptron Learning: Een perceptron leert 8×8 cijfers binnen één game. De gewichten zijn plafondhoogtes; de poppen berekenen de scores, de argmax en de update.
- Fruitvlieg-circuit: 255 neuronen afgeleid van het FlyWire-connectoom sturen de bewegingen van de marine aan.
- Transformer in één map: 3.548 poppen schrijven "SHIL" zonder Python tussen de lagen.
Zelf een netwerk bouwen
Je kunt een netwerk compileren naar een kaart en de outputs gebruiken om deuren te openen, lichten te laten knipperen of de marine te duwen.
Voorbeeldcode:
import numpy as np
import doomformer as df
# 2 inputs -> [a AND b, a OR b] -> 2 outputs, allen integers
net = df.MLP(
weights=[np.array([[1, 1], [1, 1]]), np.array([[1, 0], [0, 1]])],
biases=[np.array([-1, 0]), np.array([0, 0])],
thresholds=[np.array([1]), np.array([1])],
signed_input=False,
)
with df.compile(net) as brain:
for x in ([0, 0], [1, 0], [1, 1]):
# berekend door voodoo-poppen; gooit df.MismatchError bij verschil met net.reference
print(x, "->", brain(np.array(x)).tolist())
brain.record([np.array([0, 1]), np.array([1, 1])], "dolls.gif") # film de poppen
df.MLP.from_torch quantiseert een PyTorch-model. De API ondersteunt ook spiking networks, transformers, Conv1d en Conv2d.
Schaalbaarheid en Prestaties
Meerdere games tegelijk
Een kaart is één game en gebruikt één core. df.run_many verdeelt inputs over beschikbare games. 1.000 MNIST-testcijfers in 14 games op een 16-core machine duurden 2 uur.
Tile Maps
Dit is een multi-map ontwerp waarbij verschillende kaarten verschillende lagen representeren. Python kopieert de waarden van de ene game naar de volgende. Voor MNIST betekende dit een reductie van 5.696 game-tics (één kaart) naar 216 game-tics (tiles).
Technische Details en Beperkingen
Snelheid
DOOM is traag. De engine-tijd van één pass tegenover de NumPy-referentie (14 tot 48 µs) is ongeveer 23.000 keer trager voor een 12→16→6 MLP.
Beperkingen van de Engine
- Objectlimiet: ViZDoom lijst maximaal 4.096 objecten; daardoor worden sommige poppen niet getekend bij zeer grote modellen.
- Coördinaten: Om fouten in de BSP-node te voorkomen, past elke kaart binnen 32.000 eenheden.
- Vlakbewegingen: Een plane-mover weigert een tweede verzoek zolang de eerste beweging bezig is.
Architecturale keuzes
- Attention: Gebruikt integer-attention (ReLU-stijl in de meeste modellen). Geen LayerNorm (sommige gebruiken integer RMSNorm).
- Quantisatie: Modellen worden offline getraind in PyTorch met quantisation-aware training.
Projectstructuur
| Pad | Inhoud |
doomformer/ | Toolkit: map compiler, DOOM runtime, exact checks, CLI |
demos/ | Onderzoekscode: brain-level, walker, fly circuit, learning arena |
training/ | Quantisation-aware training voor elk model, inclusief corpora |
models/ | Getrainde modellen met logs en evaluaties |
examples/ | Twaalf eendelige voorbeelden (meestal < 15 s) |
tests/ | 479 tests, waarvan 175 op headless DOOM |
docs/ | Documentatie over werking, demo's, engine facts en gidsen |
Licenties
- Code en documentatie: MIT.
- Data: Diverse licenties (Doom Wiki CC BY-SA 4.0, id Software GPL-2.0, MNIST CC BY-SA 3.0, FlyWire CC BY-NC 4.0).
- Graphics: Freedoom is BSD-gelicenseerd.
- Merknaam: DOOM is een handelsmerk van id Software / ZeniMax; dit project is niet aan hen gelieerd.