第 41 环 · 跨趟预投(下一趟首层在上一趟末尾已在路上)
第 40 环把「算本层、取下层」搬到了描述符链上:算本层时,链把下层的 14 个字投进另一个 bank,走到下层 ST_WLD 一拍换 bank 就开跑。可它只治了一趟之内——本趟最后一层没有「下层」可预投(li_nxt ≥ layers_r),于是下一趟的第 0 层又要停下来冷启动,等链把 14 个字投完。
解码循环是一趟接一趟跑的(每生成一个 token 就是一趟),所以「每趟开头等一次」是最不该留的空转:它不随层数增长,却每趟都要付一遍。
办法是把第 40 环的预投往前多推一层:在本趟最后一层还在算的时候,就让链去投下一趟首层(层 0)的权重,落进另一个 bank。下一趟 start 时 ST_WLD 一看就绪标志正指着层 0(ld_ready && ld_layer==0),一拍换 bank 就开跑,连冷启动那一次都省了。
本趟最后一层在算 ─┬─ 链投:下一趟首层(层 0)→ ~bank_r
└─ 计算:本趟最后一层(读 bank_r)
下一趟 start → ST_WLD:ld_ready && ld_layer==0 → 一拍换 bank → 开算(等 0 拍)
三处小改动:起投(ST_PREK:本趟最后一层时 li_nxt ≥ layers_r 仍起一次链取,目标层 0)、接住(ST_IDLE 的 start 分支:跨趟预投已备好就保住就绪标志直接进 ST_WLD;还在路上就等它落地)、开关(WC_XP=0x84C,1=跨趟预投;WC_XP=0 就是第 40 环的老语义,一字未删)。
新探针 NET_XP_CNT(跨趟预投起了几次,累积)、NET_XP_STL(本趟首层在 ST_WLD 等链的拍数,每趟 start 归零),新观察点 SCREEN16(0x858)。前 40 圈地址映射一字不改。
| 证据 | 结果 | 说明 |
|---|---|---|
G2 趟(WC_PF=1+WC_XP=1)vs A 趟(第 39 环阻塞链) | 逐位一致 | 只改了「谁什么时候等」,算出来的东西一个字没变 |
| 首层等链拍数:G1(XP 打开后第一趟) | 52 拍 | 上一趟末尾没替它投过,仍是冷启动(诚实口径) |
| 首层等链拍数:G2(跨趟预投转起来) | 0 拍 | 下一趟首层在上一趟末尾已经在路上——本环唯一想要的东西 |
H 趟(WC_XP=0 回到第 40 环) | == A 趟,首层等链又回到 52 拍 | 关掉这一环就退回上一环:差别确实来自跨趟预投 |
NET_XP_CNT | 2 次 | G1/G2 两趟末尾各把下趟首层投好一次 |
| 改一条层 0 的描述符(LN 住处 12→14) | 跨趟预投路径输出跟着变 | 下趟首层读的真是内存里描述符说了算的那份权重 |
| 单元仿真:连跑 4 趟 | 844 拍 < 1000 拍(省 156,每趟 39) | 首层等链 52 → 0 拍,输出逐位一致 |
单元仿真 tb_chain_xp:预投路径 y=0xFA03FC07、链投 28 字、4 趟共 844 拍、首层等链 52 → 0 拍、与第 40 环输出逐位一致;关掉 w_xp 连跑 4 趟 1000 拍、每趟首层都要冷启动等 52 拍;公式路径(第 28~30 环)输出与之逐位一致;改层 0 描述符输出跟着变、还原后逐位回到原值;单层也照样把「下趟首层」投好(诚实口径)。
协同仿真 tb_min_soc41:整机跑完解码循环 → 采样序列仍是 [0,2,0,0,0,3](第 6 步吐停止符 3 而停);SCREEN = 0xF905FD04、SCREEN2 = 0x02040404、SCREEN15 = 0x620E3407 与第 25~40 环逐位一致。全量回归全绿。
其一(就绪标志被「惯例」清掉)。ST_IDLE 的 start 分支开头有一句无条件的 ld_ready <= 1'b0(第 29 环的写法:一开跑,上一趟攒下的就绪标志就不该再算数)。跨趟预投偏偏就靠这个标志活着——于是「已备好」那条分支进了 ST_WLD,标志却被同一拍清了 0,引擎在这个状态上空等到看门狗。修法很小(已备好分支里补一句 ld_ready <= 1'b1),难的是看出来:单看那三行每个都对,缝在同一拍的两个赋值之间。
其二(两种「在看门狗上」的区别)。第一次超时我以为是链卡住了,盯着链的表遍历找;实际上链早就投完回了空闲(ent=9、armed=1),是引擎自己在 ST_WLD 上没人叫它。先看清是「谁在等谁」,再去修「为什么等不到」——省掉一轮瞎找。
第 40 环把「一趟之内」的取权重等待收进了计算背后;这一环把趟与趟之间的那一段也收进去了。合起来:一趟解码里,权重再没有哪一段是「停下来等」的——本层在算的时候,下层已经在路上;本趟在算的时候,下趟首层已经在路上。
离目标更近的一点在于:真机连续对话时,每生成一个 token 就是一趟,趟数几乎无限。取权重若还落在关键路径上,NPU 每一趟开头都要空转一次,这个空转不随模型变大而摊薄。这一环把它抹掉,是「让阵列一直有活干」的那一步。
现在提前量只有一层:双 bank 乒乓只能装「本层 + 下一层」,所以链必须在下一层开算之前把字投完——层再大、取一层再慢,这个前提就崩。下一环把权重 bank 从两个扩成一个池(三个 bank),让预投深度变成 2:算本层时,下两层都已经在路上,链有一整层加一整层的时间把字铺完——权重池三 bank · 预投深度 2。