☰
31 · Agent 循环从 6201 ms 压到 1677 ms:共用前向 + 增量分类
2026/10/28 19:10:30 网站建设 项目流程

31 · Agent 循环从 6201 ms 压到 1677 ms:共用前向 + 增量分类

本篇对应源码:main/kmcu.c·tools/patch_cls_head.py·docs/optimization_plan.zh-CN.md

目标:Agent 每步「先判断、再生成」,不要每步把同一段上下文重算一遍。
结论:transformer 前向次数 120 → 60 → 11,8 步总耗时 6201 → 1755 ms(−71.7%),输出 SHA-256 不变。

关键词:Agent 循环 · 增量分类 · KV 复用 · 位级对拍
封面:8 步 6201 → 1677 ms,输出一字不差

一、结论先行

构建transformer 前向次数(8 步)8 步总耗时
旧路径(prefill / 逐 token 全 forward)120未单独实测
O-2a(每步完整分类)606201 ms
O-2b(增量分类)111739 / 1755 ms
O-2b + 双核输出头(E-6)111677 ms

前向次数是按执行量推导的值;耗时是板端实测。两者不混用。

二、Agent 循环在做什么

step: cls(上下文) → action ├─ generate: head(最后一个 hidden) → token → 追加 ├─ route : 换工具,继续 └─ stop : 结束

朴素实现每一步都把「上下文 + 新 token」整段重跑一遍。8 步下来,同一段
前缀被算了 8 次——这是 O-2 要解决的唯一问题。

三、O-2a:一次前向,两个出口

分类需要「整段上下文的 pooling」,生成需要「最后一个 token 的 hidden」。
两者其实是同一次 forward 的两个出口:

/* 分类,同时把末 token 的 final hidden 带出来 */intkm_cls_forward_ex(...,float*last_hidden,float*pool_sum,...);/* 从 hidden 直接算输出头,不重跑 transformer */intkm_decode_head(...,constfloat*h,...);

Agent 的 generate 分支不再重置上下文、不再重算,直接用last_hidden进 head。

关键证据(缺一不可):

  • cls last hidden == decode final hidden(FNV 哈希相等);
  • 从 hidden 算出的 token、best logit、top-8、完整 logits 哈希,与完整 decode 全等;
  • 强制选 generate 的微型模型上,三种构建(默认 /REUSE_KV=0/REUSE_KV=0+AGENT_PREFILL=0)
    的完整输出 SHA-256 完全相同。

有了tools/patch_cls_head.py之后,「强制走 generate 分支」变成可复现的实验条件,
否则 Agent 很容易第一步就 stop,什么都测不到。

四、O-2b:增量分类

分类的输入是 pooling(对 hidden 求平均)。既然 KV 已经缓存、pos已经推进,
每一步只需要处理新增的那一个 token:

intkm_decode_hidden(...);/* 单 token transformer + final_norm,写 KV,推进 pos */intkm_cls_head_from_sum(...);/* 从未除长度的 pooling 累加和算 cls_head */

两个细节决定了它必须是位级等价的:

  1. pooling 保存的是未除长度的累加和,最后一步才除——否则每步除一次会引入不同的舍入;
  2. 累加顺序严格按原 token 顺序,不允许「新 token 先加」。

首次调用仍走完整分类(需要建立初始 pooling),之后每步只做 1 次 forward。
8 步 + 初始 4 token:4 + 1 + 6 = 11次。

五、验证

四种构建(增量 / 非增量 / 不复用 KV / 旧逐 token)在强制 generate 的 8 步里
输出 SHA-256 完全相同;非增量路径的 dump 仍是 216 条MATCH。

逃生舱:-DKMCU_AGENT_INCREMENTAL=0、-DKMCU_AGENT_REUSE_KV=0、
-DKMCU_AGENT_PREFILL=0,三层都能单独回退。

六、诚实边界

  • 6201 ms 是KMCU_AGENT_INCREMENTAL=0的实测值;「120 次前向」的旧路径
    只做了执行量推导,没有单独计时。
  • 增量分类假设「上下文只追加、不修改」。如果将来加入编辑历史/回溯,这个假设要重新验证。

七、一句话

Agent 的每一步都在重算同一段前缀;把「分类」和「生成」接到同一次 forward 的两个出口上,
再把分类变成增量池化,8 步从 6.2 秒压到 1.7 秒,输出一个 bit 都没变。


对应源码

文件关键符号 / 位置支撑本文哪部分
main/kmcu.ckm_cls_forward_ex()/km_decode_head()O-2a 一次前向两个出口
main/kmcu.ckm_decode_hidden()/km_cls_head_from_sum()O-2b 增量分类
main/kmcu.ckm_agent_loop()的KMCU_AGENT_*开关三层逃生舱
tools/patch_cls_head.py--force-class强制走 generate 以覆盖测试路径

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询