为什么有时Depthwise 卷积比正常卷积更耗时

从计算量和内存占用量分析

作者:cs sun
链接:https://www.zhihu.com/question/265434464/answer/306493409
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

首先,Caffe 原先的GPU实现 group convolution 很糟糕,用for循环每次算一个卷积,速度极慢。

其次,cuDNN7.0之后直接支持了group convolution,但本人实测,速度比Github上几个直接写cuda kernel计算的depthwise convolution速度慢。例如对于 n = 128 , c = 512 , h = 32 , w = 32 , g r o u p = 512 n=128,c=512,h=32,w=32,group=512 n=128,c=512,h=32,w=32,group=512的卷积,跑100次,cuDNN7.0里的group convolution需要4秒多,而yonghenglh6/DepthwiseConvolution大概只需要1秒。

但本人分析了一下depthwise conv和标准卷积的理论计算复杂度,举例如下:

这里FLOPs的计算和参数数量的计算参考自https://www.zhihu.com/question/65305385:

对于一个标准卷积层,假设其大小是 h × w × c × n h\times w\times c\times n h×w×c×n,其中 c c c是输入通道数, n n n是输出通道数, h , w h,w h,w分别是卷积核的高度和宽度,输出的特征图大小是 H ′ × W ′ H'\times W' H×W,则该卷积层

N U M p a r a m s = n × ( h × w × c + 1 ) NUM_{params} = n\times (h\times w\times c + 1) NUMparams=n×(h×w×c+1)

N U M F L O P s = H ′ × W ′ × n × ( h × w × c + 1 ) NUM_{FLOPs} = H'\times W' \times n\times (h\times w\times c+1) NUMFLOPs=H×W×n×(h×w×c+1)

F L O P s = H ′ × W ′ × N U M p a r a m s FLOPs=H'\times W'\times NUM_{params} FLOPs=H×W×NUMparams.

  • 卷积1—标准卷积:输入为 64 × 64 × 256 64\times 64\times 256 64×64×256,输出为 64 × 64 × 256 64\times 64\times 256 64×64×256,卷积核大小是 3 × 3 3\times 3 3×3
    参数为 256 × 3 × 3 × 256 = 590 K 256\times 3\times 3\times 256=590K 256×3×3×256=590K,计算量是 64 × 64 × 256 × 3 × 3 × 256 = 2.42 G 64\times 64\times 256\times 3\times 3\times 256=2.42G 64×64×256×3×3×256=2.42G,计算过程的工作集内存总量(输入输出数据+参数)为 64 × 64 × 256 × 2 + 256 × 3 × 3 × 256 = 2.69 M 64\times 64\times 256\times 2+256\times 3\times 3\times 256=2.69M 64×64×256×2+256×3×3×256=2.69M

  • 卷积2—Depthwise Conv:输入为 64 × 64 × 256 64\times 64\times 256 64×64×256,输出为 64 × 64 × 256 64\times 64\times 256 64×64×256,卷积核大小是 3 × 3 3\times 3 3×3
    参数个数为 3 × 3 × 256 = 2.3 K 3\times 3\times 256=2.3K 3×3×256=2.3K,计算量是 64 × 64 × 256 × 3 × 3 = 9.44 M 64\times 64\times 256\times 3\times 3=9.44M 64×64×256×3×3=9.44M,计算过程中的工作集内存总量为 3 × 3 × 256 × 2 + 3 × 3 × 256 = 2.1 M 3\times 3\times 256\times 2 + 3\times 3\times 256=2.1M 3×3×256×2+3×3×256=2.1M

  • 卷积3—标准卷积:输入为 64 × 64 × 16 64\times 64\times 16 64×64×16,输出为 64 × 64 × 16 64\times 64\times 16 64×64×16,卷积核大小是 3 × 3 3\times 3 3×3
    参数个数为 16 × 3 × 3 × 16 = 2.3 K 16\times 3\times 3\times 16=2.3K 16×3×3×16=2.3K,计算量是 64 × 64 × 16 × 3 × 3 × 16 = 9.44 M 64\times 64\times 16\times 3\times 3\times 16=9.44M 64×64×16×3×3×16=9.44M,计算过程中的工作集内存总量是 64 × 64 × 16 × 2 + 16 × 3 × 3 × 16 = 133 K 64\times 64\times 16\times 2+16\times 3\times 3\times 16=133K 64×64×16×2+16×3×3×16=133K

可以看到卷积2肯定比卷积1快,因为计算量下降到了 1 / 256 1/256 1/256,但卷积2实际上无法达到卷积1速度的256倍(我记得我测得结果大概是快10倍左右),因为工作集内存大小并没有显著降低。卷积2也无法达到卷积3的速度,虽然FLOPs相同,但工作集内存大小相差了很多倍,因此单位数据的计算密度小很多,很难充分利用GPU的计算单元。

在传统的卷积层直接加group达到depth-wise的效果

cudnn 7 才开始支持 depthwise convolution,cudnn支持之前,大部分gpu下的实现都是for循环遍历所有group,所以group很多时极慢。
正确的应该用这个https://github.com/yonghenglh6/DepthwiseConvolution
优化:Optimize Deep Learning GPU Operators with TVM: A Depthwise Convolution Example

你可能感兴趣的:(深度学习)