Hacker News 中文摘要

RSS订阅

Show HN:Mini-AGI,仅需 8GB 显存即可训练的 动态持续学习模型 -- Show HN: Mini-AGI – Dynamic continual learning model trained on 8GB VRAM

迷你AGI(mini-AGI)是一个持续学习的字节级语言模型,能自行组装架构,在单张 8 GB 显存的 GPU 上从零训练,并持续从读到的一切中学习。它的权重以普通文件存在磁盘上,需要时分页调入显卡,参数量因此受剩余磁盘空间限制,而非显存。训练中不够用就长出新的容量,没人用的会被剪掉,读取与推理走同一套代码。作者注明,它目前只是个玩具级模型。

正文

mini-AGI 是一个持续学习的字节级语言模型,它自行组装架构,在单张 8 GB VRAM GPU 上从零训练,并不断从读到的一切中学习。它把权重作为普通文件存放在磁盘上,需要时再分页到显卡,因此参数数量受限于空闲磁盘空间而不是 VRAM。训练中容量不足时它会增长新容量,修剪无人请求的部分,并用与推理完全相同的代码路径读取数据。目标是在至少 8 GB VRAM 的 PC 或笔记本上运行。

注意:目前这只是一个玩具级小模型。 不要期待前沿能力。它更像一个小实验,用来证明从单一数据流持续学习而不会灾难性遗忘是可能的,而且能在普通硬件上做到。这意味着几乎每个人都能按自己的想法训练自己的版本(或继续训练这个),能力受实际硬件、规模、可用数据质量和愿意投入的训练时间限制。

运行仪表盘

这是 min-run 仪表盘的样子。模型被指向语料库,不断读取和学习。

历史里有整个训练运行到目前的样本,可以自己查看模型在训练/读取语料过程中如何改进。

权重尚未发布。当前运行仍在读语料库第一遍,读完所有语料后才会发布权重,按当前速度还要几周。

动机

今天你能真正拥有的语言模型,都是别人训练好然后冻结的。你能在边缘微调,但不能在自己的硬件上从零训练,也不能持续用每天接触的数据训练——一旦尝试,它就会忘记以前知道的东西。结果个人模型始终是别人的模型加上薄薄一层你,发布那天就停止学习。

mini-AGI 的 GPU 占用足够小,可以在一张消费级显卡上端到端训练,而且训练永不需要停止。它一次读取一个字符块,对每个块做梯度步骤,生成也走同一路径。没有单独的微调机制,也没有冻结的基础模型:读取和被训练是同一件事。

三个约束塑造设计:

  • 必须装进 8 GB。 不是量化——训练需要梯度和优化器状态,大约是权重的三倍。所以权重放在磁盘上,只驻留工作集。
  • 必须不遗忘。 一个持续学习并覆盖自己的模型,比完全不学习更糟。
  • 必须能读任何东西。 字母表是 256 个字节值,所以没有 tokenizer 要拟合,也没有数据类型需要新词表。

这个模型真正属于你:在你的硬件、你的数据上训练,从与它的每次对话中持续学习,没人能拿走或关掉它。

架构如何工作

字符(字节)不像传统 LLM 那样穿过固定层堆栈,而是先穿过两个密集前奏块,然后穿过一个循环块,最多应用 24 次,每次应用从共享池中选择自己的专家。应用之间的潜在状态从不被解码——每次都会由适配器与嵌入输入合并,因此循环不会偏离正在读的文本。三个不同块,每个字符最多 26 次块应用。

  • 自适应深度。 一个 halting head 在每个行上为每个字符打分,一旦下一行不会改变答案,字符就停止。简单字符用一行,困难字符用多行。这是 PonderNet 配方:训练时计算每个深度并按 halting 概率加权,halting head 通过这些权重学习。
  • 路由按块应用,而不是按字符。 26 次应用各自选择自己的 top-8 专家,所以一个字符接触的池远多于“top-8”所暗示的,同一专家可以在不同深度被多次选中。变化的是每个点选哪八个。
  • 没有专家被分配主题。 没有任何标签。软 top-k 路由自行把能力分布到池中,一个字符可以组合多个专家的片段。代价是能力共享参数,因此可能互相干扰。

