CUDA并行算法系列之FFT快速卷积

卷积定义

在维基百科上,卷积定义为:

离散卷积定义为:

[ 0, 1, 2, 3][0, 1, 2]的卷积例子如下图所示:

Python实现(直接卷积)

根据离散卷积的定义,用Python实现:

def conv(a, b):N = len(a)M = len(b)YN = N + M - 1y = [0.0 for i in range(YN)]for n in range(YN):for m in range(M):if 0 <= n - m and n - m < N:y[n] += a[n - m] * b[m]return y

把数组b逆序,则可以不交叉计算卷积(使用numpyarray[::-1]即可实现逆序):

import numpy as np
def conv2(a, b):N = len(a)M = len(b)YN = N + M - 1y = [0.0 for i in range(YN)]b = np.array(b)[::-1]       # 逆序for n in range(YN):for m in range(M):k = n - M + m + 1;if 0 <= k and k < N:y[n] += a[k] * b[m]return y

测试

可以利用numpy.convolve来检验计算结果的正确性:

if __name__ == '__main__':a = [ 0, 1, 2, 3 ]b = [ 0, 1, 2 ]print(conv2(a, b))print(np.convolve(a, b))

完整代码可以在Github上找到。

利用FFT快速卷积

时域的卷积和频域的乘法是等价的,同时时域的乘法和频域的卷积也是等价的。基于这个这个前提,可以把待卷积的数组进行FFT变换,在频域做乘法,然后再进行IFFT变换即可得到卷积结果。算法流程描述如下:

  1. N=len(a), M = len(b), 其中a, b为待卷积的数组,将长度增加到L>= N+M-1, L=2^n, n 属于 Z,即 L=2^(log_2^(N + M - 1) +1)。
  2. 增加a, b的长度到L,后面补零。
  3. 分别计算afft = fft(a),bfft=fft(b)。
  4. abfft = afft × bfft。
  5. 用IFFT计算abaft的FFT逆变换,取前(N + M - 1)个值即为卷积结果。

FFT快速卷积Python代码如下:

def convfft(a, b):N = len(a)M = len(b)YN = N + M - 1FFT_N = 2 ** (int(np.log2(YN)) + 1)afft = np.fft.fft(a, FFT_N)bfft = np.fft.fft(b, FFT_N)abfft = afft * bffty = np.fft.ifft(abfft).real[:YN]return y

测试

对比直接卷积、FFT卷积、numpy的卷积结果:

if __name__ == '__main__':a = [ 0, 1, 2, 3 ]b = [ 0, 1, 2 ]print(conv2(a, b))print(convfft(a, b))print(np.convolve(a, b))

可以看到,3个版本的计算结果是一致的。完整代码可以在Github上找到。

性能分析

复杂度分析

直接卷积的时间复杂度为o(MN),即o(n^2)。
FFT的时间复杂度为o(nlogn),FFT卷积复杂度为3次FFT+L次乘法,3o(nlogn)+o(n)=o(nlogn),及o(nlogn)。
在实际应用中,卷积核(b)被提前计算,则只需2次FFT变换。

运行测试

分别测试3个版本在数组长度为n * 1000 + 10, n=0,1,…,9的运行时间,并绘制运行时间曲线,编写如下测试代码:

def time_test():import timeimport matplotlib.pyplot as pltdef run(func, a, b):n = 1start = time.clock()for j in range(n):func(a, b)end = time.clock()run_time = end - startreturn run_time / nn_list = []t1_list = []t2_list = []t3_list = []for i in range(10):count = i * 1000 + 10print(count)a = np.ones(count)b = np.ones(count)t1 = run(conv, a, b)    # 直接卷积t2 = run(conv2, a, b)t3 = run(convfft, a, b) # FFT卷积n_list.append(count)t1_list.append(t1)t2_list.append(t2)t3_list.append(t3)# plotplt.plot(n_list, t1_list, label='conv')plt.plot(n_list, t2_list, label='conv2')plt.plot(n_list, t3_list, label='convfft')plt.legend()plt.title(u"convolve times")plt.ylabel(u"run times(ms/point)")plt.xlabel(u"length")plt.show()

运行得到的曲线图如下:

从图中可知,FFT卷积比直接卷积速度要快很多。完整代码可以在Github上找到。

CUDA实现

直接卷积

只需要把外层循环并行化就可以在CUDA上实现卷积,代码如下:

// 直接计算卷积
__global__ void conv_kernel(const float *ina, const float *inb, float *out, size_t len_a, size_t len_b, size_t len_out)
{const int tid = blockIdx.x * blockDim.x + threadIdx.x;if (tid >= len_out){return;}float sum = 0.0f;for (int m = 0; m < len_b; ++m){int k = tid - m;if (0 <= k && k < len_a){sum += ina[k] * inb[m];}}out[tid] = sum;
}

