第 38 环 · 链槽直喂投影(四个槽接进注意力子层,扫完即用,核不搬运)
上一环把「一整层权重」收成一条 npu.rbchain:内存里排一串矩阵描述符,硬件一条条走完,整层的结论同时留在片上四个结果槽里。可槽还在扫描引擎里、注意力子层在另一头——核得把四个槽一个个 lw 读出来、再一个个 sw 写进注意力子层的入口寄存器(MHA_Q0/MHA_Q1、K/V 追加口)。
也就是说:「扫描」收进了指令,「搬运」还留在软件手里。硬件已经算完、就在片上,却要绕一圈经核的手再送回去。这一环把这条搬运也收进硬件——链扫完即用,核不搬运。
新增 rtl/slot_feed.sv 链槽直喂引擎:输入是 row_blk 的结果槽总线(512bit = 4 槽 × 4 lane × 32bit,片上直连,不经核),按角色映射把每个槽分流到它该去的入口,并做饱和量化(8bit → [-128,127]、4bit → [-8,7]):
slot0/1/2/3 ──(每槽 2bit 角色)──▶ 0=头0 Q 1=头1 Q2 2=K 追加口 3=V 追加口
sat8 / sat4 后直连;喂完那一拍顺手把 K/V 追加进本层环
ISA 契约(核升 v10):npu.sfeed rs1(funct3=111)——一条指令:锁住「入口切到链槽」+ 把四个槽喂出去。核一个字节不搬:不读一个槽、不写一个入口。硬件的探针还会自报:本次喂了几种角色(SF_NFEED)、角色映射表(SF_ROLE)、每个入口由哪一槽喂来(SF_SRC)——这些字段核从没写过,对得上就说明布线真是映射驱动的。
同时加了一个「核搬运计数」SF_CORE:核每读一个槽 lane、每写一个注意力入口,计数 +1。直喂路径应为 0,老路径(核自己搬)应为一个正数——这就是「核不搬运」可量化的硬证据。新页 0x7B0~0x7DC,前 37 圈地址映射一字不改。
| 证据 | 结果 | 说明 |
|---|---|---|
直喂路径(npu.sfeed,核零搬运) | 多头头0 best=24、头1 best=0xFFF4 | 多头在直喂进来的 Q/K/V 上真跑出了结果 |
SF_CORE(核搬运计数) | 直喂 0 | 一个槽都没读、一个入口寄存器都没写 |
SF_NFEED / SF_ROLE / SF_SRC | 4 / 0x4E / 0x4E | Q←槽2、Q2←槽3、K←槽0、V←槽1,全由硬件自报 |
| 手动搬运路径(核自己读槽、自己写入口) | SF_CORE = 20 | 16 个槽 lane + 四个入口:搬运真的落在核手里 |
| 两条路径的多头结果 | 逐位一致 | 结果一样,差别只在「谁搬」——0 vs 20 |
| 换小激活重扫同一条链 | 四个槽 = [[2,0,2,2],[1,1,1,1],[4,4,4,4],[-2,0,-2,-2]] | 槽值全落在 INT4 范围内,两条路径可逐位对表 |
单元仿真 tb_slot_feed:角色映射(默认 0xE4 与换表 0x1B,Q/V 真的换槽)、来源探针逐位、nfeed 在「两槽同角色」时只算一次(4 → 3)、两端饱和(200 → 127、-300 → -128;4bit → 7 / -8)、同配置复现逐位一致、全零槽输出全零。
ISA 单元仿真 tb_isa_sfeed 只验契约本身:四块矩阵与段表写进 SRAM,一条 npu.rbchain 扫完 → 一条 npu.sfeed → SF_Q4/SF_Q24/SF_K4/SF_V4 对表全中、SF_CORE=0、换映射表 Q 与 V 互换、MMIO 老路径(SF_MODE+SF_GO)与指令路径逐位一致;全通 SCREEN = 0x0000E5E5。
协同仿真 tb_min_soc38:SCREEN = 0xF905FD04 与第 25~37 环逐位一致(本环只加一条直喂通路,计算面一个字没动);采样序列仍是 [0,2,0,0,0,3]。全量回归 63 项全绿。
其一:脉冲寄存器要在「每拍默认」里拉低。新加的直喂脉冲我只在复位分支里清了,没在每拍默认里清——脉冲一直挂高,忙闲永远不为空,npu.wait 把流水线冻在那里,仿真跑到超时。链启动脉冲当年也是这样写的,照抄容易漏。
其二:计数要在「每拍」那块里数读、不能在复位块里数。「核搬运计数」第一版被放进了复位分支,于是永远是 0——看上去还挺像「核零搬运」,其实是个假绿。挪进每拍默认块之后,手动路径才如实报出 20。假绿比红更危险。
其三:两条来路不能重复触发。start 一开始写成「脉冲 | 指令执行拍」,同一个 npu.sfeed 会喂两次(K/V 也多追加一条)。统一成只认那一个脉冲寄存器,指令和 MMIO 两条来路都往它写脉冲。
从「一条指令扫完一整层权重」走到「一条指令扫完、并且直接接进注意力子层」:链扫出来的四个槽不再绕经核的手,片上直连进多头块与 K/V 环。核在这一步上从「整层搬运的声明者」再退一步——连搬都不用声明了,只写一条指令,剩下的交给布线。
这正是软硬协同最实在的一层:凡是数据本来就在片上、而且去处是固定的,就不该让它回核里绕一圈。回头做整网时,一层的权重会从「核把每个矩阵搬进寄存器」变成「一条链扫完、槽直接喂下去」——核退回成那个只说话不搬东西的人。
链扫得完、槽喂得动,可这些本事目前都还只在收尾试验里跑;解码主路的每层权重仍然走「核把权重搬进片上寄存器」那条老路。下一环要让主路走链:解码循环的层权重改走描述符链与槽直喂,让整台机器的主循环本身就用上「一条指令扫一整层、扫完即用」——主路走链 · 解码循环的层权重改走描述符链与槽直喂。