核心实现:将NES硬件编译为模型权重

nesformer 实现了一种创新方案,将任天堂娱乐系统(NES)的 6502 CPU、2 KB 工作 RAM 以及卡带程序 ROM 直接编译进标准的 Qwen3_5ForCausalLM 检查点中。这些权重并非通过传统训练获得,而是由编译器计算生成。在运行游戏时,模型逐 token 预测 CPU 的执行日志,而轻量级主机程序则负责模拟其余硬件环境,包括画面绘制、控制器输入读取及 vblank 中断触发。

该项目建立在 Divyansh Agrawal 开发的 Qwenputer 基础之上,复用了其将 CHIP-8 和 RISC-V CPU 编译至 Qwen3.5-4B 的核心技术:利用模型的 Gated DeltaNet 状态作为机器内存。测试显示,对于内置游戏,nesformer 每一帧的输出均与传统 6502 模拟器完全一致。

技术原理与架构

nesformer 遵循“模型内计算,主机外渲染”的架构。模型权重包含 6502 核心的全部 151 条官方指令、寻址模式、标志位、堆栈及中断入口,以及 2 KB RAM 和 32 KB PRG-ROM。主机程序(nesformer/host.py)则处理 PPU 扫描线渲染、控制器输入及 NMI 定时。

内存映射机制:每个 DeltaNet 头维护一个 128×128 的状态矩阵。通过门控机制和 one-hot 键值,更新操作可精确覆盖矩阵行,查询操作则可读回数据。因此,卡带、RAM 和寄存器均映射在这些矩阵行中。

检查点结构:

  • 架构:基于 Qwen3.5-4B 的 DeltaNet 头形状,包含 7 层 Gated DeltaNet 层(构成电路)及 1 层全零注意力层。
  • 参数量:bf16 精度下总参数量为 4.666 亿(933 MB),但非零参数仅 930 万,使得权重压缩包仅为 3.3 MB。
  • 词表大小:263,808 个 token。

性能表现与验证

运行速度:默认使用 numba 引擎,单核速度约为 1,650 tokens/s。由于 NES 每秒需约 700,000 个 token,nesformer 的运行速度比真实主机慢 150–290 倍。处理一帧需 1–6 秒,运行 600 帧(约 10 秒游戏时间)大约需要 25 分钟。通过跳过空闲循环,token 数量可减少 2.6 至 4.2 倍。

准确性验证:

  • 参考 CPU 匹配:与 nestest 金标准日志中的 3,348 条指令匹配,并与 py65 模拟器在 570 万条游戏代码指令上保持一致。
  • 自由运行一致性:在 Thwaite 和完整游戏测试中,生成的追踪记录及保存帧与参考 CPU 完全一致,无任何不匹配。

使用局限性与要求

  • 仅限 NROM 卡带:仅支持 iNES mapper 0(如《超级马力欧兄弟》、《大金刚》)。不支持带有银行切换芯片(如 MMC1/3)的游戏,因模型内部 ROM 读取基于固定的 32 KB 表格。
  • 仅限官方指令:非官方指令会触发 HALT。
  • 代码执行限制:代码必须从卡带 ROM 运行,跳转到 RAM 会触发 HALT。
  • 功能缺失:不支持 IRQ;PPU 逐行渲染,无法重现扫描线中间效果;无声音模拟。

快速开始指南

环境要求:Python 3.11 或更高版本。

安装步骤:

python3 -m venv .venv
source .venv/bin/activate
pip install numpy scipy numba pillow

运行游戏:

解压 nesformer.zip 和 nesformer-weights.zip 到同一文件夹,确保 weights/ 目录位于 nesformer/ 包同级目录下。

python -m nesformer run roms/thwaite.nes --frames 600 --show

--show 参数可在终端实时绘制帧,运行结束后自动保存 GIF。若需运行自定义 NROM ROM,只需替换文件路径即可,无需重新编译权重。

其他命令:

  • check:并行运行权重与参考 CPU,遇差异即停止。
  • trace:打印模型的 token 流。
  • build:重新编译电路并写入权重(约 10 秒)。