🎯 圆心 · 目标(每环都从这里出发)
造一台 Salt OS 手机:基于 DeepSeek 的 AI 操作系统,iPhone 4S 外形(含底部圆形 Home 键),开机只有一个 AI(无桌面、无图标),软硬协同设计(OS + 芯片 + 主板一起做),为别人搭台、让别人在平台上赚钱。

🧭 完整框架(同心圆)

①文字闭环 → ②技术深入 → ③挂SRAM → ④权重INT4 → ⑤激活INT4 → ⑥NPU.STORE → ⑦KV Cache → ⑧注意力QK^T → ⑨Softmax → ⑩加权V → ⑪前馈FFN → ⑫残差·LayerNorm → ⑬多层堆叠 → ⑭QKV投影 → ⑮K/V分离 → ⑯自回归解码 → ⑰连续生成 → ⑱采样 → ⑲KV环形缓冲 → ⑳滑窗注意力 → ㉑多头注意力 → ㉒输出投影 W_o → ㉓多头块入残差流 → ㉔多头块进解码循环 → ㉕…

本环:让连续生成真的跑在多头上

① 目标是什么

还是同一个圆心:一台开机只有一个 AI 的手机,它得聊得住——而「聊」就是一层一层地看上下文。上一环多头块已经完整了(多头 → W_o → 残差 → 归一化),可它只被单独调用过一次做验证;真正在生成主路上跑的,还是老的单头那套:投影 → 算分 → Softmax → 加权 → 残差 LN。多头块只是摆在那儿。
更要紧的是:第 21 环把两个「头」并列起来时,两个头拿的其实是同一个 Q——那只是「同一双眼睛看两遍」,算不上多头。真多头的每个头有自己的查询投影 Wq。这一环把两件事一起做完:每个头投影出自己的 Q,然后把生成主路整条换成「多头 → 块」。

② 怎么实现(不脱圆心)

继续「端上紧凑、全定点、每步硬件自洽、只扩不推翻」的主线:
· 投影引擎加一路 Wq2——第 14 环的 qkv_proj 原本只算一个 Q;本环给它加第二路查询投影(Wq2),四个矩阵乘并行:头 0 的 Q、头 1 的 Q、共用的 K、共用的 V。每个头自己的「看问题的角度」落在硬件数据通路上;
· 头 1 的 Q 也是 INT4 契约——和头 0 一样同时吐 8bit 全精度与 INT4 饱和打包,后者直接喂多头引擎;
· 主路换轨——解码循环里每层的注意力子层改成:两头各带自己的 Q,在同一份 K/V 环上同时开跑 → 8 lane 直连 mh_block → W_o → 残差连接 → LayerNorm → 前馈 → 残差 → 残差流。单头那套(算分/Softmax/加权的单独调用)退居备用,引擎都还在、单元仿真照跑;
· V 投影改成单位阵——这是本环最不起眼却最关键的一处:第 19~23 环的 V 每行相同(加权出来是个常量向量),常量加进残差流后被 LayerNorm 的平移不变性抹掉,注意力对主路等于「使不上力」。V 改成逐 lane 不同(v = x)之后,多头加权出来的才是真正的逐 lane 向量;
· 只扩不推翻——Wq2 用已有的 QL_W_SEL 矩阵槽 3,头 1 的 Q 读口开在 0x370/0x374,前 23 圈的地址映射一个字不动。

③ 本环做了什么(真代码)

改 rtl/qkv_proj.sv(加第二路查询投影)、rtl/min_bus.sv(挂 Wq2 与 QL_Q4B/QL_Q8B),重写 circle24/gen_boot.py 的 layer_body:

// qkv_proj:四路投影并行(每个头自己的 Q + 共用的 K/V)
assign qc[d]  = dot4(wq [(d*D)*W_W +: D*W_W], x_in);   // 头 0 的 Q
assign q2c[d] = dot4(wq2[(d*D)*W_W +: D*W_W], x_in);   // 头 1 的 Q(本环新增)
assign kc[d]  = dot4(wk [(d*D)*W_W +: D*W_W], x_in);
assign vc[d]  = dot4(wv [(d*D)*W_W +: D*W_W], x_in);

