算子与计算图基础:什么是算子?计算图的概念、前向传播与反向传播
好,咱们今天聊聊算子与计算图。这两个概念,说白了就是NPU世界的“砖块”和“施工图纸”。你想想看,没有砖块盖不了楼,没有图纸你都不知道砖往哪儿砌。算子就是砖块,计算图就是图纸。
什么是算子?
算子,英文叫Operator,简称Op。我个人的理解,它就是神经网络里最基础的计算单元。比如卷积、池化、矩阵乘法、激活函数(ReLU、Sigmoid),这些都是算子。
你可能会问:“这不就是函数吗?”嗯,可以这么理解。但算子更强调“操作”本身,以及它跟硬件(比如NPU)的映射关系。我在项目中遇到过,很多刚入行的同学把算子当成黑盒,只关心输入输出,不关心它底层怎么跑。结果模型一上NPU,性能惨不忍睹。
核心要点:算子 = 计算逻辑 + 硬件映射。你不仅要懂它算什么,还得懂它怎么在NPU上高效执行。
常见的算子类型,我列个表给你看:
| 算子类别 | 典型算子 | 说明 |
|---|---|---|
| 卷积类 | Conv2D, DepthwiseConv | 图像特征提取的核心 |
| 池化类 | MaxPool, AvgPool | 降采样,减少参数量 |
| 激活类 | ReLU, Sigmoid, Tanh | 引入非线性 |
| 矩阵运算 | MatMul, Add, Mul | 全连接层、残差连接的基础 |
| 归一化类 | BatchNorm, LayerNorm | 稳定训练,加速收敛 |
计算图的概念
计算图,说白了就是用图的方式,把算子串起来。每个节点是一个算子,每条边是数据的流向(通常是张量)。
我刚开始做NPU算子映射时,总觉得计算图是框架的事,跟我没关系。后来发现,不懂计算图,你根本不知道算子之间的依赖关系,也就没法做内存复用和流水线优化。
举个例子,一个简单的计算图:
输入张量 → Conv2D → ReLU → MaxPool → 输出张量
这个图里,Conv2D的输出是ReLU的输入,ReLU的输出又是MaxPool的输入。数据流是单向的,没有分支。但在实际模型中,计算图要复杂得多,比如有残差连接(ResNet)、多分支(Inception)等。
我的经验:拿到一个模型,先画它的计算图。不用画太细,画出主干和关键分支就行。这能帮你快速定位哪些算子是性能瓶颈。
计算图还有一个重要特性:它是静态的。也就是说,图的结构在模型编译时就确定了,运行时不会改变。这跟TensorFlow的静态图、PyTorch的动态图有点区别,但NPU上我们更倾向于静态图,因为方便做编译优化。
前向传播与反向传播
前向传播,就是数据从输入层,经过一层层算子,最终得到输出。这个过程,说白了就是“算一遍”。
反向传播,则是根据损失函数,从输出层往回走,计算每个算子的梯度。这个过程,是“求导”。
你可能会问:“为什么NPU算子开发要懂反向传播?”
嗯,这个问题问得好。我告诉你,因为NPU不仅要跑推理,还要跑训练。训练时,反向传播的算子跟正向传播不一样。比如正向传播是Conv2D,反向传播可能就是Conv2DTranspose(转置卷积)。如果你不懂反向传播的算子逻辑,你写出来的算子可能只支持推理,不支持训练。
避坑指南:我曾经在开发一个自定义算子时,只实现了前向逻辑,没实现反向。结果模型训练到一半就崩了,梯度传不回来。后来花了整整两天才定位到问题。所以,如果你要写算子,一定要同时考虑前向和反向。
前向传播和反向传播的关系,我用一个简单的线性层来演示:
前向:y = W * x + b
反向:
∂L/∂W = ∂L/∂y * x^T
∂L/∂x = W^T * ∂L/∂y
∂L/∂b = ∂L/∂y
你看,反向传播的算子(矩阵转置、矩阵乘法)跟前向不一样。这就是为什么算子开发要同时支持两个方向。
知识体系总览
为了让你更直观地理解,我画了一张图:
这张图把算子、计算图、前向传播、反向传播串在了一起。你从左边看到右边,就是一条完整的学习路径。
最后说一句,算子映射这件事,说白了就是“翻译”。把框架里的算子,翻译成NPU能执行的指令。翻译得好不好,直接决定了模型的性能和精度。所以,打好这个基础,后面的路会顺很多。
一个小建议:刚开始学的时候,别贪多。先搞懂3-5个核心算子(Conv2D、MatMul、ReLU、Add、MaxPool)的计算图映射,其他的自然就通了。
想要更多资料,联系可可