3. 卷积计算核心:im2col算法、Winograd算法、FFT在卷积中的应用对比

做卷积加速器这么多年,我见过不少团队在算法选型上栽跟头。说白了,im2col、Winograd、FFT这三兄弟,各有各的脾气。选对了,事半功倍;选错了,流片回来只能干瞪眼。

今天咱们就掰开揉碎了聊聊,这三种算法到底该怎么选、怎么用。

3.1 im2col算法:最朴实的暴力美学

im2col,名字就告诉你它想干嘛——image to column。把卷积窗口拉成列向量,然后扔给矩阵乘法去算。

你想想看,卷积本来是个四重循环:遍历输出通道、输入通道、行、列。im2col把它变成了两个步骤:

  1. 数据重排:把每个卷积窗口内的数据,按行优先拉成一列
  2. 矩阵乘法:用GEMM(通用矩阵乘法)搞定剩下的计算

我在项目中遇到过,有些团队一上来就怼im2col,觉得简单粗暴。确实,它的优点是:

  • 实现简单:核心就是数据搬运 + GEMM调用
  • 硬件友好:矩阵乘法有成熟的加速方案(Systolic Array、脉动阵列)
  • 通用性强:任意卷积参数都能用,不用改算法

但坑也不少。我曾经在一个边缘设备项目里用im2col,结果发现内存爆炸了。为什么?

内存膨胀问题:im2col会把输入特征图复制多份。假设输入是C×H×W,卷积核是K×K,输出通道是M,那么im2col后的矩阵大小是 (K×K×C) × (H_out×W_out)。对于ResNet-50的第一层,这个膨胀因子能达到几十倍。

嗯,这里要注意。如果你的芯片片上SRAM有限,im2col可能直接把缓存撑爆。我建议的做法是:

  • 对于小卷积核(3×3、5×5),谨慎使用im2col
  • 对于大卷积核(7×7以上),im2col反而有优势,因为数据复用率高
  • 实在要用,考虑分块(tiling)策略,别一次性展开整个特征图

3.2 Winograd算法:小卷积核的加速利器

Winograd算法,说白了就是用更多的加法换更少的乘法。在硬件里,乘法器比加法器贵得多,所以这个交易很划算。

核心思想是这样的:对于F(2×2, 3×3)的Winograd变换,原本需要3×3=9次乘法,变换后只需要4次乘法。节省了55%的乘法量。

我个人的习惯是,在3×3卷积占主导的网络(比如ResNet、MobileNet)里,优先考虑Winograd。为什么?

Winograd的优势:
  • 乘法量减少:F(2×2, 3×3)节省55%,F(4×4, 3×3)节省67%
  • 适合小卷积核:3×3是Winograd的甜蜜点
  • 计算精度可控:用整数Winograd可以避免浮点误差

但Winograd不是银弹。我记得有一次,团队里的小伙子兴冲冲地把所有卷积都换成Winograd,结果性能反而下降了。为什么?

因为Winograd有它的适用条件:

  • 输出块大小有限:F(m×m, r×r)中,m一般取2、4、6。m越大,变换矩阵越大,额外开销越高
  • 变换开销:输入和输出都需要做变换,这部分计算不能忽略
  • 数值精度:浮点Winograd在高位宽下没问题,但低位宽(INT8以下)可能引入误差
避坑指南:我曾经在INT4量化模型里用Winograd,结果精度掉了2个点。后来发现是变换矩阵的数值范围太大,导致量化误差放大。解决方案是用Cooperative Winograd或者做变换矩阵的缩放。

在硬件实现上,Winograd需要额外的变换单元。我建议:

  • 如果芯片面积充裕,可以单独设计Winograd变换引擎
  • 如果面积紧张,可以用通用ALU来模拟变换,但要注意吞吐量匹配
  • 对于3×3卷积,F(4×4, 3×3)是性能和面积的较好折中

3.3 FFT在卷积中的应用:大卷积核的王者

FFT做卷积,原理很简单:时域卷积等于频域相乘。把输入和卷积核都做FFT,点乘,再IFFT回来。

你想想看,对于大卷积核,直接卷积的计算量是O(N²×K²),而FFT是O(N²logN)。当K足够大时,FFT的优势就体现出来了。

我在项目中遇到过,有个语音识别的模型用了15×15的大卷积核。直接卷积根本跑不动,换成FFT后,延迟降了10倍。

FFT的适用场景很明确:

卷积核大小 推荐算法 原因
1×1 直接卷积/矩阵乘 没有数据复用,im2col最直接
3×3 Winograd 乘法量减少最明显
5×5 Winograd或im2col 看具体硬件资源
7×7及以上 FFT 计算复杂度优势明显

但FFT也有它的麻烦:

  • 复数运算:硬件实现复数乘法比实数复杂得多
  • 边界效应:需要做重叠保留法或重叠相加法,增加额外开销
  • 小卷积核不划算:对于3×3,FFT的变换开销比直接计算还大
注意:FFT在硬件里实现,我建议用定点FFT而不是浮点。定点FFT的位宽要留够余量,否则频谱泄漏会严重影响精度。我一般会留2-3个bit的guard bit。

3.4 三种算法的对比与选型

说了这么多,到底该怎么选?我画了一张图,帮你理清思路:

卷积算法选型决策树 卷积核大小? 3×3 或 5×5 7×7 及以上 硬件乘法器资源? 乘法器充足 → Winograd 乘法器紧张 → im2col + GEMM 实时性要求? 高实时性 → FFT 低实时性 → im2col + 分块 总结:没有银弹,根据卷积核大小和硬件资源灵活选择

我个人总结了一个简单的选型原则:

  1. 3×3卷积:优先Winograd。如果乘法器不够,用im2col+分块
  2. 5×5卷积:看情况。Winograd和im2col差距不大,选实现简单的
  3. 7×7及以上:FFT是首选。如果实时性要求高,必须用FFT
  4. 1×1卷积:别折腾,直接矩阵乘法
我的经验:在实际项目中,我通常会做一个混合方案。比如ResNet-50,前几层用im2col(因为特征图大,卷积核小),中间层用Winograd(3×3密集),最后几层用FFT(特征图小,但通道数多)。这样能最大化利用每种算法的优势。

嗯,最后说一句。算法选型没有标准答案,关键是要理解你的硬件约束和应用场景。我见过太多团队,算法选得漂亮,但硬件实现时发现面积、功耗、延迟全都不达标。所以,做决定之前,先问问自己:我的芯片能承受多少乘法器?我的片上内存有多大?我的实时性要求是多少?

想清楚这些,你自然就知道该选谁了。

可可精品资料库