本页目录

无限引擎开源重实现(GemRB)

gemrb · gemrb/gemrb

GemRB 是 Bioware Infinity Engine 的可移植开源重实现,该引擎曾支撑《博德之门》《冰风谷》《异域镇魂曲》。项目以 C++ 编写,并用 Python 支持脚本扩展;官方描述其设计更清晰、扩展性更强,并加入若干新特性。按 README 提示,运行通...

RPG 其他 自研引擎 C 对话系统 游戏状态管理 渲染技术 专家
★ Stars 142 Forks 16 License GPL-2.0 更新 2026-11-06

国内开发环境适配

Windows
未知
Linux
未知
macOS
未知
引擎 / 框架:自研引擎 暂无中文教程标记 未标记站长实测
构建依赖 libdragon 工具链与 N64 交叉编译环境,国内拉取 GitHub 仓库与工具链可能较慢,建议配置镜像或代理;具体依赖版本待核验。

中文项目介绍

Legend of Elya(埃莉亚传说)是一款面向 Nintendo 64 的原创自制(homebrew)ROM,使用 libdragon 开发,玩法为塞尔达式地牢探索。它与普通 N64 自制游戏最大的区别在于:游戏中的 AI NPC「Sophia Elya」并非依赖预置台词或查表回复,而是由一个小型 transformer 在主机 CPU 上做实时推理生成对话。

README 将其定位为「世界首个运行在 Nintendo 64 上的 LLM」,并说明这是 Hackaday.io 上「Transformers on Retro Game Consoles」系列的一部分,同系列还有 NES、SNES、Genesis、Game Boy Color 等平台的移植版本。

需要强调的是,作者在 README 中专门用一整节公开更正了早期公布的推理速度数据,这种「把错误留在页面上」的做法本身也是项目值得阅读的部分。

技术架构分析

模型架构

README 给出的模型规格如下:

参数 数值
参数量 6,356,992(约 636 万)
层数 8
嵌入维度 256
注意力头 8(每头 32 维)
词表 256(字节级 ASCII)
上下文窗口 128 token
量化 三值(SEQ2,2-bit 权重 + float16 块缩放,32 权重一块)
权重文件 卡带 ROM 中 1,984 KB(filesystem/sophia_weights.bin,2,031,628 B)
推理数学 MIPS R4300i FPU 上的 Float32
KV 缓存 RDRAM 中 256 KB
总 RDRAM 占用 约 263 KB(KV 缓存 + 7KB 暂存)

加速:RSP 构建与 RPI 引擎

  • RSP 加速构建:通过 make base-rsp 系列目标,把矩阵向量乘交给 N64 的 RSP 向量单元执行。
  • RPI 引擎(rpi/ 目录):一个「零乘法」推理引擎方向,README 大纲中列出但正文细节有限。

构建与运行

README 中出现的构建目标包括 make base、make base-rsp、make base-rsp-ovl,以及用于速率探针的 make base EXTRA="-DRATE_PROBE -DRATE_INGAME"。仓库内提供可直接运行的 legend_of_elya.z64,可在 ares 模拟器或 EverDrive 64 上加载。具体工具链版本与 libdragon 配置请以仓库文档为准,本文不做推测。

补充说明

玩法与交互

  • 走到 Sophia Elya 附近按 A 键即可触发 AI 对话。
  • N64 CPU 会完整跑一遍 8 层 transformer:embedding → attention → FFN → logits → 采样。
  • 输出 token 逐字符显示在屏幕上,并附带一个实时 tok/s 计数器。
  • 每次回复都不同,随机种子来自 CPU 振荡器抖动(硬件熵)。
  • 内置 32 条提示词,覆盖身份、Elya 世界观、RustChain、硬件冷知识等主题。
  • 可在 ares 模拟器中运行,也可通过 EverDrive 64 在真机上运行。

