第 11 环 · 前馈网络 FFN(NPU 算两层)
同一个圆心:一台开机只有一个 AI 的手机。前四环芯片已经会算「注意力」——打分、分权重、取用最相关的历史信息。但注意力的输出还只是「上下文里最相关的那部分」,大模型里每个 Transformer 块在注意力之后,还要再过一个前馈网络 FFN:两层线性变换,中间夹一个激活(这里用 ReLU,把负值抹成 0)。本环让芯片把这个 FFN 也跑通——做完它,一个完整的 Transformer 块(注意力 → FFN)就在芯片上闭合了。
延续「端上算力紧凑、全部定点、不用浮点」的主线:
· 两层各一个 4×4 INT4 权重矩阵 W1、W2;
· 激活向量 8bit 有符号;累加 32bit 有符号;
· 第一层 h[d] = Σ W1[d][i]·x[i],过 ReLU(h)=max(0,h) 抹掉负值,第二层 y[d] = Σ W2[d][i]·r[i]。
数学上就是 FFN(x) = W2 · ReLU(W1 · x)。全部乘加是组合逻辑,核喊一声 start,硬件一次算完两层,直接吐出 4 个 lane。
新增 rtl/ffn.sv:一个 dot4 函数做「一行 INT4 权重 × 4 个 8bit 值」的点积(权重、输入都先符号扩展到 32bit 再逐项乘加),两层各调 4 次——
第 1 层(组合逻辑):h[d] = dot4(W1 第 d 行, x) → r[d] = ReLU(h[d])(负值抹 0,饱和到 8bit) 第 2 层(组合逻辑):y[d] = dot4(W2 第 d 行, r) 结果锁存:start 那拍把 y 锁进 y_out,下一拍 busy 拉低、done 拉高
min_bus.sv 挂载 FFN 引擎,新映射:
0x078 → FFN_X 输入向量(4×8bit) 0x07C → FFN_W1_LO 第 1 层权重 0~7 0x080 → FFN_W1_HI 第 1 层权重 8~15 0x084 → FFN_W2_LO 第 2 层权重 0~7 0x088 → FFN_W2_HI 第 2 层权重 8~15 0x08C → FFN_START 写 1 → 触发前馈两层计算 0x090 → FFN_OUT0 读输出 lane0(32bit 有符号) 0x094 → FFN_OUT1 读 lane1 0x098 → FFN_OUT2 读 lane2 0x09C → FFN_OUT3 读 lane3 0x0A0 → FFN_BUSY 读忙闲(含刚下发那拍,无空窗)
这一环最直观的是 ReLU 的作用:输入 [2,-1,4,-3] 有正有负,过第一层(单位阵,等于原样传递)得到 [2,-1,4,-3],ReLU 把 -1 和 -3 抹成 0,得 [2,0,4,0];再过第二层全 1 阵求和,每个 lane 都是 2+0+4+0=6。屏幕 = 0x06060606 = 101058054——证明芯片能算出一个 Transformer 块第二半的 FFN。单元仿真还单独验证了「全负输入 → ReLU 全抹 0 → 输出 0」,确认激活是真的在工作,不是摆设。
本环走完一整圈:目标 → AI 要把「最相关的信息」再加工提炼 → 硬件用两层线性 + ReLU 完成前馈 → 有符号 / 全负 / 抹负各路径全部跑通 → 回到目标。至此一个完整的 Transformer 块(注意力 → FFN)在芯片上闭合了——大模型心脏里的那「两块」,从注意力到前馈网络,都在自研芯片上跑通。下一步,把这个块的输入和输出用残差连接接起来、再归一化,让「块」真正能一层层摞起来。离一台本地跑大模型的手机,又近一圈。
第 12 环:残差连接 · LayerNorm——把注意力 + FFN 拼成一个真正的 Transformer 块(输入加回输出做残差,再归一化),让「块」能稳定地一层层摞成整个大模型。