模型如何处理一个字符

这是架构在逐个字符地自行组装,截取自实时模型——这里没有手绘内容。

左侧每个瓦片是一个专家;颜色是专家身份,整个片段保持不变。一行是循环块的一次应用,其中八个瓦片是该行实际运行的八个专家。随着模型继续,堆栈向下增长,琥珀色线是 halting 停止的位置——下方灰色行是模型拒绝花费的计算。

右侧轨迹是每个字符用了多少行。它在 4 到 14 之间不断移动,上限 24,文本下方光标显示正在读哪个字符。

位置是旋转的,不携带学习参数,因此上下文窗口可以通过继续训练来扩展,而不需要重新初始化任何东西。

……它写作时也一样

模型如何生成文本

上面的片段是模型阅读——每个字符都是展示给它的留出文本。这个是模型写作:先用一个留出故事的 2,500 个字符做提示,然后自行续写,灰色文本是给它的,绿色文本完全是它自己的。贪心解码,任何地方都不采样——运行两次会得到同一句话。

两件事值得注意。堆栈行为相同,因为在这个模型里生成和阅读是同一次前向传播——唯一区别是下一个字符来自文件还是模型自己的 argmax。而且写作比阅读花费更多深度:同一主题上每字符约 9.9 行,而阅读是 8.0。虚线标记工作集重新选择的位置,每 64 个字符一次;这个片段里没有交换,因为提示已经把正确的专家拉到卡上。

它续写一个关于樱桃树的故事,产出:

他们一起工作,看到了他们最喜欢的海岸。有一天,他们想和他们最喜欢的海岸玩。他们想和它玩,但是

语法正确且切题。目前会重复——这公平地反映了模型在 2.43 亿字符时的状态。

分页如何工作

每个专家都是磁盘上的一个文件,保存其权重和 Adam 矩。磁盘之上有两个缓存和一个工作集:

| | key | 是什么 | | --- | --- | --- | | 磁盘 | — | 模型拥有的每个专家;受空闲空间限制 | | RAM | ram_cache | 最近需要的专家,按最近最少使用淘汰 | | VRAM | resident | 工作集——一个字符可以路由经过的部分 |

在每个块之前,模型会被询问即将读取的文本想要什么,答案成为工作集。需求根据上一个块读取时调用点实际路由到的隐藏状态评分——嵌入不携带上下文,所以用原始嵌入评分会让每个主题都请求相同专家。

项目坚持两条规则:

  • Adam 矩随专家移动。 它们属于专家,而不是它碰巧占用的 VRAM 槽位。把它们留下会把一个专家的动量交给替代者,训练表面健康,但每个被换出的专家都继承了陌生专家的历史。
  • 已经在卡上的专家留在原槽位。 需求返回时已排序,所以顺序会变,而集合几乎不动。按身份而不是位置匹配,才能让加载次数等于集合真正变化的量。

因为选择基于上一个块,它看不到即将预测的文本。防止工作集因噪声震荡的是滞后:候选必须超过驻留者 margin 才能替换,新来者在阅读 dwell_chars 内安全。

增长和修剪如何工作

池在容量不足时增长,在部分不再被请求时缩小。

新专家以投机方式加入,初始门很小,几乎不改变什么,只有持续被请求才会保留。新专家通过重组构建——从多个现有专家取出完整隐藏单元——因为一个父本的克隆不够新颖,不值得路由,而随机专家计算不出值得路由的东西。有效的是从训练过的部件组装出的新颖性。

除非所有刹车都同意,否则拒绝增长:

  • room——磁盘和 VRAM 能承受
  • used——已经加入的容量正被请求
  • earning——上一批存活过了试用
  • fits——已经处于试用中的专家不太多
  • honest——训练和留出没有分离

