八. 实战:CUDA-BEVFusion部署分析-学习spconv的优化方案(Explicit GEMM conv)

news2025/1/25 9:15:18

目录

    • 前言
    • 0. 简述
    • 1. 什么是Explicit GEMM Conv
    • 2. im2col
    • 3. spconv是如何使用Explicit GEMM Conv的
    • 4. 使用Explicit GEMM Conv处理spconv的优缺点
    • 5. 拓展-conv加速
      • 5.1 Introduction
      • 5.2 im2col
      • 5.3 Forward graph
      • 5.4 Backward graph
      • 5.5 Python example for forward propagation
      • 5.6 Python example for backward propagation
      • 5.7 Im2col and Col2im sources in python
      • 5.8 Smaller example
    • 总结
    • 下载链接
    • 参考

前言

自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》,链接。记录下个人学习笔记,仅供自己参考

本次课程我们来学习下课程第八章——实战:CUDA-BEVFusion部署分析,一起来学习 spconv 的优化方案(Explicit GEMM conv)

Note:之前在学习杜老师的课程中有简单记录过 Sparse Convolution 的一些基础知识,感兴趣的可以看下:复杂onnx解决方案(以sparseconv为例)

课程大纲可以看下面的思维导图

在这里插入图片描述

0. 简述

本小节目标:理解 im2col,Explicit GEMM conv 是什么,以及理解 spconv 中使用 Gather 和 ScatterAdd 做优化的用意是什么

这节给大家讲解第八章节第 4 小节,学习 spconv 的优化方案,那这一小节我们从 Explicit GEMM Conv 显式 GEMM Conv 方式去看 spconv 是怎么优化的

1. 什么是Explicit GEMM Conv

在学 spconv 优化方案之前我们得先去理解 Explicit GEMM Conv 是什么,那要去理解 Explicit GEMM Conv 之前我们得先去理解 im2col 是什么,一层一层递进,

首先我们看一下 GEMM 是什么,GEMM 全称是 GEneral Matrix Multiplication 通用矩阵乘法,我们在第二章 CUDA 中也有涉及到,那 Explicit GEMM Conv 就是显式矩阵乘法卷积,通常指把 conv 计算的输入输出通过 im2col 的方式转换成 matrix 来进行矩阵乘法,可以高效的实现卷积(CUDA 中常用的方法,因为 CUDA 非常擅长 MxN=P 的矩阵乘法)

Conv 卷积操作过程中有权重,有输入输出维度,且维度可以是多维。我们在 CUDA、cuDNN、cuTLASS 去做卷积的时候,内部实际的计算其实是一个叫 im2col 的方式

我们可以把卷积的 input 输入铺平成一个二维的 Matrix 矩阵,这是因为任何维度的东西可以按照某一个方向,某一个顺序展开成一个二维的。比如三维的 CxHxW 可以变成二维的,四维的 NxCxHxW 也可以变成二维的

那同理 conv 的权重也是可以铺平成二维矩阵的,卷积的 weight 维度一般是 C_outxC_inxK_hxK_w,其中 Kh 和 Kw 为 Kernel 的高和宽,四个维度如果我们按照一定的方式去展开的话也是可以变成一个二维的

那所以说我们通过 im2col 可以把 conv 的输入和权重都变成二维的 Matrix,那变成二维之后,这个其实就是开始变成 CUDA 比较擅长的领域,也就是矩阵乘法

那矩阵乘法我们在第二章中讲过可以使用 shared memory 等多种优化方式,那所以就意味着我们最终需要做的是什么呢,就是把 tensor 级别上的卷积转换成 MxN=P 这么一个矩阵乘法去做计算,那这个其实就是 Explicit GEMM Conv

下面是来自 ChatGPT 关于 Explicit GEMM Conv 的解释:

卷积操作在本质上是一种多维数组的加权求和操作,然而,卷积操作本身在计算上可能并不高效,特别是在计算机硬件上执行时。为了克服这一问题,卷积操作可以被重新构造为矩阵乘法操作,这一方法在效率上可以得到很大的提升

因此,Explicit GEMM Conv 就是指通过将输入数据和卷积核重排成适合矩阵乘法的形式,然后利用 GEMM 算法来执行卷积操作

2. im2col

im2col 字面的意思是 image to column 即将图像转换为列,这个过程涉及将输入图像数据转换为列矩阵的形式,以便可以通过通用矩阵乘法(GEMM)来实现卷积操作。

