32 · 三处保序等价优化:目录缓存、共享量化、RoPE 复用
本篇对应源码:main/kmcu.c · host_verify.c · docs/optimization_plan.zh-CN.md
目标:把「每 token 重复做但结果必然相同」的事砍掉,同时保证字节级不变。 结论:三项都通过 216 条 dump 对拍;它们不加总耗时承诺,只承诺「不改变任何数」。
关键词:目录缓存 · 共享量化 · RoPE 复用 · 位级对拍 封面:三处改动,216 条 dump 全 MATCH
一、结论先行
| O-3 | 张量名查找加 64 槽直接映射缓存 | 纯查表,不改变算术 | 216 条 MATCH |
| O-4 | Q/K/V 与 gate/up 共享一次激活量化 | 同一公式、同一 xq 位模式 | 216 条 MATCH,prefill 11 项通过 |
| O-5 | RoPE 的 cos/sin 按 (pos, i) 复用 | 同一输入、同一 libm 返回值 | 216 条 MATCH |
它们属于「保序等价」:可以证明不需要重新拟合、不需要重跑参考实现, 只需要证明读到的数和算出的数一个 bit 都没变。
二、O-3:每 token 上百次字符串查找
km_find(m, "L3.e5.down") 是线性扫描张量目录。一层里要查十几次, 每 token 十层,再加上 PLE 分支,量级是「百次 × 两三百个张量名」。
#define KM_FIND_CACHE 64
static struct { const void *base; const char *name; int idx; } s_find_cache[KM_FIND_CACHE];
int km_find(const km_model_t *m, const char *name) {
uint32_t h = hash(name) & (KM_FIND_CACHE – 1);
if (s_find_cache[h].base == m->base && strcmp(s_find_cache[h].name, name) == 0)
return s_find_cache[h].idx;
...线性扫描...
}
km_open() 时整体失效,避免换模型后拿到旧下标。缓存未命中就回退线性扫描, 所以最坏情况只是「没变快」,不会变错。
三、O-4:同一份激活只量化一次
Q、K、V 三个 GEMV 用的是同一个 x。原实现各调用一次 quantize_act(x, dim, xq)——同样的 312 次缩放、同样的 312 次舍入,做三遍。
拆成两个函数后:
const float invS_qkv = 1.0f / quantize_act(h, dim, xq);
gemv_q8_pre(&f->t[idx_q], dim, dim, xq, invS_qkv, q);
gemv_q8_pre(&f->t[idx_k], kvw, dim, xq, invS_qkv, kk);
gemv_q8_pre(&f->t[idx_v], kvw, dim, xq, invS_qkv, vv);
等价性来自「同一个 xq 位模式」:量化是确定性函数,三次调用得到的 xq 和 S 完全相同,所以共享不改变任何结果。MoE 里每个专家的 gate/up 同样共享一份, 但 down 之后 xq 会被下一次量化覆盖,所以跨专家不能共用。
四、O-5:RoPE 的三角函数
RoPE 要算 cos(pos·θ^(-2i/d)),对每个 head 都一样。原实现每个 head 算一次, 12 个头就是 12 倍超越函数调用。
for (int i = 0; i < half; i++) {
float inv = 1.0f / powf(theta, (float)(2 * i) / (float)hd);
float ang = (float)pos * inv;
float cv = cosf(ang), sv = sinf(ang); /* 每个 i 只算一次 */
for (int h = 0; h < n_heads; h++) { ... } /* 所有 head 复用 */
}
等价条件很硬:同一个 pos、同一个 i、同一个 libm 实现,返回值必须逐位相同。 这也是为什么它被允许放在「保序」而不是「需重新对拍」里。
五、门禁:216 条 dump
host_verify –dump 会输出逐层 hidden、K/V、完整 logits、cls、agent 的原始 float 字节, 再由 tools/dump_diff.py 逐条比较。三项改动后全部 MATCH(tied 模型 225 条)。
这不是「文本看起来一样」,而是字节一样。
六、诚实边界
- 这三项是捆绑一起验证的,没有单独做「只开 O-3」的耗时 A/B。 它们的设计目标是「省掉重复工作且不改变结果」,不是某个具体毫秒数。
- 目录缓存在「张量名很相似」时有哈希冲突,但冲突只会退化为线性扫描,不影响正确性。
七、一句话
保序等价的优化最舒服的地方在于:它不需要重新验证精度,只需要证明 「同一输入 → 同一字节」;三项改动全部一次通过 216 条 dump 对拍。
对应源码
| main/kmcu.c | km_find() / s_find_cache | O-3 目录缓存 |
| main/kmcu.c | quantize_act() / gemv_q8_pre() | O-4 共享量化 |
| main/kmcu.c | rope_apply() | O-5 三角函数复用 |
| host_verify.c · tools/dump_diff.py | –dump + 逐条比较 | 216/225 条门禁 |
《Kestrel-MCU 手记》 · 全系列 37 篇:在 ESP32-P4 上从零训练并部署 64M 参数 MoE 大模型(5.8 tok/s),源码、权重、训练脚本与全部文章开源可复现。
仓库:https://gitee.com/pei-xiaoguang/kestrel-llm-mcu · 觉得有用欢迎 Star
网硕互联帮助中心





评论前必须登录!
注册