第 42 环 · 权重池三 bank(预投深度 2)
第 40 环让链在算本层时投下层,第 41 环连「下一趟首层」也提前投好。可池子还是两个 bank:一个在算、一个在投,投完这一层,链就只能歇着等本层算完——因为再投就没有地方放了。
真跑生成时,一趟十几层、权重几百 MB:链取一层比算一层慢是常态。两 bank 的后果是链的利用率只有一半:投一层、停一层。要让它一直跑,池子必须能同时装下「本层 + 下两层」——预投深度从 1 提到 2。
把双 bank 乒乓扩成三个 bank 的权重池,并把「投递」从「本层开头的一拍」改成连续泵:只要池子没满(下两层还没都在路上)、链又空着,就立刻接下一层,投完一层不用等。
两 bank(R41):[本层] [下层] ← 投完下层,链空闲到本层算完
三 bank(R42):[本层] [下层] [下下层]
↑ 本层开算时,链已经在投下下层(连续泵)
池子不再用「算出来」的 bank 号,而是按层号搜:哪个 bank 里装着本层,哪个就是本层要读的那一个(hit_bank)。这样三个 bank 怎么环转都不会读错。配套三处:
· 连续序列号 f_seq/lc_seq:层号每趟回绕(0..N-1),但「谁先谁后」不能回绕;用不回的序列号,才不会在趟边界把刚投过的层再投一遍。
· 消费即腾位:ST_WLD 搜到本层、换 bank_r 后立刻 lav[hit_bank]=0,泵下一拍就能把新层投进来。
· 模式切换认领:从两 bank 切进三 bank 时,池子是空的,但上一趟末尾已经把本趟首层投好了;开机那一拍把它认领进池子,不重复投。
新寄存器 WC_PF2(0x85C,1=三 bank 池);WC_PF2=0 就是第 41 环的两 bank 语义,一字未删。新探针 NET_PF2_CNT(连续泵投递次数)、NET_PF2_OVL(与计算重叠的投递字数)、NET_BANK_MSK(投递落过的 bank 集合)、NET_WL_STL(每层等链总拍数),新观察点 SCREEN17(0x870)。前 41 圈地址映射一字不改。
| 证据 | 结果 | 说明 |
|---|---|---|
| 三 bank 池 vs 两 bank 乒乓输出 | 逐位一致 0xFA03FC07 | 只改了「链什么时候投」,算出来的东西一个字没变 |
NET_BANK_MSK | 011 → 111 | 两 bank 只落 0/1;三 bank 三个都用上 |
| 4 趟×2 层,连续泵投递次数 | 10 次 | 每层都被提前投过,链不再「投一层歇一层」 |
| 快内存(链不是瓶颈) | 845 拍 vs 844 拍,持平 | 诚实口径:链够快时,池子加深没有收益 |
| 慢内存(40 拍/字) | 6332 拍 < 6336 拍 | 链一停就得等——池子把这段等待盖住了 |
| 阻塞等链(R39/40)/ 公式取(R28~30) | 逐位一致 | 老路径一条没删,切回来输出不变 |
| 改一条层 0 描述符 | 三 bank 池输出跟着变,还原后逐位回到原值 | 投递听的是内存里描述符,不是写死的公式 |
单元仿真 tb_chain_pf2:两 bank 与三 bank 输出逐位一致;三 bank 掩码 111;慢内存 3 趟 6332 < 6336;单层、交替切、改描述符、老路径全部对表。
协同仿真 tb_min_soc42:先跑一趟 P0(池子从空到满的冷启动,允许等链,诚实口径),再跑 P 趟(稳态)——首层等链 0 拍、掩码 111、投递 6 次;SCREEN = 0xF905FD04、SCREEN2 = 0x02040404、SCREEN16 = 0x34000207 与第 25~41 环逐位一致;采样序列仍是 [0,2,0,0,0,3]。全量回归 71 项全绿。
其一(1 bit 的旧宽度)。rd_bank 是从两 bank 时代留下来的、没有位宽声明的 wire——那时 bank_r 只有 1 bit,看不出毛病;扩到三 bank 后 bank 2 被截成 0,计算读到另一个 bank 的旧字(gamma 用错层,输出全错)。加宽状态,先查所有「跟着状态一起宽」的线。
其二(start 那一拍的层号还是旧的)。非阻塞赋值让 lcnt<=0 在同一拍还没生效,池子搜索若复用 any_hit,搜的是「上一趟跑完的层号」。必须显式拿 0 去搜(any_hit_z)。
其三(跨模式的序列号是脏的)。两 bank 模式从不推进 f_seq;切进三 bank 时 f_seq=0 而 lc_seq=29,泵算出的「下一层」完全错位。修法:进池子时把 f_seq 同步到 lc_seq(或认领后 +1)。
其四(只读探针忘了加读口)。WC_PF2 写进去了、模式也生效了,但读回恒 0——min_bus 的读译码漏了 6'd23。自检因此假失败。写口和读口是两张表,加寄存器要两边都加。
其五(汇编层的两个小坑)。s12/s13 不是 RISC-V 的寄存器名(只有 s0~s11);0x868(a7) 的偏移超过 12 bit 有符号范围(a7 已经是 0x800 基址,应写 0x068(a7))。
第 40 环把「一趟之内」的取权重等待收进计算背后,第 41 环补上「趟与趟之间」,这一环补的是链本身不能停:权重池深到 2,链在算本层时可以把下两层都铺在路上。三层合起来,NPU 的阵列才真正「一直有活干」。
对一台手机来说,这就是内存墙前的那一步:模型权重不在片上,必须从内存一车车拉;拉得慢不是问题,拉到一半停下来等才是问题。池子把「等」变成「提前」,这是让一个真实尺寸的模型在手机功耗里跑得动的底层条件。
池子已经有了三 bank 和连续泵,可核还是要一个个写 WC_MODE / WC_TBLS / WC_N / WC_PF / WC_XP / WC_PF2——六条 sw 才把「怎么预投」说清楚。下一环把这套配置收进 ISA:一条 npu.pool 锁住池深、预投深度与跨趟开关,核不再逐个摸寄存器——权重池 ISA 化 · 一条 npu.pool 锁住池深与预投。