使用 im2col 方法,我们可以将图像中每个卷积窗口的像素展开成一个列向量。将这些列向量堆叠起来,我们就可以得到一个大的矩阵,其中每列代表一个卷积窗口。然后卷积核也被展开成一个行向量,并与上述矩阵进行乘法运算,实现了卷积操作。

这样,原本复杂的卷积操作就被转换成了两个矩阵之间的乘法,这是现代计算硬件(特别是使用 cuBLAS 库)擅长的操作

OK,下面我们来看 im2col 的案例,看一下卷积是怎么一步步变成矩阵乘法运算的

我们先来看一个最简单的案例,如下图所示:

在这里插入图片描述

ic=oc=1的情形

在上图中我们的输入 input 维度是 1x5x5,filter 维度是 1x1x3x3,输出 output 维度是 1x3x3,stride 等于 1,padding 等于 0,我们先不看 batch 维度

从下图中可以看出卷积从头到尾 filter 一共需要滑动 9 次,每滑动一次 input 里面的 9 个元素都需要跟 filter 中的 9 个元素做一个乘加,得到一个输出数据

在这里插入图片描述

ic=oc=1的情形

那这也就意味着整个计算其实等价于一个 1x9 乘以一个 9x1 的矩阵乘法,那么所以说我们可以把这个计算换成下图这个样子:

在这里插入图片描述

ic=oc=1的情形

图中上面是 convolution,下面我们把它展平,我们把 filter 中的 9 个数据给它展开成一行,同理把 input 中每次滑动窗口的 9 个数据按列展开,那么它就变成了一行乘以一列,这样就得到了输出 9 个数据中的第 1 个数据,对于输出我们也将它按行展开

以上就是 filter 在 input 中滑动第一次的一个计算,那么同理第二次的计算中,filter 本身是不变的,变的是 input 中跟 filter 相乘的那一块,那么依此类推 filter 滑动 9 次之后就得到了完整的输出。那通过这样一个过程我们就可以把 conv 的计算等价成一个 1x9 乘以 9x9 的矩阵乘法

OK,我们理解了这个之后我们再稍微复杂一点,我们把 input 的 channel 设置成 2,那么 filter 的输入 channel 也就是 2,那么整个过程就变成了下面的样子:

在这里插入图片描述

ic=n,oc=1的情形

filter 它之前不是 9 个数据吗,那么现在我们其实有两个 channel,因此它按行展开就有 18 个数据,同理 input 中滑动窗口的 18 个数据需要在列上做展开,那么最终二者计算得到输出中的一个数值,如下图所示:

在这里插入图片描述

ic=n,oc=1的情形

那 filter 它最终滑动了多少次呢,那还是 9 次,这也就意味着 input 的宽度还是 9,不同的是高度变成了 18

那么最后我们把所有数据全展平后我们就得到一个 18x9 的矩阵,那整个 conv 过程就是 1x18 乘以 18x9 得到 1x9 的矩阵运算,那 output 的大小不变,只不过参与计算的数据变多了

OK,我们再复杂化一点,我们把 filter 的个数增加,增加成 3 个,那整个过程就变成了下面的样子:

在这里插入图片描述

ic=n,oc=m的情形

如果说只有一个 filter 我们按行展开就行了,那现在多个 fiter 意味着每个 filter 就是一行,最终展开变成了如下的样子:

在这里插入图片描述

ic=n,oc=m的情形

从图中我们能看出不再是 vector 和 matrix 相乘了,而是 matrix 和 matrix 相乘,这是因为我们的 filter 也变成了一个矩阵,维度是 3x18,与 input 中的 18x9 维的数据相乘得到最终的 output 输出

以上就是 im2col 的简单分析了,下面我们从公式的角度来分析下

我们规定下:

  • I C IC IC I H IH IH I W IW IW 是 input 的 c c c h h h w w w 的大小
  • K H KH KH K W KW KW 是 kernel 的 h h h w w w 的大小
  • O C OC OC O H OH OH O W OW OW 是 output 的 c c c h h h w w w 的大小

那么 filter 每滑动一次时,input 中与 filter 参与计算的数量是:

  • I C × K H × K W = 18 IC\times KH\times KW =18 IC×KH×KW=18

filter 滑动的次数是:

  • ( I H − K H + s t r i d e ) × ( I W − K W + s t r i d e ) = 9 (IH-KH+stride)\times(IW-KW+stride)=9 (IHKH+stride)×(IWKW+stride)=9

