在8美元微控制器上运行28.9M参数LLM
这是一个2890万参数的语言模型,运行在ESP32-S3(一款约8美元的微控制器)上。它完全在芯片本地运行,无需向服务器发送任何数据,以每秒约9个token的速度将每个单词写入连接芯片的小屏幕。此前在类似芯片上跑过的语言模型只有26万参数,因此这个模型规模大了约一百倍。能塞进去是因为模型大部分存放在闪存而非RAM中,利用了Google Gemma模型中的“逐层嵌入”(Per-Layer Embeddings)技术。
数字一览
| 参数 | 存储2890万(其中2500万在闪存查找表中) |
| 芯片 | ESP32-S3,约8美元,512KB SRAM、8MB PSRAM、16MB闪存 |
| 速度 | 端到端约9.5 tok/s(纯计算约9.7 tok/s) |
| 连接 | 无,全在设备本地运行 |
| 模型大小 | 4位量化下14.9MB |
难点在哪,又如何解决
微控制器的快速内存极其有限。ESP32-S3只提供512KB SRAM。通常整个模型必须驻留在SRAM中才能被访问,这导致只能跑很小的模型——此前类似芯片上的模型只有26万参数就是这个原因。
解决办法是干脆不让模型进入快速内存。语言模型的参数大部分放在嵌入表(embedding table)里,模型只需读取而非计算这些参数。因此你可以把那2500万行的表留在慢速闪存中,每处理一个token仅拉取所需的几行(约450字节),而真正做计算的小部分留在快速内存中。这样一来,大模型几乎不花什么运行成本,因为你从未加载它的大部分内容。它只是躺在闪存里,每次被采样一小部分。
这个思路就是Google的逐层嵌入,来自Gemma 3和Gemma 4。这里它运行在微控制器的内存布局上,而非手机或GPU。据我所知,之前没人尝试过这么小的芯片。
SRAM (快、小) "思考"核心,每个token都用
PSRAM (中等) 输出层和工作内存
FLASH (大、慢) 2500万参数表,每token读取约6行(~450字节)
它能做什么,不能做什么
模型是在 TinyStories 上训练的,因此它只会写简短、简单的故事,而且大多能保持连贯。它不会回答问题、执行指令、写代码或掌握事实。这个局限来自模型中进行推理的那一小部分,内存技巧并不能改变这一点。真正有意思的是架构——把一个大模型塞进一个小芯片,而不是一个 2890 万参数模型能说些什么。
自己运行
固件、接线和刷写步骤在 firmware/esp32_llm/README.md 中。训练、消融和量化代码在 src/ 和 experiments/ 中。完整方法、消融实验和片上测量结果写在了 RESULTS.md 中。
致谢
TinyStories 是训练所用的数据集:短小的合成故事,简单到小模型也能学会连贯地写(Ronen Eldan 和 Yuanzhi Li,微软研究院,arXiv:2305.07759)。另一半是 Per-Layer Embeddings,来自 Google 的 Gemma 模型设计,它让大模型能塞进小芯片。
Andrej Karpathy 的 llama2.c 是许多人(包括我)相信你可以训练一个小型语言模型并用纯 C 来运行的原因。这个项目就是由此发展而来的。
实际过程
我故意在仓库中保留了混乱的历史。这包括我在自己的参数核算中发现的一个 bug,它导致早期数字被夸大,以及修复后得到的修正结果。提交历史和 RESULTS.md 显示了数字的变化及其原因。