死意味着无人寻址。 增长刹车和修剪器都读取距离上次有东西请求专家过了多久,而从不读它的门控值。这是仓库里最有用的发现:门控在这里不仅无信息,而且是反预测的。最小的门控属于最忙的专家——一个表现得像汇点、不断被选中且每字符贡献很少的专家,在门控测试中读起来像死的;而一个高门控专家,数十万个片段里没人想要,却读起来像活的。

新专家无论如何都有完整生存窗口的安全期,因此在有机会被选中前不能评判。模型增长专家时会出现新文件;修剪时该文件被删除。

持续学习如何工作

在单一流上一次一个主题训练,是灾难性遗忘的经典配方。以专家自身学习率读取 50 万字符国际象棋,会让其他七个主题从 1.12 涨到 3.73 nats。

主干学习率是机制。 主干——嵌入、注意力、路由器、halting head——是每个字符都经过的部分,承载 97.6% 的平方梯度范数。以专家学习率的 0.1 倍运行,把遗忘从 +2.2300 降到 +0.0067 nats,即相对随机保留了 99.84% 的进展。

| 配置 | 未读主题 | 相对随机保留 | | --- | --- | --- | | 工作集冻结,主干 LR = 专家 LR | +2.5871 | 42.88% | | 交换,主干 LR = 专家 LR | +2.2300 | 50.68% | | 交换,主干 0.1x——运行所用 | +0.0067 | 99.84% | | 对照:读取全部七个主题 | -0.0077 | - |

三种配置下的遗忘

这是整个设计所依赖的测量。 模型以 batch 1 读取 524,000 个国际象棋字符,不读其他,左轴是它没读的七个主题发生了什么——零表示没有遗忘,向上表示更差。三条线,各一个变量。其中两条爬到 +2.2 和 +2.6 nats,即模型失去了大部分知道的东西。第三条,主干学习率为专家的十分之一,始终没有离开地板:单个主题五十万字符后 +0.0067 nats。

灰色虚线是对照——同样探针但读取全部七个主题,按构造遗忘不可能发生。

右图把同样三条臂转换成相对随机保留的进展。50.68% 和 99.84% 之间的差距是配置文件里的一个数字。

这里有两点解读,第二点纠正了项目自己早先的说法:

  • 专家池不是防止遗忘的原因。 冻结工作集——去掉让池成为池的唯一属性——只付出 0.3571 nats,占效应的 13.8%。在那个臂中 136 个专家有 93 个完全没有梯度,模型仍然崩溃。保留大部分权重并不足够。
  • 损害是位移,不是破坏。 严重损害模型后再读一遍所有内容:四分之三在 131,000 字符内回来,而第一次学这些主题约花了 50M 字符。必须重新学习的知识不会快 380 倍回来。“灾难性”描述的是曲线底部的样子,不是权重发生了什么。

大规模读取期间每个主题,以及池被触及多少

同一次读取从内部看是什么样。 这是工作配置——主干 0.1x——在完全相同的 524,000 字符国际象棋探针期间。左图是每个主题相对于起点的变化。正在读的国际象棋提高 0.013 nats。阴影带是未读取的七个主题的范围,整个探针内保持在 ±0.02 nats 内:学一件事没有在任何其他地方造成可测量代价。这是第一段里的论断,画出来而不是断言。

右图是为什么可能。整个探针中只有 136 个专家中的 54 个收到任何梯度——60% 的模型在结构上未被触及,因为路由从未选中它。这正是池的用途:把更新限制在文本实际寻址的模型部分。

学习率没有调度。 余弦调度断言运行会结束,对于持续读取的模型这是假的。取而代之,控制器观察留出损失并双向调整速率:明显改善就多给一点,没有证据就降下来,确认的留出跳变就重新调高。

读取你自己的文件

这是通往一个懂你关心之事的模型的最短路径。