关键实现细节

  • Float32 推理:所有激活值、注意力分数与累加均为 IEEE 754 float32。
  • 即时反量化:权重以压缩形式留在 ROM 中(发布版三值 blob 每权重 2 bit),每次矩阵乘法时再解压。
  • 自研 Taylor exp():用 exp(x) = exp(x/128)^128 做范围缩减,配合 4 阶泰勒展开与 7 次平方;刻意不使用 float→int 转换,以避开 R4300i 缺失的 trunc.w.s 指令。
  • Quake III 快速平方根倒数:0x5f3759df 位技巧加两次 Newton-Raphson 迭代,用于 RMS 归一化。
  • 大端处理:权重文件由 Python 导出为小端,N64 为大端,用 swap16/swap32 处理头部字段与 float16 缩放。
  • 硬件熵:MIPS CP0 Count 寄存器与帧计数器异或后作为 RNG 种子。
  • 贪心采样:在可打印 ASCII(32–126)范围内取 argmax。
  • 嵌入缩放还原:Q8 导出会归一化到 [-1,1],原始缩放系数(em=3.5)存于头部字节,初始化时还原。

关于 tok/s 的公开更正

这是 README 中最重要的一节。项目曾公布约 60 tok/s,作者后来审计发现该计数器根本没有测量任何时间:

  • 旧代码用 gen_out_count * 60.0f / elapsed 计算速率,而 G.frame 统计的是游戏主循环迭代次数,且每次迭代恰好生成一个 token,因此比值恒为 1,读数被钉死在源码里的 60.0f。
  • 视频中出现的 61.8 是同一表达式的差一错误:60 × (k+1)/k,当 k=33 时正好是 61.818。
  • 作者在同一构建中并列运行新旧计数器,旧表达式在每次运行、每个 token 上都输出 60.00,而真实速率在标量构建与 RSP 构建之间相差 1.8 倍。

作者还验证了模拟器时钟是否说谎:通过自旋循环消耗已知 CP0 计数并统计 VI 场消隐次数,两者在 90 秒内偏差小于 0.25%,且误差不随时长增长,说明 ares 对 CP0 COUNT 的推进是可信的。剩余约 −0.18% 的常数偏差来自真实 N64 的约 59.83 Hz NTSC 逐行输出,而非 60/1.001 广播标准。

修正后的数据

发布配置(SEQ2 三值 blob、ctx=128、SGAI_KV_INT8、PSE_FIX=1、REP_FIX=1、16 个生成 token、ares 147 headless):

构建 tok/s(CP0) tok/s(vblank) 秒/token
标量 CPU(make base) 1.230 1.23 0.81
RSP 叠加(make base-rsp-ovl) 2.187 2.19 0.46
RSP 叠加 + 尾声重叠 3.026 3.03 0.33

在真实 vsync 锁定的游戏循环中(-DRATE_INGAME),标量为 1.22 tok/s,RSP 叠加为 2.16 tok/s。RSP 相对标量的加速比为 1.78 倍,且 rsp=1968 cpu=0,说明所有矩阵乘法确实交给了向量单元。

作者特别澄清:这个 1.78 倍不是仓库别处引用的 4.769 倍——后者是 int8 blob 上的 CP0 周期数比值,并非发布配置;也不是真机数据。

尚未验证的部分

README 明确列出未完成事项:

  • 当前 6.36M 三值 blob 与 RSP 叠加路径尚未在真机硅片上运行,所有速率均在 ares 模拟器下测得。
  • 完整演示视频拍摄自真实 N64(经 EverDrive 64,2026 年 2 月,819K 参数 Q8 float32 标量构建);「首次连贯输出」片段则是 ares 录制。
  • 模拟器在宿主机上以 0.35–0.63 倍实时速度运行,因此只能引用模拟时间推导的速率,不能引用宿主机墙钟时间。

学习建议

这个项目适合已经了解 transformer 基本原理、想看看「极限受限环境下的推理实现」的读者。建议阅读顺序:

  1. 先看架构表,理解 636 万参数、2-bit 三值量化、128 上下文这些约束如何互相牵制。
  2. 再看关键实现细节,尤其是自研 exp()、快速平方根倒数和即时反量化——这些是为 MIPS R4300i 的指令缺失与内存限制量身定制的。
  3. 最后读 tok/s 更正一节,它是一份关于「如何审计自己性能数据」的完整案例:从发现恒定读数、并列复现、验证模拟器时钟,到区分 CP0 时间与宿主机墙钟。

需要注意,项目当前的真机验证状态有限,引用性能数字时应同时说明「ares 模拟器、未上硅片」这一前提。

开源许可证与版权说明

许可证标识:GPL-2.0。源码、资源与依赖许可可能不同,请以官方仓库为准。本站仅做索引,不托管源码。

相似项目