据我们所知,这是第一个在 ESP32 上跑起来的中文语言模型。
一颗几块钱的 ESP32-S3,8 MB PSRAM,没有加速卡、没有联网、没有云端——上电 3 秒加载完模型, 然后开始用中文一个字一个字地讲儿童故事,每秒 8 个 token,一篇几百字的故事半分钟讲完。 模型是我们自己从零训练的:145 万参数,fp32 权重 5.9 MB,在一张 RTX 4060 Ti 上练了一个半小时。
从前,有一个小女孩叫米亚。米亚喜欢和她的朋友们一起玩捉迷藏。有一天,米亚在玩具箱里发现了一个有趣的游戏…… 米亚的妈妈抱住她说:"没关系,米亚。我们可以把它放回那个游戏里。"
— 上面这段就是 ESP32-S3 板上生成的原文,未经挑选。
English: a 1.45M-parameter Llama-2-style model trained from scratch on Chinese TinyStories, shipped in llama2.c format. To our knowledge the first Chinese language model running on an ESP32: ~8 tok/s on ESP32-S3 with 8 MB PSRAM, fully offline. A flash-and-run ESP-IDF demo is included.
| 文件 | 说明 |
|---|---|
stories1.4M-zh.bin |
权重,llama2.c 格式(version 0),fp32,5.9 MB |
tokenizer.bin |
llama2.c 格式分词器 |
tok4096.model / tok4096.vocab |
sentencepiece 分词器原文件 |
ckpt.pt |
PyTorch 检查点(step 20000,含优化器状态,可续训) |
esp32s3-demo/ |
ESP-IDF 工程,任何 ESP32-S3 + 8 MB PSRAM 的板子 idf.py flash monitor 就能看它讲故事 |
需要:ESP32-S3 开发板,模组带 8 MB 八线 PSRAM(常见的 N16R8 / N8R8 都是),flash ≥ 8 MB;ESP-IDF 5.1 以上。
git clone https://github.com/ModenCn/tinyllamas-zh.git
cd tinyllamas-zh/esp32s3-demo
idf.py set-target esp32s3 && idf.py -p /dev/ttyUSB0 flash monitor编译时会把仓库根目录的模型和分词器打成 SPIFFS 镜像一起烧进去,不用手动传文件。 串口(UTF-8)会看到:
I (3645) demo: model loaded in 2626 ms: dim=128 layers=5 heads=4 kv_heads=2 vocab=4096 seq=512
===== 第 1 篇 =====
从前,有一个小女孩名叫莉莉。她喜欢看卡通片,尤其是彩色的卡片。有一天,她和妈妈一起去了公园……
[8.9 tok/s, 668 bytes, free heap 207907, psram 1125640]
idf.py menuconfig → tinyllamas-zh demo 可以设故事开头(比如"从前,有一只会飞的狗叫波波")、温度和间隔。
四线 PSRAM 的板子把 sdkconfig.defaults 里的 SPIRAM_MODE_OCT 改成 QUAD——但 2 MB PSRAM 装不下这个模型。
在电脑上跑也行:
git clone https://github.com/karpathy/llama2.c && cd llama2.c && make run
./run ../tinyllamas-zh/stories1.4M-zh.bin -z ../tinyllamas-zh/tokenizer.bin -t 0.8 -p 0.9
./run ../tinyllamas-zh/stories1.4M-zh.bin -z ../tinyllamas-zh/tokenizer.bin -i "从前,有一只会飞的狗叫波波"| 项目 | 数值 |
|---|---|
| 模型加载 | 2.7 s(SPIFFS → 16 字节对齐的 PSRAM 缓冲,不经中转) |
| 生成速度 | 8~9 tok/s,中文约一字一 token |
| 每 token 计算 | 约 290 万次乘加,esp-dsp dsps_dotprod_f32_aes3 点积 + 双核并行 |
| 内存 | 权重 5.9 MB + KV 缓存 1.3 MB 在 PSRAM;demo 跑起来后内部 RAM 还剩约 200 KB |
| 一篇故事 | 200 |
同一套推理内核在 BOX3(带 LCD 的 ESP32-S3 板)上还接了乐鑫 esp-tts,故事写完直接用中文读出来—— 一块板子、完全离线的"讲故事机"。板级工程带屏幕和喇叭驱动,未随本仓库发布。
| 项 | 值 |
|---|---|
| 架构 | Llama 2:RMSNorm、RoPE、SwiGLU、分组查询注意力,仅解码器 |
| dim / 层数 | 128 / 5 |
| 注意力头 / KV 头 | 4 / 2(head dim 32) |
| 前馈隐藏维 | 352 |
| 上下文 | 512 |
| 词表 | 4096,sentencepiece BPE,中文基本一字一 token(character_coverage=0.9995,生僻字走字节回退) |
| 参数量 | 1,447,296(嵌入与输出层共享;文件里另存 RoPE 表 16K 个数) |
- 数据:adam89/TinyStoriesChinese, 即 jia-zhuang/chinese-llama2.c 把 TinyStories 翻成中文的约 400 万条故事, 约 1 G 字符,分词后约 6.5 亿 token。
- 代码:chinese-llama2.c 的
tinystories.py/train.py(= llama2.c 训练脚本),只改一处:train_vocab里character_coverage=0.9995(这份语料有 4741 个不同汉字,100% 覆盖要 5000+ 词表)。 - 命令:
python tinystories.py download python tinystories.py train_vocab --vocab_size=4096 python tinystories.py pretokenize --vocab_size=4096 python tokenizer.py --tokenizer-model=data/tok4096.model python -u train.py --compile=False --vocab_source=custom --vocab_size=4096 \ --dim=128 --n_layers=5 --n_heads=4 --n_kv_heads=2 --max_seq_len=512 \ --batch_size=128 --learning_rate=1e-3 --warmup_iters=500 --max_iters=20000 \ --eval_interval=1000 --always_save_checkpoint=True --out_dir=out - 单张 RTX 4060 Ti 16 GB,bf16,0.26 s/步,20000 步约 1.5 小时(约 13 亿 token,数据约 2 轮)。
- 损失(train / val):1000 步 2.79 → 5000 步 2.31 → 11000 步 2.20 → 20000 步 2.13,无过拟合。
- 经验:一张 16 GB 卡一次只跑一个这种小模型——两个并跑会把显存挤爆,Windows 开始换页,每步从 0.26 s 变 25 s,最后 CUDA 报错。
网上现成的中文 TinyStories 模型最小也是 15M 参数(fp32 60 MB,int8 也要 15 MB),ESP32 的 8 MB PSRAM 装不下, 算力上也慢到几秒一个字。要在单片机上讲中文故事,只能把模型缩到 1/10 再从零训。 145 万参数够用的原因是题材极窄:儿童故事的词汇和套路,小模型记得住。
typedef float v4sf __attribute__((aligned(16)))这种带对齐属性的标量按值传参,ESP-IDF 5.4 的 GCC 14 会传坏 (采样函数收到乱值 → token 变 NaN → 崩)。标量一律用普通float。- esp-dsp 的
aes3点积走 128 位加载,权重和运行态缓冲必须 16 字节对齐;权重直接fread进对齐的 PSRAM 缓冲, 别先整包读进来再复制(5.9 MB 会瞬间占 12 MB)。 - esp32-llm 原版的双核握手有竞态:主任务给出信号量后自己算完又去取同一个信号量,助手任务稍慢一步就永远挂死。
改成主任务只等助手的完成位(
xEventGroupWaitBits/SetBits),顺带还快了 20%。
只会写这一种题材的儿童故事;不能对话、不会回答问题;人物名偶尔混用,长句偶有重复。 它的意义不在"聪明",在于证明:一颗 ESP32 也能完整地跑一个中文语言模型。
权重与本仓库文件采用 MIT 许可。训练数据 TinyStoriesChinese 是 TinyStories(CDLA-Sharing-1.0)的翻译版,请遵守其许可。
站在这些工作的肩膀上:karpathy/llama2.c、 DaveBben/esp32-llm、 jia-zhuang/chinese-llama2.c、 adam89/TinyStoriesChinese。