当然,可以使用共享内存和常量内存(卷积核存入常量内存)进行优化,优化的代码请查看Github。

cuFFT卷积

使用CUDA的cuFFT可以方便的进行快速傅里叶变换,cuFFT的详细说明可以查看NVIDIA的官方文档。本文主要使用到一下两个函数:

  • cufftExecR2C:实数到复数的快速傅里叶变换(FFT)
  • cufftExecC2R:复数到实数的快速傅里叶逆变换(IFFT)

基于cuFFT的实数到复数的快速傅里叶变换代码如下:

void fft(float *in, Complex *out, size_t size)
{cufftHandle plan;cufftPlan1d(&plan, size, CUFFT_R2C, 1);cufftExecR2C(plan, in, out);cufftDestroy(plan);
}

基于cuFFT的复数到实数的快速傅里叶逆变换代码如下:

void ifft(Complex *in, float *out, size_t size)
{cufftHandle plan;cufftPlan1d(&plan, size, CUFFT_C2R, 1);cufftExecC2R(plan, in, out);cufftDestroy(plan);
}

其中Complex被定义为float2typedef float2 Complex;

有了FFT,那么基于CUDA的卷积代码可如下编写:

void convfft( float *ina, float *inb, float *out, size_t len_out, size_t L, size_t numThreads)
{thrust::device_vector<Complex> d_a_fft(L);thrust::device_vector<Complex> d_b_fft(L);thrust::device_vector<Complex> d_c_fft(L);Complex *raw_point_a_fft = thrust::raw_pointer_cast(&d_a_fft[0]);Complex *raw_point_b_fft = thrust::raw_pointer_cast(&d_b_fft[0]);Complex *raw_point_c_fft = thrust::raw_pointer_cast(&d_c_fft[0]);fft(ina, raw_point_a_fft, L);fft(inb, raw_point_b_fft, L);// 计算 d_c_fft = d_a_fft * d_b_fft;ifft(raw_point_c_fft, out, L);
}

最后只剩下乘法运算了,可以自己编写一个复数乘法的内核,也可以使用thrush的transform。使用thrush实现复数乘法,首先定义一个复数乘法操作函数(可以参考Transformations):

struct complex_multiplies_functor
{const int N;complex_multiplies_functor(int _n) : N(_n) {}__host__ __device__ Complex operator()(const Complex &a, const Complex &b) const{Complex c;c.x = (a.x * b.x - a.y * b.y) / N;c.y = (a.x * b.y + a.y * b.x) / N;return c;}
};

然后使用thrush::transform即可完成计算:

// 计算 d_c_fft = d_a_fft * d_b_fft;
thrust::transform(d_a_fft.begin(), d_a_fft.end(), d_b_fft.begin(), d_c_fft.begin(), complex_multiplies_functor(L));

结语

本文首先简要介绍了卷积运算,然后使用Python实现了卷积运行的代码,接着讨论了基于FFT的快速卷积算法,并使用Python实现了FFT卷积,接着对直接卷积和基于FFT的快速卷积算法的性能进行了分析,从实验结果可以看出,FFT卷积相比直接卷积具有更快的运行速度。最后,基于CUDA实现了直接卷积算法,并且使用cuFFT和thrush在CUDA平台实现了基于FFT的快速卷积算法。

本文完整代码可在Github上下载。

参考文献

  1. 维基百科.卷积.https://zh.wikipedia.org/zh/%E5%8D%B7%E7%A7%AF
  2. 百度文库.利用FFT计算卷积.http://wenku.baidu.com/view/5606967101f69e3143329407.html
  3. 用Python做科学计算.FFT卷积的速度比较.http://old.sebug.net/paper/books/scipydoc/example_spectrum_fft_convolve_timeit.html
  4. NVIDIA.cuFFT.https://developer.nvidia.com/cufft
  5. thrust. https://github.com/thrust/thrust/tree/master/thrust
分类: CUDA&GPGPU, 算法, CUDA并行算法