filter 的个数是:

  • O C OC OC

在这里插入图片描述

由此可见,我们是可以把参与 conv 计算的激活值、权重以及输出给放在一个矩阵里,用矩阵乘法的方式去计算 conv 即 M × N = P M\times N=P M×N=P

M M M 激活值的大小:

  • O C × ( I C × K H × K W ) OC\times (IC\times KH\times KW) OC×(IC×KH×KW)

N N N 权重的大小:

  • ( I C × K H × K W ) × ( O H × O W ) (IC\times KH\times KW) \times (OH\times OW) (IC×KH×KW)×(OH×OW)

P P P 输出的大小:

  • O C × ( O H × O W ) OC\times (OH\times OW) OC×(OH×OW)

那其实我们在做计算的时候就非常适合 CUDA 加速了,这个模式大家可能都已经看过很多遍了,就是每一个线程负责一个点,那每一个点其实就是利用 shared memory 做一个乘加

Explicit GEMM Conv 就是显性的分配额外的空间用来做 im2col 的处理,将 N 维的数据转为 2 维,并用优化过的矩阵乘法算法来加速

3. spconv是如何使用Explicit GEMM Conv的

谈到 spconv 的加速我们不得不聊一个 repo,就是 https://github.com/traveller59/spconv

这个 repo 实现的 spconv 是相当不错的,现在都有很多人在用它去加速,它这里面采用的方式有 Explicit GEMM Conv,对稀疏矩阵乘法做了很大程度上的加速,最新的 spconv2.3 已经支持 int8

在这里插入图片描述

我们可以看到这里面作者其实就是充分利用了 CUDA 的一些特性去做一个加速,那现在整个更新已经到 CUDA-12.0 了,还是比较新的,我们可以直接用 pip 方式去安装

我们可以看他现在的 spconv 已经更新到 2.3,可以支持 int8 的量化,那里面有一些使用案例,大家感兴趣的可以看下

同时我们之前不是做了 CenterPoint 的环境搭建吗,那 CenterPoint 中 SCN 网络的 spconv 加速其实就是用它这个库来进行加速的

在这里插入图片描述

所以由此可见这个 spconv 它影响力还是比较大,那大家安装之后可以根据它的 README example 先跑一跑一些示例,看看里面的 spconv 加速是怎么实现的

OK,我们下面再来看 spconv 是如何使 Explicit GEMM Conv 做加速的,官方提供了一个文档用来说明 spconv 算法的加速,具体在 spconv/docs/spconv2_algo.pdf 中,我们一起来看看他是怎么做的

在这里插入图片描述

这里面的 input 输入数据是 5x5 的,filter 是 3x3 的,其中 input 的 25 个数据只有 5 个数据是有效的,其它全为 0,那它来跟这个 filter 来做计算得到一个 output,其中 output 中有 6 个有效数据

那么 filter 在这里通过滑动窗口来进行计算,那滑动窗口中只要有一个数据在 filter 里面,也就是 input 滑动窗口中只要有一个数据被 filter 盖住了,那它就参与计算

那这里我们可以看到 filter 有 6 次 的滑动会捕捉到信息,那这里面有个点大家不能忽视,就是我们要对 input 和 filter 通过 im2col 展开,那展开的话其实我们能发现 input 中存在大量的无效数据也就是 0

也就是说我们转换成矩阵运算之后,虽然可以把很多计算给省掉,但是依旧存在很多无用的计算,主要原因在于我们的 input 是稀疏的,转换成的矩阵也是稀疏的

那所以作者就说 Too much zeros!,这里面的 0 太多了,我们需要想办法把这些 0 给去除掉

那作者是怎么做的呢,这里面主要是两个步骤即 Gather 和 ScatterAdd,就是用 Gather 和 Scale 的方式来做一个压缩和扩充,如下图所示:

在这里插入图片描述

我们每一列可能 0 特别多,那么我们就可以通过 Gather 把里面的数据给压缩,把一列中所有的 0 全给去除掉就只保留没有 0 的部分,比如图中 5 个数据有 2 个是 0,通过 Gather 我们就可以把这 2 个 0 给去除掉

接着我们让它再跟权重去做计算,那无用的计算就会少很多,通过这种方式得到输出值。此外我们还需要保证输入和输出的维度大小一致,因此我们还要把之前删除的 0 再加上去,通过 ScatterAdd 的方式来完成

所以总结下来 traveller59 作者

