nesformerとは?
nesformerは実験的なNESランタイムです。コンソールの6502 CPU、2 KBのワークRAM、カートリッジのプログラムROMが、標準のQwen3_5ForCausalLMチェックポイントの重みと状態の中で動作します。重みは学習されていません。代わりに、コンパイラーがCPU回路を実装するよう計算して生成しています。
ゲーム中、モデルはCPUの実行ログを1トークンずつ予測します。Pythonホストプログラムは残りのコンソール部品を担当します。グラフィックスの描画、コントローラー入力の読み取り、タイミング処理、I/O読み取りへの応答、垂直ブランキングNMI割り込みの注入を行います。
このプロジェクトはQwenputerを基盤とし、そのコンパイラーと中核的なメモリ技術を再利用しています。各Gated DeltaNetヘッドは状態行列を保持します。ワンホットキーとクエリにより、その行列の行をメモリセルとして扱い、レジスター、RAM、カートリッジデータを格納できます。
主な機能
- 学習ではなくコンパイル:ゼロではない重みはすべて、回路仕様から生成されます。
- 標準チェックポイント形式:チェックポイントは通常のHugging Faceフォルダーであり、カスタムモデルコードなしで
Qwen3_5ForCausalLM.from_pretrainedから読み込めます。 - 公式6502命令セットを完全実装:回路は151個の公式オペコード、アドレッシングモード、フラグ、スタック操作、BRK、NMIエントリーをすべて実装します。
- 再利用可能なNROM対応:起動時に選択したカートリッジをモデル状態へ書き込むため、同じ重みで異なるiNES mapper 0 ROMを実行できます。
- スクリプト制御:コマンドラインからフレーム単位でボタン入力をスケジュールできます。
- 映像エクスポート:実行結果を対応ターミナルに表示し、GIFまたはPNGフレームとして保存できます。
- 実行検証:
checkコマンドはモデル出力を同梱のリファレンスCPUと比較し、最初の差分で停止します。 - 複数の実行エンジン:nesformerは高速なnumbaエンジン、通常のNumPyエンジン、標準のTransformersエンジンを提供します。
モデル内部で動作するもの
チェックポイントには6502コア、CPUレジスターとフラグ、2 KBのワークRAM、固定長32 KBのプログラムROMテーブルが含まれます。プログラムフェッチ、データ読み取り、スタック操作、OAM DMA読み取りにも対応します。
ホストは引き続き、PPUレンダラー、パレット、コントローラー、vblank NMIのタイミング、サイクルカウント、APUレジスター処理を担当します。サウンドは実装されていません。ホストはI/O読み取りとNMIイベントの値をトークンストリームへ注入しますが、出力されたトレース以外でモデルのレジスターやRAMを直接調べることはありません。
nesformerはモデル推論によってCPUをエミュレートしますが、重みだけで完全なNESを実装しているわけではありません。
必要条件とフォルダー構成
Python 3.11以降、nesformerのソースアーカイブ、別途提供される重みアーカイブが必要です。両方のアーカイブを同じトップレベルフォルダーに展開してください。weightsディレクトリはPythonパッケージの中ではなく、その隣に配置する必要があります。
nesformer/
├── nesformer/
├── chipformer/
├── roms/
├── examples/
├── tests/
├── pyproject.toml
└── weights/
├── config.json
├── generation_config.json
├── model.safetensors
├── nesformer.json
└── SHA256SUMS
以降のコマンドはすべて、上記のトップレベルディレクトリから実行してください。CLIはデフォルトで、nesformer/パッケージの隣にあるweights/を探します。
ダウンロードした重みを検証する
同梱のチェックサムファイルを使用して、重みが正しく展開されたことを確認します。
shasum -a 256 -c weights/SHA256SUMS
チェックポイントを別の場所に保存している場合は、run、check、traceの実行時に--weights /path/to/weightsで場所を指定してください。infoコマンドではチェックポイントのパスを直接指定できます。
nesformerをインストールする
仮想環境を作成して有効化し、必要な数値計算および画像処理パッケージをインストールします。
python3 -m venv .venv
source .venv/bin/activate
pip install numpy scipy numba pillow
Windowsでは、次のコマンドで環境を有効化します。
.venv\Scripts\activate
デフォルトのfastエンジンはnumbaを使用します。初回実行時はエンジンのコンパイルに約20秒かかります。2回目以降はそのコンパイル結果が再利用されます。
最初のゲームを実行する
このリポジトリには、自由にライセンスされたテストROMが含まれています。次のコマンドはThwaiteを600フレーム実行し、ターミナルにフレームを表示し、StartボタンとAボタンの入力シーケンスをスクリプトで指定します。
python -m nesformer run roms/thwaite.nes --frames 600 --show \
--buttons "start@90-95+200-205,a@230-233+250-253+270-273+290-293+310-313+330-333+350-353+370-373+390-393+410-413+430-433+450-453+470-473+490-493+510-513+530-533+550-553+570-573+590-593"
--showオプションは、iTerm2やWindows Terminalなど、24ビットカラーに対応したターミナルに各生成フレームを描画します。実行が完了すると、CLIはROM名にちなんだアニメーションGIF(例:thwaite.gif)を保存します。
実機のNESよりも大幅に低速です。初回コンパイル後も、1フレームの処理には通常約1~6秒かかります。600フレームの実行はNES時間で10秒に相当し、約25分かかる場合があります。
互換性のあるROMを実行する
合法的に入手したROMをroms/ディレクトリに配置するか、別のパスを指定してください。ROMはiNES mapper 0として識別されるNROMボードを使用している必要があります。互換性のある例としてSuper Mario Bros.、Donkey Kong、Excitebikeがありますが、これらの商用ROMは同梱されていません。
この例ではSuper Mario Bros.を起動し、右を押し続け、複数回のジャンプをスケジュールします。
python -m nesformer run roms/smb.nes --frames 600 --show \
--buttons "start@60-65,right@200-600,a@250-265+300-315+360-375+420-435+480-495+540-555"
起動時にROMがモデルのDeltaNet状態へ読み込まれます。互換性のあるゲームごとにチェックポイントを再構築する必要はありません。
スクリプトでコントローラー入力を指定する
--buttonsオプションは、カンマ区切りのエントリを受け取ります。各エントリには、1つ以上のボタン名、@記号、1つ以上のフレーム範囲を指定します。
start@60-65
right@200-600
a@250-265+300-315
right+b@200-600
start@60-65は、フレーム60からフレーム65までStartを押します。right@200-600は、指定された区間でRightを押し続けます。a@250-265+300-315は、Aボタンを2回押すようスケジュールします。right+b@200-600は、RightとBを同時に押し続けます。
使用できる名前はa、b、select、start、up、down、left、rightです。フレームは電源投入時を起点とし、1秒あたり60フレーム進みます。したがって、フレーム600はNES時間で10秒に相当します。
移動できない画面ではゲームが操作を無視するため、通常は少し早めに押し始めても問題ありません。ボタンを押す長さが影響することもあります。たとえば、Super Mario Bros.ではAを長く押すほど高くジャンプします。
フレーム出力を制御する
デフォルトでは、runは120フレームを実行し、ROM名に基づく名前のGIFを書き出します。出力オプションで結果をカスタマイズできます。
python -m nesformer run roms/thwaite.nes \
--frames 300 \
--every 2 \
--scale 3 \
--gif output/thwaite.gif
--frames NはNフレーム後に停止します。--gif PATHはアニメーションGIFの出力先を指定します。--png DIRは保持したフレームを個別のPNGファイルとして保存します。--every NはNフレームごとに1フレームを保持します。--scale Nは保存するフレームを整数倍に拡大します。--showは生成したフレームをターミナルに表示します。
--pngだけを使用するとGIF出力は無効になります。PNGフレームとアニメーションの両方が必要な場合は、明示的に--gifオプションを追加してください。
python -m nesformer run roms/thwaite.nes \
--frames 120 \
--png frames \
--gif thwaite.gif
実行を調査・検証する
参照CPUと比較する
checkコマンドは、チェックポイントとプロジェクト標準の参照用6502を並行して実行します。最初のトークン差分で停止します。
python -m nesformer check roms/thwaite.nes --tokens 50000
デフォルトの比較長は50,000トークンです。他の実行コマンドで使用できる--buttons、--weights、--engine、--no-skipオプションも指定できます。
モデルのトークンストリームを表示する
traceを使用すると、モデルが出力したCPUレコードを調査できます。
python -m nesformer trace roms/thwaite.nes --tokens 100
トレース長のデフォルトは40トークンです。ほとんどのCPU命令は1トークンを必要としますが、I/O、ポインター読み取り、スタック操作、DMAを伴う命令では複数トークンのレコードが生成されることがあります。
実行中にトークンIDを保存する
runコマンドでは、生成されたトークンIDをファイルに書き出して後で分析できます。
python -m nesformer run roms/thwaite.nes \
--frames 60 \
--trace trace.txt
--max-tokens Nを使用して、フレーム数の制限だけに頼らず、生成トークン数が一定数に達した時点で実行を停止することもできます。
実行エンジンを選択する
nesformerには、性能と依存関係が異なる3つのエンジンがあります。
- fast: デフォルトのnumba実装です。READMEによると、開発マシンのCPU 1コアで毎秒約1,650トークンです。
- numpy: より単純なNumPy実装で、毎秒約210トークンと報告されています。
- hf: 標準のHugging Face Transformersで、毎秒約10トークンと報告されています。
--engineでエンジンを選択します。
python -m nesformer run roms/thwaite.nes --frames 3 --engine numpy
標準のTransformersを使用する
Hugging Faceエンジンを選択する前に、追加の依存関係をインストールしてください。
pip install "torch>=2.6" "transformers>=5.22"
python -m nesformer run roms/thwaite.nes --frames 3 --engine hf
チェックポイントにカスタムモデル実装は必要ありません。内部ではnesformerがチェックポイントを読み込み、パディングトークンを1つデコードし、初期マシン状態をキャッシュのDeltaNet状態へ書き込んだ後、生成されたトークンを1つずつ入力し直します。
高度なヒント
重みをリポジトリの外に置く
チェックポイントが別の場所に保存されている場合は、--weightsを使用します。
python -m nesformer run roms/thwaite.nes \
--weights /path/to/weights \
--frames 120
チェックポイントのメタデータを調べる
情報コマンドを使用して、チェックポイントの情報を表示します。
python -m nesformer info
重みがデフォルトのディレクトリにない場合は、infoにそのパスを指定してください。
チェックポイントを再構築する
リポジトリには、回路チェックポイントを再生成するために必要なコンパイラーが含まれています。通常のビルドではweights/に出力され、READMEによると約10秒かかります。
python -m nesformer build
次のコマンドで別の出力ディレクトリを選択できます。
python -m nesformer build --out compiled-weights
アイドルループのスキップについて理解する
NESゲームでは、vblankを待つためにタイトなループを頻繁に実行します。ホストがRAMやI/Oへの書き込みを伴わない完全に反復するループを検出すると、クロックを次のPPUイベントの直前まで進め、残りの反復を実行します。この最適化により、表示フレームとI/Oのタイミングを維持しながら、生成されるトークン数を大幅に削減できます。
--no-skipを渡すと、アイドルループの各反復をすべて実行します。処理は遅くなりますが、完全なトークン列を調べる場合に役立ちます。
python -m nesformer trace roms/thwaite.nes --tokens 100 --no-skip
テストスイートを実行する
テスト用の依存関係をインストールして、pytestを実行します。
pip install pytest py65
python -m pytest -q tests
テストには数分かかります。リファレンスCPUとnestestログを比較し、コンパイル済み回路をリファレンスと照合し、フリーランニング重みを検証するとともに、アイドルループ最適化をテストします。
重要な制限事項
- NROMのみ: ゲームはiNESマッパー0を使用する必要があります。MMC1やMMC3などのバンク切り替えマッパーはサポートされません。
- 公式オペコードのみ: 非公式の6502オペコードに遭遇すると、
HALTを出力します。 - ROMからの実行: コードはカートリッジROMから実行する必要があります。RAMへのジャンプは
HALTを出力します。 - IRQ非対応: BRKとNMIは動作しますが、APUフレームIRQとDMC IRQには対応していません。
- I/O動作の制限: I/Oレジスタに対するリード・モディファイ・ライト命令ではI/O書き込みが発生せず、
$4020から$7FFFまでのアドレスは0として読み取られます。 - グラフィックスの詳細は近似: 描画は1スキャンラインずつ行われ、スキャンライン途中の効果は再現されません。また、スプライト0ヒットとスプライトオーバーフローも近似です。
- 音声なし: APUレジスタへの書き込みは受け付けますが、音声は生成されません。
- 実行速度が遅い: アイドルループのスキップを有効にしても、実機よりおよそ150~290倍遅く動作します。
まとめ
nesformerは、Transformerチェックポイントが学習済み言語モデルではなく、正確にコンパイルされたコンピューターとして機能できることを示します。ソースと重みを正しく配置すれば、対応するNROMゲームを実行し、コントローラー入力を自動化し、フレームをエクスポートし、CPUトークンを調べ、リファレンス6502との実行結果を比較できます。マッパー、グラフィックス、音声、速度に制限があるため実験的なシステムではありますが、標準のQwen3.5チェックポイントと検証ツールにより、モデルの重みとDeltaNet状態に直接エンコードされた計算を実際に研究できます。
