第 9 环 · Softmax(分数归一化)
同一个圆心:一台开机只有一个 AI 的手机。上一环芯片能算出「当前这个字跟历史上每个字有多相关」的原始分数了;但分数只是「谁高谁低」,还没法直接用——真正要的是「各占几分」:一串加起来正好是 1 的权重。大的更大、小的趋近 0,这样后面才能拿权重去加权历史信息。这一步就是 Softmax 归一化。本环给芯片造一台 Softmax 引擎。
延续「端上算力紧凑、内存带宽优先」的主线,把 Softmax 做进硬件,且全部用定点/近似,不用浮点:
· 防溢出:先减最大值再取指数(exp(score − max)),最大那个恒为 1.0,避免指数爆掉;
· 指数用 8 位定点查表(e^-d × 256),256 = 1.0;
· 除法用移位相减(restoring)17 拍实现。
同时这一环把 OS 的驱动方式升级了:不再用固定空等,而是轮询 busy 寄存器——真设备驱动的写法(发起 → 等就绪 → 取结果)。
新增 rtl/softmax.sv:分数读端口 + 权重写端口 + 三趟遍历 FSM——
趟1 MAX:走一遍所有分数,找最大值(数值稳定) 趟2 EXP:算 exp(score−max) 查表(Q8),累加得 sum_exp 趟3 DIV:每个槽位 (exp<<8)/sum_exp → Q8 权重,写回权重 RAM
attn_qk.sv 增加「分数落盘」端口:算分时把每个槽位的分数写进片上分数 RAM(上一环只取了最大值)。min_bus.sv 挂载 softmax + 分数 RAM + 权重 RAM,映射:
0x048 → SM_START 写 1 → 触发三趟归一化 0x04C → SM_SUM_EXP 读 exp 之和(归一化分母) 0x050 → SM_MAX 读本趟最大分数 0x054 → SM_W_ADDR 写槽位号 → 选中权重 0x058 → SM_W_DATA 读该槽位 Q8 权重(256 = 1.0) 0x05C → SM_BUSY 读引擎忙闲
软硬协同还顺手补了两处真实缺陷(都是为了让轮询驱动能用):
· 核 v5:分支在 IF 拍判决、靠转发取数;若它依赖的 load 还在 ID/EX,旧值会导致跳错——新增「载入-分支冒险 IF 冻结」,直到 load 走到 MEM 才放行;
· 状态寄存器无空窗:核写完 START 立刻读 BUSY 必须为 1(把「刚下发那拍」也算进忙),否则驱动会误判完成冲过去。
归一化的意义在这里看得很清楚:原始分数 8 和 7 只差 1,归一化后权重却是 76 : 27——差了近 3 倍,相关的那一项被显著放大,不相关的被压小。权重合计 255(≈256=1.0,最后 1 是定点截断误差),说明确实是「概率分布」。屏幕 = (862<<8)|76 = 0x35E4C = 220748——证明芯片能算出一串真正可用的注意力权重。
本环走完一整圈:目标 → AI 要「按相关度分配注意力」 → 硬件三趟算出归一化权重 → 权重和≈1/放大比例/有符号校验跑通 → 回到目标。注意力从「打分」走到「分权重」,且驱动方式升级成真正的设备轮询。下一步,权重就能拿去加权历史信息 V——注意力的输出就齐了。离一台本地跑大模型的手机,又近一圈。
第 10 环:注意力加权 V · 输出——用这一串归一化权重,对每个历史 V 做加权求和,得到注意力的最终输出(下一步进大模型前馈的两个矩阵之一)。