traveller 提供的解决方案是使用 Gather 和 ScatterAdd。**通过 Gather 将 input 中参与计算但没有意义的 0 点数据去除掉,**只留下有用的数据,这样 GEMM 的计算会变得更加 Dence,GEMM 计算完了以后,再通过 ScatterAdd 将 0 点添加进去。那这个就是作者提供的用 Explicit GEMM Conv 做 spconv 加速的一个方案

这里博主有些困惑,那在 filter 的 9 次滑动中其实 6 次是有效的,但不知道为什么图中只计算了 5 次,博主按照自己的理解,绘制了一个草图,描述了整个过程,如下图所示:

在这里插入图片描述

4. 使用Explicit GEMM Conv处理spconv的优缺点

OK,我们讲完 Explicit GEMM Conv 之后,我们思考一下它有什么缺点,我们说 Explicit 代表显性的意思,那显性去做 im2col 就意味着我们需要分配额外的空间,那额外的开销它其实是不能忽略的,在某种意义上是会产生很大的延迟的

那这部分其实我们也需要去考虑是否能够去除掉,因为它导致会有很多没有必要的 memory R/W,那这个地方就很容易成为瓶颈,那这个就是显式做 GEMM Conv 的一个问题

那有了这个问题,那么肯定就会有很多人去想怎么去解决它,那么所以有一个相应的概念叫做 Implicit GEMM Conv,隐式的 GEMM Conv

在这里插入图片描述

这里先做个预告,如上图所示,我们可以看下显式 GEMM Conv 把 N 维的 tensor 给转换成矩阵,然后通过 GEMM 计算得到一个值,那这个是显式的

那隐式是怎么做的呢,隐式我们可以发现它没有通过 im2col 这个操作去生成矩阵,那它直接就是在 tensor 这个维度上去做矩阵乘法

矩阵乘法中的每一个数据我们不再给给它分配空间了,我们直接通过索引的方式去寻找这个矩阵方法所需要的计算点到底对应 tensor 中的哪一个点,那这样通过索引的方式去寻找计算点可以直接跳过 im2col 这个步骤

那这个方式其实也是 spconv 可以选择的一种加速方式,那这部分具体怎么做,我们下节课再给大家讲解

OK,本次课程到这里就结束了

5. 拓展-conv加速

以下内容翻译自 https://leonardoaraujosantos.gitbook.io/artificial-inteligence/machine_learning/deep_learning/convolution_layer/making_faster

5.1 Introduction

这里我们将展示一种将卷积运算转换为矩阵乘法的方法。这种方法的优点是计算速度更快,但内存使用量更大。我们采用 im2col 操作,将输入图像转换为矩阵,然后将该矩阵与重塑后的 Kernel 相乘。最后我们再通过 col2im 运算将乘积矩阵重塑为图像

5.2 im2col

按照正常方式我们需要使用大量 for 循环来实现卷积,虽然这有助于理解,但速度不够快,这里我们将学习如何以矢量化方式实现卷积。

首先,如果我们仔细观察,卷积运算基本上是 Kernel 与移动窗口选择的局部区域之间的点积,而移动窗口选择的局部区域大小与我们的 Kernel 相同,如果我们在内存中扩展所有可能的窗口,并将点乘作为矩阵乘法来执行,会发生什么情况?答案是速度提高 200 倍或更多,但会消耗更多内存

在这里插入图片描述

比如,如果输入值为 [227x227x3],需要用步长为 4、填充为 0 的 11x11x3 滤波器进行卷积,那么我们就需要在输入值中提取 [11x11x3] 个像素块,然后将每个像素块拉伸为大小为 11*11*3=363 的列向量

以输入 227 计算,步长为 4,填充为 0,则宽度和高度上都有 ((227-11)/4)+1=55 个位置,从而得到大小为 [363x3025] 的输出矩阵 X_col。在这里,每一列都是一个拉伸的感受野,总共有 55*55=3025 个

总结一下我们如何计算 im2col 的输出大小:

[img_height, img_width, img_channels] = size(img);
newImgHeight = floor(((img_height + 2*P - ksize) / S)+1);
newImgWidth  = floor(((img_width  + 2*P - ksize) / S)+1);        
cols = single(zeros((img_channels*ksize*ksize),(newImgHeight * newImgWidth)));

CONV 层的权重也同样被拉伸成行。例如,如果有 96 个大小为 [11x11x3] 的滤波器,那么矩阵 W_row 的大小为 [96x363],其中 11x11x3=363

