训练一个模型必须要有显卡或者数据中心,这几乎是行业默认的常识。一个名为 Qapla’ 的开源项目给出了另一种答案:一块八美元的 ESP32-S3 单片机,也能从零训练出一个 Transformer。

这里的关键词是「训练」,不是「运行」。前向传播、反向传播、权重更新,整个学习循环全部发生在芯片内部。
和以往的边缘 AI 项目差在哪
边缘 AI 并不新鲜。TinyML 在单片机上做推理已有多年,社区把安德烈·卡帕西(Andrej Karpathy)的极简项目 llama2.c 移植到 ESP32 上,跑起了约 26 万参数的 Transformer;最近还有项目在同样八美元的 ESP32-S3 上跑起了将近 2900 万参数的模型。
作者指出,这些工作都很出色,但它们有一个共同点——都是推理。模型诞生在别处,在显卡或数据中心上用数据训练好、量化,然后才被装进芯片里运行。用项目作者的比喻说,大脑是在外面煮熟了端上来的。
Qapla’ 想回答的是另一个问题:如果模型根本没法在外面诞生,会怎么样?
作者举了两个设想中的场景:一个螺栓固定在田间农机上的传感器,需要学习这一台机器的正常振动特征——它和世界上任何其他机器都不一样——从而在出故障前发现异常、安排检修;或者一块地里的传感器,学习这片土壤如何变干,它的土质、日照和排水情况,在作物受影响之前预测何时需要灌溉。这些场景里,数据在设备装上去之前根本不存在,没有人能预先训练它,芯片只能在原地独自学习。
为什么选了克林贡语
手头没有农机可供实验,作者就用唯一现成的对象来测试芯片的真实学习能力:语言。
在单片机里训练,规则和数据中心完全不同。ESP32-S3 只有几 MB 的 RAM/PSRAM,而不是几十 GB,这直接锁死了模型规模——量级是几十万参数而非几百万。这么小的模型能学会一门语言的结构(构词方式、音位组合规则、部分语法),但学不会整门语言的深层语义,所以任务必须裁到与模型相称。同时,小模型不需要海量文本,需要的是紧凑、干净、结构清晰,并且在版权上没有麻烦的语料。
三个约束叠加,问题就变得具体了:哪种语言同时满足这些条件?作者的答案是克林贡语。
这个选择工程考量多过极客趣味。克林贡语由语言学家马克·奥克兰(Marc Okrand)在 1984 年创造,是一门有系统音系、语法和形态的人造语言,采用不常见的宾语—动词—主语语序,构词后缀规则严格。也就是说,它有真实的结构可供学习,这正好能证明小模型是真的在学规律,而不是在死记噪声。同时它字母表很小,按字符处理只有约 30 个符号,模型可以做得足够小;社区还有一部开源许可的词典 boQwI’,语料不涉及版权风险。
作者也把丑话说在前面:这不是口袋版 ChatGPT,它不聊天、不答题、不推理,学到的是语言的形状而不是意义;它产出的克林贡语在语义上并不完美;它不快,训练要花好几个小时,甚至好几天。
一颗芯片里的完整训练循环
在 ESP32-S3 内部完成的步骤包括:权重随机初始化、读取并切分语料、前向传播、交叉熵损失计算、反向传播(导数全部手写)、权重更新(带 0.9 动量的 SGD 加余弦学习率)、把检查点存入 LittleFS 闪存,以及用学到的权重生成文本。芯片之外要做的事:没有。
架构本身没有新意,是一个微型但完整的 Transformer:单个模块、单头因果注意力、权重绑定、ReLU 前馈网络和层归一化,按字符工作,词表约 31 个符号,上下文 32 个字符,总计约 319,000 个参数。
这里没有 PyTorch,也没有自动求导,前向传播的每一个导数都用 C 显式写出。为确认没写错,每个梯度都与前向传播的中心差分做了对比,最差相对误差为 1.07e-08,而阈值是 1e-4;内核还先用 ESP-IDF 构建并在 QEMU 的 xtensa 模拟环境中跑通,然后才烧进真实硅片。这两项检查都放在仓库的 tests/ 目录里,可以自行复现。
作者强调,真正的难点不是参数量,而是内存。31.9 万参数「只」占约 1.3 MB,但训练不只是放下权重:同一时刻内存里还要有等量大小的梯度副本、一份优化器动量、一份最优模型备份,加上反向传播需要的全部中间激活值,再加上语料本身——加起来是芯片 PSRAM 里的好几 MB。这就是推理与训练的本质差别,也是这个项目与其他边缘 AI 项目的分野。
跑出来的数字
全部训练在芯片内完成,供电靠一个手机充电器。参数约 319,000 个,词表 31 个字符,上下文 32 个字符,优化器为带动量(0.9)的 SGD 加余弦学习率,共 5000 步,耗时约两天,硬件是 ESP32-S3 N16R8 搭配一块 SH1106 OLED 屏。
损失下降过程如屏幕上所显示:第 1495 步批次损失 2.193、移动平均 2.137;第 2549 步为 1.982 与 2.035;第 4905 步为 1.996 与 1.871。作者提醒,真正该看的是移动平均——单个批次的损失取决于抽到哪些句子,波动很大,而平均值在稳步下降。芯片最终收在约 1.87。
模型吐出的文本长这样:
hIngan motlh puS ruq tuq DujDaq SISwI’ nge’vI’
>
vIn SuvwI’ yIvwI’ qarghtaHvIS SIchoH
不算完全通顺的克林贡语,但形态学值得一看:SuvwI’ 意为「战士」,由词根 Suv(战斗)加后缀 -wI’(做某事的人)构成,是一个构造正确的真实词汇;DujDaq 意为「在船上」,由 Duj(船)加处所后缀 -Daq 构成;qarghtaHvIS 带有复合后缀 -taHvIS(表示「当……时」,持续体加状语),位置分毫不差;hIngan 距离 tlhIngan——也就是「克林贡」这个词本身——只差两个字母。
其余一些词构造合规但并不存在。没有人给模型任何规则、语法标签或形态切分,喂进去的只是逐字符的原始文本,它从中捕捉到了足够的规律,把词根和后缀重组成站得住脚的形状。作者也坦承,仅凭两个样本无法判断其中多少是真正的泛化、多少是语料的统计回声,「一个用小语料训练的 31.9 万参数模型,预期就是二者兼有」。
想自己试的话
语料 corpus/klingon.txt 已随仓库提供,采用 Apache 2.0 许可,可用 corpus/build_corpus.py 重新生成,克隆下来就能跑。不过作者认为更有意思的实验不是重复他的,而是把这套东西对准自己的文本:用 tools/gen_header.py 把任意文本转成头文件,再用 PlatformIO 执行 pio run -t upload 烧录,然后用 pio device monitor 看着损失实时下降。
硬件需要一块带 PSRAM 的 ESP32-S3(型号 N16R8),一块通过 I2C 连接的 SH1106 OLED 屏(可选,但作者说少了它就少了一半乐趣)。芯片从随机权重开始训练,会定期保存训练损失移动平均最低的检查点;中途拔电,上次存档之后学到的东西就没了,跑完再拔,「大脑」还在。
需要提醒的是:这真的要花好几个小时,很多个小时,甚至好几天。用作者的话说,这不是 bug,而是重点所在——它慢,但它确实能跑通。
项目代码采用 Apache 2.0 许可开源。作者表示,接下来真正有意思的部分,是把这套引擎推到极限,去处理一门有真实使用者、语料远大于克林贡语的真实小语种。
来源:GitHub / Qapla’ 项目主页(经 Hacker News)







