news 2026/11/2 1:50:45

36 · FASTSEEK 快 10.6 ms,但同一 token 每次开机读到不同的字节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
36 · FASTSEEK 快 10.6 ms,但同一 token 每次开机读到不同的字节

36 · FASTSEEK 快 10.6 ms,但同一 token 每次开机读到不同的字节

本篇对应源码:main/main.c·sdkconfig.defaults·docs/optimization_plan.zh-CN.md

目标:PLE 行读每 token 花 14.35 ms(其中 13.47 ms 是fseek),用 CLMT 干掉它。
结论:性能拿到了(−6%),但数据错了——同一 token 的行跨冷启动读出不同字节。已回退。

关键词:FATFS · FASTSEEK · 冷启动一致性 · 指纹
封面:seek 快 10.6 ms,但同一行每次读到不同字节

一、结论先行

项关闭 FASTSEEK打开 FASTSEEK
PLE 行读fseek13.47 ms/token2.90 ms
PLE 行读合计14.35 ms3.70 ms
total(19 token 均值)160.3 ms149.2 ms
TTFT445 ms392 ms
跨冷启动可复现✅ 三次一致❌ 每次不同

收益是真的,代价是「同一个输入不再给同一个输出」。按项目规则:判否。

二、为什么 243 B 要 14 ms

pleb阶段里有一次「按 token id 取 PLE table 的一行」:256 字节。
它占 14.35 ms,其中fread只占 0.88 ms,fseek占13.47 ms。

原因不是数据量,是位置:

  • PLE table 在文件靠前的位置;
  • 每 token 的权重读取已经把文件指针推到 20 MB 之后;
  • 所以这是一次跨 cluster 的反向 seek。

FatFs 在没有 CLMT(cluster link map table)时,反向 seek 必须从文件首 cluster
沿 FAT 链逐跳走到目标,每一跳都要访问 FAT。加上这张卡只能以4 MHz / 1-bit
挂载(20 MHz 4-bit 初始化直接 0x107),单扇区读的成本被放大,链式遍历就成了毫秒级。

三、打开 CLMT 之后

CONFIG_FATFS_USE_FASTSEEK=y CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE=1024

ESP-IDF 的vfs_fat_open对只读文件会自动建表(f_lseek(file, CREATE_LINKMAP)),
之后fseek变成 O(1) 查表。模型 20.77 MB / 64 KB cluster ≈ 325 个 cluster,
1024 个 DWORD 的缓冲区能覆盖 511 个碎片段;建表失败会释放并回退原路径。

实测就是第一张表的右列:−10.6 ms/token,decode 从 170–173 掉到 160–165 ms。

四、异常:同一条固件,两次开机两个答案

第一次发现是在对比生成序列时:同一条固件(ELF SHA25669cfdc129…)、
同一个 prompt{415,1628,5255,2495},续写的第一个 token 分别是
1190、24368、356。

同一次开机内重复跑模式 0,序列完全一致;换一次冷启动,序列就变。
这不是随机噪声,是「整个开机周期内固定、跨开机变化」的初始状态差异。

五、定位:两个指纹

在main.c里加了两个 FNV-1a:

/* 1) 展开后的权重:只覆盖真正写入的区域,跳过对齐空洞 */printf("[SD-RESIDENT] weights fnv1a = %08x\n",fh);/* 2) 模式 0 期间 SD 读到的全部字节(常驻 decode 下就是 PLE 行)+ 最终 KV */printf("[GEN] in fnv: sd=%08x kv=%08x\n",g_rd_fnv,kh);

七次冷启动的结果:

配置启动weights fnvsd fnv续写第 1 token
FASTSEEK 开A4d0adf33e981dfaf24370
FASTSEEK 开B4d0adf332b4c362027777
FASTSEEK 开 + 512B 对齐读C—817575ea28713
FASTSEEK 开 + 512B 对齐读D—2d70b01128713
FASTSEEK 关E/F/G4d0adf33817147c7356

