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

CommandoWat DOOM doetTijdMoeilijkheidsgraad
doomformer helloKlein MLP, 5 willekeurige inputs2 sI'm too young to die
doomformer draw --digit 7Marine loopt een 7, level leest dit15 s(Niet gespecificeerd)
doomformer mnistLeest 4 testcijfers of eigen afbeelding1.5 - 3 minHey, not too rough
doomformer demoTransformer schrijft een level-naam15 - 60 sHurt me plenty
doomformer demo --nightmare2-laags model, 106 tile maps3.5 - 7 minNightmare!
doomformer ask "[vraag]"Antwoorden uit 189 DOOM-feiten~11 minUltra-Violence
doomformer machine subleq allSUBLEQ-machine draait drie programma's~5 min(Niet gespecificeerd)
doomformer learn digitsPerceptron 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

PadInhoud
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.