在这里插入图片描述

图像和 Kernel 转换成矩阵后,卷积可以通过简单的矩阵乘法实现,在我们的例子中就是 W_col[96x363] 乘以 X_col[363x3025],得到一个矩阵 [96x3025],需要将其重塑为 [55x55x96]

最后的重塑也可也通过一个名为 col2im 的函数来实现

值得注意的是,im2col 的某些实现会将结果转置,如果是这种情况,则必须改变矩阵乘法的顺序

在这里插入图片描述

5.3 Forward graph

为了帮助我们使用 im2col 进行卷积,并推导出反向传播,我们以图形的形式展示了使用 im2col 进行的卷积,如下图所示。这里的输入张量是单一的 3 通道 4x4 图像,它将进入一个卷积层,卷积层的 S:1 P:0 K:2 F:1(输出量)

在这里插入图片描述

5.4 Backward graph

使用 im2col 技术,计算图与 FC 层详细,有着相同的公式 f ( x , θ , β ) = ( x . θ T ) + β f(x,\theta,\beta)=(x.\theta^T)+\beta f(x,θ,β)=(x.θT)+β,不同的是现在我们有了 reshape、transpose、im2col 块

关于反向传播过程中的 reshape 和 transpose,你只需要使用另一种 reshape 或 transpose 来反转它们的操作即可,需要注意的是,如果在前向传播过程中使用的是行为主的 reshape,那么在反向传播过程中也需要使用行为主的 reshape

唯一需要注意的是 im2col 的反向传播操作,它不能使用简单的 reshape 来实现,因为 patches 实际上可能会重叠(取决于步长),因此需要对 patches 相交处的梯度求和

在这里插入图片描述

5.5 Python example for forward propagation

def conv_forward_naive(x, w, b, conv_param):
  """
  A naive implementation of the forward pass for a convolutional layer.

  The input consists of N data points, each with C channels, height H and width
  W. We convolve each input with F different filters, where each filter spans
  all C channels and has height HH and width HH.

  Input:
  - x: Input data of shape (N, C, H, W)
  - w: Filter weights of shape (F, C, HH, WW)
  - b: Biases, of shape (F,)
  - conv_param: A dictionary with the following keys:
    - 'stride': The number of pixels between adjacent receptive fields in the
      horizontal and vertical directions.
    - 'pad': The number of pixels that will be used to zero-pad the input.

  Returns a tuple of:
  - out: Output data, of shape (N, F, H', W') where H' and W' are given by
    H' = 1 + (H + 2 * pad - HH) / stride
    W' = 1 + (W + 2 * pad - WW) / stride
  - cache: (x, w, b, conv_param)
  """
  out = None
  pad_num = conv_param['pad']
  stride = conv_param['stride']
  N,C,H,W = x.shape
  F,C,HH,WW = w.shape
  H_prime = (H+2*pad_num-HH) // stride + 1
  W_prime = (W+2*pad_num-WW) // stride + 1
  out = np.zeros([N,F,H_prime,W_prime])
  #im2col
  for im_num in range(N):
      im = x[im_num,:,:,:]
      im_pad = np.pad(im,((0,0),(pad_num,pad_num),(pad_num,pad_num)),'constant')
      im_col = im2col(im_pad,HH,WW,stride)
      filter_col = np.reshape(w,(F,-1))
      mul = im_col.dot(filter_col.T) + b
      out[im_num,:,:,:] = col2im(mul,H_prime,W_prime,1)
  cache = (x, w, b, conv_param)
  return out, cache

5.6 Python example for backward propagation

