用 DOOM 遊戲構建神經網路:完整教學與實作指南
I built a neural network using DOOM games
DOOM 被用來執行完整的神經網路推理,作者示範如何將 MLP、Transformer 等模型編譯成遊戲地圖並在 ViZDoom 執行。
- 模型範例:1,355 個 voodoo dolls 代表 784‑pixel MNIST 輸入,通過 64‑dim 隱藏層、45 個比較和 10 個勝利計數,最終輸出正確數字,100% 精確。
此專案將機器學習模型對映至 DOOM 遊戲引擎,示範如何以遊戲作為精確數值運算平臺,對學習者瞭解硬體實作與離線推理有實際參考。
在 DOOM 遊戲機制上運作的神經網路:巫毒娃娃、傳送帶、熔岩與門扉。
觀看影片 · 在 Colab 嘗試: 畫一個數字,DOOM 讀取,無需安裝
一個數字是傳送帶上巫毒娃娃的群體。啟用函式是一座他們必須攀爬的樓梯。矩陣乘法是娃娃在熔岩線上奔跑,每條線提升房間的光照等級。注意力是娃娃被另一個數字降低的天花板壓縮。當娃娃停止時,光照等級從正在執行的遊戲中讀回,並逐位與獨立的整數參考比較。若有一個值偏差一,執行失敗。
海軍陸戰隊員的腦袋被編譯進他正在戰鬥的關卡中。右側的點代表他 1,345 個巫毒娃娃。它們執行他的 Transformer,決定下一步;關卡會偵測到惡魔並觸發。觀看影片.
走一個數字,DOOM 讀取它
輸入是一個 28×28 的地板。海軍陸戰隊員在按下 FIRE 時走過的每一格都會被墨水記錄,並且關卡將該畫素寫入腦中。他按下 READ。1,355 個巫毒娃娃執行 MNIST 網路,比較所有 45 對數字分數並統計勝利。只有勝者的娃娃能通過,並點亮牆上的該數字。Python 先進行時鐘更新,然後讀回 784 個畫素、隱藏層、每一次比較以及答案,並將所有 248 個值與 NumPy 做比對。
doomformer draw # opens a window: arrow keys to walk, hold Ctrl to draw, Space on the READ switch doomformer draw --digit 7 # headless: the marine walks a 7 (Python presses his keys in place of a player)
▄▄▄▄▄▄▄▄▄▄▄▄▄▄
▄████████████████
████▀▀ ▀████▀
▀▀▀ ▄███▀
████
███
███
███
████
███▀
████
▀███
DOOM reads: 7 (right) -- the winner's doll lit it on the 7-segment display
EXACT: all 248 values DOOM computed (hidden layer, 45 score comparisons and 10 win counts, each with its twin, and the one-hot answer) equal the NumPy integer reference computed from the 784 pixel ports DOOM holds (tolerance 0); one display firing
pixels: the 784 ports equal the level's canvas (the tiles the pen inked)
READ pass: 5,705 game tics = 163 s of real-time DOOM at 35 tics/s, 79 s wall | Python sent 58 commands, 29 readout scripts each followed by the ack script it waits for (0 waited more than one advance), and nothing else (0 rejected)
每個 0 至 9 的數字都有三種走路變體:共 30 個正確讀取,全部 30 個完全一致。在視窗模式下 DOOM 以即時方式執行,READ 需要 2¾ 分鐘;無頭模式則 76 到 99 秒。工作原理 · 影片
選擇你的技能等級
pip install git+https://github.com/mottopanikeiku/doomformer # Python 3.11+, Linux or macOS第一次執行會建構 DOOM 的指令碼編譯器,因此你需要 git、cmake 以及 C 編譯器。或者直接跳過:Colab 筆記本會安裝它,讓你畫一個數字讓 DOOM 讀取,並執行 softmax 示範。
| 技能 | 指令 | DOOM 的功能 | 時間 |
|---|---|---|---|
| 我還太年輕不會死 | doomformer hello |
執行一個小型神經網路 | 3 秒 |
| 嘿,別太嚴苛 | doomformer mnist |
讀取手寫數字 | ~2 分鐘 |
| 傷害我不少 | doomformer demo |
創造一個 DOOM 關卡名稱 | 20 秒 |
| 極端暴力 | doomformer ask "what does iddqd do" |
回答你的問題 | 7-11 分鐘 |
| 噩夢! | doomformer demo --nightmare |
106 張關卡,56,390 個巫毒娃娃 | 2-4 分鐘 |
時間來自於一臺 16 核 Linux 機器,同時執行 8 個 DOOM 遊戲。
我還太年輕不會死:你將看到的
tiny integer MLP 4-6-5-3 compiled into one DOOM map: 60 voodoo dolls, 1222 sectors
input 1/5 x=[-71, -82, 64, 37] -> y=[11, 4, -15] EXACT (3 outputs + 22 hidden readouts equal the integer reference; 488 game tics)
...
5/5 EXACT
嘿,別太嚴苛:DOOM 讀取手寫數字
784 個畫素 → 64 → 45 次比較 → 10 次勝利計數 → 答案,全部在一張 DOOM 地圖中完成:1,355 個巫毒娃娃和 158,550 個區段。每一個墨水畫素會釋放一個娃娃,沿著隱藏層的權重線奔跑。娃娃接著比較每一對數字分數,只有贏得全部九次比較的數字能通過。DOOM 選擇該數字,Python 進行核對。在 DOOM 中,網路正確讀取了前 100 個 MNIST 測試數字中的 99 個,從全部 10,000 個中抽樣 1,000 個時讀取 966 個,全部都完全一致。對所有 10,000 個,該整數網路的準確率為 97.16%。
$ doomformer mnist --digits 2 --seed 1
███
████ ▄▄▄█████████▄▄
████ ▀█████▀▀▀▀▀████
████ ▄▄███▀
███▄ ▄▄▄███████▄▄
████ ████▀▀▀▀▀▀█████
████ ▀███
████ ▄▄ ▄██▀
▀████ █████▄▄██████▀
▀██ ▀▀▀▀▀▀▀▀▀▀
DOOM reads: 1 right DOOM reads: 3 right
test #46 test #51
2/2 read correctly | 2/2 EXACT: every value DOOM computed (248 per digit: hidden layer, comparisons, win counts and
the one-hot answer) equals the NumPy integer reference (tolerance 0); Python only compared | 5,696 game tics per digit | 88 s
要從照片或繪圖中讀取你自己的數字:doomformer mnist --image my7.png。
傷害我不少:transformer 為一個關卡命名
一個訓練於 136 個官方 DOOM 關卡名稱的 transformer,拆分成 38 張小關卡,供一組 DOOM 遊戲執行。Python 在遊戲間複製整數,並從 DOOM 計算的 logits 中挑選每個字母。
DOOM wrote: 'GHEER' (NOT in its training corpus: the model made it up)
174 tile runs in 8 parallel DOOM games | 10,462 voodoo dolls in the tile maps |
6,902 values read back and compared with the NumPy integer reference (tolerance 0) | 15 s
EXACT: every value DOOM exposed equals the integer reference, and the reference writes the same name
--seed 3、--seed 4 等可產生其他名稱。--one-map 將整個 transformer 放入單張關卡(嵌入、注意力、MLP 與 logits,層間無 Python),每個字母大約 90 秒。
--softmax 執行相同的模型,使用 softmax attention 訓練。娃娃計算指數值(階梯式,步階像對數一樣間隔)並於執行時除以總和(比較次數)。其運作方式見於 docs/HOW_IT_WORKS.md.
DOOM wrote: 'FINAL INEALL' (NOT in its training corpus: the model made it up)
494 tile runs in 8 parallel DOOM games | 4,886 voodoo dolls in the tile maps |
106,834 values read back and compared with the NumPy integer reference (tolerance 0) | 59 s
EXACT: every value DOOM exposed equals the integer reference, and the reference writes the same name
--one-map --softmax 在單一圖中完成,層與層之間無 Python:4,209 個娃娃,每個字母約 2 分鐘。
--model doomnames_modern 在 softmax attention 上加入 RMSNorm 與閘控(ReGLU)MLP。娃娃亦計算平方和(如奇數之和)、整數平方根(階梯式 d·k²)以及除法。它在 170 個 tile 執行中寫出 MILL,所有 62,000 個值皆精確。
它自己寫。 --one-map --loop 建立一張圖,能在不使用 Python 的迴圈中寫完整個名字。每個位置,娃娃比較所有 30 個 logits,勝出的娃娃將下一位置的 token 輸入移至其字母。關卡隨即回溯並自行執行下一輪,共五輪以處理 6 個 token 的上下文。Python 只進行時間同步與讀取:每一輪的每個值皆被檢查,最終文字必須等於貪婪生成。它寫了 DAN(5,875 個娃娃,約 3 分鐘/輪),以及 DAGE 搭配 --softmax。
Ultra-Violence:向 DOOM 提問
一個 2 層 transformer,訓練於 189 個關於 DOOM 的事實。跨越數千個 DOOM 遊戲例項的娃娃計算每個值,且每個值皆被檢查。首字母需數分鐘,之後每 20 秒左右到達一次。部分答案,皆在 DOOM 中精確:
| 你問 | DOOM 回答 |
|---|---|
| iddqd 會做什麼 | GOD MODE |
| 什麼是愛 | INVULNERABILITY |
| 什麼是娃娃 | BERSERK |
| 它能跑 DOOM 嗎 | SIXTY SECONDS |
它知道自己的作弊碼。再問其他問題,你會得到自信的 DOOM 無稽之談。
Nightmare!:大腦
這個 2 層、24,279 引數的 transformer,訓練於 11,341 個真實 DOOM 關卡名稱,編譯成 106 張圖、56,390 個娃娃與 785,460 個區域。以此設定,它寫出了 DEMON CANYON,這並非其 12,934 個訓練名稱之一:
106 tile maps (56,390 voodoo dolls, 785,460 sectors) built in 6 s; running them in 8 DOOM game processes ...
DOOM wrote: 'DEMON CANYON'
1,690 tile runs in 8 parallel DOOM games | 56,390 voodoo dolls in the tile maps |
120,666 values read back and compared with the NumPy integer reference (tolerance 0) | 135 s
EXACT: every value DOOM exposed equals the integer reference, and the reference writes the same name
關閉位於一個神經元前的門(L1.m 單元 33),同一腦部寫出 DEMON DEATH。
運作方式
![]() |
![]() |
| 矩陣乘法。每個娃娃是啟用的一個單位,越過的每條熔岩線為房間的光照級別加上權重。 | 注意。娃娃在天花板閘門下擠壓,另一個活值已降低;計算誰能通過即為乘法。 |
![]() |
![]() |
| 切除即為門。射擊開關,門關閉,某個神經元的娃娃永不到達。 | DEMON CANYON,根據 DOOM 腦部寫出的名稱構建的關卡,由一名大兵(其腦也在 DOOM 上執行)遊玩。 |
flowchart LR
NET["your network<br/>(PyTorch / NumPy)"] --> INT["integers<br/>(quantised)"]
INT --> MAP["a DOOM map<br/>lanes, gates, lava lines, doors"]
MAP --> RUN["ViZDoom runs it<br/>voodoo dolls walk"]
RUN --> READ["read back<br/>sector light levels"]
READ --> CHECK{"== NumPy integer<br/>reference?"}
CHECK -->|"every value"| EXACT["EXACT"]
CHECK -->|"off by one"| ERR["MismatchError"]
載入
DOOM 將每個額外的 player-1 開始點轉化為娃娃:玩家的副本,沿輸送帶行進並觸發其越過的每條線。啟用值 5 表示五個娃娃。階梯式允許娃娃在每一步僅在值超過該步高度時通過,這即是 ReLU、四捨五入與限制於一個形狀。總和累積於區域光照級別,結果為飽和 int16 值。
取得精確結果意味著測量引擎。同一 tic 中同一區域的兩次地板移動:一個會靜默遺失。傳送線會取消同一 tic 中先前越過的線。寬度超過約 34,000 單位的圖會將物件放至錯誤房間。完整清單見於 docs/HOW_IT_WORKS.md 與 docs/engine_limits.md。
名人堂
更大的實驗,全部準確。程式碼在 demos/,訓練資料在 training/,每個訓練好的模型在 models/。每個的數字和備註在 docs/DEMOS.md 和 docs/FLY.md。
![]() |
一個名為 DOOM 地圖的 Transformer。 DEMON CANYON,跨越 1,594 個 DOOM tile 例項編寫。film |
![]() |
DOOM 玩 DOOM。 一名海軍陸戰隊員,其政策基於 55 場 DOOM 遊戲,走過名為 DOOM 的地圖,雖未見過:門、兩次擊殺、出口。102 個決策,全部準確。film |
![]() |
DOOM 學習。 權重是天花板高度,學習則是通靈娃娃升降它們。一個空白大腦在一場遊戲內由瞄準機器人教導,第一生命得 4 次擊殺,之後 24、26、22、24。931/931 個決策皆精確。film |
![]() |
一隻果蠅。 來自 FlyWire 連結圖的 255 個神經元迴路,在一張地圖內引導海軍陸戰隊員:吃、拒絕、梳理、逃跑。169/169 步驟皆精確。film · code |
![]() |
一張地圖中的完整 Transformer。 嵌入、注意力、MLP 與 logits,層間無 Python:3,548 個娃娃寫出 SHIL。film |
- 他的腦袋就是地圖。 地圖指令碼感知最近的惡魔,1,345 個通靈娃娃執行他的 Transformer 並挑選動作,地圖觸發。Python 推進時鐘並讀取。50/50 個決策皆精確。film · code
- 腦切除術。 一發 7 砲彈霰彈槍擊中他的 DOOM 大腦,將他變成零擊殺的和平主義者。此病灶來自 3,000 次隨機擊中的掃描。film
- 最大的那個。 一個四層 Transformer 在 10,166 個 DOOM tile 例項與 1.9 百萬遊戲滴答上寫出 UNHOLY STRONGHOLD。film
- Showreel。
自行打造
將你自己的網路編譯進 DOOM 地圖,讓輸出開門、閃燈或推動海軍陸戰隊員。
import doomformer as df net = df.MLP.from_torch(my_torch_mlp, calib_x, levels=8) # or df.MLP(...) by hand, or df.MLP.from_numpy(...) print(net.quantization_report(test_x)) # how close the integer model is to your float model with df.compile(net, actions={"y0": [df.act.door_open(tag=7)]}, world=my_room) as brain: y = brain(net.encode(x)) # computed by voodoo dolls, verified exactly brain.record([net.encode(x)], "dolls.gif") # film the dolls doing it
Spiking networks (df.SpikingNet)、transformers (df.Transformer.load("doomnames"))、卷積網路 (from_torch 精確降低 Conv1d/2d) 以及在地圖內更新權重的學習者 (df.Perceptron) 運作方式相同。df.with_argmax 讓娃娃選擇勝出輸出。df.Agent 將感官與肌肉也放入地圖。模型過大無法放在一張地圖,會分散在 DOOM 遊戲池中使用 mode="tiles",doomformer plan 告訴你需要哪一個。
指南在 docs/BUILD_YOUR_OWN.md,還有十二個小型可執行 範例。做出什麼詛咒?Show and tell。
更多 DOOM
一張地圖即一場遊戲,一場遊戲使用一個核心。df.run_many 將每個輸入傳給下一個閒置遊戲,檢查每一次通過,並按順序返回輸出。遊戲在你的機器或任何你自行啟動的 Ray 叢集上執行。
在一臺 16 核機器上,14 場 DOOM 遊戲處理 1,000 個 MNIST 測試數字花了 2 小時:966 個正確讀取,1,000 個完全正確,
5,696,000 個遊戲滴答,即 45 小時即時 DOOM。每個數字的結果都在
models/mnist/doom_1000.json。
ys = df.run_many(net, xs, workers=8) # 8 DOOM games on this machine ys = df.run_many(net, xs, backend="ray") # every CPU of your Ray cluster
pip install "doomformer[ray]" ray start --head # then `ray start --address=HEAD:6379` on the other machines doomformer mnist --all --ray --record mnist.json # all 10,000 MNIST test digits, every result in one file
每場遊戲預算一個核心和 1.25 GB RAM 用於 MNIST 地圖。詳情見 docs/scaling.md。
盒子裡包含
doomformer/ |
工具包:地圖編譯器、DOOM 執行環境、精確檢查、CLI |
demos/ |
研究程式碼:他的腦袋就是地圖、行走器與地圖產生器、果蠅迴路、學習場、相機 |
training/ |
對每個模型進行量化感知訓練,以及包含來源的地圖名稱和 DOOM 事實語料庫 |
models/ |
每一個訓練好的模型及其訓練日誌與評估 |
examples/ |
十二個單檔範例,最多 15 秒 |
tests/ |
187 次測試,其中 88 次在實際無頭 DOOM 上,包括必須失敗的篡改控制 |
docs/ |
工作原理、每個演示與其數值、引擎事實、工具包指南 |
作弊程式碼(常見問題)
IDCLIP 這真的就是 DOOM 嗎?
它是 ViZDoom 1.3.2,一個基於 ZDoom 的引擎,使用 Freedoom 的免費
資產,無頭執行。地圖使用 ZDoom 的 UDMF 格式和 ACS 等級指令碼。ACS 移動輸入閘門,重置傀儡並複製結果。 在封閉迴路等級中,它還感知(最近的惡魔、海軍陸戰隊員走過的畫布瓦片)並執行傀儡選擇的動作。每個乘加和啟用都是傀儡穿過線條,
DRAWPAD、mnist 和 他的腦是關卡 之後,最終選擇也是如此。
IDKFA 那麼 Python 做了什麼?
它編譯地圖,寫入輸入,等待,讀取結果並檢查。在 DRAWPAD 和 他的腦是關卡 中,關卡寫入自己的輸入。對於分佈在多張地圖上的模型,Python 在遊戲之間複製整數,對於文字,它從 DOOM 計算的 logits 中挑選每個字母,除非使用 demo --one-map --loop,此時傀儡選擇每個字母並自行寫回。注意力是 ReLU 風格的整數注意力,或由傀儡計算的 softmax 注意力(demo --softmax);doomnames_modern 新增 RMSNorm 和一個門控 MLP。訓練在 PyTorch 中離線進行,除了在地圖內更新自身權重的學習者。
IDDQD 你怎麼知道是 DOOM 計算的,而不是 Python?
每個值都從正在執行的遊戲中讀回,並與從未見過地圖的 NumPy 整數參考進行比較,容差為 0。測試還嘗試破壞它。它們將編譯地圖中的一行權重從 5 提升到 6,檢查在傀儡穿過該線的輸入上恰好 +1 失敗。它們讓讀出回聲 Python 寫入的值,構建不含引擎所需障礙的地圖,並讓 settle 檢查信任 ViZDoom 的 4,096 個物件列表。這些都必須被捕捉。
IDSPISPOPD 它快嗎?
不。一次小型 MLP 通過在 DOOM 中耗時 726 毫秒,在 NumPy 中耗時 11 微秒,大約慢 70,000 倍。讀取一個手寫數字耗時 5,705 個遊戲滴答:76 到 99 秒無頭,2 ¾ 分鐘即時 DOOM。當海軍陸戰隊員思考時,惡魔被凍結。速度從未是目標。
IDBEHOLD 海軍陸戰隊員真的學會玩了嗎?
他模仿一個指令碼化的自動瞄準器,能看到精確的惡魔位置,並將其分為 86 種可能的觀測。這是監督式模仿。沒有基於獎勵的 RL,也沒有從畫素學習。這裡重要的是網路執行的位置。
IDDT 為什麼?
人們曾在計算機、孕檢試紙和割草機上執行 DOOM。這次則相反:DOOM 成為電腦。
授權
程式碼與檔案:MIT。隨附的關卡名稱列表(Doom Wiki,CC BY-SA 4.0;id Software 的原始碼,GPL-2.0)以及 100 個樣本 MNIST 數字(CC BY-SA 3.0)保持其授權;請參閱 THIRD_PARTY.md。Freedoom 圖形採 BSD 授權。DOOM 是 id Software / ZeniMax 的商標,本專案與其無關。












