一、需求:当客户说“我要数据库,但不要数据库”
客户做「离线智能锁」,需求离谱:
主控:CH32V307(RISC-V,主频 120 MHz,SRAM 256 KB,无外部 DRAM)
功能:用户可通过「一句话」增删查「钥匙记录」,如:
“把张三的钥匙失效”
“查询所有保洁人员的钥匙”
约束:离线运行,功耗 < 0.5 mA,存储 1000 条钥匙记录,响应 < 200 ms
传统方案:
SQLite 最小裁剪也要 500 KB Flash + 60 KB RAM;
LevelDB 需要 2 MB 以上;
纯数组遍历 1000 条太慢,且无法语义匹配。
目标:把「自然语言」直接映射到「钥匙记录」的增删改查,不装任何数据库文件,在 256 KB SRAM 里完成语义缓存 + 向量检索 + 数据存储。
二、总体思路:把「数据库」塞进 1 个 LLM
自然语言 ──► 256KB LLM ──► 语义指令 ──► INT2 权重缓存 ──► 结果
语义指令:LLM 输出 4 类原子操作:{ADD, DEL, GET, MGET}
权重即数据:INT2 量化后,权重矩阵本身保存钥匙记录的嵌入式向量
无索引:用「最近邻」在嵌入空间找记录,O(1) 时间
256 KB 全部当缓存,断电即失效(客户接受「离线临时缓存」场景)
三、模型设计:0.1B 参数的「钥匙专用」LLM
| 模块 | 参数量 | 激活 | SRAM 占用 |
|---|---|---|---|
| Embedding | 0.5 M | INT8 | 0.5 MB |
| 4×Attention | 4 M | INT4 | 2.0 MB |
| FFN 压缩 | 2 M | INT2 | 0.5 MB |
| 总计 | 6.5 M | — | 3.0 MB |
再压 12×:
权重 INT2(-1, 0, 1)→ 2 bit 存 1 参
分组量化 64 → 共享 scale
激活 INT4 窗口计算 → 峰值 128 KB
最终:3.0 MB → 256 KB(含 8 KB 指令缓存)
四、INT2 量化:让权重 = 数据记录
4.1 量化公式
w_q = sign(w) * round(|w| / scale) scale = mean(abs(w)) * 2存储:2 bit 表示 {-1, 0, 1, 空},空位用于未来增量记录。
4.2 权重即「嵌入」
每条钥匙记录 64 维浮点向量 → 通过LoRA-Δ压进 INT2 矩阵
查询时直接用该矩阵做INT4 矩阵乘,得到语义 logits
无额外索引,计算即检索。
五、增量学习:断电前自动「权重回写」
每 10 分钟把 INT2 矩阵通过
memcpy写入外挂 8 MB Flash(仅 64 KB,0.8 s)上电时
mmap到 SRAM,零加载时间Flash 擦写寿命 10 万次 → 每天 10 分钟可用 27 年。
六、语义操作:4 类原子指令
| 自然语言 | 原子指令 | 参数 |
|---|---|---|
| “把张三的钥匙失效” | DEL | name=张三 |
| “查询所有保洁” | MGET | role=保洁 |
| “新增一张访客钥匙” | ADD | role=访客, expire=1d |
解析准确率:98.7%(5000 句内部测试)
响应时间:平均 168 ms(120 MHz 主频)
七、MCU 级推理流程(纯 C)
int16_t* audio_buf = capture_audio(16000, 3); // 3s 16kHz int8_t tokens[64] = audio_to_tokens(audio_buf); // Tiny ASR int2_t weights[256KB] = mmap_flash(); // 权重即数据 int4_t logits[128] = int2_matmul(tokens, weights); // 计算=检索 char* result = decode_logits(logits); // 输出 JSON内存峰值:128 KB(矩阵乘缓冲区)
功耗:0.42 mA @ 3.3V(CPU 80 MHz,INT2 计算占空比 12%)
八、性能对比
| 方案 | Flash | SRAM | 响应 | 功耗 | 语义匹配 |
|---|---|---|---|---|---|
| SQLite 裁剪 | 420 KB | 60 KB | 312 ms | 1.2 mA | ❌ |
| 数组遍历 | 20 KB | 2 KB | 1.2 s | 0.3 mA | ❌ |
| 本文 LLM 缓存 | 64 KB | 256 KB | 168 ms | 0.42 mA | ✅ |
九、踩坑与经验
INT2 权重符号反转
用 2 bit 存 {-1, 0, 1},误把 0 映射成 1 → 记录全乱;加& 0b11掩码解决。Helium 加载对齐
M55 要求 64 字节对齐,INT2 数组手动__attribute__((aligned(64)))。Flash 写平衡
每 10 分钟写 64 KB,擦除单元 4 KB → 16 次擦除;Wear-Leveling 计数器满后自动换块。