def conv_backward_naive(dout, cache):
  """
  A naive implementation of the backward pass for a convolutional layer.

  Inputs:
  - dout: Upstream derivatives.
  - cache: A tuple of (x, w, b, conv_param) as in conv_forward_naive

  Returns a tuple of:
  - dx: Gradient with respect to x
  - dw: Gradient with respect to w
  - db: Gradient with respect to b
  """
  dx, dw, db = None, None, None

  x, w, b, conv_param = cache
  pad_num = conv_param['pad']
  stride = conv_param['stride']
  N,C,H,W = x.shape
  F,C,HH,WW = w.shape
  H_prime = (H+2*pad_num-HH) // stride + 1
  W_prime = (W+2*pad_num-WW) // stride + 1

  dw = np.zeros(w.shape)
  dx = np.zeros(x.shape)
  db = np.zeros(b.shape)

  # We could calculate the bias by just summing over the right dimensions
  # Bias gradient (Sum on dout dimensions (batch, rows, cols)
  #db = np.sum(dout, axis=(0, 2, 3))

  for i in range(N):
      im = x[i,:,:,:]
      im_pad = np.pad(im,((0,0),(pad_num,pad_num),(pad_num,pad_num)),'constant')
      im_col = im2col(im_pad,HH,WW,stride)
      filter_col = np.reshape(w,(F,-1)).T

      dout_i = dout[i,:,:,:]
      dbias_sum = np.reshape(dout_i,(F,-1))
      dbias_sum = dbias_sum.T

      #bias_sum = mul + b
      db += np.sum(dbias_sum,axis=0)
      dmul = dbias_sum

      #mul = im_col * filter_col
      dfilter_col = (im_col.T).dot(dmul)
      dim_col = dmul.dot(filter_col.T)

      dx_padded = col2im_back(dim_col,H_prime,W_prime,stride,HH,WW,C)
      dx[i,:,:,:] = dx_padded[:,pad_num:H+pad_num,pad_num:W+pad_num]
      dw += np.reshape(dfilter_col.T,(F,C,HH,WW))
  return dx, dw, db

5.7 Im2col and Col2im sources in python

该实现将接收三维张量 [channels, rows, cols],并创建二维矩阵 [rows=(new_h*new_w), cols=(kw*kw*C)],注意该算法将输出上图的转置版本

def im2col(x,hh,ww,stride):

    """
    Args:
      x: image matrix to be translated into columns, (C,H,W)
      hh: filter height
      ww: filter width
      stride: stride
    Returns:
      col: (new_h*new_w,hh*ww*C) matrix, each column is a cube that will convolve with a filter
            new_h = (H-hh) // stride + 1, new_w = (W-ww) // stride + 1
    """

    c,h,w = x.shape
    new_h = (h-hh) // stride + 1
    new_w = (w-ww) // stride + 1
    col = np.zeros([new_h*new_w,c*hh*ww])

    for i in range(new_h):
       for j in range(new_w):
           patch = x[...,i*stride:i*stride+hh,j*stride:j*stride+ww]
           col[i*new_w+j,:] = np.reshape(patch,-1)
    return col
def col2im(mul,h_prime,w_prime,C):
    """
      Args:
      mul: (h_prime*w_prime*w,F) matrix, each col should be reshaped to C*h_prime*w_prime when C>0, or h_prime*w_prime when C = 0
      h_prime: reshaped filter height
      w_prime: reshaped filter width
      C: reshaped filter channel, if 0, reshape the filter to 2D, Otherwise reshape it to 3D
    Returns:
      if C == 0: (F,h_prime,w_prime) matrix
      Otherwise: (F,C,h_prime,w_prime) matrix
    """
    F = mul.shape[1]
    if(C == 1):
        out = np.zeros([F,h_prime,w_prime])
        for i in range(F):
            col = mul[:,i]
            out[i,:,:] = np.reshape(col,(h_prime,w_prime))
    else:
        out = np.zeros([F,C,h_prime,w_prime])
        for i in range(F):
            col = mul[:,i]
            out[i,:,:] = np.reshape(col,(C,h_prime,w_prime))

    return out
def col2im_back(dim_col,h_prime,w_prime,stride,hh,ww,c):
    """
    Args:
      dim_col: gradients for im_col,(h_prime*w_prime,hh*ww*c)
      h_prime,w_prime: height and width for the feature map
      strid: stride
      hh,ww,c: size of the filters
    Returns:
      dx: Gradients for x, (C,H,W)
    """
    H = (h_prime - 1) * stride + hh
    W = (w_prime - 1) * stride + ww
    dx = np.zeros([c,H,W])
    for i in range(h_prime*w_prime):
        row = dim_col[i,:]
        h_start = (i / w_prime) * stride
        w_start = (i % w_prime) * stride
        dx[:,h_start:h_start+hh,w_start:w_start+ww] += np.reshape(row,(c,hh,ww))
    return dx

5.8 Smaller example

为了让问题变简单,我们以 X[3x3] 与 W[2x2] 的卷积为例进行说明

在这里插入图片描述

总结

这节课程我们学习了 Explicit GEMM Conv 去加速 spconv 的方案,我们首先介绍了 im2col,卷积的计算实际上是可以转换为矩阵乘法运算的,接着我们分析了使用 Explicit GEMM Conv 方法加速 spconv,主要是通过 Gather 和 ScatterAdd 两个步骤来完成的,最后我们分析了 Explicit GEMM Conv 的缺点主要是需要额外分配内存空间,内存的读写非常耗时,因此引出了 Implicit GEMM Conv 的加速方案