python3 train.py read ~/notes # 一次干读——不保留任何东西

python3 train.py read ~/src ~/docs --passes 3 --save

指向文件或目录。无需准备:字母表是 256 个字节值,所以文件已经用模型唯一拥有的词表写好了。目录会被遍历,二进制通过采样内容而不是相信扩展名来跳过,每个文件从头读到尾,因为文档有顺序。

它使用与训练相同的路径:相同分块、相同缓存、相同梯度步骤。

| 标志 | | | --- | --- | | --passes N | 把整个集合读 N 遍 | | --save | 保留学到的;没有它 weights/ 不变 | | --lr | 默认 5e-5,低于训练运行:读取应调整模型,而不是覆盖它 | | --mix "" | 跳过前后评分 |

两个默认值值得知道。没有 --save 就不保存任何东西,所以读取在你决定之前是干跑。而且它会在前后对留出混合评分,然后直白说明读取你的文件是否让模型在其他地方付出代价——遗忘问题按每次读取测量,而不是假设不存在。

基准

下面的数字用于跟踪,随着运行继续会变化。留出损失报告标准误,评估规模决定该误差——比它小的差异是仪器而不是结果。

这些数字下面还有第二种方差。同一配置运行两次会相差约 0.014,因为 CUDA 上的专家调度不是确定性的。把约 0.03 当作真正差异的阈值,而不是单个分数旁印的误差条。

模型在哪里(已读 3.181 亿字符,169 个专家):

| | nats/char | bits/byte | | --- | --- | --- | | 留出,全部八个主题 | 0.8336 ± 0.0331 | 1.2026 | | 训练 | 0.6809 | 0.9823 |

每个主题的留出损失:

| 主题 | nats/char | bits/byte | | --- | --- | --- | | chess | 0.552 | 0.796 | | stories | 0.637 | 0.919 | | arithmetic | 0.657 | 0.948 | | code | 0.739 | 1.066 | | reasoning | 0.794 | 1.145 | | chat | 0.831 | 1.199 | | chat_hermes | 1.178 | 1.699 | | wikipedia | 1.280 | 1.847 |

数据缩放

相对于已发表字节级和子词模型的数据缩放

这张图上的每个点都是拥有已发表 bits-per-byte 的模型——这是唯一在 tokenizer 变化后仍存活的损失单位,所以字节级模型才能和 GPT-3 并列。

涉及三个留出集——PG19、Pile-CC 和这个项目自己的混合——因此不同颜色的垂直位置并不严格可比。MambaByte-353M 是最接近的同参数类、每字节 FLOPs 基本相同的模型,它读取的数据是这个模型的 94 倍。Transformer-320M 读取了 251 倍。

迄今为止的结果有希望。 红线是拟合的幂律,L ∝ D^-0.239,在预热后每个点上 R² 为 0.96——一个干净、健康的指数,位于 Kaplan 的 0.095 和 Chinchilla 的 0.28 之间,并且已经保持超过一个数量级的数据。它看起来多陡取决于拟合从哪里开始:40M 到 150M 的窗口给出 0.21 到 0.32,图中的带覆盖了这个范围,而不是假装一个数字。

直接从趋势读出,并记住目标是这个模型自己的混合而不是 PG19:

| 留出 | 所需字节 | 按约 778 字符/秒的天数 | | --- | --- | --- | | 1.10 BPB | 0.51B | 约 3 | | 1.00 BPB | 0.75B | 约 6 | | 0.93 BPB | 1.02B | 约 10 | | 0.80 BPB | 1.92B | 约 24 |

这些是一台笔记本 GPU 上数天到数周的阅读,不是数年,而且都在 78.7 亿字符语料库的单遍之内。

右图显示哪些主题仍在移动。Code、chat、stories 和 reasoning 是陡的;wikipedia 和 chat_hermes 损失最大、斜率最浅,这是诚实的平衡——昂贵领域不是最快的。