CUDA并行算法系列之FFT快速卷积相关推荐

  1. 数字信号处理实验1:线性卷积与圆周卷积的计算、利用FFT快速卷积

    杭电_数字信号处理课程设计_实验1 一.实验目的 实验一目的: 1.掌握计算机的使用方法和常用系统软件及应用软件的使用. 2.通过MATLAB编程,上机调试程序,进一步增强使用计算机解决问题的能力. ...

  2. 快速卷积与快速相关——MATLAB

    一.实验目的 1.学会FFT算法程序(或函数)的使用方法; 2.了解序列的线性卷积和圆周卷积之间的关系; 3.验证有限长FFT算法实现线性相关运算的快速计算方法; 4.解FFT的点数对快速卷积与快速相 ...

  3. 解题报告(二)C、(darkBZOJ 2194) 快速傅立叶之二(FFT、卷积的概念、常用变换)

    繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 11 ...

  4. matlab序列谱分析,基于MATLAB序列谱分析及FFT实现快速卷积.pdf

    数字信号处理大作业 基于MATLAB 的序列谱分析与FFT 实现快速卷积 学 院(系): 软件学院 专 业: 网络工程 学 生 姓 名: 学 号: 班 级: 完 成 日 期: 大连理工大学 Dalia ...

  5. 数字信号处理 实验三 FFT 应用及 CZT (fft在快速卷积,相关,功率谱及CZT应用)

    快速傅里叶变化FFT的应用 前言 快速傅里叶变换 快速卷积计算 快速相关计算 功率谱计算 线性调频Z变换(CZT) 全部程序可点此处下载 前言 傅里叶变换在时频域转换和频域分析上有着重要的作用.但是如 ...

  6. FFT 快速傅里叶变换 初探

    一直认为很高深的东西其实也并不很难. 以下内容部分来自qy大神的ppt,同时结合了自己的理解.但理解还不是很深,需要继续研究. 开头 首先什么是傅里叶变换:傅立叶变换能将满足一定条件的某个函数表示成三 ...

  7. 实验三采用快速卷积法对先前的生成的混杂噪声的音频信号进行滤波

    实验三采用快速卷积法对先前的生成的混杂噪声的音频信号进行滤波 要求:选择子作业1中的音频信号,自行给定滤波器的单位取样响应,采用快速卷积实现对音频信号的滤波,比较滤波前后信号的波形和回放的效果. 最终 ...

  8. Winograd快速卷积相关研究综述

    摘要 卷积神经网络(CNN)已经被广泛应用到各个领域并发挥了重要作用.卷积算子是卷积神经网络的基础组件,同时也是最耗时的部分.近年来,研究者提出了包括基于FFT和Winograd的若干种快速卷积算法. ...

  9. java转安卓快吗_安卓Kotlin开发系列之Java快速转Kotlin

    原标题:安卓Kotlin开发系列之Java快速转Kotlin 自从Kotlin被宣布为Android开发语言的官方支持后,如今可谓是火的一塌糊涂,作为一名Android程序员,如何快速爬坑?今天为大家 ...

最新文章

  1. tdi_fw贴码析(TDI开源网络防火墙分析)
  2. java传参怎么理解_如何理解Java的值传递
  3. rpa机器人平台_RPA在财务领域的三大应用场景解析
  4. C#面向过程之编译原理、变量、运算符
  5. java与数据库连接实验报告_数据库原理与应用java实验报告
  6. FlexSim软件PF模块标识详细解释
  7. 女生学计算机和遥感哪个好就业,遥感专业女生就业方向 遥感专业毕业生可以从事哪些工作...
  8. android分享截屏到微信,Android截屏分享功能
  9. excel多元线性拟合_使用Excel数据分析工具进行多元回归分析的方法
  10. 电压信号与电流信号的转换
  11. latex 编译生成的 PDF 中字体有锯齿
  12. 数据驱动测试该怎么理解?真的像传说中的那么diao吗?
  13. 四川全国计算机一级考试查询系统,2013四川计算机一级成绩查询入口
  14. 关于JavaScript(JS)
  15. java sof栈泄露_java虚拟机(四)--内存溢出、内存泄漏、SOF
  16. mysql的联接算法_【MySQL—SQL编程】联接
  17. 苹果账户登录_iOS 13的通过 Apple 登录第三方应用
  18. Echarts 当Y轴取值存在正负值的时候,x轴文字与x轴贴合(不在底部显示)
  19. html5复选框控制按钮状态,HTML input checkbox复选按钮简介说明
  20. Mavlink协议概要

热门文章

  1. 论记笔记的重要性:以三个电影为例
  2. 山东大学和哈工大的教师招聘条件对比,心里要有点数
  3. mysql内存不断被占用,导致每隔一个多月就自动重启,修改数据库配置后,问题解决...
  4. 二叉树两个结点的最低公共父结点 【微软面试100题 第七十五题】
  5. Android开发入门教程--Android应用程序结构分析
  6. 模拟IE登录一个需要(windows身份)验证的网站
  7. hdu 5086(dp)
  8. linux 压缩解压归档
  9. 记在两周Android实训之后
  10. JSP简单练习-用Servlet获取表单数据