从零训练到真板运行: 我们怎样把中文模型放进 ESP32

FIELD NOTE / 2026

从零训练到真板运行:
我们怎样把中文模型放进 ESP32

BUILD LOG · AUG 2026

hello 啊,我是 CY。

最近一起在玩 EasyInput这个开发板。

做了一些相关的案例。然后呢,突然有一天想到。能不能给它放进去一个端侧的模型。

没想到真的成了。把过程记录一下,和大家分享。

一块只有 8 MB PSRAM、16 MB Flash 的 ESP32-S3,能不能离线运行一个真正会逐词生成答案的中文模型?我们用一套从随机参数开始训练的 783 万参数模型,完成了从产品知识、训练、量化到真板运行的整条链路。

本地网页连接真实开发板后的运行界面:电脑只负责 USB 传输与显示。

783 万

可训练参数

4.14 MiB

完整板端模型包

1.5–2.25 秒

真板生成完整回答

我们把这条已经走通的工程路径完整拆开:怎样把产品知识整理成数据,怎样训练一个符合芯片资源的模型,怎样把它压缩成板子装得下的形式,再怎样用 C 语言让它在 ESP32 上独立完成中文分词、推理和逐 token 生成。

面对资源有限的设备,第一反应往往是把一个大模型尽量压小。真正开始做之后,我们发现更关键的一步发生在训练之前:先把“它需要解决什么问题”说清楚。模型大小、数据组织、训练方法和部署方式,都要围绕这个边界共同设计。

01

先看结果:模型已经在真板上回答中文问题

目前,用户可以在本地网页输入中文问题,问题通过 USB 发送到开发板。板上的 Tokenizer 把文字转换成数字,模型在板上逐 token 生成答案,再通过 USB 把结果传回网页。电脑只负责传输和显示,不保存答案表,也不代替板子做推理。

当网页里出现第一段由开发板逐 token 返回的中文答案时,前面的数据、训练、量化和 C 语言运行时才真正连成了一件事:这个答案确实来自那块小小的板子。

真板演示:开发板通过 USB 与本地网页连接,中文问题的分词、推理和逐 token 生成都在板上完成。

一次真实板端问答:首 token 181 ms,总耗时 1,518 ms,输出 20 tokens。

正在运行当前模型的 EasyInput 开发板实物。

观察项

真板结果

芯片与存储

ESP32-S3R8,8 MB PSRAM,16 MB Flash

模型初始化后的 PSRAM 余量

约 5.42 MB

运行频率

CPU 240 MHz,双核并行运行正常

首个 token 延迟

约 123–473 ms

交互方式

USB 真板问答与本地网页界面均可使用

当前状态说明:这是开发验证版本,现有 41 项质量检查通过 34 项。剩余 7 项主要集中在少量问题的语义分流、量化差异和答案结束稳定性。它已经能用于真板体验,也清楚地告诉了我们下一轮训练该改什么;完成全部质量检查后,才会进入正式发布阶段。

02

小芯片上的模型,先从边界开始

“端侧模型”指的是直接运行在设备一侧的模型。它不依赖云端服务器完成核心推理,因此可以离线工作,响应路径更短,数据也不必为了得到答案而离开设备。

当前 EasyInput V2.0 开发板正面部件说明:电源、USB-C、8 个机械按键、旋钮、RGB、麦克风孔、BOOT、状态灯、调试触点与固定柱。

把通用大模型直接压小,通常无法解决端侧设备真正面对的资源问题。ESP32 的内存、算力和存储都很有限,更适合先做好一个边界清楚的任务。我们选择的是中文单轮垂直问答:模型只围绕当前 EasyInput 开发板回答问题,内容聚焦当前硬件,也不承担开放世界聊天。

开始整理数据前,我们先回答了三个问题:

1这块设备必须知道哪些事实?

2用户可能用哪些方式询问这些事实?

3哪些问题应该正常回答,哪些问题应该要求澄清或明确超出范围?

这三个问题看起来简单,却决定了后面的模型规模和训练难度。边界越清楚,有限的参数越能用在真正需要的能力上。

03

把产品知识变成可以训练的数据

我们先把当前开发板的资料整理成 55 条“原子事实”。原子事实是一条尽量只表达一件事、能够单独检查对错的知识,例如某个接口的用途、某个操作的步骤,或者某项能力的边界。

每条事实先对应一份规范答案,再扩展成多种真实问法。用户可能说“这个接口做什么”,也可能说“这里应该接什么”或“为什么没有反应”。模型需要理解这些表述指向同一个事实,而不是只记住一种固定句式。

数据组成

数量

作用

基础问答

4,664 条

覆盖 55 条原子事实及其常见问法

补充问法

608 条

补足更自然、更口语化的表达

语义对照数据

648 条

帮助模型区分看起来相似、答案却不同的问题

合计

5,920 条

共同构成当前训练数据

这 5,920 条记录围绕 55 条可靠事实展开,从不同角度教模型理解、区分和表达。数据量增加的是问法、对照和表达路径,而不是未经核验的知识。

同一模型对问候、麦克风和扬声器问题的真实板端回答。

