学习初稿 · 第 18 课。 本篇已开放网页阅读,用于先建立完整认识。文中手算值和示意记录不代表真实训练成绩;真实数据训练、预训练权重和板端测试以本篇具体说明为准,本篇已附代码练习,实际验证范围见文末与包内记录。
模型更小,为什么不一定更快
参数数量告诉我们需要存储多少可学习数值,不能独立决定推理延迟。中间激活、内存访问、算子实现、并行能力和输入分辨率都影响执行。一个参数很少但包含许多小操作的模型,可能因调度和数据搬移而变慢。
因此,轻量化首先要明确约束:内存不够、单帧延迟过高、功耗过大,还是吞吐不够?它们需要的优化未必相同。
手算一个普通卷积
忽略偏置,卷积核大小 K、输入通道 Cin、输出通道 Cout,对应参数数为 K²×Cin×Cout。每个输出位置都应用这些运算,乘加次数约为 Hout×Wout×K²×Cin×Cout。
将一次乘法加一次加法记为一次 MAC;某些资料把它记成两次 FLOPs,比较前先统一统计口径。以下例子使用 3×3、Cin=16、Cout=32、输出 32×32:
|
深度可分离卷积先在每个输入通道内做空间卷积,再用 1×1 卷积混合通道。这里得到 656 个参数,相比 4608 显著减少。但它改变了结构和表达方式,不能把已训练普通卷积随意替换后就期待输出不变。MobileNet 原论文
激活可能比权重更占地方
形状 [1,32,224,224] 的 float32 特征图约占 1×32×224×224×4 字节,即约 6.42 MB 十进制存储。一次计算可能同时保留多个中间张量,训练还要保存反向传播需要的信息。
输入宽高各缩小一半,空间位置减少到四分之一,但目标细节也可能丢失。对于小目标,盲目降低分辨率可能使任务本身无法完成,所以资源优化必须连同质量评估进行。
减少参数、剪枝、蒸馏和量化分别作用于不同方面。非结构化零权重若没有稀疏执行支持,未必减少实际耗时;剪掉整个通道会改变结构,需要正确重建后续层并评估质量。
延迟与吞吐要分开测
单张输入的端到端延迟包括预处理、传输、模型和后处理。批量吞吐关注单位时间完成多少张,可能通过增大 batch 提升,但也增加排队和内存,不一定适合实时摄像头。
测量前固定输入尺寸、数值精度、batch、线程数、设备模式和功耗策略。先预热再记录多次耗时,报告中位数与高分位,并注明是否包含模型加载。异步加速后端需要使用其同步或事件计时机制,否则计到的可能只是提交任务的时间。
还应观察长时间运行:温度上升后降频、内存碎片、输入队列堆积都可能改变表现。桌面 CPU 上的排序不能直接推导 NPU 上的排序,尤其当某些算子回退到 CPU 时。
如何选择一个候选模型
先以现有小 CNN 建立基线,再选择一个轻量结构做相同任务对照。比较验证质量、峰值内存、单帧延迟和实际算子支持。预训练模型与从零训练模型需要分别标明,避免把训练条件变化隐藏在“网络更轻”之后。
本课的手算只说明理论计算差异,没有声称某个板卡达到特定 FPS。设备测试放到最后的部署验收课,那里会明确输入、后端和时间边界。
练习
- FP32 权重 100 万个,仅权重理想大小是多少?
- batch=8 的吞吐提高,是否表示单个请求延迟必然降低?
- 为什么深度可分离卷积理论 MAC 更少,仍需在目标设备测量?
参考答案
约 4 MB 十进制,不含元数据与激活。吞吐和延迟不同,批处理可能带来等待。硬件内核、内存带宽、调度及回退会影响实际速度,理论计数没有覆盖这些成本。
下一课开始从整图分类走向目标检测:同一张图里可能有多个目标,输出也随之改变。
配套代码练习
实验:普通卷积与深度可分离卷积成本
查看完整代码:main.py
"""普通卷积与深度可分离卷积成本。运行方式与练习见 README.md。"""
cin,cout,k,h,w = 32,64,3,32,32
regular = cin*cout*k*k
separable = cin*k*k+cin*cout
print('不含 bias 参数:',regular,separable)
print('MACs:',regular*h*w,separable*h*w)
print('参数比例:',separable/regular)
print('FP32 输出激活字节:',cout*h*w*4)
assert regular == 18432 and separable == 2336
assert regular*h*w == 18874368
# MAC 一次乘加按一个单位;若将乘与加各算一次,FLOPs 约为两倍。
在项目根目录执行;下载包解压后,在解压目录执行 python3 main.py。
cd 'content/posts/AI-深度学习/从零搭建视觉模型/18-轻量模型与计算成本/code' && python3 main.py
本篇已附独立实验包,在代码卡片中查看源码、下载或复制运行命令。解压后执行 python3 main.py;依赖安装见包内 README.md。
观察目标: 普通/可分离参数为 18432/2336;32×32 输出下普通卷积为 18874368 MACs。
动手修改: 保持通道不变将分辨率翻倍,再将通道翻倍,分别观察参数量和 MACs;解释为何这还不能预测设备延迟。
所有输入均在本篇代码中构造;运行范围和验证记录见包内 verification.md。
