是什么:三者的关系是一组同心圆 —— 人工智能(AI) ⊃ 机器学习(ML) ⊃ 深度学习(DL) ⊃ 大模型(LLM)。AI 是"让机器表现出智能"这个大目标(含规则专家系统、搜索等非学习类方法);机器学习是其中"不从规则编程、而从数据中自动归纳规律"的方法族;深度学习是机器学习中"用多层神经网络自动学习特征"的分支;大模型(如 GPT、LLaMA)则是深度学习中"参数量巨大(十亿级以上)、靠海量数据预训练 + Transformer 架构"的一类模型 —— 2017 年 Transformer 论文《Attention Is All You Need》之后,几乎所有大模型都长在同一个骨架上。LeNet(1998)位于最内圈的最里层起点:它就是深度学习最早的实用化成功之一。
为什么 IC 工程师必须懂:因为 NPU 设计的每一个关键决策都来自算法的数学形状,这叫算法-硬件协同设计 —— ①算子种类决定数据通路:卷积的滑窗复用催生了脉动阵列与 line buffer,池化催生了比较树,激活催生了查找表/分段线性电路(详见 08-07);②数值精度决定位宽与数据类型:算法侧发现 INT8 量化掉点可接受(08-08),硬件才敢用 8bit MAC 把能效翻 4 倍;③访存模式决定存储层级:权重被反复复用 → 权重常驻 SRAM,激活流式生产消费 → 行缓冲;④模型演进决定指令集寿命:如果算法明年后主流换成你的硬件跑不动的算子(比如动态形状的注意力),这颗芯片的商业生命就结束了。不懂算法的 NPU 工程师,只能被动实现别人给的 Spec;懂算法的,能参与决定 Spec。
怎么做(给自己建一张图):把"AI 大家族"记成下面这张表,每个概念都问一句"它长在哪一层、硬件上意味着什么":
| 概念 | 一句话定义 | 代表 | 对硬件的关键含义 |
|---|---|---|---|
| 人工智能 AI | 让机器表现出类人智能的总目标 | 专家系统、搜索、机器学习 | 方法不固定,只有学习类方法能被 NPU 加速 |
| 机器学习 ML | 从数据中自动归纳规律,而非手工写规则 | 线性回归、SVM、决策树、神经网络 | 传统 ML 算子碎片化,通用 CPU 往往更划算 |
| 深度学习 DL | 多层神经网络逐层自动提取特征 | LeNet、AlexNet、ResNet | 算子高度规则(卷积/矩阵乘),专用加速的空间来源 |
| 大模型 LLM | 十亿级参数 + 预训练 + Transformer | GPT、LLaMA、Qwen | 矩阵乘为主但访存密集,推理受带宽约束;端侧靠量化压缩 |
面试怎么问:①"讲讲机器学习、深度学习、大模型的关系?"(同心圆 + 各自的判断标准:是否从数据学习 / 是否多层神经网络 / 是否大规模预训练);②"你是硬件工程师,为什么要懂算法?"(答算子决定数据通路、精度决定位宽、访存决定存储层级三条 + 举一个 INT8 量化的例子);③"Transformer 和 CNN 对 NPU 设计各意味着什么?"(CNN 滑窗规则适合空间阵列;注意力是动态权重矩阵乘,QKV 访存大,KV Cache 是带宽瓶颈)。
易错点:①"深度学习 = AI"错,AI 里还有非学习的经典方法;②"大模型是新架构"错,大模型主要是 Transformer 的规模化,不是全新计算原理,底层仍是矩阵乘与非线性;③ IC 视角常犯的错是"把算法当黑盒只拿 netlist 式的算子列表" —— 不理解算法意图,做量化/稀疏/融合决策时会掉坑(如对 softmax 前的 logits 量化会直接掉点)。
是什么:按"数据带什么标签、反馈是什么"划分的三种学习方式。监督学习(supervised):训练数据自带标准答案(标签),模型学"输入→输出"的映射 —— 图像分类(MNIST 每张图都标了 0~9)、回归(房价预测);无监督学习(unsupervised):数据没有标签,模型自己找结构 —— 聚类(用户分群)、降维(PCA)、自编码器;强化学习(reinforcement):没有标准答案,只有环境反馈的奖励信号,智能体通过"试错 + 延迟奖励"学策略 —— AlphaGo、机器人步态策略(本站 04-04 的 RL 训练就是它)。
为什么重要:三种范式决定了训练负载与部署负载的巨大差异,直接影响芯片定位 —— 监督学习的训练是"前向 + 反向 + 更新"三段,算力需求约为推理的 2~3 倍;推理只有前向,可以极致量化压缩。端侧 NPU 几乎只做推理,训练发生在数据中心;而强化学习因为要在"交互回路"里高频推理 + 训练,常需要"训推一体"的近端算力 —— 机器人本体上 RL 策略的部署就是纯推理(策略网络 forward 一次 = 一步动作)。
| 维度 | 监督学习 | 无监督学习 | 强化学习 |
|---|---|---|---|
| 数据形态 | (输入, 标准答案) 成对 | 只有输入,无标签 | 状态 + 奖励信号(可能延迟) |
| 学习信号 | 预测与标签的损失(交叉熵/MSE) | 数据内在结构(距离/重构误差) | 累计奖励最大化 |
| 典型任务 | 分类、回归、检测 | 聚类、降维、异常检测 | 围棋、机器人控制、自动驾驶决策 |
| 代表算法 | LeNet、ResNet、Transformer 微调 | K-means、PCA、自编码器 | Q-learning、PPO(04-04 步态训练用) |
| 硬件负载特点 | 训练 3 段循环,算力大 | 任务碎片化,多为 CPU/传统算法 | 训练环境仿真重,部署端只跑策略推理 |
| 机器人中的例子 | 识别零件缺陷、手势分类 | 点云分割、运动模式发现 | 人形机器人学会走路、抓取 |
面试怎么问:①"三大范式各举一个例子,说明学习信号的区别";②"为什么端侧 NPU 只做推理?"(部署负载只有前向;反向传播需要保存中间激活做梯度,内存翻数倍;训练精度要求高,INT8 训练掉点难控);③"机器人步态用强化学习训练后,部署到机载芯片上跑的是什么?"(只有策略网络的前向推理,观测→动作,通常需要量化到 INT8/FP16 满足实时性)。
易错点:①把"无监督"理解成"没有监督学习准" —— 无监督只是学习信号不同,不是降级版;②半监督/自监督(大模型预训练本质是自监督:用文本后半句预测前半句式地造标签)是混合形态,面试提一句加分;③强化学习的"奖励"不是标签,别混着说。
是什么:人工神经元是对生物神经元的极度简化 —— 输入 x₁…xₙ 加权求和,加偏置 b,再过一个非线性激活函数 f 输出:
权重 w 表示"这条输入有多重要",偏置 b 表示"这个神经元有多容易被激活"(相当于阈值的负数)。注意这个运算对硬件工程师应该一眼亲切:乘累加(MAC)+ 查表/比较,正是 DSP/NPU 里最基本的单元 —— 一个神经元 = 一个点积,一层网络 = 一次矩阵-向量乘(GEMV),一批样本 = 矩阵-矩阵乘(GEMM)。深度学习的全部计算,展开到最底层就是这两件事(08-07 页的 GEMM 视角)。
为什么需要非线性激活:如果不用激活函数(或用线性激活 f(z)=z),n 层网络叠起来等价于一层:
线性变换的复合仍是线性变换 —— 叠 100 层的表达能力等于叠 1 层,连"异或(XOR)"这种最简单的非线性可分问题都学不会。非线性激活是把"线性代数"升级为"万能函数拟合器"的开关(理论上,带非线性的足够宽网络可逼近任意连续函数,即通用近似定理)。对 NPU 的启示:激活函数无处不在、每层必有,所以 NPU 必须内置高效的激活单元 —— ReLU 是一条 max 指令,sigmoid/tanh 常用分段线性近似或查找表实现。
怎么做(记住三个经典激活函数):
| 激活函数 | 输出范围 | 导数 | 优点 | 缺点 | 硬件实现 |
|---|---|---|---|---|---|
| Sigmoid | (0, 1) | σ(1−σ),最大 0.25 | 可解释为概率,平滑 | 梯度消失(导数≤0.25);含指数运算;输出非零中心 | 查表/分段线性,指数电路贵 |
| Tanh | (−1, 1) | 1−tanh²,最大 1 | 零中心,收敛快于 sigmoid | 仍有梯度消失;同样含指数 | 查表/分段线性 |
| ReLU | [0, +∞) | 正半轴恒 1,其余 0 | 不衰减梯度,计算极简,稀疏激活 | 负半轴"死区":神经元可能永久不激活(dying ReLU) | 一个 max 比较器,NPU 最爱 |
LeNet 诞生时(1998)主流用的是 sigmoid/tanh;现代复现(含本页 PyTorch 实战)几乎都换成 ReLU —— 这也是 08-07 页讲"为什么 NPU 激活单元按 ReLU 优化、sigmoid 用近似器"的历史脉络。
面试怎么问:①"为什么需要激活函数?去掉会怎样?"(线性复合仍是线性,XOR 反例);②"ReLU 相比 sigmoid 好在哪?"(导数恒 1 不衰减、计算只是一个 max、稀疏激活带来计算/存储节省 + 死区缺点);③"从硬件角度比较 ReLU 与 sigmoid 的实现代价"。
易错点:①把"神经元"过度拟人化 —— 它只是一个点积加非线性,没有任何智能;②混淆激活函数与其导数(sigmoid 导数最大 0.25,不是输出最大 0.25);③LeNet 原论文用的是 S2/S4 下采样层里的 tanh 式 squashing,现代 PyTorch 版普遍改 ReLU,读论文和读代码时别打架。
是什么:多层感知机 = 神经元按层组织:输入层 → 若干隐藏层 → 输出层,每层神经元与下一层全连接(dense/linear),信息单向从输入流到输出。数学上每层就是一次矩阵乘加激活:
为什么:单神经元只能画一条分界线;多层叠加后,第一层学简单特征、第二层在特征之上组合出更复杂的特征 —— "逐层组合"是深度学习所有结构(包括 LeNet)的共同思想。MLP 的矩阵表达直接对应硬件上的 GEMM 引擎,这也是它对 IC 工程师最友好的地方。
怎么做(数一个 MLP 的参数):一层全连接的权重矩阵是 W∈ℝ^{m×n}、偏置 b∈ℝ^m,参数量 = m×n+m;MAC 数(一个样本)= m×n。例如输入 784 维(MNIST 拉平)、隐藏 128、输出 10 的 MLP:参数 = 784×128+128 + 128×10+10 = 101,770,比 LeNet 还多 65%,但精度反而更低 —— 卷积的权值共享 + 局部连接能用更少参数换更高精度,这正是下一节的主角。
面试怎么问:①"MLP 每层的计算在硬件上对应什么操作?"(GEMV/GEMM + 激活);②"全连接层参数量怎么算?给一个 784-128-10 的 MLP 估算参数量"(10.2 万);③"为什么图像任务用 MLP 不划算?"(拉平丢失空间结构 + 全连接参数爆炸,没有平移不变性)。
易错点:①"输入层"通常不计入层数与参数(它没有权重),3 层 MLP 指两个隐藏层 + 输出层的写法都存在,面试先统一口径;②参数量与计算量别混:参数是存储(权重个数),计算量是 MAC 次数,MLP 里二者接近,卷积里计算量远大于参数量(复用);③不要以为"深度"必须很深 —— LeNet 只有 2 个卷积层 + 3 个全连接层,7 层网络(含输入输出)在 1998 年已经是"deep"。
是什么:卷积层用一个可学习的小窗口(卷积核,如 5×5)在输入特征图上按步长滑动,每个位置做一次"窗口内对应元素相乘再求和",得到输出特征图的一个像素。三个关键机制:①局部连接 —— 每个输出只看输入的一小块(图像局部相关性);②权值共享 —— 同一个核扫遍全图(平移不变的特征检测器,参数量从"每像素一组权重"降到"每组核一组权重");③多通道堆叠 —— 输出图的数量 = 卷积核组数,每组核又能看到输入的全部通道,层层组合出边缘→纹理→部件→整体的层次特征。
为什么(NPU 视角):卷积把"图像知识"写成了极规则的访存与计算模式 —— 窗口固定、步进固定、内积固定长度。规则 = 可以流水线化、可以空间展开成阵列、可以把窗口数据在片上反复复用。08-07 页的脉动阵列、im2col+GEMM、line buffer,全部建立在这一节的三个公式上。
怎么做(必背三公式,考 NPU 岗必出):
其中 K 卷积核边长、P 填充、S 步长、C_in/C_out 输入/输出通道数。两个例子现场算一遍:①LeNet C1:K=5,P=0,S=1,C_in=1,C_out=6,H=32 → H_out=28;参数 = 5×5×1×6+6 = 156;MAC = 28×28×6×25 = 117,600。②ResNet 的 3×3 卷积一层:K=3,P=1,S=1 → 尺寸不变("same"卷积),这是现代网络保持分辨率的标准手法,而 LeNet 时代靠手工算尺寸。验证:K=3,P=1,S=1 时 (H+2−3)/1+1 = H ✓。
相关概念感受野(receptive field):输出某像素对应的输入区域大小。两层 3×3 堆叠感受野 5×5,但参数只有 2×9=18 < 25,且多了两次非线性 —— "小核堆深"胜过"大核堆浅",这是 VGG 之后的共识。感受野递推:
多通道怎么卷:一组核的形状是 K×K×C_in(立方体),在 C_in 个通道上同时滑动求和,压成 1 张输出图;C_out 组这样的核 → C_out 张输出图。硬件含义:卷积本质是"K²C_in 长度的点积",与 GEMM 的内层完全同构 —— 这就是 im2col(把滑窗展开成矩阵的一列)能把一切卷积变 GEMM 的原因,也是 08-07 页数据通路的起点。
面试怎么问:①"现场推卷积输出尺寸公式,并算 32×32 输入、5×5 核、stride 1、no pad 的输出"(28×28);②"卷积参数量和计算量为什么差这么多?"(权值共享:参数只有一组核,计算却要每个位置都做);③"im2col 是什么?为什么能把卷积变成 GEMM?"(把每个滑窗位置展开成矩阵一列,卷积核展开成行,内积即输出 —— 08-07 页展开);④"感受野怎么算?两层 3×3 与一层 5×5 谁好?"。
易错点:①输出尺寸公式忘了先加 2P 再减 K,或除 S 不取整;②参数量漏掉偏置 C_out,或多算"每个输出位置一份权重"(那是全连接的算法,卷积共享);③MAC 与 FLOP 混用:1 MAC = 1 乘 1 加 = 2 FLOPs,对比论文数据先统一口径;④多通道卷积的核是 K×K×C_in 三维,不是"每通道独立输出一张图再相加"的误解 —— 是"先逐通道乘加再跨通道累加",两种顺序等价但硬件遍历顺序不同。
是什么:池化(subsampling/downsampling)在特征图上按 2×2 窗口(典型)取最大值(max pooling)或平均值(average pooling),把特征图边长减半、像素数减为 1/4。LeNet 的 S2/S4 就是 2×2 平均池化(当年叫"下采样"),现代网络多用 max。池化没有任何可学习参数,也没有跨通道混合 —— 每个通道独立做。
为什么:①降维省算力:下一层卷积的输出位置数按面积缩 4 倍,MAC 同步缩 4 倍;②平移不变性:目标平移一两个像素,max 池化大概率取到同一个值,分类结果稳定 —— 手写数字写歪一点也能认;③扩大感受野:分辨率减半后,后续同一个 5×5 核覆盖的"原始输入区域"翻倍。
怎么做(硬件视角):池化单元 = 4 输入比较树(max)或 4 输入加法树 + 移位(avg,除以 4 就是右移 2 位),无权重、无乘法器,是 NPU 里最便宜的功能单元,常与卷积输出级联成"卷积-池化融合流水线"(一次 SRAM 写入都没有,详见 08-07 的算子融合)。LeNet 的 S2 严格说是"2×2 平均 + 乘系数加偏置"(当年带 2 个可训练参数),现代实现直接 pooling 不带参数。
面试怎么问:①"max 与 average 池化怎么选?"(分类特征保留最显著响应用 max;LeNet 时代/量化友好的平滑场景用 avg,现代还有 stride 卷积替代池化的做法);②"池化层有参数吗?有梯度吗?"(无参数;有梯度 —— max 把梯度路由给最大值那个输入,avg 平均分配);③"为什么说池化带来平移不变性?"。
易错点:①池化没有权重,数参数量时 S2/S4 贡献为 0(现代写法);②池化不跨通道 —— C_out 不变,变的是 H×W;③stride=2 的卷积也能降维,现代网络常"卷积代池化",别把池化当成唯一下采样手段。
是什么:全连接层(FC/Linear/dense)把输入向量 x∈ℝⁿ 变换为输出向量 y∈ℝᵐ:
它"看见全部输入",最适合做"把前面提好的特征综合起来做最终判决" —— LeNet 的 C5、F6、输出层就是三级全连接,把 120 维特征逐级压到 84 维再到 10 类。
为什么 FC 与卷积"是一家人":①FC 是全窗口的卷积:当卷积核尺寸 K 等于输入特征图尺寸时,输出 1×1×C_out,数学上与 FC 完全等价 —— LeNet 的 C5 层在论文里写作卷积(5×5 卷到 5×5 图上出 1×1),在现代框架里常直接写成 Linear,两者参数量分毫不差(5×5×16×120+120 = 16×5×5×120+120 = 48,120);②反过来,FC 可当全局卷积用:1×1 卷积 = 在通道维做 FC,是现代网络跨通道信息混合的标准手法。硬件视角:FC 层权重矩阵稠密、无复用(每个权重只用一次,权重访存 = MAC 数),数据复用率远低于卷积层 —— 大模型推理的带宽瓶颈就出在 FC/矩阵乘的权重搬运上(08-07/08-08 展开)。
| 对比项 | 卷积层 | 全连接层 |
|---|---|---|
| 连接方式 | 局部窗口 + 权值共享 | 全连接,每个输入都连 |
| 输入结构 | 保留 H×W×C 三维空间结构 | 须先 Flatten 拉平成向量 |
| 权重复用 | 一个核复用于所有位置(复用 H_out×W_out 次) | 无复用,每权重用一次 |
| 参数量例子(LeNet C5) | 5×5×16×120+120 = 48,120 | 16×5×5 → 120 等价 FC:48,120(完全相同) |
| 对平移的敏感度 | 平移不变(池化加强) | 敏感,特征位置变了权重对不上 |
| 硬件形态 | 滑窗 + 点积阵列(可脉动化) | 纯 GEMM(矩阵乘引擎直接吃) |
面试怎么问:①"全连接层和卷积层是什么关系?"(K=输入尺寸的卷积 ≡ FC;1×1 卷积 = 通道维 FC);②"为什么 FC 层是访存瓶颈?"(权重无复用,权重带宽 ≈ MAC 吞吐);③"Flatten 丢掉了什么?"(空间排布信息,只留数值 —— 换来的是与位置无关的综合能力)。
易错点:①Conv2d(16,120,5) 作用在 5×5 特征图上输出 120×1×1,与 Linear(400,120) 等价但权重排布顺序不同(通道优先 vs 拉平优先),迁移 pretrained 权重时踩过坑的人都懂;②FC 参数量巨大:LeNet 61,706 个参数里全连接侧 C5+F6+OUT 占 96%(48,120+10,164+850 = 59,134),这解释了为什么后来 NiN/GAP(全局平均池化)要消灭大 FC 层;③别把"全连接"写成"全边接"或与"fully convolutional(全卷积网络)"混淆 —— 后者恰是"把 FC 换成卷积"的网络。
是什么:LeNet-5(Yann LeCun 等,1998,《Gradient-Based Learning Applied to Document Recognition》)是第一个大规模商用的卷积神经网络,用于银行 ATM/支票手写数字识别。结构 8 段(含输入输出):
逐层算账(本页核心表格,请对照图②手工验算一遍):按第 3 节公式逐层代入,全部数值可复算。
| 层 | 类型(K/S/P) | 输出尺寸 | 参数量 | MAC 计算量 | 算账过程 |
|---|---|---|---|---|---|
| INPUT | 输入 1 通道 | 1×32×32 | 0 | 0 | MNIST 28×28 补边到 32×32 |
| C1 | 卷积 K=5,S=1,P=0 | 6×28×28 | 156 | 117,600 | 5×5×1×6+6;(28×28)×6×25 |
| S2 | 池化 2×2(S=2) | 6×14×14 | 0(现代) | ≈0(比较/加法) | 每 2×2 取一值,6 通道独立 |
| C3 | 卷积 K=5,S=1,P=0 | 16×10×10 | 2,416 | 240,000 | 5×5×6×16+16;(10×10)×16×150 |
| S4 | 池化 2×2(S=2) | 16×5×5 | 0(现代) | ≈0 | 同 S2 |
| C5 | 卷积(≈FC)K=5 | 120×1×1 | 48,120 | 48,000 | 5×5×16×120+120;1×1×120×400 |
| F6 | 全连接 120→84 | 84 | 10,164 | 10,080 | 120×84+84;120×84 |
| OUT | 全连接 84→10 | 10 | 850 | 840 | 84×10+10;84×10 |
| 合计 | 7 层可学习结构 | — | 61,706 | 416,520(≈0.42 MMAC) | 乘加各计 1 次;FLOPs 口径 ×2 ≈ 0.83 M |
面试怎么问:①"背一遍 LeNet 结构,并说每层输出尺寸"(按本节公式现场推,别硬背数字);②"LeNet 参数量与计算量各多少、分布在哪?"(6.17 万 / 0.42 MMAC;参数 96% 在全连接侧,计算 86% 集中在 C1+C3 两个卷积层);③"为什么 LeNet 计算量这么小还重要?"(确立了卷积-池化-FC 范式与权值共享思想,后续 30 年 CNN 都是这个骨架的放大版)。
易错点:①把 61,706 说成 60K 时不加口径 —— 原论文含 RBF 输出层与 S 层可训练系数,数字略有出入,报数先报口径;②C5 在论文里是卷积层(5×5 核卷完 5×5 图),写成 FC 虽等价但要知道来龙去脉;③MAC 与 FLOPs 差 2 倍,不同论文口径不同。
①为什么输入是 32×32 而 MNIST 是 28×28?LeCun 的两个考虑:一是让感受野"呼吸" —— 5 层卷积/池化后,最外层像素对应的感受野才能覆盖整个数字,关键笔画(如 7 的折角)不会贴边丢失;二是早期字符集是 32×32 规格化输入,与预处理流水线一致。28×28 输入补 2 圈零边(P=2)即成 32×32。②为什么 C3 是 16 张图而不是 6×16=96 组核全连接?原论文用一张手工配置的连接表:每张 C3 特征图只连 S4 前身 S2 的部分通道(如 3 个或 4 个),16 张图对应 60 组核。动机是打破对称性(不同特征图看不同输入组合)+ 削减参数。现代复现一律改成完全连接(16 组核各看全部 6 通道),参数量从 1,516 变 2,416,精度几乎不变 —— 你在 PyTorch 里写的 Conv2d(6,16,5) 就是简化版。③原始与现代版的差异清单:
| 项目 | 原始 LeNet-5(1998) | 现代复现/PyTorch 版(本页实战) |
|---|---|---|
| 激活函数 | 缩放 tanh(squashing,幅值受限) | ReLU |
| 池化 | 2×2 平均 + 可训练系数/偏置 | MaxPool2d/AvgPool2d,无参数 |
| C3 连接 | 连接表,不完全连接(60 组核) | 完全连接 Conv2d(6,16,5) |
| 输出层 | 84→10 RBF 欧氏距离(模板匹配) | Linear(84,10)+ softmax/交叉熵 |
| 输入 | 32×32,白底黑字,值域[−0.1,1.175] | 28×28 输入 + padding=5 亦可,常用 padding=2 直接 28 起步 |
| 损失 | MSE(对 RBF 输出) | 交叉熵 CrossEntropyLoss |
| 参数量 | 约 60K(含 RBF/S 层系数) | 61,706(上表现代口径) |
面试怎么问:①"为什么 LeNet 输入要 padding 到 32×32?"(感受野覆盖 + 历史预处理规格);②"原始 C3 的不完全连接有什么好处?"(破对称、省参数、特征多样性 —— 类似后来 Group Convolution 的思想雏形);③"你会怎么把 1998 年的 LeNet 改成 2020 年代风格?"(ReLU、max pool、BN、softmax 交叉熵、Adam —— 本页实战代码即答案)。
易错点:①论文、教材、框架三方的"LeNet"细节都不同(d2l 用 28×28 输入 + sigmoid + avg pool),谈结构先声明版本;②C3 连接表数字(60 组核)常被忽略,被问"为什么 16 张图恰好是 16"时答不上;③原始输出层是 RBF 不是 FC,损失是 MSE —— 别把现代写法安到 1998 年头上。
是什么:MNIST 是 LeCun 等人 1998 年整理的手写数字数据集,60,000 张训练图 + 10,000 张测试图,每张 28×28 单通道灰度图,标签 0~9 共 10 类。源自美国人口普查局与高中学生的真实手写样本,是 LeNet 论文的基准任务。文件为 IDX 格式(大端二进制:先魔数/维度头,后逐像素 uint8),四个文件:train-images-idx3-ubyte、train-labels-idx1-ubyte、t10k-images-idx3-ubyte、t10k-labels-idx1-ubyte。
怎么做(加载与归一化):现代工程几乎不手解 IDX,用 torchvision 一行加载,download=True 时自动从镜像拉取(torchvision 官方文档见参考来源;原始页面 yann.lecun.com/exdb/mnist/ 目前已停止维护,访问仅剩目录列表,不再作为下载入口)。关键一步是归一化:原始像素 0~255 → ToTensor 缩到 [0,1] → 再按训练集经验均值 0.1307、标准差 0.3081 标准化到零附近:
from torchvision import transforms, datasets
tf = transforms.Compose([
transforms.ToTensor(), # 像素 0~255 → [0,1],形状 (1,28,28)
transforms.Normalize((0.1307,), (0.3081,)), # (x−0.1307)/0.3081,零均值单位方差
])
train_set = datasets.MNIST("./data", train=True, download=True, transform=tf)
test_set = datasets.MNIST("./data", train=False, download=True, transform=tf)
为什么必须归一化:①不同量纲的输入让损失面呈狭长椭圆,梯度下降在陡方向震荡、缓方向爬行,归一化后损失面更圆,收敛快且稳;②输入零均值 → 第一层权重的梯度分布均衡,配合合适的初始化,各层激活值不会逐层放大/消失(硬件视角:定点/量化部署时,输入范围已知且集中,正是 INT8 量化的前提,详见 08-08)。
面试怎么问:①"MNIST 规模、尺寸、类别数?"(60k/10k、28×28 灰度、10 类,秒答);②"为什么要减均值除方差?"(零均值单位方差 → 损失面圆、收敛稳、利于量化);③"LeNet 为什么还要把 28×28 补成 32×32?"(见 4.2)。
易错点:①把 Normalize 的均值方差写成 (0.5,0.5) 不算错但不精确 —— 0.1307/0.3081 是 MNIST 实测统计值;②混淆"测试集"与"验证集":调参应该用从训练集切出的验证集,MNIST 测试集只许最后汇报,反复"偷看"测试集调参 = 数据泄漏;③IDX 是大端字节序,自己写解析器时用 >iiii 之类的格式串,直接小端读会得到天文数字。
是什么:前向传播 = 把一张图从输入层推到输出层,逐层做"乘累加 + 激活"。用矩阵/张量语言重述 LeNet(现代版,输入 28×28、C1 用 padding=2 保持尺寸):
其中 softmax 把 10 个原始得分(logits)变成概率分布:
怎么做(逐层用形状说话):调试任何网络的第一个手段就是把每层输出 shape 打出来(本页实战代码里的注释就是 shape 流水账)。每一步的"数学形状→硬件动作"对应关系:Conv = 滑窗点积(乘累加阵列),Pool = 比较树,Flatten = 纯重排(不计算,只改寻址 —— NPU 里就是 DMA/地址生成器的事),FC = 矩阵向量乘。一张 28×28 的图走完全程约 0.42 MMAC,人脑级芯片(NPU 仿真器)毫秒内完成。
面试怎么问:①"给一张 28×28 图,说出 LeNet 每层输出 shape"(背本节链条);②"Flatten 层做了什么计算?"(不做计算,只重排 —— 面试陷阱题);③"softmax 为什么要有指数归一化?"(把任意实数得分变合法概率分布,且保持可导、放大最大者)。
易错点:①logits 与概率不分:交叉熵损失接收的是 logits(框架内部帮你 softmax),重复 softmax 会掉点;②softmax 的数值稳定性:z 很大时 e^z 溢出,工程上先减 max(z)(框架已内置,自己实现要记得);③Flatten 的顺序(通道优先 CHW 拉平)与权重文件的排布必须一致,否则加载权重后精度乱掉。
是什么:损失函数把"预测 p 与标签 y 的差距"变成一个标量,训练的全部目标就是让它变小。两个最常用者:
(标签 one-hot 时交叉熵只剩正确类那一项 —— 你给正确类的概率越低,罚得越重,对数把"差得多"放大成"罚得多得多"。)
为什么分类用交叉熵:关键在梯度形状。对 softmax 输出 z 求导,两种损失天差地别:
交叉熵的梯度就是"预测减标签",干净且永不饱和 —— 错得越离谱,|p−y| 越大,学习信号越强;而 MSE 多乘一个激活函数导数因子,输出饱和(对/错得很夸张)时 σ′→0,"错得越狠反而学得越慢"。加上交叉熵源自极大似然估计(最小化 KL 散度),统计意义上就是"最合理解",所以分类默认它。MSE 则是回归(预测连续值)的默认选择。
| 对比项 | MSE(均方误差) | 交叉熵(CrossEntropy) |
|---|---|---|
| 公式 | ½Σ(p−y)² | −Σ y·log p |
| 任务 | 回归(角度、力矩、坐标) | 分类(类别、词表) |
| 配输出层 | 恒等输出 | softmax + logits |
| 错误大时 | 梯度线性增长,但 sigmoid 下被 σ′ 掐死 | 梯度 = p−y,永不饱和 |
| 概率解释 | 高斯噪声假设 | 极大似然 / KL 散度 |
| LeNet 用法 | 原始论文(RBF 输出) | 现代复现(本页实战) |
面试怎么问:①"为什么分类不用 MSE?"(梯度饱和公式 + 极大似然解释,两层都说才算完整);②"交叉熵在 one-hot 下化简成什么?"(−log p_correct);③"回归任务什么时候用 L1 而不是 L2?"(离群点鲁棒性,L1 梯度恒定不炸)。
易错点:①框架里 CrossEntropyLoss = LogSoftmax + NLLLoss,吃 logits;自己写了 softmax 再喂给它 = softmax 两次,精度下降;②交叉熵的 p 要夹在 (0,1) 开区间,log(0) = −∞,工程上 clip/加 ε;③别把"交叉熵小 = 准确率高"直接划等号 —— 校准与精度是两件事。
是什么:训练要回答"每个权重该往哪边、挪多大"。反向传播(backpropagation)就是两步:①前向算出损失 L;②反向从 L 出发,沿计算图把误差信号用链式法则逐层乘回去,得到每个参数的梯度 ∂L/∂W。它不是新算法,只是"把链式法则按拓扑序组织,复用中间结果"的动态规划 —— PyTorch 的 loss.backward() 一行,底层就是这套机制(自动微分)。
怎么做(手推两层网络,面试白板题标准答案):设两层网络,样本 x,标签 y,激活函数 f:
从输出往回乘,每一步只用"上游梯度 × 本地导数":
四个记忆点:①对权重的梯度 = "到达该权重的误差信号" × "该权重前向时的输入"(∂L/∂W₂ 用 a₁,∂L/∂W₁ 用 x);②误差跨层回传要过 Wᵀ(反向走前向的转置路);③激活处逐元素乘本地导数 f′;④所有中间梯度都只需算一次(复用),这就是反传比数值微分快几个数量级的原因。
为什么(与硬件的关系):训练 = 前向 + 反向 + 更新,反向多出三个硬件需求:①前向算子的镜像算子(卷积反传是 W 旋转 180° 的卷积、池化反传要记住 max 位置);②中间激活必须暂存(反传要用前向的 a₁、x,显存/缓冲翻倍 —— 08-07 存储层级的训练口径);③更新阶段是读改写(θ ← θ − η·g,读写带宽型负载)。端侧 NPU 只做推理,恰恰因为可以砍掉这一整套。
面试怎么问:①"白板推两层网络 ∂L/∂W₁"(默写上面六连乘);②"反向传播和链式法则什么关系?"(链式法则是数学,反传是按计算图拓扑序调度链式法则并复用中间量的实现);③"为什么训练显存比推理大?"(要缓存中间激活供反传用,粗估为前向的 2~3 倍);④"梯度消失/爆炸的原因各是什么?"(消失:激活导数<1 连乘;爆炸:权重初值大或梯度>1 连乘,clip/BN/残差应对)。
易错点:①∂L/∂a₁ 那步忘了转置 W₂ᵀ(反向矩阵要转置);②把"激活函数导数"与"权重梯度"混为一谈;③softmax+交叉熵组合的 f′ 因子已被吸收成 p−y,此时再额外乘 σ′ 就重复扣减(经典实现 bug);④梯度消失不是 sigmoid 输出小,而是导数小于 1 连乘 —— 口误会被面试官抓住。
是什么:有了梯度,沿负梯度方向更新权重,迭代逼近损失最小点 —— 这就是梯度下降。SGD 用小批量(mini-batch)估计梯度直接走;动量(Momentum)给更新加"惯性":历史速度的指数加权平均,坑洼(梯度噪声)被抹平,平坦方向越走越快;Adam 同时维护梯度的一阶矩 m(方向)与二阶矩 v(幅度)的自适应估计,给每个参数自动配步长,默认超参即可用,是首选起点。
(g_t 为小批量梯度;Adam 的 m̂/v̂ 是偏差修正项,β₁=0.9、β₂=0.999、ε=10⁻⁸、η 默认 10⁻³。)
| 优化器 | 更新特点 | 优点 | 缺点 | 典型学习率 | 硬件视角 |
|---|---|---|---|---|---|
| SGD | 沿当前小批量梯度直走 | 实现最简,泛化常更好 | 对学习率敏感,沟壑地形震荡 | 0.01~0.1(d2l LeNet 用 0.9) | 状态零开销,只需读写 θ |
| SGD+动量 | 梯度指数加权平均后走 | 抗噪声、加速谷底方向 | 仍需调 η 与 β | 0.01,β=0.9 | 多存一份速度 v(同 θ 大小) |
| Adam | 一阶/二阶矩自适应步长 | 鲁棒、收敛快、默认参数能用 | 显存 ×3,泛化有时逊于 SGD | 10⁻³ | 训练芯片要存 m/v,更新阶段访存重 |
怎么做(学习率诊断法):学习率 η 是最重要的一个旋钮,看损失曲线就能诊断:
| 症状(训练损失曲线) | 诊断 | 处方 |
|---|---|---|
| 缓慢下降,几十 epoch 还没起色 | η 太小 | ×3~×10 上调,或换 Adam |
| 震荡剧烈甚至 NaN,损失飙升 | η 太大 | ÷10 下调;或加梯度裁剪 |
| 先快后平台,再不动 | η 不随进度衰减 | 阶梯衰减(stepLR)或余弦退火 |
| 训练损失降、验证损失回头 | 过拟合(非 η 问题) | 转入第 7 节正则化手段 |
batch_size 的隐影响:大 batch 梯度估计稳但每 epoch 步数少,常配合放大 η(线性缩放法则);小 batch 噪声大,反而有一定正则效果。LeNet 在 MNIST 上 batch=64~256、Adam 1e-3,几个 epoch 就能到 98%+ —— 对硬件工程师的意义:batch 决定了单次前向的并行度,NPU 的 PE 阵列利用率、SRAM 分块(tiling)都按 batch×通道×尺寸来规划(08-07 的 N/M/T 参数)。
面试怎么问:①"SGD、动量、Adam 的区别,各自什么时候用?"(简→惯性→自适应;调参预算少/新任务用 Adam,刷泛化上限用 SGD+动量+调 η);②"Adam 为什么显存 ×3?"(m、v 两份状态 + 梯度);③"学习率怎么定?"(说诊断法 + log 尺度扫描,别只背"1e-3")。
易错点:①把动量公式两种常见变形(v=βv−ηg 或 v=βv+g 后乘 η)当成对错之分 —— 等价,系数约定不同;②Adam 的 β₂ 接近 1 时前期 v̂ 被偏差修正救回来,自己实现漏掉 m̂/v̂ 会前几步步长异常;③η 与 batch_size 联动调,只动一个看结论容易误判;④"损失没降"先查数据与标签、再查学习率,新手 90% 的锅是标签错/归一化错,不是优化器。
是什么:模型把训练集"背"下来了(包括噪声),但没学到规律 —— 表现为训练损失持续下降、验证/测试损失先降后升,两条曲线分叉形成"U 形剪刀差"。与之相对:欠拟合是训练损失本身降不下去(模型太小/训练太久没到位/特征太弱)。LeNet 只有 6.17 万参数、MNIST 有 6 万样本,基本不会过拟合;但把 LeNet 放大 10 倍或把样本砍到 1 千,剪刀差立刻出现 —— 参数/样本比是直观预警指标。
为什么(从硬件视角理解):过拟合的根源是"模型容量 >> 数据信息量",网络把冗余容量用来记噪声。正则化的全部手段,本质都是主动限制有效容量或增加数据信息量。这个"容量-数据匹配"思想对 NPU 同样成立:芯片算力固定,越大的模型越要靠压缩/量化/稀疏砍掉冗余容量才能塞进去 —— 08-08 的量化在算法侧做的正是同方向的事。
怎么做(四大手段,按工程使用频率排序):
| 手段 | 机制 | 怎么用(代码) | 适用/代价 |
|---|---|---|---|
| 数据增强 | 免费扩数据:旋转/平移/翻转/裁剪,给模型"变着花样看同一张图" | transforms.RandomRotation(10) 等,只在训练集上加 | 首选,零参数代价;要符合任务先验(数字 6/9 别随便转 180°) |
| Dropout | 训练时以概率 p 随机置零神经元,迫使网络不依赖任何单一路径;推理时关闭(框架自动缩放) | nn.Dropout(0.5) 加在 FC 层之间 | FC 层标配;卷积层用少;训练/推理行为不一致(对量化部署有影响) |
| L2 正则(权重衰减) | 损失加 λ‖W‖²:大权重被惩罚,解偏向平滑小权重 | optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) | 一行参数;偏置/BN 参数通常不加 |
| 早停(early stopping) | 监控验证损失,连续 N 个 epoch 不再改善就停,回取最优点 | 手写计数器或 EarlyStopping 回调 | 零成本;需要可靠的验证集 |
面试怎么问:①"怎么判断过拟合?列出至少四种缓解手段"(训练/验证曲线分叉 + 上表四招);②"Dropout 训练和推理为什么行为不同?"(训练随机置零+期望缩放,推理用全网络;框架 eval() 自动切换);③"L2 正则为什么又叫权重衰减?"(更新式 θ ← θ(1−ηλ)−η∇L,每步先乘一个小于 1 的系数);④"为什么 Dropout 层对量化/部署工程师是麻烦?"(推理时要折叠缩放系数、随机性不可综合,部署前通常做等价吸收)。
易错点:①把"训练精度 100%"当好事 —— 先看验证集再庆祝;②数据增强用错先验(水平翻转对 MNIST 会把 2 变成 5 的镜像);③Dropout 放在输入层或卷积层乱用,掉点反而怪正则化无效;④权重衰减与 L2 在 Adam 里并非严格等价(AdamW 才是正确解耦)—— 深究者加分。
是什么:下面四段代码拼起来是一个完整可运行的训练脚本(约 60 行):定义 LeNet → 加载 MNIST → 配置损失/优化器 → 训练并每个 epoch 汇报测试精度。本机有 GPU 用 GPU,没有也能跑(CPU 约 1~2 分钟/5 epoch)。运行前 pip install torch torchvision。
第 ① 段 · 模型定义(对应第 2~4 节):把图②的每一层翻译成 nn 模块,注释即 shape 流水账。
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
class LeNet(nn.Module):
"""现代简化版 LeNet:ReLU + MaxPool,输入 1x28x28(C1 用 padding=2 保持 28)"""
def __init__(self, num_classes=10):
super().__init__()
self.feat = nn.Sequential( # 卷积编码器
nn.Conv2d(1, 6, kernel_size=5, padding=2), # -> 6 x 28 x 28
nn.ReLU(), # 逐元素 max(0,x)
nn.MaxPool2d(2), # -> 6 x 14 x 14
nn.Conv2d(6, 16, kernel_size=5), # -> 16 x 10 x 10
nn.ReLU(),
nn.MaxPool2d(2), # -> 16 x 5 x 5
)
self.cls = nn.Sequential( # 分类器
nn.Flatten(), # -> 400(纯重排,不算)
nn.Linear(16*5*5, 120), nn.ReLU(), # C5 -> 120
nn.Linear(120, 84), nn.ReLU(), # F6 -> 84
nn.Linear(84, num_classes), # -> 10(logits)
)
def forward(self, x):
return self.cls(self.feat(x))
读法:Conv2d(1,6,5) 的三个数字就是 C_in、C_out、K —— 用第 3 节公式口算参数量验证:5×5×1×6+6=156 ✓。输出层不给 softmax,因为下一段的 CrossEntropyLoss 内部已含 LogSoftmax(见 5.3 易错点)。
第 ② 段 · 数据加载(对应 5.1 节):
tf = transforms.Compose([
transforms.ToTensor(), # 0~255 -> [0,1]
transforms.Normalize((0.1307,), (0.3081,)), # MNIST 经验均值/方差
])
train_ds = datasets.MNIST("./data", train=True, download=True, transform=tf) # 60000 张
test_ds = datasets.MNIST("./data", train=False, download=True, transform=tf) # 10000 张
train_dl = DataLoader(train_ds, batch_size=64, shuffle=True) # 训练集要打乱
test_dl = DataLoader(test_ds, batch_size=512, shuffle=False)
第 ③ 段 · 训练配置(对应 5.3/6.2 节):
device = "cuda" if torch.cuda.is_available() else "cpu"
model = LeNet().to(device)
lossf = nn.CrossEntropyLoss() # 交叉熵,输入 logits
opt = torch.optim.Adam(model.parameters(), lr=1e-3) # 想对照 d2l 就换 SGD(lr=0.9)
第 ④ 段 · 训练 + 评估循环(对应 5.2/6.1 节):每个 batch 内部发生的事就是"前向 → 损失 → backward 反传 → step 更新",正是图③的循环体。
for epoch in range(5):
model.train() # 训练模式(本模型无 Dropout/BN,写上是好习惯)
for x, y in train_dl:
x, y = x.to(device), y.to(device)
opt.zero_grad() # 1. 清上一批梯度(PyTorch 梯度默认累加!)
out = model(x) # 2. 前向:形状 (64,10)
loss = lossf(out, y) # 3. 交叉熵标量
loss.backward() # 4. 反向传播:链式法则自动求所有梯度
opt.step() # 5. 优化器按梯度更新权重
model.eval() # 评估模式
correct = 0
with torch.no_grad(): # 推理不需要梯度,省显存提速
for x, y in test_dl:
pred = model(x.to(device)).argmax(1).cpu() # 取得分最大的类
correct += (pred == y).sum().item()
print(f"epoch {epoch+1}/5 test acc = {correct/len(test_ds):.4f}")
# 预期输出:第 1 个 epoch 即 ~0.98,5 个 epoch 稳定在 0.988~0.992
为什么值得 IC 工程师亲手跑:跑完你就拥有了一张"活的"对照表 —— loss.backward() 对应反传硬件的多路梯度数据流,opt.step() 对应权重 SRAM 的读改写,no_grad() 的省显存对应推理芯片不用存激活。下一步把 model(x) 换成你自己的算子,或者用 torch.onnx.export 导出 ONNX 喂给 08-08 的量化工具链,这条链就是"算法→芯片"的真实工作流。
| 超参 | 推荐起点 | 安全范围 | 调大效果 | 调小效果 | 对硬件的含义 |
|---|---|---|---|---|---|
| 学习率 lr | 1e-3(Adam) | 1e-4~1e-2 | 收敛快,过大发散/NaN | 更稳但慢,可能陷平台 | 不进硬件,只进固件/驱动 |
| batch_size | 64 | 16~512 | 梯度稳、吞吐高,泛化略降,显存↑ | 噪声大正则强,利用率低 | 决定 NPU 单次并行宽度与 SRAM 分块 |
| epoch | 5 | 1~20 | 训练损失更低,过拟合风险↑ | 欠拟合 | 决定总计算量 = 单次 MAC × 60k × epoch |
| 激活/池化 | ReLU + MaxPool | — | 换 tanh/AvgPool 可复现 1998 味道(掉点) | — | ReLU=max 电路,是 NPU 激活单元的最爱 |
| 症状 | 最可能原因 | 快速验证 |
|---|---|---|
| acc 卡在 ~10%(瞎猜水平) | 标签错位/损失喂了 softmax 两遍/学习率爆炸 | 打印前 8 个 (pred,label) 对;查 CrossEntropyLoss 输入 |
| acc 卡在 ~11.2% | 数据全是一个类别(打乱失效/索引错) | Counter(y_train) 看类别分布 |
| loss=NaN | lr 太大;或 Normalize 用错统计量产生除零 | lr ÷10 重跑;打印 x.mean()/x.std() |
| CPU 跑太慢 | batch 太小/开了不必要的多线程 | batch 提到 256;DataLoader 加 num_workers |
面试怎么问:①"loss.backward() 前为什么要 zero_grad()?"(PyTorch 梯度累加语义,为梯度累积/大 batch 拆分留机制,不清零 = 把多批梯度加起来);②"torch.no_grad() 做了什么?"(不建计算图、不存中间量 —— 显存从训练口径降到推理口径);③"训练脚本的哪几行最消耗算力?各对应硬件什么单元?"(前向 conv 与 backward 卷积反传 —— MAC 阵列;step() —— 权重存储带宽)。
易错点:①忘 model.eval() —— 本模型没有 Dropout/BN 时恰好无害,换 ResNet 后测试精度随机抖动,极难排查;②忘 shuffle=True,若按类别顺序喂 batch,梯度估计系统性偏差;③把 numpy 张量直接喂模型(device 不匹配报错);④统计精度时忘了 item() 把 0 维张量转 Python 数,累加出一堆张量,最后除出来形状对不上。
是什么:把 LeNet 不当作"识别数字的智能",而当作一张算子执行清单(op graph):卷积 ×2、池化 ×2、逐元素激活 ×4、矩阵乘 ×3、重排 ×1。硬件工程师要回答的问题立刻具体化 —— 每种算子需要什么计算单元?权重/激活各占多少存储?哪种算子吃算力、哪种吃带宽?把 4.1 的逐层表按算子类型聚合,得到下面这张 NPU 设计的直接输入表:
| 算子类型 | 出现次数 | 权重数(存储) | MAC 计算 | 计算/访存比 | 需要什么硬件单元 |
|---|---|---|---|---|---|
| 标准卷积(Conv) | 2 | 2,572(156+2,416) | 357,600(86%) | 高(权重复用 H_out×W_out 次)→ 算力型 | MAC 阵列/乘法器树 + 加法树,line buffer 滑窗,权重常驻 SRAM |
| 全连接/矩阵乘(FC) | 3 | 59,134(96% 总权重) | 58,920(14%) | 低(每权重用 1 次)→ 带宽型 | GEMM 引擎,大权重搬运带宽,无滑窗 |
| 池化(Pool) | 2 | 0 | ≈0(比较/加法) | 纯访存 | 4 输入比较树/加法树,可与卷积融合成流水 |
| 激活(ReLU) | 4 | 0 | ≈0(max) | 纯访存 | 1 个 max 比较器;sigmoid/tanh 需查表/分段线性 |
| Flatten/重排 | 1 | 0 | 0 | 纯地址运算 | 地址生成器/DMA,零计算单元 |
| Softmax+损失 | 1 | 0 | ≈10⁻³ MAC(10 维指数+归一) | 控制流复杂 | 常在 CPU/矢量单元做;端侧 NPU 常不含(推理输出 logits 即可) |
| 合计 | 13 个算子节点 | 61,706 | 416,520 | — | — |
怎么用(这张表直接喂给后续三章):①读表找主要矛盾:86% 的计算在卷积 → PE 阵列按卷积设计(08-07 脉动阵列);96% 的权重在全连接 → 权重缓冲容量与带宽按 FC 压测;池化/激活零参数 → 融合进卷积流水,不落 SRAM;②估算存储:权重 INT8 ≈ 61.7 KB(一片小 SRAM 放得下),C1 输入 28×28×8bit ≈ 6.3 KB,激活峰值 6×28×28×8bit ≈ 37.6 KB —— 128 KB 片上存储即可全图驻留,这正是小 NPU 教学项目的可行边界;③算周期:0.42 MMAC ÷(64 MAC/拍 × 利用率 50%)≈ 13K 拍,@100 MHz ≈ 0.13 ms/张 —— 与 08-07 页的逐层映射表对得上;④进 RTL:把表首列当成 08-11 页控制模块的状态机状态名(Conv/Pool/FC/Done),算子清单就是 Spec 的雏形。
面试怎么问:①"拿到一个 ONNX 模型,你的第一份硬件分析报告包含什么?"(算子直方图、权重/激活存储分布、MAC 分布、计算-访存比分类 —— 即本表);②"为什么 FC 层是带宽问题而卷积层是算力问题?"(复用率决定计算访存比);③"哪些算子可以融合?融合省的是什么?"(卷积+激活+池化,省的是中间激活写回 SRAM/DRAM 的往返 —— 能耗大头在访存);④"Softmax 为什么常丢给 CPU?"(指数/除法不规则,占比极小,专用电路不划算)。
易错点:①只看 MAC 不看访存 —— LeNet 量级无所谓,放大到 ResNet/Transformer 后带宽是第一瓶颈;②权重数与 MAC 数分布"错位"忘了分账(存储按 96% FC 规划、算力按 86% 卷积规划);③把损失/softmax 算进端侧推理预算 —— 部署时输出 logits 给 CPU 就行;④忘了 dtype:61.7K 参数是个数,61.7 KB 才是存储,FP32 直接 ×4。
本页知识点以 LeCun 1998 年 LeNet-5 原论文为事实基准,结构讲法参考《动手学深度学习》与 PyTorch 官方教程,视频直觉推荐 3Blue1Brown 与「跟李沐学AI」。以下链接均经核实可访问。