Skip to content

About

1.4M-param Chinese TinyStories Llama-2 in llama2.c format; runs on ESP32-S3 at ~8 tok/s

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

tinyllamas-zh — 让 ESP32 说中文故事的语言模型

据我们所知,这是第一个在 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 "从前,有一只会飞的狗叫波波"

板上实测(ESP32-S3R8,240 MHz,八线 PSRAM 80 MHz)

项目 数值
模型加载 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
一篇故事 200400 字,2040 s

同一套推理内核在 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 万参数够用的原因是题材极窄:儿童故事的词汇和套路,小模型记得住。

把 llama2.c 搬上 ESP32 踩过的坑(都在 esp32s3-demo/main/llm.c 里修好了)

  1. typedef float v4sf __attribute__((aligned(16))) 这种带对齐属性的标量按值传参,ESP-IDF 5.4 的 GCC 14 会传坏 (采样函数收到乱值 → token 变 NaN → 崩)。标量一律用普通 float。
  2. esp-dsp 的 aes3 点积走 128 位加载,权重和运行态缓冲必须 16 字节对齐;权重直接 fread 进对齐的 PSRAM 缓冲, 别先整包读进来再复制(5.9 MB 会瞬间占 12 MB)。
  3. 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。

About

1.4M-param Chinese TinyStories Llama-2 in llama2.c format; runs on ESP32-S3 at ~8 tok/s

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors