🎯 圆心 · 目标(每环都从这里出发)
造一台 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分离 → ⑯自回归解码 → …

本环:把「一份 K/V」拆成「两份 K/V」,再让每一层都自己投影

① 目标是什么

同一个圆心:一台开机只有一个 AI 的手机。上一环注意力的 Q/K/V 终于由残差流投影出来了,但还有两处「简化」撑着:一是历史 K/V 挤在同一条槽位载荷里——算分引擎(读 K)和加权引擎(读 V)只能分时抢同一个读端口,而且「K 的长度」和「V 的长度」被迫绑成一个数;二是投影权重只有一套,多层堆叠时每一层只能共用同一组权重。真模型里 K 和 V 是两份不同的张量:各自投影、各自缓存、各自被不同的引擎消费;而每一层都有自己的一套 Wq/Wk/Wv。这一环把这两处补齐——K/V 分离 + 投影按层驻留,接进多层块。

② 怎么实现(不脱圆心)

延续「端上紧凑、全定点、权重 INT4、激活 INT8」的主线,新增一个引擎 kv_split,并给多层投影另开一页寄存器:
· K bank / V bank 两份独立存储——各有自己的追加端口与写指针(k_len / v_len),也各有自己的读端口。算分引擎只读 K bank,加权引擎只读 V bank,两个引擎可以真的同时读,不再分时抢同一个端口;K 的长度和 V 的长度也就解绑了;
· 两个 bank 都按「层」分区(每层一套独立指针)——第 ℓ 层追加的 K/V 只进第 ℓ 层那一格,第 ℓ 层算分只看得到第 ℓ 层的 K,层与层互不串扰;
· 投影权重按层驻留——新增 ql_w[层][矩阵](每层一套 Wq/Wk/Wv),核给一个层号 + 一条残差流,硬件就用那一层的权重算出那一层的 Q/K/V;
· 只扩不推翻——新增一页寄存器(0x300 起),用页码守卫把它和前 14 圈的老映射隔开,老地址一个字不动;KVS_MODE=1 时两个引擎才改读分离后的 bank,默认还是老行为。

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

新增 rtl/kv_split.sv:K/V 双 bank,独立指针 + 独立读端口 + 按层分区:

K bank:append → k_mem[层][槽] ,写指针 k_len[层] 自增(满则丢弃)
       读端口 → 1 拍延迟(算分引擎专用)
V bank:append → v_mem[层][槽] ,写指针 v_len[层] 自增(与 K 完全独立)
       读端口 → 1 拍延迟(加权引擎专用,与 K 端口彼此独立)

min_bus.sv 挂载并新映射(K/V 分离页,0x300 起):

0x300 → KVS_K_APPEND  写 1 → K 进本层 K bank
0x304 → KVS_K_DATA    待追加的 K(D 个 INT4 打包)
0x308 → KVS_V_APPEND  写 1 → V 进本层 V bank
0x30C → KVS_V_DATA    待追加的 V
0x310 → KVS_LAYER     当前层号(决定读写哪一层的 bank)
0x314 → KVS_MODE      1 = 引擎改读分离后的 K/V bank
0x318/0x31C → KVS_K_LEN0/1   逐层 K 长度(与 V 各自独立计数)
0x320/0x324 → KVS_V_LEN0/1   逐层 V 长度
0x328/0x32C → KVS_K_RD/RDATA 核读回 K bank
0x330/0x334 → KVS_V_RD/RDATA 核读回 V bank
0x338/0x33C → QL_W_LO/HI     待提交的半矩阵(行 0~1 / 行 2~3)
0x340 → QL_W_SEL     提交到 ql_w[层][矩阵]({层, 矩阵})
0x344 → QL_X         本层投影输入:残差流
0x348 → QL_LAYER     用哪一层的投影权重
0x34C → QL_START     写 1 → 用本层权重投影 Q/K/V
0x350/0x354/0x358 → QL_Q4/K4/V4  投影结果(INT4 饱和打包)
0x35C/0x360/0x364 → QL_Q8/K8/V8  投影结果(8bit 全精度)
0x36C → QL_BUSY      多层投影引擎忙闲(含刚下发那拍,无空窗)

