第 22 环 · 输出投影 W_o(把 8lane 多头融合回 4lane)
还是同一个圆心:一台开机只有一个 AI 的手机,它得聊得住。上一环把「头」并列了起来——同一份上下文,两个头各带自己的 Q,同时看、各看各的角度,输出在片上按 lane 拼成 8 lane。可这 8 lane 只是并排摆着:头 0 的四路是头 0 的,头 1 的四路是头 1 的,彼此不通;而往下走的残差流只有 4 lane。多头把话说到了八条道上,却没有收口的地方。
真模型在这里有一层输出投影 W_o:把各头的结论「学着揉成一条」再往下走。它不是简单的挑一路——它是一张可学的矩阵,允许「头 0 的第 1 路 和 头 1 的第 3 路 一起决定第 2 路的输出」,也就是各头的角度真正互相混合。这一环就把这个收口做成硬件。
继续「端上紧凑、全定点、每步硬件自洽、只扩不推翻」的主线,不新造轮子:
· 一张 4×8 的 INT4 矩阵——W_o 有 4 行(对应 4 条输出 lane)、8 列(对应进来的 8 条多头 lane),行优先打包;
· 跨头混合——每行是一整条 8 列点积,同一行里可以同时吃到两个头的 lane。哪几个头、哪几路一起决定哪条输出,全在这张表里,核可以改;
· 重量化位移——8 列累加比原来 4 列大一倍,硬件按核给的 WO_SHIFT(0..7)做算术右移,把幅度压回 INT8 动态范围,再做饱和;这正是量化推理里「INT32 累加 → 位移 + 饱和 → INT8」的标准收尾步骤,落在通路上;
· 输出就是残差流格式——投完的 4 lane 是 INT8 饱和值,跟残差流的 lane 一样,下一环可以直接接进块里;同时保留一份 INT4 饱和打包供下层/观察,还有一份未位移的原始累加可读回,调试时一眼看清是哪一步被裁掉的;
· 只扩不推翻——输出投影页开在 0x500~0x55C(bit10 页内再用 addr[9]/addr[8] 分出第三块),前 21 圈的地址映射一个字不动。
新增 rtl/out_proj.sv:输出投影引擎(8 lane → W_o → 4 lane,含重量化与饱和)。
// D 行并行:一行 INT4 权重 × 8 个 32bit 多头 lane,32bit 有符号累加
for (g = 0; g < D; g = g + 1) begin : g_row
assign ac[g] = dotn(w_o[g*NIN*W_W +: NIN*W_W], x_in);
end
// 重量化:算术右移把 8 列混合后的幅度压回残差流动态范围
assign q[g] = ac[g] >>> shift;
// 输出:INT8 饱和(= 残差流 lane 格式)+ INT4 饱和打包 + 原始累加可读
y8_out[k*OUT_W +: OUT_W] <= sat8(q[k]);
y4_out[k*4 +: 4] <= sat4(q[k]);
acc_out[k*ACC_W +: ACC_W] <= ac[k];
映射:WO_X0..X7(8 lane 输入)、WO_W0..W3(W_o 四行)、WO_SHIFT、WO_START、WO_Y0..Y3、WO_ACC0..ACC3、WO_Y4、WO_BUSY。
这一环的关键是「拿真多头的 8 lane 当输入,看 W_o 到底有没有把它们揉到一起」。OS 把多头引擎刚吐出的 8 lane(头 0 的 [5,0,1,1] 与头 1 的 [1,1,5,1])原样读回来,喂进输出投影引擎,然后跑两次对比:
· 试验 A「分层抽取」——每行只从一个头里取一路,得 [5,0,1,1]:这条路是「只挑不混」,同时证明 8 lane 的 lane 顺序真的和「哪个头」对得上;
· 试验 B「跨头融合」——每行 = 两个头同一 lane 相加,得 [6,1,6,2](5+1 / 0+1 / 1+5 / 1+1):两个头的 lane 在同一行里相乘累加,这才是 W_o;
· 换 W_o 输出就变([5,0,1,1] → [6,1,6,2])——要是两行算出来一样,OS 直接把屏幕写成 0xDEADBEEF 当场自曝,说明「W_o 根本没在融合各头」;
· 重量化再验一道——同一个融合 W_o,把 WO_SHIFT 打开成 2,输出变成 [1,0,1,0](6>>2 / 1>>2 / 6>>2 / 2>>2):幅度真被压回,位移没生效同样自曝;
· 屏幕 SCREEN = 0xFD0701FB = 4245094907(最后一步残差流,与第 19/20/21 环同值:主通路一个字没动),SCREEN2 = 0x50116162(A 的四条 lane / B 的四条 lane),与软件参考模型逐位一致;
· 单元仿真覆盖「分层抽取」「跨头融合」「换 W_o 输出就变」「重量化位移(含负数的算术右移)」「饱和 [-128,127] 夹紧、进位不丢且原始累加可读」「INT4 打包 = sat4(重量化值)」「同配置复现逐位一致」「全零 W_o → 全零输出」;输入不是手塞常数,而是真跑一棵 kv_ring + 第 21 环 mha 拿到的真 8 lane。
本环走完一整圈:目标 → 一台只有一个对话窗口的手机,它得聊得住,而多头把结论摊在 8 条道上、没法往下走 → 硬件给 8 lane 接一层可学的 W_o:跨头混合 + 可编程重量化,输出直接就是残差流的 4 lane 格式 → OS 用「换 W_o 输出就变 + 位移真生效」两次自检把「W_o 真在融合各头」钉死 → 单元仿真 + 协同仿真 + 回归 30 项全绿 → 回到目标。多头到此才长进 Transformer 块里:几个头看完不同角度,能收成一条继续往下走。离一个真能聊得住的 AI 手机,又近一圈。下一环把这 4 lane 送回层里——接进残差相加与 LayerNorm,让多头注意力第一次以完整块的形态(多头 → W_o → 残差 → LN)在层里闭环跑起来。
第 23 环:多头块入残差流 · W_o 输出接回残差 + LayerNorm——收好口的 4 lane 得回到层里,接上残差相加与归一化,多头注意力才第一次以「完整块」的形态在层里闭环;层里原来那套单头注意力,到此可以被整块替换掉。