# 生成主路:每层的注意力子层 = 多头块
lw   t0, 0x354(x0)    # QL_K4 → KR_K_APPEND(K 进本层环)
lw   t0, 0x358(x0)    # QL_V4 → KR_V_APPEND(V 进本层环)
lw   t0, 0x350(x0)    # QL_Q4  = 头 0 的 Q → MHA_Q0
lw   t0, 0x370(x0)    # QL_Q4B = 头 1 的 Q → MHA_Q1(本环新增的读口)
sw   t1, 0x710(x0)    # MHA_START:两头在同一份 K/V 环上同时开跑
poll MHA_BUSY
lw   t2, 0x718(x0)    # 头 0 落点
lw   t3, 0x720(x0)    # 头 1 落点
sub  t2, t2, t3
or   s9, s9, t2       # 累计:主路上两头到底看过不同角度没有
sw   s4, 0x560(x0)    # MB_X = 本层输入残差流
sw   t1, 0x564(x0)    # MB_GO:8 lane 直连 → W_o → 残差连接 → LayerNorm
poll MB_BUSY
# 块的 LN 输出就是注意力子层的输出 → 前馈 → 残差 → 残差流

新增读口:0x370 QL_Q4B(头 1 的 Q,INT4)、0x374 QL_Q8B(8bit 全精度)。

④ 怎么算成(闭环验证)
生成主路走多头块 · RESULT=PASS
两头各自的 Q 投影 → 多头 → mh_block → 前馈 · 主路两头 3/6 步盯上不同 token · 屏幕 0xF905FD04 · SCREEN2 = 0xFA05FE04 · 单元仿真 + 协同仿真 + 全量回归 33 项全部通过

这一环要证明「多头块真的在主路上跑」,三条硬证据:
· 证据一:主路上两头真的看不同角度——解码 6 步里,层 0 有 3 步、层 1 有 3 步两个头的最高分落在不同的 token 上(OS 用 s9 全程累计,一次都没出现就把屏幕写成 0xDEADBEEF 自曝);
· 证据二:主路被换掉了——最终残差流从第 19~23 环的 0xFD0701FB 变成 0xF905FD04([4,-3,5,-7])。参考模型里直接断言「若等于旧值就报错」,防止有人把主路悄悄改回去;
· 证据三:喂给前馈层的就是块吐出的那份——SCREEN2 = 0xFA05FE04 = 最后一轮解码里末层多头块的 LayerNorm 输出 [4,-2,5,-6],与软件参考模型逐位一致(单头通路永远算不出这份「8 lane → W_o → 残差 → LN」的值);
· 采样序列 [0,2,0,0,0,3]:第 6 步(超过环深 4)吐停止符 3 而停,采样器的抽签翻盘 3 次;
· 单元仿真新增 tb_qkv_proj 的用例 3:同一笔残差流、两张不同的查询矩阵,头 0 的 Q 不变([3,5,6,2])、头 1 的 Q 另外算([-4,-4,-4,-4]),两头查询互不干扰。

⑤ 顺手拔掉的一颗旧钉子

跑全量回归时,第 23 环留下的核单元测试一直假失败:断言写成 32'h0BEF_F000(Verilog 忽略下划线,等于 0x0BEFF000),可 lui x9, 0xBEEF 的正确结果就是显示里的 0x0BEEF000——测试期望值自己写错了位。修好后回归才真正全绿。这也说明「每一圈必须真跑一遍全量回归」不是形式:上一次的「33 项通过」其实有一项是红的。

⑥ 回到目标

本环走完一整圈:目标 → 一台只有一个对话窗口的手机,它得一层层看上下文 → 硬件给每个头各自的 Q 投影,再把生成主路的注意力子层整条换成「多头 → W_o → 残差 → LN → 前馈」 → OS 用「两头落点全程有差异 + 屏幕值与旧环不同 + 块 LN 输出逐位对表」三条自检钉死,顺手修掉一条假失败的测试 → 单元仿真 + 协同仿真 + 回归 33 项全绿 → 回到目标。芯片的注意力主路,从「一个角度看上下文」正式升级成「几个头一起看」。下一环把前馈块也串进同一个引擎——整层一块。