最小 OS:两层块,每层都走完整的一圈(自己投影 → 自己 K/V → 自己注意力 → 残差 → FFN → 残差):

层 ℓ:LST_X(本层输入残差流)
   ① 历史 token 投影(本层权重)→ K 进 K bank、V 进 V bank(各自指针)
   ② 查询 token 投影(本层权重)→ Q 喂算分引擎,K/V 也各进各的 bank
   ③ QK^T(读 K bank)→ Softmax → 加权 V(读 V bank)
   ④ RSN(a=注意力输出, b=本层输入)   # 残差 + LayerNorm #1
   ⑤ FFN(归一化输出)              # 前馈两层
   ⑥ RSN(a=FFN 输出, b=LN#1)       # 残差 + LayerNorm #2
      LST_ADV(残差流锁回 XSR,层号 +1;K bank / V bank 各报长度)
最后读回 XSR → 屏幕
④ 怎么算成(闭环验证)
两层各自投影 + K/V 分离 → 最终残差流 [7,-5,3,-6] → 屏幕 0xFA03FB07 · RESULT=PASS
单元仿真(K/V 独立指针 · 按层分区 · 双读端口同时读 · 满丢弃)+ 核-OS-分离bank-多层块协同仿真 + 全量回归 16 项 全部通过

这一环最直观的是「K 和 V 真的是两份东西,而且每一层真的是自己投影的」,每一步都能手算对得上:
· 层 0 输入残差流 x=[6,-4,3,-5],历史 token [1,2,0,-1];投影 Q=[3,5,6,2](P=0x2653)、K=[6,-4,3,-5](P=0xB3C6)、V=[2,2,2,2](P=0x2222)。算分只读 K bank:分数 [11,6],Softmax 权重 [254,1](分歧极大,注意力几乎全压在历史那条上),加权 V(读 V bank)得 [3,3,3,3];
· 残差 + LN #1 → [5,-4,2,-5],FFN → [7,7,7,7],残差 + LN #2 → 层 0 输出 [5,-3,2,-4],锁回 XSR;
· 层 1 用自己那套权重从新的残差流再投影:Q=[3,4,5,2]、K=[5,-3,2,-4]、V=[7,7,7,7];分数 [6,5],权重 [187,68],加权 V 得 [5,5,5,5];再走两个残差 + LN 与 FFN,得到最终残差流 [7,-5,3,-6];
· 屏幕 0xFA03FB07 = 4194564871,与软件参考模型逐位一致;K bank / V bank 的长度各自独立计数(层 0 与层 1 各 2 条,互不串扰)。
单元仿真另外验证了 kv_split 的四条铁律:K 追加 2 条、V 追加 1 条时两份长度各算各的;往层 1 追加不改动层 0;K 端口与 V 端口同一拍同时读各自取回各自的数据;写满 DEPTH 后继续追加被丢弃、长度不变。

⑤ 回到目标

本环走完一整圈:目标 → 真模型里 K/V 是两份张量、每层有自己的一套投影权重,共用一份载荷和一套权重是「假的多层」 → 硬件把 KV 存储拆成 K bank / V bank(独立指针、独立端口、按层分区),并把投影权重按层驻留、接进多层循环 → 两层端到端跑通、单元仿真 + 协同仿真 + 回归 16 项全绿 → 回到目标。这块芯片上的多层块第一次「每层都是完整的」:自己投影、自己 K/V、自己注意力。离一台本地跑大模型的手机,又近一圈。下一环补最后一块:现在残差流跑完还是要靠核读回屏幕——下一步让芯片自己从残差流算出下一个 token(LM 头 + 采样),把生成的字追加回 KV bank,真正「一个字一个字自己往下写」。