深度学习基础回顾:神经网络基本概念、常见算子类型、计算图与数据流
各位同学,欢迎来到《NPU算子映射全流程实战》的第二讲。今天咱们不急着上手写代码,先把深度学习这块地基夯实了。你想想看,算子映射说白了就是把神经网络里的各种计算单元,搬到NPU这个新家里。如果连这些单元长什么样、干什么活都不清楚,那映射起来肯定要出乱子。
我个人习惯,在讲任何技术细节之前,先画一张全景图。这样大家心里有个谱,知道今天要聊的这些东西,在整个深度学习链条里到底处在什么位置。
一、神经网络基本概念:从神经元说起
深度学习里的神经网络,灵感确实来自人脑,但别把它想得太玄乎。说白了,它就是一个巨大的数学函数组合器。
神经元是网络的最小单元。一个神经元做的事很简单:接收一堆输入,每个输入乘以一个权重,再加个偏置,最后扔进一个激活函数里。
核心公式: y = f( Σ(wᵢ · xᵢ) + b )
其中 w 是权重,x 是输入,b 是偏置,f 是激活函数。
我记得刚入行那会儿,带我的老工程师跟我说:「别看现在网络动辄几百层,底层逻辑就是这一个公式的反复堆叠。」当时我不太信,后来自己动手搭了几个模型,才明白这话有多实在。
层(Layer)就是神经元的集合。常见的层有:
- 输入层:接收原始数据,比如一张图片的像素值
- 隐藏层:中间的计算层,负责提取特征
- 输出层:给出最终结果,比如分类概率
权重(Weights)和偏置(Biases)是网络要学习的参数。训练的过程,就是不断调整这些参数,让网络的输出越来越接近真实值。
💡 我的经验:在NPU上做算子映射时,权重数据的排布方式(比如NHWC还是NCHW)直接影响访存效率。我建议你在设计算子时,提前想清楚数据布局,不然后面改起来很痛苦。
二、常见算子类型:NPU要伺候的"主顾"们
算子(Operator),就是神经网络里一个个具体的计算单元。NPU的职责,就是高效地执行这些算子。咱们来看看最常见的几位:
2.1 卷积算子(Convolution)
卷积是CNN的绝对核心。它用一个小的卷积核在输入数据上滑动,提取局部特征。
关键参数:
| 参数 | 含义 | 对NPU的影响 |
|---|---|---|
| Kernel Size | 卷积核尺寸(如3x3) | 决定计算密度和访存量 |
| Stride | 滑动步长 | 影响输出特征图尺寸 |
| Padding | 边缘填充方式 | 决定边界处理逻辑 |
| Dilation | 空洞率 | 影响感受野大小 |
⚠️ 避坑指南:我曾经在一个项目中,因为没处理好卷积的padding模式(SAME vs VALID),导致NPU输出的特征图尺寸和GPU对不上。排查了整整两天才发现是边界填充的索引算错了。所以,映射卷积算子时,一定要把padding的边界条件写清楚。
2.2 池化算子(Pooling)
池化就是降采样,目的是减少数据量,同时保留主要特征。常见的有:
- 最大池化(Max Pooling):取窗口内的最大值。我习惯用它来保留纹理特征。
- 平均池化(Average Pooling):取窗口内的平均值。适合保留背景信息。
池化算子没有需要学习的参数,计算逻辑也简单。但在NPU上实现时,要注意数据对齐——尤其是当输入尺寸不是池化窗口的整数倍时,边界处理要格外小心。
2.3 全连接算子(Fully Connected)
全连接层,说白了就是一个矩阵乘法加上偏置加法。每个输出节点都和所有输入节点相连。
// 全连接层的伪代码示例
for i in range(output_size):
sum = bias[i]
for j in range(input_size):
sum += input[j] * weight[i][j]
output[i] = activation(sum)
全连接算子在NPU上的优化重点在于矩阵乘法。我个人习惯用分块(tiling)策略,把大矩阵切成小块,充分利用NPU的片上缓存。
2.4 激活函数算子(Activation)
激活函数给网络引入非线性。没有它,再多层也只是线性变换的堆叠,表达能力有限。
常见激活函数:
- ReLU: f(x) = max(0, x)。简单高效,梯度不饱和。现在90%以上的网络都在用。
- Sigmoid: f(x) = 1/(1+e⁻ˣ)。输出范围(0,1),适合二分类。但梯度容易消失。
- Tanh: f(x) = (eˣ - e⁻ˣ)/(eˣ + e⁻ˣ)。输出范围(-1,1),零中心化。
💡 我的建议:在NPU上实现ReLU非常简单,就是一个比较和选择。但Sigmoid和Tanh涉及指数运算,计算量较大。我一般会建议算法团队尽量用ReLU系列,除非有特殊需求。如果非要用Sigmoid,可以考虑用分段线性近似来加速。
三、计算图与数据流:NPU眼中的神经网络
计算图(Computation Graph)是深度学习框架(如TensorFlow、PyTorch)内部表示网络的方式。它把网络拆成一个个节点(算子)和边(数据张量)。
为什么NPU工程师要懂计算图?因为算子映射的本质,就是把计算图中的每个节点,对应到NPU上的硬件指令或微内核。
计算图有两种执行模式:
- 静态图(Eager Execution):先定义后执行。框架先构建完整的图,再交给NPU运行。优点是便于优化,缺点是调试不方便。
- 动态图(Graph Mode):边定义边执行。灵活性强,但优化空间小。
数据流(Data Flow)描述的是张量在计算图中的流动方向。前向传播时,数据从输入层流向输出层;反向传播时,梯度从输出层流回输入层。
核心要点:在NPU上做算子映射时,你不仅要关心单个算子的计算逻辑,还要关心算子之间的数据依赖关系。比如,卷积算子的输出是池化算子的输入,那这两个算子的数据排布必须兼容,否则中间要插入额外的格式转换操作,影响性能。
我记得有一次做MobileNet的算子映射,就是因为没注意深度可分离卷积中逐通道卷积和逐点卷积之间的数据流,导致NPU频繁地在不同计算单元之间搬运数据,性能直接腰斩。后来我把两个算子融合成一个kernel,数据直接在片上流转,速度才提上来。
嗯,今天的内容就到这里。深度学习基础这块,看似简单,但每个细节都可能在NPU映射时给你挖坑。希望大家把这些概念吃透,后面讲具体算子映射时,咱们就能直接切入正题了。