跳到主要内容
AI教程

在编译后的 Qwen3.5 模型中运行 NES 游戏:nesformer

本教程介绍 nesformer 如何将 NES 6502 CPU、内存和卡带 ROM 编译为标准 Qwen3.5 检查点。你将安装 CLI、运行兼容游戏、自动化控制器输入、导出动画、将执行结果与参考 CPU 对比,并探索其他引擎。

在编译后的 Qwen3.5 模型中运行 NES 游戏:nesformer

什么是 nesformer?

nesformer 是一个实验性的 NES 运行时环境,主机的 6502 CPU、2 KB 工作 RAM 和卡带程序 ROM 都运行在标准 Qwen3_5ForCausalLM 检查点的权重与状态中。这些权重并未经过训练,而是由编译器计算生成,用于实现 CPU 电路。

游戏运行期间,模型逐个 token 预测 CPU 执行日志。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 渲染器、调色板、控制器、垂直消隐 NMI 时序、周期计数和 APU 寄存器处理。系统未实现声音。主机会将 I/O 读取和 NMI 事件的值注入 token 流,但不会直接检查模型寄存器或 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 秒编译引擎。之后的运行会复用已完成的编译工作。

运行你的第一个游戏

该仓库包含采用自由许可证发布的测试 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 选项会在支持 24 位色彩的终端中绘制每一帧,例如 iTerm2 或 Windows Terminal。执行完成后,CLI 会保存一个以 ROM 命名的动画 GIF,例如 thwaite.gif。

性能比实体 NES 慢得多。初始编译完成后,每帧通常需要约一到六秒。运行 600 帧相当于 10 秒的 NES 时间,可能需要约 25 分钟。

运行你自己的兼容 ROM

将合法获取的 ROM 放入 roms/ 目录,或提供其他路径。ROM 必须使用 NROM 主板,其标识为 iNES mapper 0。兼容示例包括《超级马力欧兄弟》、《大金刚》和《越野机车》,但这些商业 ROM 不包含在内。

此示例启动《超级马力欧兄弟》,按住右键,并安排多次跳跃:

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 选项接受以逗号分隔的条目。每个条目包含一个或多个按钮名称、一个 @ 符号,以及一个或多个帧范围。

start@60-65
right@200-600
a@250-265+300-315
right+b@200-600
  • start@60-65 会在第 60 帧至第 65 帧按下 Start。
  • right@200-600 会在指定时间段内持续按住右键。
  • a@250-265+300-315 会安排两次 A 键按下。
  • right+b@200-600 会同时按住右键和 B 键。

可用名称包括 a、b、select、start、up、down、left 和 right。帧从开机时开始计数,并以每秒 60 帧的速度推进。因此,第 600 帧对应 NES 时间中的 10 秒。

通常可以稍早开始按键,因为在无法移动的画面中,游戏会忽略控制输入。按键持续时间可能很重要;例如,在《超级马力欧兄弟》中,按住 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 帧保留一帧。
  • --scale N 按整数倍放大保存的帧。
  • --show 在终端中显示生成的帧。

单独使用 --png 会禁用 GIF 输出。如果同时需要 PNG 帧和动画,请添加明确的 --gif 选项。

python -m nesformer run roms/thwaite.nes \
  --frames 120 \
  --png frames \
  --gif thwaite.gif

检查并验证执行

与参考 CPU 比较

check 命令会并行运行检查点和项目惯用的 6502 参考实现,并在首次出现 token 差异时停止:

python -m nesformer check roms/thwaite.nes --tokens 50000

默认比较长度为 50,000 个 token。你可以传入其他执行命令所接受的相同 --buttons、--weights、--engine 和 --no-skip 选项。

打印模型的 token 流

使用 trace 检查模型生成的 CPU 记录:

python -m nesformer trace roms/thwaite.nes --tokens 100

默认跟踪长度为 40 个 token。大多数 CPU 指令需要一个 token,而涉及 I/O、指针读取、栈操作或 DMA 的指令可能会生成包含多个 token 的记录。

在运行期间保存 token ID

run 命令可以将生成的 token ID 写入文件,以便后续分析:

python -m nesformer run roms/thwaite.nes \
  --frames 60 \
  --trace trace.txt

你还可以使用 --max-tokens N,在生成固定数量的 token 后停止执行,而不只依赖帧数限制。

选择执行引擎

nesformer 提供三种引擎,性能和依赖项各不相同:

  • fast:默认的 numba 实现。README 报告称,在开发机器的一个 CPU 核心上,速度约为每秒 1,650 个 token。
  • numpy:更简单的 NumPy 实现,报告速度约为每秒 210 个 token。
  • hf:标准 Hugging Face Transformers,报告速度约为每秒 10 个 token。

使用 --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 会加载检查点,解码一个填充 token,将初始机器状态写入缓存的 DeltaNet 状态,然后逐个将生成的 token 反馈回去。

高级提示

将权重放在仓库之外

当检查点存储在其他位置时,使用 --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 mapper 0。不支持 MMC1 和 MMC3 等银行切换 mapper。
  • 仅支持官方操作码:遇到非官方 6502 操作码时会输出 HALT。
  • 从 ROM 执行:代码必须从卡带 ROM 执行;跳转到 RAM 时会输出 HALT。
  • 不支持 IRQ:BRK 和 NMI 可用,但不支持 APU 帧 IRQ 和 DMC IRQ。
  • I/O 行为有限:I/O 寄存器上的读-改-写指令不会产生 I/O 写入,$4020 到 $7FFF 范围内的地址读取结果均为零。
  • 图形细节为近似值:渲染逐扫描线进行,不会复现扫描线中途的效果,sprite-0 命中和精灵溢出也只是近似实现。
  • 无音频:APU 寄存器写入会被接受,但不会生成声音。
  • 执行速度慢:即使启用空闲循环跳过,项目运行速度仍约为真实主机的 1/150 到 1/290。

结论

nesformer 展示了 transformer 检查点如何作为精确编译后的计算机运行,而不仅仅是一个经过训练的语言模型。只要正确安排源代码和权重,就可以运行兼容的 NROM 游戏,自动化控制器输入,导出帧,检查 CPU 令牌,并将执行结果与参考 6502 进行比较。它在 mapper、图形、音频和速度方面的限制使其成为一个实验性系统,但其标准 Qwen3.5 检查点和验证工具提供了一种实用方式,用于研究直接编码在模型权重和 DeltaNet 状态中的计算。