04

中文怎样变成模型能处理的数字

神经网络不能直接读取汉字,它处理的是整数编号。把文字切成若干最小处理单位并映射成编号的工具,叫作 Tokenizer;切出来的每个单位,叫作 token。

01输入“开发板怎么连接”,Tokenizer 把文字切成 token。

02每个 token 被转换成一个整数 ID,模型在这些数字上进行计算。

03模型生成新的 ID,再由 Tokenizer 还原成可以显示的中文。

我们的 Tokenizer 是一套确定性的中文切分规则:同一句话在 Python 训练程序和 C 语言板端运行时里,必须得到完全相同的 token ID。规范数据需要保持有效 UTF-8,编码后再解码要能无损还原,也不能因为未知 token 而丢失信息。

项目使用两套词表:输入词表有 6,400 个 token,输出词表有 4,096 个 token。输入侧需要覆盖更多可能的问法;输出侧只保留答案真正需要生成的内容,从而减少最后一层计算和权重。输出 token 还会映射回输入词表,让已经生成的内容可以继续参与下一步预测。

05

一个 783 万参数的小模型长什么样

这里说的“参数”,就是模型在训练中不断调整并最终保存下来的数字。当前模型共有 7,834,112 个参数,采用 Decoder-only Transformer,按顺序一个 token 接一个 token 地生成文本,直到输出结束标记。Transformer 可以理解为一套让模型在上下文中寻找关联、组合信息并预测下一个 token 的结构。

每一层都包含注意力机制和前馈网络。注意力机制帮助模型判断问题中哪些 token 更重要;前馈网络负责进一步变换和组合这些信息。我们还使用 RoPE 表示 token 在句子中的相对位置,并使用 SwiGLU 形式的前馈网络提升有限参数下的表达能力。

PLE 是 Per-Layer Embeddings 的缩写,可以理解为“每一层自己的辅助记忆表”。普通词向量告诉模型“这个 token 是什么”,PLE 则让不同层在处理同一个 token 时获得不同的补充信息。前面的层可以更关注文字和局部结构,后面的层可以更关注答案选择与生成。

结构项

当前设置

输入词表 / 输出词表

6,400 / 4,096

隐藏维度

128

Transformer 层数

6 层

注意力头数

4 头

前馈网络维度

384

PLE 维度

128

上下文 / 最长答案

128 / 32 token

参数总量

7,834,112

06

模型怎样从随机参数学会回答

这次训练从随机初始化开始,没有加载外部预训练权重。模型最初的 7,834,112 个参数都是随机数,它对 EasyInput 一无所知。训练的过程,就是让这些随机参数在一次次问答练习中逐渐形成稳定的预测规律。

一条问答怎样变成一次练习

训练样本会被组织成“问题 + 答案”的 token 序列,并加入表示开始和结束的特殊 token。训练时,模型每走一步都尝试预测答案中的下一个 token。我们只在答案区域计算误差,不要求模型复述问题。

训练还会采用 teacher forcing:预测下一个 token 时,给模型看到前面正确的答案 token。这样学习过程更稳定,也能让模型清楚地知道每一步应该接什么。

模型怎样根据错误调整自己

预测结果与正确答案之间的差距会被计算成一个数值,这就是损失。随后,反向传播会找出每个参数对这次错误产生了多大影响,AdamW 优化器再沿着减小损失的方向更新参数。不断重复“预测、计算损失、反向传播、更新参数”,模型才从随机状态逐渐学会回答。

训练设置

当前数值

训练记录

5,920 条

Batch size

32

训练轮数

7 轮,每轮 185 step

参数更新次数

1,295 次

学习率 / 梯度裁剪

0.001 / 1.0

训练环境

Mac 上的 PyTorch + Apple MPS

每一轮训练结束后都会保存一份 checkpoint,也就是某一时刻的模型参数快照。不同轮次会接受同一套评测,最终依据综合表现选择,而不直接把最后一轮当成最好的一轮。

07

为什么量化必须从训练阶段开始

如果把全部权重都用 FP32 保存,也就是每个数值使用 32 位浮点数,模型权重大约需要 29.9 MiB,已经超过这块板子的可用空间。量化的作用,就是用更少的位数表达权重和中间数值。

当前板端使用 Q4 权重和 int8 激活:Q4 用大约 4 位表示一个权重,int8 用 8 位整数表示计算过程中的中间值。权重主体因此压缩到约 3.86 MiB,加入 Tokenizer、词表、形状信息和校验信息后,完整模型包为 4,338,384 字节,约 4.14 MiB。

量化会引入误差,所以我们没有等模型训练完再一次性压缩,而是在训练时同时观察三条数值路径:

高精度路径

用 FP32 观察模型本身是否学会。

Q4 权重路径

让模型提前适应低比特权重带来的误差。

部署近似路径

使用 Q4 权重与 int8 激活,尽量靠近板端计算。

这种做法属于量化感知训练。它让模型在学习知识的同时,也学习怎样在低精度计算下保持答案稳定。