三行结论:

  1. weights fnv恒定 → 20 MB 的权重展开逐字节一致,不是 SD 把权重读坏了,
    也不是展开流程读了非确定内容;
  2. 唯一跨启动变化的是PLE 行读的内容(sd fnv每次都不同),KV 与 token 跟着变;
  3. 关掉 FASTSEEK 后三次冷启动的sd fnv与序列完全一致,而且
    356,272,3500,…与更早(还没引入 FASTSEEK 时)的记录一致。

六、做过但无效的补救

第一反应是 FatFs 的FIL偏扇区缓存:fread在非扇区对齐时会从fp->buf
取数据,而fseek的 fast path 只在dsc != fp->sect时才重填。于是把所有
小读都扩成512 B 扇区对齐读,强制走f_read的直接disk_read分支。

结果(C/D 两行):sd fnv依然每次都不同。

这说明缺陷不在扇区缓存,而在fast-seek 的 CLMT 定位路径。这一层在
ESP-IDF 内部,继续深挖的性价比不高,且属于「改了别人的文件系统」。

七、处置与一条新规则

处置:

  • CONFIG_FATFS_USE_FASTSEEK恢复默认关闭,sdkconfig.defaults里保留
    「为什么不能用」的说明;
  • 代价是每 token 多约 13.5 ms(8%),换取确定性输出;
  • 如果将来一定要拿回这 13.5 ms,只能绕开 FatFs:自己建 offset→物理扇区映射
    并先证明映射正确,再用sdmmc_read_sectors直读。

新规则(写进方案书):

跨冷启动比较任何性能或质量结论之前,必须先把weights fnv与sd fnv对齐。
否则「A 比 B 快/好」里可能混着「A 这次读到的字不一样」。

E-6 的单核/双核对拍是在同一次开机内完成的,不受这条影响——这也是为什么
当时的结论仍然成立。

八、一句话

13.47 ms 的 seek 值得修,但如果修完读数变了,那修的不是性能而是正确性;
weights fnv/sd fnv两个指纹把「快了多少」和「读到了什么」彻底分开了。


对应源码

文件关键符号 / 位置支撑本文哪部分
main/main.csdcard_read_cb()的 seek/read 计时13.47 ms 归因
main/main.c[SD-RESIDENT] weights fnv1a展开权重指纹
main/main.c[GEN] in fnv: sd/kvPLE 行与 KV 指纹
sdkconfig.defaultsCONFIG_FATFS_USE_FASTSEEK注释为什么保持关闭

《Kestrel-MCU 手记》· 全系列 37 篇:在 ESP32-P4 上从零训练并部署 64M 参数 MoE 大模型(5.8 tok/s),源码、权重、训练脚本与全部文章开源可复现。

仓库:https://gitee.com/pei-xiaoguang/kestrel-llm-mcu · 觉得有用欢迎 Star

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 1:50:40

aubo学习:机械臂上相机三维点云实时显示

一、启动机械臂和相机source /opt/ros/noetic/setup.bash source ~/aubo_ws/devel/setup.bash roslaunch aubo_driver aubo_real_camera.launch二、验证TF和点云话题1.检查TF坐标树rosrun tf tf_echo base_link camera_color_optical_frame2 检查点云话题是否正常发布rostopic …

作者头像 李华
网站建设 2026/11/2 1:50:18

国内交叉科学学院有哪些

国内的交叉科学相关学院可以分为独立建制、直接以“交叉科学学院/交叉学科研究院”命名的实体学院,以及依托交叉学科门类建设的特色交叉培养平台两大类,截至2026年10月,核心名单如下: 🏛️ 独立命名的实体交叉科学学院…

作者头像 李华
网站建设 2026/11/2 1:48:35

第13章 JVM运行原理:回到程序幕后

第13章 JVM运行原理:回到程序幕后 已经写过方法、对象、线程和反射,再看编译、类加载、内存区域与垃圾回收,就有自己的代码可以对照了。遇到 HotSpot 的具体实现时,要把它与所有 JVM 都应遵守的规则分开。 本章路线 第1节 Java…

作者头像 李华