OK,以上就是第 4 小节有关 Explicit GEMM conv 优化方案的全部内容了,下节我们将去学习 spconv 另一种优化方案即 Implicit GEMM conv,敬请期待😄

下载链接

  • 论文下载链接【提取码:6463】
  • 数据集下载链接【提取码:data】
  • 代码和安装包下载链接【提取码:cuda】

参考

  • 复杂onnx解决方案(以sparseconv为例)
  • 矩阵乘法的 CUDA 实现、优化及性能分析
  • https://github.com/traveller59/spconv
  • https://leonardoaraujosantos.gitbook.io/artificial-inteligence/machine_learning/deep_learning/convolution_layer/making_faster

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1383504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

研0或研一|如何快速入门深度学习?

一、经验建议 1️⃣课程篇 直接上手B站【小土堆PyTorch深度学习快速入门教程】,共计9h50min左右,预计一周就可以学完,比较偏向理论和实践相结合跟李沐学AI B站【动手学深度学习 PyTorch版】刘二大人B站【PyTorch深度学习实践】,…

Python武器库开发-武器库篇之Whois信息收集模块化(四十五)

Python武器库开发-武器库篇之Whois信息收集模块化(四十五) 我们在进行渗透的时候,需要进行全面的信息收集,除了主动信息收集之外,我们还经常会进行被动信息收集,Whois信息收集就是其中的一种,我们可以利用一些网站进行Whois信息收…

uniCloud 云数据库(1)

目录 1:云数据库入门,基本概念了解 1.1 云数据库是关系型还是Nosql? 1.2 uniCloud 云数据库和关系型数据库的对比 1.3 官方文档传送门 2: 基本操作表 创建 在uniCloud web控制台 进行创建 数据表的3个组成部分 通过传统方式操作数据库 获取集合的引用 集…

完成源示例

本主题演示如何创作和使用自己的完成源类&#xff0c;类似于 .NET 的 TaskCompletionSource。 completion_source 示例的源代码 下面的列表中的代码作为示例提供。 其目的是说明如何编写自己的版本。 例如&#xff0c;支持取消和错误传播不在此示例的范围内。 #include <w…

【Nacos】Nacos 双端版本升级实战手册

背景 由于原来使用的 Nacos 版本&#xff08;1.1.4&#xff09;存在安全漏洞&#xff0c;需要进行升级修复。经过查询后&#xff0c;决定将版本升级到2.2.4。 Nacos 服务共有三个&#xff1a; 192.168.2.190:8848192.168.2.191:8848192.168.2.192:8848 步骤 服务端升级&am…

宝塔安装redis并且远程连接redis教程

第一步&#xff1a;搜索redis并安装 第二步&#xff1a;在防火墙添加端口6379 第三步&#xff1a;查看宝塔防火墙是否开启了6379端口 firewall-cmd --zonepublic --list-ports 很显然并没有开启 第四步&#xff1a;开启防火墙的6379端口 firewall-cmd --zonepublic --add-po…

SLAM第十四讲

基础知识 四元数 先将三维空间的点p(x,y,z) 变成四元数的表示q(0,x,y,z) 其中0为四元数的实部&#xff0c;x,y,z为四元数的虚部。 实部为0的四元数也叫纯虚四元数。 通过 左乘四元数&#xff…

Vue3项目引入canvaskit-wasm库(skia库的wasm版)

1 安装canvaskit-wasm npm install canvaskit-wasm 或者 yarn add canvaskit-wasm 2 将文件node_modules/canvaskit-wasm/bin/canvaskit.wasm复制到public目录 3 引入到组件中 <template><img :src"imgData"/> </template><script setup>…

Redis命令 - Sets命令组常用命令

Set集合&#xff0c;无序&#xff0c;一堆不重复值的组合。利用redis提供的set数据结构&#xff0c;可以存储一些集合性的数据。 使用场景&#xff1a;例如&#xff0c;实现如共同关注、共同喜好、二度好友等 1、SADD key member [member …] 向集合中添加一个或者多个成员 …

顺序表实现(下)(C语言)

