第二章 神经网络基础回顾

好,咱们正式开始。这一章我打算带大家快速过一遍神经网络的基础。你可能会问:做NPU硬件,为什么还要学这些?

原因很简单——不懂算法,你设计出来的加速器就是瞎蒙。我在项目里见过太多硬件工程师,把乘法器堆得飞快,结果发现算法根本用不上那种并行度。嗯,那叫一个尴尬。

2.1 感知机与多层感知机

先说感知机。这是神经网络的老祖宗,1957年由Frank Rosenblatt提出。说白了,就是一个线性二分类器。

// 感知机的数学形式
y = sign(w·x + b)

它做的事情很简单:输入特征加权求和,加上偏置,过个阶跃函数,输出0或1。我在做第一版NPU原型时,就用感知机做过一个简单的AND门验证——结果发现单层感知机连XOR都搞不定。

核心局限:单层感知机只能解决线性可分问题。XOR这种非线性问题,它无能为力。

怎么解决?加层。这就是多层感知机(MLP)的由来。

MLP在输入和输出之间插入了隐藏层,每个隐藏层都带非线性激活函数。常用的激活函数有Sigmoid、Tanh、ReLU。我个人习惯用ReLU,原因很简单——计算快,梯度不饱和。

// 两层MLP的前向计算
h = ReLU(W1·x + b1)
y = softmax(W2·h + b2)

你想想看,MLP本质上就是在做特征变换。把原始输入空间映射到更高维的隐空间,让数据变得线性可分。我在做NPU的矩阵计算单元时,MLP的矩阵乘法模式就是最基础的算子模板。

硬件视角:MLP的计算核心是矩阵-向量乘法和激活函数。NPU设计时,这两个模块要重点优化。

2.2 卷积神经网络(CNN)核心算子

CNN是图像处理的标配。它的核心思想是局部连接和权值共享。说白了,就是用一个小窗口(卷积核)在图像上滑动,提取局部特征。

卷积操作其实不复杂:

// 2D卷积示例(简化版)
for (int i = 0; i < H_out; i++) {
    for (int j = 0; j < W_out; j++) {
        sum = 0;
        for (int m = 0; m < K; m++) {
            for (int n = 0; n < K; n++) {
                sum += input[i+m][j+n] * kernel[m][n];
            }
        }
        output[i][j] = sum;
    }
}

这段代码看着简单,但硬件实现时坑很多。我曾经在项目里遇到过卷积核尺寸不匹配的问题——输入特征图和卷积核的通道数对不上,结果仿真跑出来全是NaN。排查了整整两天。

CNN里还有几个关键算子:

  • 池化(Pooling):最大池化、平均池化。作用是降采样,减少计算量。
  • 批归一化(BatchNorm):稳定训练,加速收敛。推理时可以融合进卷积层。
  • 全连接层:其实就是MLP,放在CNN尾部做分类。

这里有个避坑指南:我曾经在NPU设计时,把卷积的stride和padding搞混了。stride是步长,控制窗口移动的间隔;padding是填充,控制输出尺寸。这两个参数直接影响硬件的数据流调度,千万要分清楚。

参数 作用 硬件影响
stride 卷积核移动步长 决定数据读取间隔
padding 输入边缘填充 影响输出尺寸和存储
dilation 空洞卷积的扩张率 改变感受野,增加计算复杂度

注意:CNN的硬件加速器设计,核心是数据复用。输入特征图、卷积核、输出特征图,这三者的数据流要精心设计,否则带宽会爆炸。

2.3 循环神经网络(RNN)与LSTM

RNN处理的是序列数据。它的特点是:当前时刻的输出,依赖于上一时刻的隐藏状态。

// RNN单元
h_t = tanh(W_hh * h_{t-1} + W_xh * x_t + b_h)
y_t = softmax(W_hy * h_t + b_y)

看着简单吧?但RNN有个致命问题——梯度消失/爆炸。序列长了,前面的信息传不过来。我在做语音识别NPU时,试过用普通RNN处理10秒的音频,结果后面完全学不到前面的内容。

LSTM就是来解决这个问题的。它引入了门控机制:遗忘门、输入门、输出门。说白了,就是让网络自己决定:哪些信息要记住,哪些要忘掉。

// LSTM单元(简化版)
f_t = sigmoid(W_f * [h_{t-1}, x_t] + b_f)  // 遗忘门
i_t = sigmoid(W_i * [h_{t-1}, x_t] + b_i)  // 输入门
o_t = sigmoid(W_o * [h_{t-1}, x_t] + b_o)  // 输出门
c_t = f_t * c_{t-1} + i_t * tanh(W_c * [h_{t-1}, x_t] + b_c)
h_t = o_t * tanh(c_t)

你想想看,LSTM的计算量比普通RNN大了好几倍。每个时间步要算4个门控的矩阵乘法。硬件设计时,我建议把LSTM的矩阵运算和逐元素运算分开——矩阵乘法用专门的MAC阵列,逐元素运算用ALU流水线。

硬件优化点:LSTM的四个门控矩阵可以合并成一个大的矩阵乘法,减少访存次数。我在项目里用这个技巧,把LSTM的推理速度提升了30%。

2.4 Transformer与注意力机制

Transformer是2017年Google提出的,现在几乎统治了NLP领域。它的核心是注意力机制(Attention)。

注意力机制说白了就是:让模型在计算某个位置的输出时,能够关注到输入序列中所有位置的信息,并给每个位置分配不同的权重。

// Scaled Dot-Product Attention
Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V

Q是查询,K是键,V是值。Q和K的点积表示相似度,softmax归一化成权重,再加权求和V。这个操作在硬件上就是矩阵乘法加softmax。

Transformer里还有个关键设计——多头注意力(Multi-Head Attention)。把Q、K、V分别投影到多个子空间,并行计算注意力,再拼接起来。这样做的好处是:模型可以从不同角度关注信息。

我在设计NPU时,专门为Transformer做了优化:

  • 矩阵乘法单元:支持大尺寸矩阵分块计算
  • Softmax加速器:用查找表+线性插值实现快速指数运算
  • LayerNorm:用定点数实现,避免浮点开销

个人经验:Transformer的推理瓶颈往往在注意力层。QK^T的计算复杂度是O(n²),序列长度n一长,计算量就爆炸。我建议在硬件里加入稀疏注意力支持,只计算部分位置的注意力权重。

还有一个重要概念——位置编码(Positional Encoding)。因为Transformer没有循环结构,它不知道输入的顺序。所以需要显式地加入位置信息。常用的有正弦余弦编码和可学习的位置编码。

嗯,到这里,神经网络的基础就回顾完了。这些内容看似简单,但都是NPU设计的基石。下一章,我们会深入NPU的微架构设计,从计算单元开始讲起。

最后提醒:别小看这些基础算子。我见过太多人一上来就搞大模型加速器,结果连卷积的数据复用都搞不清楚。基础不牢,地动山摇。