运行它

  1. 确保有 CUDA 能力、至少 8 GB VRAM 的 GPU,以及 Python 3.10 或更新。参考机器是 RTX 3070 Laptop GPU 8 GB。
  2. 克隆仓库:git clone <repository-url>,cd mini-AGI。
  3. 安装依赖:pip install torch numpy pyyaml matplotlib;flask 用于 serve.py;tokenizers chess zstandard 用于构建语料;scipy 用于部分分析工具。PyTorch 必须匹配 CUDA 版本——见 PyTorch 安装页。参考环境是 torch 2.6.0+cu124 与 numpy 1.24.4。训练只需要第一行。
  4. 构建语料。一个命令下载四个公共数据集并生成另外四个通道:python3 -m corpora all # 全部八个主题,几 GB;python3 -m corpora all --limit 5000 # 先小切片尝试;python3 -m corpora all --full # 整个数据集:几十 GB,数小时。已存在的通道会保留,所以中断的构建可以重跑。也可以单独通道,python3 -m corpora 会列出,或者完全跳过,把模型指向你自己的文件。
  5. 开始读取。权重目录第一次会从 config.yaml 创建,无需设置:python3 train.py read data/train --save --weights-dir weights --held-out data/val --sample-every 10。
  6. 提供服务:python3 serve.py --port 8080,然后打开 http://127.0.0.1:8080。

运行会写样本日志,边跑边重绘图表,每隔几分钟检查点。它就是要被放着跑几天。

其他一切

python3 -m minagi.store weights # 模型现在是什么

python3 -m corpora # 每个语料目标

python3 -m corpora all --only wikipedia stories # 重建特定通道

python3 -m corpora expand # .bin -> 读到的文本文件

python3 train.py read --help # 读取器的每个旋钮

python3 train.py stream --steps 140000 --lr 2e-4 # 打包语料路径

python3 train.py ponder-probe --ckpt weights # 深度与难度

每个工具都接受 --ckpt weights——目录就是模型,没有 .pt 文件要跟踪。

初始化

新模型从小开始,长成自己的形状。上下文窗口从 model.context_start 开始,一次扩展一个字符,但只有当模型仍能从已有窗口远端获得东西时才会扩展。专家池从 pool.experts 开始,然后增长。

这意味着运行的最初几小时看起来和之后完全不同。损失快速下降,池不断变动,窗口很短,学习率控制器还没有足够评估来行动。这些都不是要修的问题。

如果运行发散,它会自我修复:当留出超过最佳值超过 --revert-factor(默认 1.5x)时,运行重新加载 weights/,把学习率减半,拉回上下文并继续。超过 --max-reverts 后停止,而不是反复折腾。

布局

minagi/ 是模型。没有命令行。

  • config.py 读取 config.yaml,构建和训练都用它。
  • precision.py 模型用什么计算,矩如何存储。
  • tokenizer.py 字节进,字节出——256 个值加结构标记。
  • model.py transformer:RMSNorm、旋转位置、SwiGLU、flash attention。
  • decode.py 如何选择字符,不使用随机数生成器。
  • ingest.py 把一堆文件变成可读的东西。
  • pool.py 专家池,以及它增长和缩小的规则。
  • paged.py 同一个池分布在磁盘、RAM 和 VRAM。
  • recur.py 带自适应深度的潜在循环。
  • stream.py 在 KV 缓存后读取语料,一次一个块。
  • store.py 权重目录,它就是模型。
  • optim.py 梯度中有多少是信号。
  • plasticity.py 学习率,由留出损失治理。
  • live.py 服务一个在底下被训练的模型。
  • report.py 模型从自己的权重读统计。
  • create.py 从 config.yaml 写一个新的权重目录。

train.py read | stream | ponder-probe

serve.py 本地 Web UI

config.yaml 值得改的设置

corpora/ python3 -m corpora all——整个语料,下载并制作

weights/ 每个专家一个文件。这个目录就是模型。