训练还会关注正确答案与错误答案之间的分数间隔。假设正确答案得分为 10.01,错误答案为 10.00,表面上虽然选对了,但只要量化误差稍微变化,结果就可能翻转。我们把目标间隔设为 0.5,让正确答案不只是“刚好领先”,而是留出更可靠的安全空间。

08

怎样让模型分清相似问题,并证明它真的学会

小模型常见的问题不是完全不会,而是把相似问题混在一起。例如,“你是谁”“你会什么”“这个问题不属于你的范围”都可能使用相似的词,但需要走向不同答案。

因此,我们把训练样本分成六种语义角色:身份、能力、需要澄清、超出范围、当前开发板事实和日常问候;每种角色再覆盖直接、委婉、口语化和带上下文等四种表达风格。训练时会同时观察这些分组,避免某一类表现很好、另一类却被掩盖。

把注意力放在答案真正分叉的地方

完整答案可能有很多相同的开头,真正决定答案类别的往往只是前几个关键 token。我们会找到能够唯一识别某个答案的最短前缀,并重点检查模型在这个“分叉点”上是否足够确定。

同时,评测不能只把正确答案强行喂给模型。真实生成时,模型需要沿着自己已经生成的前缀继续走。训练和检查中会从可到达的正确前缀出发,观察下一步预测,尽量缩小“训练时会做,真正生成时走偏”的差距。

用不同数据回答不同问题

数据集合

它回答的问题

训练数据

模型怎样更新参数

选择集(480 条)

七轮训练中,哪一轮更值得继续检查

独立审计集(480 条)

模型是否只适应了选择集

公开验证集(1,146 条)

最终统一观察完整回答、量化一致性和语义分组表现

当前开发模型在 1,146 条公开验证问题上,高精度路径完整回答正确 1,001 条;Q4 路径和部署近似路径各正确 996 条。41 项质量检查通过 34 项,但七轮中还没有一轮同时满足全部条件。

这组结果把问题进一步缩小到了少量身份与歧义问题的分流、个别量化差异、答案结束标记的稳定性,以及部分数据组无法同时达标。下一轮训练因此有了明确目标:针对这些薄弱点补强,而不是继续盲目增加轮数。

09

从 PyTorch 到 ESP32,中间还缺一整条工程链

电脑上训练成功,只说明得到了一组参数。要让模型真正运行在 ESP32 上,还需要把训练资产、数值规则、C 语言运行时、固件和交互界面接成一条能够互相校验的链路。

产品知识与问答数据

▼

Tokenizer 与模型训练

▼

Q4 量化与模型包

▼

ESP32 C 语言运行时

▼

USB 逐 token 返回

▼

本地网页显示

模型包。板端读取的不是一份裸权重文件,而是一个完整资产包,其中包含 Tokenizer、输出词表映射、Q4 权重、张量形状和 SHA-256 身份校验。训练、导出、固件和运行时会核对资产身份,只有四个环节完全一致,系统才允许继续工作。

C 语言运行时。它负责中文 Tokenizer、模型包验证、Q4 矩阵计算、int8 激活、Transformer 前向计算、KV cache、逐 token 生成、结束判断和 UTF-8 安全输出。KV cache 会保存前面 token 已经算过的注意力结果,避免生成每个新 token 时全部重算。

内存分工。Flash 适合保存大量只读权重,并通过内存映射直接访问;PSRAM 容量较大,适合中间状态和较大的临时缓冲;内部 SRAM 更快,留给当前计算热点、控制结构和小型高频缓冲。模型能否稳定运行,不只取决于总大小,还取决于每一类数据被放在哪里、活多久。

固件与网页。固件负责装载模型、管理内存、接收 USB 文本并调用运行时。电脑端的串口工具和本地网页只转发问题、显示逐 token 结果与设备状态,不包含模型、Tokenizer、固定答案表或云端生成逻辑。这样测到的能力才真正属于板上的模型。

10

这套方法还能用在哪里

如果要把这条路线迁移到另一块板、另一个产品或另一类任务,真正需要重新设计的通常有四部分:

01知识边界:设备必须知道什么,又明确不处理什么。

02训练数据:怎样把可靠事实扩展成真实问法、语义对照和可检查答案。

03模型预算:根据 Flash、PSRAM、速度和任务复杂度决定词表、层数、宽度和上下文。

04部署链路:让 Python、导出工具、C 语言运行时、固件和真板测试遵守同一套格式与数值规则。

真正可复用的是这条完整闭环:先限定需求,再整理知识;用可检查的数据从随机参数开始训练;在训练阶段考虑量化;用相互隔离的数据评测;最后把同一套 Tokenizer、模型和数值规则搬到芯片上验证。

小芯片上的模型能力,最终由任务边界、数据、模型结构、训练目标和部署环境共同决定。

这块 ESP32 上的模型还在继续改进,但从知识整理、随机初始化训练、Q4 量化、C 语言推理,到 USB 真板问答和本地网页体验,完整路径已经被实际跑通。接下来要做的,是沿着已经暴露出来的具体问题继续提高质量,让“可以运行”一步步走向“稳定、可复现、可以正式交付”。

发表评论