English version:en/06-pitfall-cheatsheet.md
本篇对应源码:
main/kmcu.c·main/main.c·sdkconfig.defaults·docs/milestones.md
本项目的所有坑汇总。遇到问题先来这里按「症状」检索。
环境(详见 01)
| # | 症状 | 原因 | 解决 |
|---|---|---|---|
| 1 | cannot read spec file 'E:/\x0a/...' | 项目路径含中文 | 构建副本放纯 ASCII 路径C:\kmcu |
| 2 | fatal error: bits/c++config.h | 工具链路径超 MAX_PATH(260) | subst X:短盘符 +CONFIG_COMPILER_CXX_RTTI=y |
| 3 | git 卡在 esp32-wifi-lib,HTTP 423 | Gitee 封锁该子模块 | IDF_SKIP_CHECK_SUBMODULES=1 |
| 4 | 标准库头文件编译错 | 默认 picolibc 兼容问题 | CONFIG_LIBC_NEWLIB=y |
| 5 | CONFIG_SPIRAM_MODE_QUAD无效 | P4 的 PSRAM 是八线 | CONFIG_SPIRAM_MODE_HEX |
| 6 | 所有优化收益远低于理论 | 默认-Og压制循环优化 | CONFIG_COMPILER_OPTIMIZATION_PERF=y |
| 7 | idf.py 报 venv 路径不一致退出 | build 用 X: venv 配置 | 把X:\python_env\...\Scripts放 PATH 最前 |
TF 卡(详见 03)
| # | 症状 | 原因 | 解决 |
|---|---|---|---|
| 8 | 双重 deinit 崩溃(Instruction access fault) | esp_vfs_fat_sdmmc_mount失败时内部已 deinit | 失败后不要再调sdmmc_host_deinit() |
| 9 | TF 卡无响应(CMD1 超时) | 默认 slot 把 GPIO45 当 D4 抢走(它是卡电源开关) | 显式slot.width = 4;供电GPIO45 = 0(低有效) |
推理 / 脚手架(详见 03)
| # | 症状 | 原因 | 解决 |
|---|---|---|---|
| 10 | 序列与参考对不上 | 贪心循环把生成结果写回 prompt 覆盖 | 先预处理整条 prompt,再自回归 |
| 11 | 每步打印寄存器转储 | 计算型任务不喂狗 | CONFIG_ESP_TASK_WDT_EN=n |
| 12 | printf 输出错乱 | riscv32 的uint32_t是unsigned long | 用PRIu32或显式转unsigned |
性能 / 编译器(详见 04)
| # | 症状 | 原因 | 解决 |
|---|---|---|---|
| 13 | unroll 只换来 4%,行对齐反而变慢 | 全程-Og | 切-O2(坑 6 的延伸,最隐蔽) |
| 17 | decode 20.6s/token,慢得离谱 | 全流式每 token 重读全量权重(SD_STREAM) | 方案 B(SD_RESIDENT):权重常驻 PSRAM,只流式读 embed,131× |
数值 / fp16 转换(详见 13)
| # | 症状 | 原因 | 解决 |
|---|---|---|---|
| 16 | 板端 argmax 翻转(如 650→1306),PC 参考一致 | km_f16_to_f32subnormal 分支指数误写-15(应为-14),14.2% 的 scale 被减半 | 改127-14-e |
PIE 汇编(详见 05)
| # | 症状 | 原因 | 解决 |
|---|---|---|---|
| 14 | illegal operands 'esp.srs.s.xacc t2,t1' | PIE 标量操作数不能用 t0-t2(x5-x7) | 用 a0-a7 或 t3+(x28+) |
| 15 | 随机数据对拍正确、真实权重全错 | scratch 的 int16 缓冲未 16 字节对齐 | heap_caps_aligned_alloc(16, ...) |
四条最重要的经验
-Og是隐形杀手:它会系统性压低所有性能读数,且不会报错。做性能优化前先确认构建在-O2。「随机数据对拍通过」≠「实现正确」:量化满量程、dot 大值才暴露对齐/溢出类 bug。
对拍要覆盖满量程输入和真实权重。实测胜于推论:理想化的「向量化 = 8×」在带宽受限的 CPU 上是误导。
纯计算 21× 的 PIE,接入后只有 2.11×——每一步都用板端硬数据界定。手写 fp16→fp32 转换必须单独验证 subnormal 分支:normal 分支对拍测不出 subnormal 的
指数 off-by-one(-15vs-14),而它能把 14.2% 的 scale 减半、翻转 argmax。
「量化精度导致翻转」这类归因,先用同量化权重的 PC 参考做逐 GEMV 模拟排除量化嫌疑。
对应源码
| 文件 | 关键符号 / 位置 | 支撑本文哪部分 |
|---|---|---|
main/kmcu.c | km_f16_to_f32() | 坑 16 fp16→fp32 subnormal 指数 off-by-one |
main/main.c | heap_caps_aligned_alloc(16, ...)、PRIu32 | 坑 15 scratch 对齐 / 坑 12 printf |
sdkconfig.defaults | CONFIG_COMPILER_OPTIMIZATION_PERF、CONFIG_SPIRAM_MODE_HEX、CONFIG_ESP_TASK_WDT_EN | 坑 5/6/11 配置类 |
docs/milestones.md | esp_vfs_fat_sdmmc_mount、sdmmc_host_deinit() | 坑 8 双重 deinit 崩溃 |
docs/agent_loop_router.md | idf.py --no-ccache | 坑 1/2 构建环境三坑 |
《Kestrel-MCU 手记》· 全系列 28 篇:在 ESP32-P4 上从零训练并部署 32M~64M 参数 MoE 大模型(5.7~6.4 tok/s),源码、权重、训练脚本与全部文章开源可复现。
仓库:https://gitee.com/pei-xiaoguang/kestrel-llm-mcu · 觉得有用欢迎 Star