weights/ 第一次运行时写入,data/ 由 corpora 写入;两者都不在仓库里。上面其他都在。

权重目录就是模型

weights/

  • manifest.json 存在什么、形状、来自哪里
  • core.npz 嵌入、注意力、范数、适配器、halting head
  • routers.npz 门控、段路由器,每个专家每个位置一行
  • optim.npz 主干和路由器的 Adam 矩
  • experts/ 每个专家一个文件:w1、w3、w2 及其自己的 Adam 矩
    • e00000.npz ...

训练从它恢复——权重、Adam 矩和步数——并在运行改进现有内容时推进它,所以只为检查而跑的会话如果发现任何东西,仍会贡献。目录保存模型达到的最佳状态,不是最近状态。写入是原子的:每个文件先写 .tmp 再重命名,因此中断的保存不会留下半写权重。

目录在第一次运行时写入。

模型

字节级——词表 265:256 个字节值加 9 个结构标记(<think>…</think> 草稿板、<user>/<bot> 轮次、<g> 用于游戏、文本结束)。上下文 4,096。

| | | | --- | --- | | body | RMSNorm、RoPE、SwiGLU、通过 scaled_dot_product_attention 的 flash attention | | depth | 3 个不同块,每字符最多 26 次块应用 | | recurrence | 一个权重共享块最多应用 24 次;潜在状态从不解码 | | halting | PonderNet——每个字符独立停止,所以困难字符获得更多深度 | | routing | 每次块应用 top-8 专家,按字符选择 | | paging | 32 个专家驻留卡上;其余在磁盘 |

参数数量会移动,因为池在训练中增长和修剪。python3 -m minagi.store weights 打印现在是多少。写作时:

core 8.27M 嵌入、注意力、范数、适配器、halting head

routers 0.17M 每个专家每个调用点一行,加深嵌入

experts 531.6M 169 x 3.15M 每个 (3 x 512 x 2048)

total 540.1M

VRAM 由工作集设定,不是池。 一次只有 32 个专家驻留——约 109M 参数,占 540M——所以池能在 8 GB 卡上继续增长。每字节训练约 2.4 GFLOPs,与稠密 400M 字节级 transformer 同一计算级别。

评论

社区对仅需8GB VRAM的“Mini-AGI”项目整体好奇且鼓励,但技术分歧明显:乐观派视其为本地AGI或人脑式连续学习的突破,审慎派则要求验证泛化与记忆能力并对比RAG方案。

认为这是迈向本地AGI或原型AGI的突破

  • 多名读者对仅需8GB VRAM运行“Mini-AGI”感到振奋,认为本地AGI不再遥不可及,甚至称其名副其实、概念接近人脑工作机制。(认可度:较多人) > 第一次见到真正像原型AGI的东西,它配得上这个名字(@bananaflag)

质疑架构的真实泛化与记忆能力

  • 技术读者要求提供泛化测试证据,如数字加法或未见数据集上的预测率,并询问话题切换后早期记忆是否保留,怀疑其依赖记忆而非真正泛化。(认可度:较多人) > 这架构真能泛化还是主要靠记忆?试过基础泛化任务吗,比如数字加法?(@cpldcpu)

怀疑相比RAG与上下文管理的优势

  • 有人提出,相对于精于上下文管理和RAG,训练知识因有损而不可靠,质疑此项目的实际优势。(认可度:少数人) > 相比做好上下文管理和RAG,这么做有何优势?训练知识天生有损所以不可靠(@awfm9)

赞赏原生避免灾难性遗忘的连续学习

  • 有开发者赞赏项目从底层构建以避免灾难性遗忘,认为值得克隆尝试。(认可度:少数人)

有价值的补充

  • 社区观察者指出无人会扔石头指责,大家普遍对大公司既好奇又怀疑,更想亲自动手,因为预期成本将很快下降(@whizzter)。
  • 一名用户激动表示该项目正是缺失的拼图并致谢(@hanselot)。