几道相关例题,帮助大家更好理解顺序表. 文章目录 前言 一、顺序表二、创建顺序表并初始化三.删除非递减顺序表L中的重复元素四.在非递减顺序表中删除[s,t]之间的元素五.设计算法逆置顺序表L,并将序列L循环左移六.顺序表A和B的元素个数分别为m,n.A表升序排序,B表降序排序,两表中…

四、Qt 的第一个demo

在上一篇章节里《三、Qt Creator 使用》&#xff0c;我们介绍了如何使用Qt Creator创建一个简单的带窗体的demo&#xff0c;在这一章节里&#xff0c;我们详细讲解一下这个demo的文件组成&#xff0c;及主函数&#xff0c;并在UI上加一些控件&#xff0c;实现一些简单的功能。 …

SDRAM小项目——写模块

写模块跟着视频看了一个多星期&#xff0c;一开始始终有点弄不清楚&#xff0c;现在记录一下理解的过程。 阅读文档信息&#xff1a; 首先阅读文档信息&#xff0c;了解SDRAM写过程的状态转换和时序图 SDRAM整体状态流程如图所示&#xff1a; 在SDRAM整体系统中&#xff0c…

谈⼀谈你对TCPIP四层模型,OSI七层模型的理解

TCP/IP四层模型 对比 OSI七层模型 OSI七层模型 为了增强通⽤性和兼容性&#xff0c;计算机⽹络都被设计成层次机构&#xff0c;每⼀层都遵守⼀定的规则。因此有了OSI这样⼀个抽象的⽹络通信参考模型&#xff0c;按照这个标准使计算机⽹络系统可以互相连接 物理层 通过⽹线、光…

南京观海微电子----时序分析基本概念(一)——建立时间

1. 概念的理解 以上升沿锁存为例&#xff0c;建立时间&#xff08;Tsu&#xff09;是指在时钟翻转之前输入的数据D必须保持稳定的时间。如下图所示&#xff0c;一个数据要在上升沿被锁存&#xff0c;那么这个数据就要在时钟上升沿的建立时间内保持稳定。 建立时间是对触发器而…

Akira勒索软件团伙及其策略的全面解析

Sophos MDR威胁情报团队曾于2023年5月发表过一篇博文&#xff0c;称Akira勒索软件“将1988年的时光带回”。起因是Akira会将受害者网站篡改为具有复古美学的页面&#xff0c;让人想起20世纪80年代的绿色屏幕控制台。而且&#xff0c;Akira的名字也可能取自1988年流行的同名动画…

HCIA的访问控制列表ACL

ACL&#xff1a;访问控制列表 -----控制列表&#xff08;策略列表&#xff09; 功能&#xff1a; 1&#xff1a;定义感兴趣的路由&#xff08;控制层面&#xff09; 可以定义感兴趣的路由&#xff0c;可以把感兴趣的路由抓取出来&#xff0c;给它做过滤&#xff0c;也可以改变…

polar CTF CB链

一、题目 二、解答 1、通过jar包&#xff0c;可以看到/user路由下有反序列化操作 看到存在commons-beanutils依赖且版本为1.9.2&#xff0c;可利用CB链Getshell。 使用ysoserial项目中的CommonsBeanutils1链写一个POC&#xff0c;注意确保ysoserial项目中的pom.xml中的comm…

寡年是否适合结婚?寡妇年结婚有什么禁忌吗?让程序来告诉你有多少人是寡妇年结婚的。

什么是寡年&#xff1f; 百度百科 原文&#xff1a;寡年-百度百科 指整年没有“立春”的日子就是“盲年”&#xff0c;俗称寡年。又名滑头年 社会上流传的“寡妇年”&#xff0c;是指整个农历年都没有立春的年份。以农历2005年的鸡年为例&#xff0c;立春在公历2月4日&…

Rust-内存安全

堆和栈 一个进程在执行的时候&#xff0c;它所占用的内存的虚拟地址空间一般被分割成好几个区域&#xff0c;我们称为“段”(Segment)。常见的几个段如下。 代码段。编译后的机器码存在的区域。一般这个段是只读的。bss段。存放未初始化的全局变量和静态变量的区域。数据段。…

java: 5-6 break

文章目录 1. break1.1 介绍1.2 语法和流程图1.3 入门练习1.4 细节说明1.5 练习 【老韩视频p137-】 1. break 看个需求&#xff1a;随机生成 1-100 的一个数&#xff0c;直到生成了 97 这个数&#xff0c;看看你一共用了几次? 【思路分析:循环&#xff0c;但是循环的次数不知道…