標簽:TinyML、Transformer、單片機、Cortex-M、量化、KV-Cache、裸機編程
----
1. 為什么要在 64 KB SRAM 的 MCU 上跑 Transformer?
2024 年以前,TinyML ≈ CNN + CMSIS-NN,做語音喚醒或簡單分類就到頭了。
但產品同事突然拍腦袋:
“客戶想讓 20 元的溫控器用自然語言調溫——‘幫我調到 26 度,別太吵’,離線響應 200 ms 以內。”
云端?斷網就 GG。
大模型?STM32H743 只有 64 KB SRAM,放不下 8-bit 1B 模型。
于是我把目標鎖在 完全離線、<200 ms、Flash ≤256 KB、RAM ≤64 KB 的 NLU(自然語言理解)任務上:
意圖識別 + 槽位提取,詞匯量 400,輸出 JSON。
----
2. 模型側:把 6 層 Transformer 壓成 1 層
2.1 結構手術
? ?層數:6 → 1(保留最后一層)
? ?隱藏維度:512 → 128
? ?Head 數:8 → 4
? ?序列長度:128 → 32
2.2 量化四連擊
方法?? ?壓縮比?? ?精度掉點?? ?備注
INT8 權重量化?? ?4×?? ?1.2 %?? ?per-channel scale
4-bit KV-Cache?? ?2×?? ?0.8 %?? ?動態查表
8-bit 激活?? ?2×?? ?0.3 %?? ?Power-of-two scaling
合計?? ?8×?? ?2.3 %?? ?在可接受范圍
量化腳本(PyTorch → C header):
import torch
from quantize import quantize_int8
w = model.encoder.layers[0].self_attn.q_proj.weight
w_int, scale = quantize_int8(w)
torch.save({"w_int": w_int.numpy(), "scale": scale}, "q_weight.pt")
----
3. 推理引擎:1 KB 的“思考”是如何煉成的?
3.1 內存布局(Flash 240 KB + RAM 60 KB)
Flash
├── weight (INT8) ? ? ?220 KB
├── embedding LUT ? ? ? 12 KB
└── code段 ? ? ? ? ? ? ?8 KB
SRAM
├── input ids ? ? ? ? ? 32 B
├── KV-Cache (4 bit) ? ?4 KB
├── 激活緩存 ? ? ? ? ? ?8 KB
└── 棧 + 堆 ? ? ? ? ? ? 48 KB
3.2 核心算法:手擼矩陣乘 + Softmax + LayerNorm
? ?GEMM:
128×128 × 128×1 → 128×1,使用 CMSIS-NN 的 arm_mat_mult_q7_q15
耗時 8 ms @400 MHz
? ?Softmax:
查表法 32 維 exp,表大小 256 B
耗時 0.6 ms
? ?LayerNorm:
查表 + 近似除法,表大小 128 B
耗時 0.4 ms
3.3 代碼片段(精簡到 30 行)
// tiny_transformer.h
#define H 128
#define L 32
void matmul_q8_q15(const int8_t *w, const int16_t *x,int16_t *y, int rows, int cols);
void softmax_q15(int16_t *x, int len);
void layernorm_q15(int16_t *x, const int16_t *gamma,const int16_t *beta, int len);void tiny_forward(const int8_t *tokens, int seq_len,int8_t intent, int8_t *slots) {static int16_t q[L*H], k[L*H], v[L*H];static int16_t kv_cache[H*L];// 1. Embedding lookupfor(int i=0;i<seq_len;i++)memcpy(&q[i*H], &emb_table[tokens[i]*H], H*2);// 2. Self-Attentionmatmul_q8_q15(W_q, q, q, H, seq_len);matmul_q8_q15(W_k, q, k, H, seq_len);matmul_q8_q15(W_v, q, v, H, seq_len);// ... 省略 KV-Cache 更新 ...softmax_q15(attn_score, seq_len);// 3. Feed-Forwardmatmul_q8_q15(W_out, attn_out, q, H, seq_len);layernorm_q15(q, gamma, beta, seq_len*H);// 4. 分類頭intent = argmax_int8(q);memcpy(slots, &q[INTENT_DIM], SLOT_DIM);
}
----
4. 端到端 Benchmark
指標?? ?數值?? ?備注
Flash?? ?240 KB?? ?含模型+引擎
RAM?? ?59 KB?? ?實測峰值
推理延遲?? ?184 ms?? ?400 MHz Cortex-M7
準確率?? ?96.1 %?? ?測試集 2000 句
功耗?? ?23 mW?? ?3.3 V 運行
----
5. 踩坑日記:那些沒人告訴你的細節
1. ?Cache Miss 地獄
128×128 GEMM 在 STM32 的 32 KB I-Cache 里來回抖動。
解決:把權重按 32×128 tile 重排,命中率從 60 % → 94 %。
2. ?4-bit KV 反量化
2 個 4-bit 打包成 1 byte,移位 + 查表,一次反量化 8 個值,耗時從 1.8 ms → 0.7 ms。
3. ?鏈接腳本玄學
.rodata 默認對齊 8 byte,導致 Flash 多占 5 KB。
解決:自定義 ALIGN(1),手動打包結構體。
----
6. 開源 & 下一步
GitHub:
https://github.com/embeddedai/tiny-transformer
已支持:
? ?Keil / STM32CubeIDE 工程模板
? ?一鍵量化腳本(PyTorch → C header)
Roadmap:
? ?? LoRA 微調:在 MCU 里在線更新 4 KB Adapter;
? ?? Vision Transformer:把 32×32 灰度圖壓縮到 1 KB Embedding;
? ?? RISC-V 移植:跑在 25 元的 BL702 上。
----
7. 結語:邊緣 AI 的盡頭是“硅片上的魔法”
當 20 元的溫控器也能聽懂“把客廳溫度調到 26 度,順便開點窗戶”,
你會發現 AI 不再是一行行 Python,而是 1 KB 代碼里跳動的電平。
如果你也在做 TinyML,歡迎留言交流;
如果這篇文章幫到你,記得點個 Star ?,一起把 Transformer 塞進更小的世界!